Merge pull request #188 from Dvorinka/feat/paid-shares

feat: paid share links with credit purchase and resume tickets
pull/3587/head
Tomáš Dvořák 2 weeks ago committed by GitHub
commit a717ed5c6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -173,7 +173,7 @@ Order = user-visible value first; each ships with backend + UI + tests.
- [x] PR #140 — `allow_upload`/`allow_edit`/`preview_only`/`upload_only` props, props-derived capability sets enforced server-side (`writePermitted`), same-share move/copy, anonymous upload, drop-box listing suppression, download denial via `IsDownloadCtxKey` hooks (fixes #3555 preview-only, #3340 drop-box) - [x] PR #140 — `allow_upload`/`allow_edit`/`preview_only`/`upload_only` props, props-derived capability sets enforced server-side (`writePermitted`), same-share move/copy, anonymous upload, drop-box listing suppression, download denial via `IsDownloadCtxKey` hooks (fixes #3555 preview-only, #3340 drop-box)
- [x] File/dir ACL entity — `(subject_type ∈ user|group|anonymous|everyone, subject_id)` → R/C/U/D bitmask; Permissions dialog under More actions; enforced in share-navigator capability checks; group bit 15 gate (#181, fixes #3517) - [x] File/dir ACL entity — `(subject_type ∈ user|group|anonymous|everyone, subject_id)` → R/C/U/D bitmask; Permissions dialog under More actions; enforced in share-navigator capability checks; group bit 15 gate (#181, fixes #3517)
- [x] Default shares — `setting.default_symbolics` + group `default_pinned` chip-input of share IDs; materialize as share-shortcut entries on fs init (#180) - [x] Default shares — `setting.default_symbolics` + group `default_pinned` chip-input of share IDs; materialize as share-shortcut entries on fs init (#180)
- [ ] Paid shares — `share.price_points` + gate page + purchase/resume-ticket flow; needs B.4 credits first - [x] Paid shares — `share.price_points` + `share_purchase` (buyer debit → owner income at `share_score_rate`) + resume ticket; download/thumb gated in share navigator, listing stays visible; `share_sell` (bit 29) gates price-setting, `share_free` (bit 8) bypasses paywall; `PaidShareGate` UI + restore via `purchase_ticket`
2. **Storage policy advanced** — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a. 2. **Storage policy advanced** — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a.
- [x] PR #175 — resumable admin relocation task (entities or whole-policy scope), encryption-aware re-wrap, admin UI + per-policy migrate action (#9, #125, #136) - [x] PR #175 — resumable admin relocation task (entities or whole-policy scope), encryption-aware re-wrap, admin UI + per-policy migrate action (#9, #125, #136)
- [x] Group→policies M:N (`allowed_policies` edge, empty = legacy single) + group-editor multi-select; per-directory `sys:preferred_policy` metadata marker with nearest-ancestor precedence (invalid marker cuts inheritance); user `preferred_policy` setting applied in own tree only; `load_balance` policy type with weighted children resolved before drivers (#182, fixes #2961) - [x] Group→policies M:N (`allowed_policies` edge, empty = legacy single) + group-editor multi-select; per-directory `sys:preferred_policy` metadata marker with nearest-ancestor precedence (invalid marker cuts inheritance); user `preferred_policy` setting applied in own tree only; `load_balance` policy type with weighted children resolved before drivers (#182, fixes #2961)
@ -186,7 +186,7 @@ Order = user-visible value first; each ships with backend + UI + tests.
- [x] `sku` entity (storage-capacity + group-upgrade types, duration, cash+points price, label, bullets); points purchase → atomic debit+grant; admin SKU tables; `/shop` page (Memberships/Storage/Redeem tabs) + nav entry - [x] `sku` entity (storage-capacity + group-upgrade types, duration, cash+points price, label, bullets); points purchase → atomic debit+grant; admin SKU tables; `/shop` page (Memberships/Storage/Redeem tabs) + nav entry
- [x] `user.credits` + `credit_txn` ledger (guarded atomic adjust); Finance settings tab (balance + grants + redeem + ledger); admin manual adjust (#183) - [x] `user.credits` + `credit_txn` ledger (guarded atomic adjust); Finance settings tab (balance + grants + redeem + ledger); admin manual adjust (#183)
- [x] `gift_code` entity (points/storage/group × amount × duration) + `user_grant` expiring grants + `grant_expire` cron; admin generate/list/revoke + user redeem (#183) - [x] `gift_code` entity (points/storage/group × amount × duration) + `user_grant` expiring grants + `grant_expire` cron; admin generate/list/revoke + user redeem (#183)
- [ ] Paid-share `price_points` wired to the ledger + purchase/resume-ticket flow - [x] Paid-share `price_points` wired to the ledger + purchase/resume-ticket flow
5. **System extensions** — activity/audit log, site announcements, node selection, report-abuse queue (fixes #3480, #3479 IP whitelist). See §1.3a. 5. **System extensions** — activity/audit log, site announcements, node selection, report-abuse queue (fixes #3480, #3479 IP whitelist). See §1.3a.
- [x] PR #144 — task `creator_ip` capture with CIDR-capable admin filter (#115 OSS half), group remote-download quotas per count + per volume (#16), yt-dlp downloader provider (#88), progressive image preview (#113), v3 migrator `DatabaseURL` passthrough (#42) - [x] PR #144 — task `creator_ip` capture with CIDR-capable admin filter (#115 OSS half), group remote-download quotas per count + per volume (#16), yt-dlp downloader provider (#88), progressive image preview (#113), v3 migrator `DatabaseURL` passthrough (#42)
- [x] `activity_event` entity (immutable, tx-aware, actor+IP+CID) + per-file Activity dialog + admin `/admin/event` feed + per-type enablement + retention cron (#184) - [x] `activity_event` entity (immutable, tx-aware, actor+IP+CID) + per-file Activity dialog + admin `/admin/event` feed + per-type enablement + retention cron (#184)

@ -34,6 +34,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/setting" "github.com/cloudreve/Cloudreve/v4/ent/setting"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
@ -86,6 +87,8 @@ type Client struct {
Setting *SettingClient Setting *SettingClient
// Share is the client for interacting with the Share builders. // Share is the client for interacting with the Share builders.
Share *ShareClient Share *ShareClient
// SharePurchase is the client for interacting with the SharePurchase builders.
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders. // Sku is the client for interacting with the Sku builders.
Sku *SkuClient Sku *SkuClient
// StoragePolicy is the client for interacting with the StoragePolicy builders. // StoragePolicy is the client for interacting with the StoragePolicy builders.
@ -126,6 +129,7 @@ func (c *Client) init() {
c.Passkey = NewPasskeyClient(c.config) c.Passkey = NewPasskeyClient(c.config)
c.Setting = NewSettingClient(c.config) c.Setting = NewSettingClient(c.config)
c.Share = NewShareClient(c.config) c.Share = NewShareClient(c.config)
c.SharePurchase = NewSharePurchaseClient(c.config)
c.Sku = NewSkuClient(c.config) c.Sku = NewSkuClient(c.config)
c.StoragePolicy = NewStoragePolicyClient(c.config) c.StoragePolicy = NewStoragePolicyClient(c.config)
c.Task = NewTaskClient(c.config) c.Task = NewTaskClient(c.config)
@ -242,6 +246,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Passkey: NewPasskeyClient(cfg), Passkey: NewPasskeyClient(cfg),
Setting: NewSettingClient(cfg), Setting: NewSettingClient(cfg),
Share: NewShareClient(cfg), Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg), Sku: NewSkuClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg), StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg), Task: NewTaskClient(cfg),
@ -285,6 +290,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Passkey: NewPasskeyClient(cfg), Passkey: NewPasskeyClient(cfg),
Setting: NewSettingClient(cfg), Setting: NewSettingClient(cfg),
Share: NewShareClient(cfg), Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg), Sku: NewSkuClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg), StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg), Task: NewTaskClient(cfg),
@ -322,7 +328,8 @@ func (c *Client) Use(hooks ...Hook) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount, c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group, c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.Sku, c.StoragePolicy, c.Task, c.User, c.UserGrant, c.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
c.UserGrant,
} { } {
n.Use(hooks...) n.Use(hooks...)
} }
@ -335,7 +342,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount, c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group, c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.Sku, c.StoragePolicy, c.Task, c.User, c.UserGrant, c.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
c.UserGrant,
} { } {
n.Intercept(interceptors...) n.Intercept(interceptors...)
} }
@ -382,6 +390,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.Setting.mutate(ctx, m) return c.Setting.mutate(ctx, m)
case *ShareMutation: case *ShareMutation:
return c.Share.mutate(ctx, m) return c.Share.mutate(ctx, m)
case *SharePurchaseMutation:
return c.SharePurchase.mutate(ctx, m)
case *SkuMutation: case *SkuMutation:
return c.Sku.mutate(ctx, m) return c.Sku.mutate(ctx, m)
case *StoragePolicyMutation: case *StoragePolicyMutation:
@ -3398,6 +3408,22 @@ func (c *ShareClient) QueryFile(s *Share) *FileQuery {
return query return query
} }
// QueryPurchases queries the purchases edge of a Share.
func (c *ShareClient) QueryPurchases(s *Share) *SharePurchaseQuery {
query := (&SharePurchaseClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := s.ID
step := sqlgraph.NewStep(
sqlgraph.From(share.Table, share.FieldID, id),
sqlgraph.To(sharepurchase.Table, sharepurchase.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, share.PurchasesTable, share.PurchasesColumn),
)
fromV = sqlgraph.Neighbors(s.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks. // Hooks returns the client hooks.
func (c *ShareClient) Hooks() []Hook { func (c *ShareClient) Hooks() []Hook {
hooks := c.hooks.Share hooks := c.hooks.Share
@ -3425,6 +3451,173 @@ func (c *ShareClient) mutate(ctx context.Context, m *ShareMutation) (Value, erro
} }
} }
// SharePurchaseClient is a client for the SharePurchase schema.
type SharePurchaseClient struct {
config
}
// NewSharePurchaseClient returns a client for the SharePurchase from the given config.
func NewSharePurchaseClient(c config) *SharePurchaseClient {
return &SharePurchaseClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `sharepurchase.Hooks(f(g(h())))`.
func (c *SharePurchaseClient) Use(hooks ...Hook) {
c.hooks.SharePurchase = append(c.hooks.SharePurchase, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `sharepurchase.Intercept(f(g(h())))`.
func (c *SharePurchaseClient) Intercept(interceptors ...Interceptor) {
c.inters.SharePurchase = append(c.inters.SharePurchase, interceptors...)
}
// Create returns a builder for creating a SharePurchase entity.
func (c *SharePurchaseClient) Create() *SharePurchaseCreate {
mutation := newSharePurchaseMutation(c.config, OpCreate)
return &SharePurchaseCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of SharePurchase entities.
func (c *SharePurchaseClient) CreateBulk(builders ...*SharePurchaseCreate) *SharePurchaseCreateBulk {
return &SharePurchaseCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *SharePurchaseClient) MapCreateBulk(slice any, setFunc func(*SharePurchaseCreate, int)) *SharePurchaseCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &SharePurchaseCreateBulk{err: fmt.Errorf("calling to SharePurchaseClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*SharePurchaseCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &SharePurchaseCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for SharePurchase.
func (c *SharePurchaseClient) Update() *SharePurchaseUpdate {
mutation := newSharePurchaseMutation(c.config, OpUpdate)
return &SharePurchaseUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *SharePurchaseClient) UpdateOne(sp *SharePurchase) *SharePurchaseUpdateOne {
mutation := newSharePurchaseMutation(c.config, OpUpdateOne, withSharePurchase(sp))
return &SharePurchaseUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *SharePurchaseClient) UpdateOneID(id int) *SharePurchaseUpdateOne {
mutation := newSharePurchaseMutation(c.config, OpUpdateOne, withSharePurchaseID(id))
return &SharePurchaseUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for SharePurchase.
func (c *SharePurchaseClient) Delete() *SharePurchaseDelete {
mutation := newSharePurchaseMutation(c.config, OpDelete)
return &SharePurchaseDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *SharePurchaseClient) DeleteOne(sp *SharePurchase) *SharePurchaseDeleteOne {
return c.DeleteOneID(sp.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *SharePurchaseClient) DeleteOneID(id int) *SharePurchaseDeleteOne {
builder := c.Delete().Where(sharepurchase.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &SharePurchaseDeleteOne{builder}
}
// Query returns a query builder for SharePurchase.
func (c *SharePurchaseClient) Query() *SharePurchaseQuery {
return &SharePurchaseQuery{
config: c.config,
ctx: &QueryContext{Type: TypeSharePurchase},
inters: c.Interceptors(),
}
}
// Get returns a SharePurchase entity by its id.
func (c *SharePurchaseClient) Get(ctx context.Context, id int) (*SharePurchase, error) {
return c.Query().Where(sharepurchase.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *SharePurchaseClient) GetX(ctx context.Context, id int) *SharePurchase {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryShare queries the share edge of a SharePurchase.
func (c *SharePurchaseClient) QueryShare(sp *SharePurchase) *ShareQuery {
query := (&ShareClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := sp.ID
step := sqlgraph.NewStep(
sqlgraph.From(sharepurchase.Table, sharepurchase.FieldID, id),
sqlgraph.To(share.Table, share.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, sharepurchase.ShareTable, sharepurchase.ShareColumn),
)
fromV = sqlgraph.Neighbors(sp.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryBuyer queries the buyer edge of a SharePurchase.
func (c *SharePurchaseClient) QueryBuyer(sp *SharePurchase) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := sp.ID
step := sqlgraph.NewStep(
sqlgraph.From(sharepurchase.Table, sharepurchase.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, sharepurchase.BuyerTable, sharepurchase.BuyerColumn),
)
fromV = sqlgraph.Neighbors(sp.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *SharePurchaseClient) Hooks() []Hook {
hooks := c.hooks.SharePurchase
return append(hooks[:len(hooks):len(hooks)], sharepurchase.Hooks[:]...)
}
// Interceptors returns the client interceptors.
func (c *SharePurchaseClient) Interceptors() []Interceptor {
inters := c.inters.SharePurchase
return append(inters[:len(inters):len(inters)], sharepurchase.Interceptors[:]...)
}
func (c *SharePurchaseClient) mutate(ctx context.Context, m *SharePurchaseMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&SharePurchaseCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&SharePurchaseUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&SharePurchaseUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&SharePurchaseDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown SharePurchase mutation op: %q", m.Op())
}
}
// SkuClient is a client for the Sku schema. // SkuClient is a client for the Sku schema.
type SkuClient struct { type SkuClient struct {
config config
@ -4226,6 +4419,22 @@ func (c *UserClient) QueryGrants(u *User) *UserGrantQuery {
return query return query
} }
// QuerySharePurchases queries the share_purchases edge of a User.
func (c *UserClient) QuerySharePurchases(u *User) *SharePurchaseQuery {
query := (&SharePurchaseClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := u.ID
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, id),
sqlgraph.To(sharepurchase.Table, sharepurchase.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.SharePurchasesTable, user.SharePurchasesColumn),
)
fromV = sqlgraph.Neighbors(u.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks. // Hooks returns the client hooks.
func (c *UserClient) Hooks() []Hook { func (c *UserClient) Hooks() []Hook {
hooks := c.hooks.User hooks := c.hooks.User
@ -4409,14 +4618,14 @@ type (
hooks struct { hooks struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity, AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient, File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
OAuthGrant, Passkey, Setting, Share, Sku, StoragePolicy, Task, User, OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
UserGrant []ent.Hook User, UserGrant []ent.Hook
} }
inters struct { inters struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity, AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient, File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
OAuthGrant, Passkey, Setting, Share, Sku, StoragePolicy, Task, User, OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
UserGrant []ent.Interceptor User, UserGrant []ent.Interceptor
} }
) )

@ -31,6 +31,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/setting" "github.com/cloudreve/Cloudreve/v4/ent/setting"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
@ -115,6 +116,7 @@ func checkColumn(table, column string) error {
passkey.Table: passkey.ValidColumn, passkey.Table: passkey.ValidColumn,
setting.Table: setting.ValidColumn, setting.Table: setting.ValidColumn,
share.Table: share.ValidColumn, share.Table: share.ValidColumn,
sharepurchase.Table: sharepurchase.ValidColumn,
sku.Table: sku.ValidColumn, sku.Table: sku.ValidColumn,
storagepolicy.Table: storagepolicy.ValidColumn, storagepolicy.Table: storagepolicy.ValidColumn,
task.Table: task.ValidColumn, task.Table: task.ValidColumn,

@ -237,6 +237,18 @@ func (f ShareFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ShareMutation", m) return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ShareMutation", m)
} }
// The SharePurchaseFunc type is an adapter to allow the use of ordinary
// function as SharePurchase mutator.
type SharePurchaseFunc func(context.Context, *ent.SharePurchaseMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f SharePurchaseFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.SharePurchaseMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SharePurchaseMutation", m)
}
// The SkuFunc type is an adapter to allow the use of ordinary // The SkuFunc type is an adapter to allow the use of ordinary
// function as Sku mutator. // function as Sku mutator.
type SkuFunc func(context.Context, *ent.SkuMutation) (ent.Value, error) type SkuFunc func(context.Context, *ent.SkuMutation) (ent.Value, error)

@ -28,6 +28,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/setting" "github.com/cloudreve/Cloudreve/v4/ent/setting"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
@ -604,6 +605,33 @@ func (f TraverseShare) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.ShareQuery", q) return fmt.Errorf("unexpected query type %T. expect *ent.ShareQuery", q)
} }
// The SharePurchaseFunc type is an adapter to allow the use of ordinary function as a Querier.
type SharePurchaseFunc func(context.Context, *ent.SharePurchaseQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f SharePurchaseFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.SharePurchaseQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SharePurchaseQuery", q)
}
// The TraverseSharePurchase type is an adapter to allow the use of ordinary function as Traverser.
type TraverseSharePurchase func(context.Context, *ent.SharePurchaseQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseSharePurchase) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseSharePurchase) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.SharePurchaseQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.SharePurchaseQuery", q)
}
// The SkuFunc type is an adapter to allow the use of ordinary function as a Querier. // The SkuFunc type is an adapter to allow the use of ordinary function as a Querier.
type SkuFunc func(context.Context, *ent.SkuQuery) (ent.Value, error) type SkuFunc func(context.Context, *ent.SkuQuery) (ent.Value, error)
@ -780,6 +808,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
case *ent.ShareQuery: case *ent.ShareQuery:
return &query[*ent.ShareQuery, predicate.Share, share.OrderOption]{typ: ent.TypeShare, tq: q}, nil return &query[*ent.ShareQuery, predicate.Share, share.OrderOption]{typ: ent.TypeShare, tq: q}, nil
case *ent.SharePurchaseQuery:
return &query[*ent.SharePurchaseQuery, predicate.SharePurchase, sharepurchase.OrderOption]{typ: ent.TypeSharePurchase, tq: q}, nil
case *ent.SkuQuery: case *ent.SkuQuery:
return &query[*ent.SkuQuery, predicate.Sku, sku.OrderOption]{typ: ent.TypeSku, tq: q}, nil return &query[*ent.SkuQuery, predicate.Sku, sku.OrderOption]{typ: ent.TypeSku, tq: q}, nil
case *ent.StoragePolicyQuery: case *ent.StoragePolicyQuery:

File diff suppressed because one or more lines are too long

@ -578,6 +578,7 @@ var (
{Name: "downloads", Type: field.TypeInt, Default: 0}, {Name: "downloads", Type: field.TypeInt, Default: 0},
{Name: "expires", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}}, {Name: "expires", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "remain_downloads", Type: field.TypeInt, Nullable: true}, {Name: "remain_downloads", Type: field.TypeInt, Nullable: true},
{Name: "price_points", Type: field.TypeInt, Default: 0},
{Name: "props", Type: field.TypeJSON, Nullable: true}, {Name: "props", Type: field.TypeJSON, Nullable: true},
{Name: "file_shares", Type: field.TypeInt, Nullable: true}, {Name: "file_shares", Type: field.TypeInt, Nullable: true},
{Name: "user_shares", Type: field.TypeInt, Nullable: true}, {Name: "user_shares", Type: field.TypeInt, Nullable: true},
@ -590,18 +591,56 @@ var (
ForeignKeys: []*schema.ForeignKey{ ForeignKeys: []*schema.ForeignKey{
{ {
Symbol: "shares_files_shares", Symbol: "shares_files_shares",
Columns: []*schema.Column{SharesColumns[10]}, Columns: []*schema.Column{SharesColumns[11]},
RefColumns: []*schema.Column{FilesColumns[0]}, RefColumns: []*schema.Column{FilesColumns[0]},
OnDelete: schema.SetNull, OnDelete: schema.SetNull,
}, },
{ {
Symbol: "shares_users_shares", Symbol: "shares_users_shares",
Columns: []*schema.Column{SharesColumns[11]}, Columns: []*schema.Column{SharesColumns[12]},
RefColumns: []*schema.Column{UsersColumns[0]}, RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.SetNull, OnDelete: schema.SetNull,
}, },
}, },
} }
// SharePurchasesColumns holds the columns for the "share_purchases" table.
SharePurchasesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "points", Type: field.TypeInt},
{Name: "ticket", Type: field.TypeString, Unique: true},
{Name: "share_id", Type: field.TypeInt},
{Name: "buyer_id", Type: field.TypeInt},
}
// SharePurchasesTable holds the schema information for the "share_purchases" table.
SharePurchasesTable = &schema.Table{
Name: "share_purchases",
Columns: SharePurchasesColumns,
PrimaryKey: []*schema.Column{SharePurchasesColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "share_purchases_shares_purchases",
Columns: []*schema.Column{SharePurchasesColumns[6]},
RefColumns: []*schema.Column{SharesColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "share_purchases_users_share_purchases",
Columns: []*schema.Column{SharePurchasesColumns[7]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "sharepurchase_share_id_buyer_id",
Unique: true,
Columns: []*schema.Column{SharePurchasesColumns[6], SharePurchasesColumns[7]},
},
},
}
// SkusColumns holds the columns for the "skus" table. // SkusColumns holds the columns for the "skus" table.
SkusColumns = []*schema.Column{ SkusColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
@ -831,6 +870,7 @@ var (
PasskeysTable, PasskeysTable,
SettingsTable, SettingsTable,
SharesTable, SharesTable,
SharePurchasesTable,
SkusTable, SkusTable,
StoragePoliciesTable, StoragePoliciesTable,
TasksTable, TasksTable,
@ -860,6 +900,8 @@ func init() {
PasskeysTable.ForeignKeys[0].RefTable = UsersTable PasskeysTable.ForeignKeys[0].RefTable = UsersTable
SharesTable.ForeignKeys[0].RefTable = FilesTable SharesTable.ForeignKeys[0].RefTable = FilesTable
SharesTable.ForeignKeys[1].RefTable = UsersTable SharesTable.ForeignKeys[1].RefTable = UsersTable
SharePurchasesTable.ForeignKeys[0].RefTable = SharesTable
SharePurchasesTable.ForeignKeys[1].RefTable = UsersTable
StoragePoliciesTable.ForeignKeys[0].RefTable = NodesTable StoragePoliciesTable.ForeignKeys[0].RefTable = NodesTable
TasksTable.ForeignKeys[0].RefTable = UsersTable TasksTable.ForeignKeys[0].RefTable = UsersTable
UsersTable.ForeignKeys[0].RefTable = GroupsTable UsersTable.ForeignKeys[0].RefTable = GroupsTable

File diff suppressed because it is too large Load Diff

@ -118,6 +118,12 @@ func (m *ShareMutation) SetRawID(t int) {
// SetUpdatedAt sets the "updated_at" field. // SetUpdatedAt sets the "updated_at" field.
func (m *SharePurchaseMutation) SetRawID(t int) {
m.id = &t
}
// SetUpdatedAt sets the "updated_at" field.
func (m *SkuMutation) SetRawID(t int) { func (m *SkuMutation) SetRawID(t int) {
m.id = &t m.id = &t
} }

@ -63,6 +63,9 @@ type Setting func(*sql.Selector)
// Share is the predicate function for share builders. // Share is the predicate function for share builders.
type Share func(*sql.Selector) type Share func(*sql.Selector)
// SharePurchase is the predicate function for sharepurchase builders.
type SharePurchase func(*sql.Selector)
// Sku is the predicate function for sku builders. // Sku is the predicate function for sku builders.
type Sku func(*sql.Selector) type Sku func(*sql.Selector)

@ -25,6 +25,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/schema" "github.com/cloudreve/Cloudreve/v4/ent/schema"
"github.com/cloudreve/Cloudreve/v4/ent/setting" "github.com/cloudreve/Cloudreve/v4/ent/setting"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
@ -483,6 +484,35 @@ func init() {
shareDescDownloads := shareFields[2].Descriptor() shareDescDownloads := shareFields[2].Descriptor()
// share.DefaultDownloads holds the default value on creation for the downloads field. // share.DefaultDownloads holds the default value on creation for the downloads field.
share.DefaultDownloads = shareDescDownloads.Default.(int) share.DefaultDownloads = shareDescDownloads.Default.(int)
// shareDescPricePoints is the schema descriptor for price_points field.
shareDescPricePoints := shareFields[5].Descriptor()
// share.DefaultPricePoints holds the default value on creation for the price_points field.
share.DefaultPricePoints = shareDescPricePoints.Default.(int)
// share.PricePointsValidator is a validator for the "price_points" field. It is called by the builders before save.
share.PricePointsValidator = shareDescPricePoints.Validators[0].(func(int) error)
sharepurchaseMixin := schema.SharePurchase{}.Mixin()
sharepurchaseMixinHooks0 := sharepurchaseMixin[0].Hooks()
sharepurchase.Hooks[0] = sharepurchaseMixinHooks0[0]
sharepurchaseMixinInters0 := sharepurchaseMixin[0].Interceptors()
sharepurchase.Interceptors[0] = sharepurchaseMixinInters0[0]
sharepurchaseMixinFields0 := sharepurchaseMixin[0].Fields()
_ = sharepurchaseMixinFields0
sharepurchaseFields := schema.SharePurchase{}.Fields()
_ = sharepurchaseFields
// sharepurchaseDescCreatedAt is the schema descriptor for created_at field.
sharepurchaseDescCreatedAt := sharepurchaseMixinFields0[0].Descriptor()
// sharepurchase.DefaultCreatedAt holds the default value on creation for the created_at field.
sharepurchase.DefaultCreatedAt = sharepurchaseDescCreatedAt.Default.(func() time.Time)
// sharepurchaseDescUpdatedAt is the schema descriptor for updated_at field.
sharepurchaseDescUpdatedAt := sharepurchaseMixinFields0[1].Descriptor()
// sharepurchase.DefaultUpdatedAt holds the default value on creation for the updated_at field.
sharepurchase.DefaultUpdatedAt = sharepurchaseDescUpdatedAt.Default.(func() time.Time)
// sharepurchase.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
sharepurchase.UpdateDefaultUpdatedAt = sharepurchaseDescUpdatedAt.UpdateDefault.(func() time.Time)
// sharepurchaseDescTicket is the schema descriptor for ticket field.
sharepurchaseDescTicket := sharepurchaseFields[3].Descriptor()
// sharepurchase.TicketValidator is a validator for the "ticket" field. It is called by the builders before save.
sharepurchase.TicketValidator = sharepurchaseDescTicket.Validators[0].(func(string) error)
skuMixin := schema.Sku{}.Mixin() skuMixin := schema.Sku{}.Mixin()
skuMixinHooks0 := skuMixin[0].Hooks() skuMixinHooks0 := skuMixin[0].Hooks()
sku.Hooks[0] = skuMixinHooks0[0] sku.Hooks[0] = skuMixinHooks0[0]

@ -31,6 +31,10 @@ func (Share) Fields() []ent.Field {
field.Int("remain_downloads"). field.Int("remain_downloads").
Nillable(). Nillable().
Optional(), Optional(),
// Points price visitors must pay before downloading. 0 = free share.
field.Int("price_points").
Default(0).
NonNegative(),
field.JSON("props", &types.ShareProps{}).Optional(), field.JSON("props", &types.ShareProps{}).Optional(),
} }
} }
@ -42,6 +46,7 @@ func (Share) Edges() []ent.Edge {
Ref("shares").Unique(), Ref("shares").Unique(),
edge.From("file", File.Type). edge.From("file", File.Type).
Ref("shares").Unique(), Ref("shares").Unique(),
edge.To("purchases", SharePurchase.Type),
} }
} }

@ -0,0 +1,56 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// SharePurchase records a points purchase of a paid share. One row per
// (share, buyer); the ticket acts as a bearer credential to restore access
// after session loss.
type SharePurchase struct {
ent.Schema
}
// Fields of the SharePurchase.
func (SharePurchase) Fields() []ent.Field {
return []ent.Field{
field.Int("share_id"),
field.Int("buyer_id"),
field.Int("points"),
field.String("ticket").
Unique().
NotEmpty(),
}
}
// Edges of the SharePurchase.
func (SharePurchase) Edges() []ent.Edge {
return []ent.Edge{
edge.From("share", Share.Type).
Ref("purchases").
Field("share_id").
Unique().
Required(),
edge.From("buyer", User.Type).
Ref("share_purchases").
Field("buyer_id").
Unique().
Required(),
}
}
func (SharePurchase) Indexes() []ent.Index {
return []ent.Index{
index.Fields("share_id", "buyer_id").
Unique(),
}
}
func (SharePurchase) Mixin() []ent.Mixin {
return []ent.Mixin{
CommonMixin{},
}
}

@ -72,6 +72,7 @@ func (User) Edges() []ent.Edge {
edge.To("credit_txns", CreditTxn.Type), edge.To("credit_txns", CreditTxn.Type),
edge.To("redeemed_codes", GiftCode.Type), edge.To("redeemed_codes", GiftCode.Type),
edge.To("grants", UserGrant.Type), edge.To("grants", UserGrant.Type),
edge.To("share_purchases", SharePurchase.Type),
} }
} }

@ -37,6 +37,8 @@ type Share struct {
Expires *time.Time `json:"expires,omitempty"` Expires *time.Time `json:"expires,omitempty"`
// RemainDownloads holds the value of the "remain_downloads" field. // RemainDownloads holds the value of the "remain_downloads" field.
RemainDownloads *int `json:"remain_downloads,omitempty"` RemainDownloads *int `json:"remain_downloads,omitempty"`
// PricePoints holds the value of the "price_points" field.
PricePoints int `json:"price_points,omitempty"`
// Props holds the value of the "props" field. // Props holds the value of the "props" field.
Props *types.ShareProps `json:"props,omitempty"` Props *types.ShareProps `json:"props,omitempty"`
// Edges holds the relations/edges for other nodes in the graph. // Edges holds the relations/edges for other nodes in the graph.
@ -53,9 +55,11 @@ type ShareEdges struct {
User *User `json:"user,omitempty"` User *User `json:"user,omitempty"`
// File holds the value of the file edge. // File holds the value of the file edge.
File *File `json:"file,omitempty"` File *File `json:"file,omitempty"`
// Purchases holds the value of the purchases edge.
Purchases []*SharePurchase `json:"purchases,omitempty"`
// loadedTypes holds the information for reporting if a // loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not. // type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool loadedTypes [3]bool
} }
// UserOrErr returns the User value or an error if the edge // UserOrErr returns the User value or an error if the edge
@ -84,6 +88,15 @@ func (e ShareEdges) FileOrErr() (*File, error) {
return nil, &NotLoadedError{edge: "file"} return nil, &NotLoadedError{edge: "file"}
} }
// PurchasesOrErr returns the Purchases value or an error if the edge
// was not loaded in eager-loading.
func (e ShareEdges) PurchasesOrErr() ([]*SharePurchase, error) {
if e.loadedTypes[2] {
return e.Purchases, nil
}
return nil, &NotLoadedError{edge: "purchases"}
}
// scanValues returns the types for scanning values from sql.Rows. // scanValues returns the types for scanning values from sql.Rows.
func (*Share) scanValues(columns []string) ([]any, error) { func (*Share) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns)) values := make([]any, len(columns))
@ -91,7 +104,7 @@ func (*Share) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case share.FieldProps: case share.FieldProps:
values[i] = new([]byte) values[i] = new([]byte)
case share.FieldID, share.FieldViews, share.FieldDownloads, share.FieldRemainDownloads: case share.FieldID, share.FieldViews, share.FieldDownloads, share.FieldRemainDownloads, share.FieldPricePoints:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case share.FieldPassword: case share.FieldPassword:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
@ -173,6 +186,12 @@ func (s *Share) assignValues(columns []string, values []any) error {
s.RemainDownloads = new(int) s.RemainDownloads = new(int)
*s.RemainDownloads = int(value.Int64) *s.RemainDownloads = int(value.Int64)
} }
case share.FieldPricePoints:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field price_points", values[i])
} else if value.Valid {
s.PricePoints = int(value.Int64)
}
case share.FieldProps: case share.FieldProps:
if value, ok := values[i].(*[]byte); !ok { if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field props", values[i]) return fmt.Errorf("unexpected type %T for field props", values[i])
@ -218,6 +237,11 @@ func (s *Share) QueryFile() *FileQuery {
return NewShareClient(s.config).QueryFile(s) return NewShareClient(s.config).QueryFile(s)
} }
// QueryPurchases queries the "purchases" edge of the Share entity.
func (s *Share) QueryPurchases() *SharePurchaseQuery {
return NewShareClient(s.config).QueryPurchases(s)
}
// Update returns a builder for updating this Share. // Update returns a builder for updating this Share.
// Note that you need to call Share.Unwrap() before calling this method if this Share // Note that you need to call Share.Unwrap() before calling this method if this Share
// was returned from a transaction, and the transaction was committed or rolled back. // was returned from a transaction, and the transaction was committed or rolled back.
@ -271,6 +295,9 @@ func (s *Share) String() string {
builder.WriteString(fmt.Sprintf("%v", *v)) builder.WriteString(fmt.Sprintf("%v", *v))
} }
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("price_points=")
builder.WriteString(fmt.Sprintf("%v", s.PricePoints))
builder.WriteString(", ")
builder.WriteString("props=") builder.WriteString("props=")
builder.WriteString(fmt.Sprintf("%v", s.Props)) builder.WriteString(fmt.Sprintf("%v", s.Props))
builder.WriteByte(')') builder.WriteByte(')')
@ -289,5 +316,11 @@ func (e *Share) SetFile(v *File) {
e.Edges.loadedTypes[1] = true e.Edges.loadedTypes[1] = true
} }
// SetPurchases manually set the edge as loaded state.
func (e *Share) SetPurchases(v []*SharePurchase) {
e.Edges.Purchases = v
e.Edges.loadedTypes[2] = true
}
// Shares is a parsable slice of Share. // Shares is a parsable slice of Share.
type Shares []*Share type Shares []*Share

@ -31,12 +31,16 @@ const (
FieldExpires = "expires" FieldExpires = "expires"
// FieldRemainDownloads holds the string denoting the remain_downloads field in the database. // FieldRemainDownloads holds the string denoting the remain_downloads field in the database.
FieldRemainDownloads = "remain_downloads" FieldRemainDownloads = "remain_downloads"
// FieldPricePoints holds the string denoting the price_points field in the database.
FieldPricePoints = "price_points"
// FieldProps holds the string denoting the props field in the database. // FieldProps holds the string denoting the props field in the database.
FieldProps = "props" FieldProps = "props"
// EdgeUser holds the string denoting the user edge name in mutations. // EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user" EdgeUser = "user"
// EdgeFile holds the string denoting the file edge name in mutations. // EdgeFile holds the string denoting the file edge name in mutations.
EdgeFile = "file" EdgeFile = "file"
// EdgePurchases holds the string denoting the purchases edge name in mutations.
EdgePurchases = "purchases"
// Table holds the table name of the share in the database. // Table holds the table name of the share in the database.
Table = "shares" Table = "shares"
// UserTable is the table that holds the user relation/edge. // UserTable is the table that holds the user relation/edge.
@ -53,6 +57,13 @@ const (
FileInverseTable = "files" FileInverseTable = "files"
// FileColumn is the table column denoting the file relation/edge. // FileColumn is the table column denoting the file relation/edge.
FileColumn = "file_shares" FileColumn = "file_shares"
// PurchasesTable is the table that holds the purchases relation/edge.
PurchasesTable = "share_purchases"
// PurchasesInverseTable is the table name for the SharePurchase entity.
// It exists in this package in order to avoid circular dependency with the "sharepurchase" package.
PurchasesInverseTable = "share_purchases"
// PurchasesColumn is the table column denoting the purchases relation/edge.
PurchasesColumn = "share_id"
) )
// Columns holds all SQL columns for share fields. // Columns holds all SQL columns for share fields.
@ -66,6 +77,7 @@ var Columns = []string{
FieldDownloads, FieldDownloads,
FieldExpires, FieldExpires,
FieldRemainDownloads, FieldRemainDownloads,
FieldPricePoints,
FieldProps, FieldProps,
} }
@ -109,6 +121,10 @@ var (
DefaultViews int DefaultViews int
// DefaultDownloads holds the default value on creation for the "downloads" field. // DefaultDownloads holds the default value on creation for the "downloads" field.
DefaultDownloads int DefaultDownloads int
// DefaultPricePoints holds the default value on creation for the "price_points" field.
DefaultPricePoints int
// PricePointsValidator is a validator for the "price_points" field. It is called by the builders before save.
PricePointsValidator func(int) error
) )
// OrderOption defines the ordering options for the Share queries. // OrderOption defines the ordering options for the Share queries.
@ -159,6 +175,11 @@ func ByRemainDownloads(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRemainDownloads, opts...).ToFunc() return sql.OrderByField(FieldRemainDownloads, opts...).ToFunc()
} }
// ByPricePoints orders the results by the price_points field.
func ByPricePoints(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPricePoints, opts...).ToFunc()
}
// ByUserField orders the results by user field. // ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) { return func(s *sql.Selector) {
@ -172,6 +193,20 @@ func ByFileField(field string, opts ...sql.OrderTermOption) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newFileStep(), sql.OrderByField(field, opts...)) sqlgraph.OrderByNeighborTerms(s, newFileStep(), sql.OrderByField(field, opts...))
} }
} }
// ByPurchasesCount orders the results by purchases count.
func ByPurchasesCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newPurchasesStep(), opts...)
}
}
// ByPurchases orders the results by purchases terms.
func ByPurchases(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newPurchasesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newUserStep() *sqlgraph.Step { func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep( return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID), sqlgraph.From(Table, FieldID),
@ -186,3 +221,10 @@ func newFileStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn), sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn),
) )
} }
func newPurchasesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(PurchasesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, PurchasesTable, PurchasesColumn),
)
}

@ -95,6 +95,11 @@ func RemainDownloads(v int) predicate.Share {
return predicate.Share(sql.FieldEQ(FieldRemainDownloads, v)) return predicate.Share(sql.FieldEQ(FieldRemainDownloads, v))
} }
// PricePoints applies equality check predicate on the "price_points" field. It's identical to PricePointsEQ.
func PricePoints(v int) predicate.Share {
return predicate.Share(sql.FieldEQ(FieldPricePoints, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field. // CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Share { func CreatedAtEQ(v time.Time) predicate.Share {
return predicate.Share(sql.FieldEQ(FieldCreatedAt, v)) return predicate.Share(sql.FieldEQ(FieldCreatedAt, v))
@ -480,6 +485,46 @@ func RemainDownloadsNotNil() predicate.Share {
return predicate.Share(sql.FieldNotNull(FieldRemainDownloads)) return predicate.Share(sql.FieldNotNull(FieldRemainDownloads))
} }
// PricePointsEQ applies the EQ predicate on the "price_points" field.
func PricePointsEQ(v int) predicate.Share {
return predicate.Share(sql.FieldEQ(FieldPricePoints, v))
}
// PricePointsNEQ applies the NEQ predicate on the "price_points" field.
func PricePointsNEQ(v int) predicate.Share {
return predicate.Share(sql.FieldNEQ(FieldPricePoints, v))
}
// PricePointsIn applies the In predicate on the "price_points" field.
func PricePointsIn(vs ...int) predicate.Share {
return predicate.Share(sql.FieldIn(FieldPricePoints, vs...))
}
// PricePointsNotIn applies the NotIn predicate on the "price_points" field.
func PricePointsNotIn(vs ...int) predicate.Share {
return predicate.Share(sql.FieldNotIn(FieldPricePoints, vs...))
}
// PricePointsGT applies the GT predicate on the "price_points" field.
func PricePointsGT(v int) predicate.Share {
return predicate.Share(sql.FieldGT(FieldPricePoints, v))
}
// PricePointsGTE applies the GTE predicate on the "price_points" field.
func PricePointsGTE(v int) predicate.Share {
return predicate.Share(sql.FieldGTE(FieldPricePoints, v))
}
// PricePointsLT applies the LT predicate on the "price_points" field.
func PricePointsLT(v int) predicate.Share {
return predicate.Share(sql.FieldLT(FieldPricePoints, v))
}
// PricePointsLTE applies the LTE predicate on the "price_points" field.
func PricePointsLTE(v int) predicate.Share {
return predicate.Share(sql.FieldLTE(FieldPricePoints, v))
}
// PropsIsNil applies the IsNil predicate on the "props" field. // PropsIsNil applies the IsNil predicate on the "props" field.
func PropsIsNil() predicate.Share { func PropsIsNil() predicate.Share {
return predicate.Share(sql.FieldIsNull(FieldProps)) return predicate.Share(sql.FieldIsNull(FieldProps))
@ -536,6 +581,29 @@ func HasFileWith(preds ...predicate.File) predicate.Share {
}) })
} }
// HasPurchases applies the HasEdge predicate on the "purchases" edge.
func HasPurchases() predicate.Share {
return predicate.Share(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, PurchasesTable, PurchasesColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasPurchasesWith applies the HasEdge predicate on the "purchases" edge with a given conditions (other predicates).
func HasPurchasesWith(preds ...predicate.SharePurchase) predicate.Share {
return predicate.Share(func(s *sql.Selector) {
step := newPurchasesStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.Share) predicate.Share { func And(predicates ...predicate.Share) predicate.Share {
return predicate.Share(sql.AndPredicates(predicates...)) return predicate.Share(sql.AndPredicates(predicates...))

@ -13,6 +13,7 @@ import (
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/inventory/types"
) )
@ -137,6 +138,20 @@ func (sc *ShareCreate) SetNillableRemainDownloads(i *int) *ShareCreate {
return sc return sc
} }
// SetPricePoints sets the "price_points" field.
func (sc *ShareCreate) SetPricePoints(i int) *ShareCreate {
sc.mutation.SetPricePoints(i)
return sc
}
// SetNillablePricePoints sets the "price_points" field if the given value is not nil.
func (sc *ShareCreate) SetNillablePricePoints(i *int) *ShareCreate {
if i != nil {
sc.SetPricePoints(*i)
}
return sc
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (sc *ShareCreate) SetProps(tp *types.ShareProps) *ShareCreate { func (sc *ShareCreate) SetProps(tp *types.ShareProps) *ShareCreate {
sc.mutation.SetProps(tp) sc.mutation.SetProps(tp)
@ -181,6 +196,21 @@ func (sc *ShareCreate) SetFile(f *File) *ShareCreate {
return sc.SetFileID(f.ID) return sc.SetFileID(f.ID)
} }
// AddPurchaseIDs adds the "purchases" edge to the SharePurchase entity by IDs.
func (sc *ShareCreate) AddPurchaseIDs(ids ...int) *ShareCreate {
sc.mutation.AddPurchaseIDs(ids...)
return sc
}
// AddPurchases adds the "purchases" edges to the SharePurchase entity.
func (sc *ShareCreate) AddPurchases(s ...*SharePurchase) *ShareCreate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return sc.AddPurchaseIDs(ids...)
}
// Mutation returns the ShareMutation object of the builder. // Mutation returns the ShareMutation object of the builder.
func (sc *ShareCreate) Mutation() *ShareMutation { func (sc *ShareCreate) Mutation() *ShareMutation {
return sc.mutation return sc.mutation
@ -240,6 +270,10 @@ func (sc *ShareCreate) defaults() error {
v := share.DefaultDownloads v := share.DefaultDownloads
sc.mutation.SetDownloads(v) sc.mutation.SetDownloads(v)
} }
if _, ok := sc.mutation.PricePoints(); !ok {
v := share.DefaultPricePoints
sc.mutation.SetPricePoints(v)
}
return nil return nil
} }
@ -257,6 +291,14 @@ func (sc *ShareCreate) check() error {
if _, ok := sc.mutation.Downloads(); !ok { if _, ok := sc.mutation.Downloads(); !ok {
return &ValidationError{Name: "downloads", err: errors.New(`ent: missing required field "Share.downloads"`)} return &ValidationError{Name: "downloads", err: errors.New(`ent: missing required field "Share.downloads"`)}
} }
if _, ok := sc.mutation.PricePoints(); !ok {
return &ValidationError{Name: "price_points", err: errors.New(`ent: missing required field "Share.price_points"`)}
}
if v, ok := sc.mutation.PricePoints(); ok {
if err := share.PricePointsValidator(v); err != nil {
return &ValidationError{Name: "price_points", err: fmt.Errorf(`ent: validator failed for field "Share.price_points": %w`, err)}
}
}
return nil return nil
} }
@ -323,6 +365,10 @@ func (sc *ShareCreate) createSpec() (*Share, *sqlgraph.CreateSpec) {
_spec.SetField(share.FieldRemainDownloads, field.TypeInt, value) _spec.SetField(share.FieldRemainDownloads, field.TypeInt, value)
_node.RemainDownloads = &value _node.RemainDownloads = &value
} }
if value, ok := sc.mutation.PricePoints(); ok {
_spec.SetField(share.FieldPricePoints, field.TypeInt, value)
_node.PricePoints = value
}
if value, ok := sc.mutation.Props(); ok { if value, ok := sc.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value) _spec.SetField(share.FieldProps, field.TypeJSON, value)
_node.Props = value _node.Props = value
@ -361,6 +407,22 @@ func (sc *ShareCreate) createSpec() (*Share, *sqlgraph.CreateSpec) {
_node.file_shares = &nodes[0] _node.file_shares = &nodes[0]
_spec.Edges = append(_spec.Edges, edge) _spec.Edges = append(_spec.Edges, edge)
} }
if nodes := sc.mutation.PurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec return _node, _spec
} }
@ -539,6 +601,24 @@ func (u *ShareUpsert) ClearRemainDownloads() *ShareUpsert {
return u return u
} }
// SetPricePoints sets the "price_points" field.
func (u *ShareUpsert) SetPricePoints(v int) *ShareUpsert {
u.Set(share.FieldPricePoints, v)
return u
}
// UpdatePricePoints sets the "price_points" field to the value that was provided on create.
func (u *ShareUpsert) UpdatePricePoints() *ShareUpsert {
u.SetExcluded(share.FieldPricePoints)
return u
}
// AddPricePoints adds v to the "price_points" field.
func (u *ShareUpsert) AddPricePoints(v int) *ShareUpsert {
u.Add(share.FieldPricePoints, v)
return u
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (u *ShareUpsert) SetProps(v *types.ShareProps) *ShareUpsert { func (u *ShareUpsert) SetProps(v *types.ShareProps) *ShareUpsert {
u.Set(share.FieldProps, v) u.Set(share.FieldProps, v)
@ -749,6 +829,27 @@ func (u *ShareUpsertOne) ClearRemainDownloads() *ShareUpsertOne {
}) })
} }
// SetPricePoints sets the "price_points" field.
func (u *ShareUpsertOne) SetPricePoints(v int) *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.SetPricePoints(v)
})
}
// AddPricePoints adds v to the "price_points" field.
func (u *ShareUpsertOne) AddPricePoints(v int) *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.AddPricePoints(v)
})
}
// UpdatePricePoints sets the "price_points" field to the value that was provided on create.
func (u *ShareUpsertOne) UpdatePricePoints() *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) {
s.UpdatePricePoints()
})
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (u *ShareUpsertOne) SetProps(v *types.ShareProps) *ShareUpsertOne { func (u *ShareUpsertOne) SetProps(v *types.ShareProps) *ShareUpsertOne {
return u.Update(func(s *ShareUpsert) { return u.Update(func(s *ShareUpsert) {
@ -1133,6 +1234,27 @@ func (u *ShareUpsertBulk) ClearRemainDownloads() *ShareUpsertBulk {
}) })
} }
// SetPricePoints sets the "price_points" field.
func (u *ShareUpsertBulk) SetPricePoints(v int) *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.SetPricePoints(v)
})
}
// AddPricePoints adds v to the "price_points" field.
func (u *ShareUpsertBulk) AddPricePoints(v int) *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.AddPricePoints(v)
})
}
// UpdatePricePoints sets the "price_points" field to the value that was provided on create.
func (u *ShareUpsertBulk) UpdatePricePoints() *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) {
s.UpdatePricePoints()
})
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (u *ShareUpsertBulk) SetProps(v *types.ShareProps) *ShareUpsertBulk { func (u *ShareUpsertBulk) SetProps(v *types.ShareProps) *ShareUpsertBulk {
return u.Update(func(s *ShareUpsert) { return u.Update(func(s *ShareUpsert) {

@ -4,6 +4,7 @@ package ent
import ( import (
"context" "context"
"database/sql/driver"
"fmt" "fmt"
"math" "math"
@ -13,19 +14,21 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
) )
// ShareQuery is the builder for querying Share entities. // ShareQuery is the builder for querying Share entities.
type ShareQuery struct { type ShareQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []share.OrderOption order []share.OrderOption
inters []Interceptor inters []Interceptor
predicates []predicate.Share predicates []predicate.Share
withUser *UserQuery withUser *UserQuery
withFile *FileQuery withFile *FileQuery
withFKs bool withPurchases *SharePurchaseQuery
withFKs bool
// intermediate query (i.e. traversal path). // intermediate query (i.e. traversal path).
sql *sql.Selector sql *sql.Selector
path func(context.Context) (*sql.Selector, error) path func(context.Context) (*sql.Selector, error)
@ -106,6 +109,28 @@ func (sq *ShareQuery) QueryFile() *FileQuery {
return query return query
} }
// QueryPurchases chains the current query on the "purchases" edge.
func (sq *ShareQuery) QueryPurchases() *SharePurchaseQuery {
query := (&SharePurchaseClient{config: sq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := sq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := sq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(share.Table, share.FieldID, selector),
sqlgraph.To(sharepurchase.Table, sharepurchase.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, share.PurchasesTable, share.PurchasesColumn),
)
fromU = sqlgraph.SetNeighbors(sq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Share entity from the query. // First returns the first Share entity from the query.
// Returns a *NotFoundError when no Share was found. // Returns a *NotFoundError when no Share was found.
func (sq *ShareQuery) First(ctx context.Context) (*Share, error) { func (sq *ShareQuery) First(ctx context.Context) (*Share, error) {
@ -293,13 +318,14 @@ func (sq *ShareQuery) Clone() *ShareQuery {
return nil return nil
} }
return &ShareQuery{ return &ShareQuery{
config: sq.config, config: sq.config,
ctx: sq.ctx.Clone(), ctx: sq.ctx.Clone(),
order: append([]share.OrderOption{}, sq.order...), order: append([]share.OrderOption{}, sq.order...),
inters: append([]Interceptor{}, sq.inters...), inters: append([]Interceptor{}, sq.inters...),
predicates: append([]predicate.Share{}, sq.predicates...), predicates: append([]predicate.Share{}, sq.predicates...),
withUser: sq.withUser.Clone(), withUser: sq.withUser.Clone(),
withFile: sq.withFile.Clone(), withFile: sq.withFile.Clone(),
withPurchases: sq.withPurchases.Clone(),
// clone intermediate query. // clone intermediate query.
sql: sq.sql.Clone(), sql: sq.sql.Clone(),
path: sq.path, path: sq.path,
@ -328,6 +354,17 @@ func (sq *ShareQuery) WithFile(opts ...func(*FileQuery)) *ShareQuery {
return sq return sq
} }
// WithPurchases tells the query-builder to eager-load the nodes that are connected to
// the "purchases" edge. The optional arguments are used to configure the query builder of the edge.
func (sq *ShareQuery) WithPurchases(opts ...func(*SharePurchaseQuery)) *ShareQuery {
query := (&SharePurchaseClient{config: sq.config}).Query()
for _, opt := range opts {
opt(query)
}
sq.withPurchases = query
return sq
}
// GroupBy is used to group vertices by one or more fields/columns. // GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum. // It is often used with aggregate functions, like: count, max, mean, min, sum.
// //
@ -407,9 +444,10 @@ func (sq *ShareQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Share,
nodes = []*Share{} nodes = []*Share{}
withFKs = sq.withFKs withFKs = sq.withFKs
_spec = sq.querySpec() _spec = sq.querySpec()
loadedTypes = [2]bool{ loadedTypes = [3]bool{
sq.withUser != nil, sq.withUser != nil,
sq.withFile != nil, sq.withFile != nil,
sq.withPurchases != nil,
} }
) )
if sq.withUser != nil || sq.withFile != nil { if sq.withUser != nil || sq.withFile != nil {
@ -448,6 +486,13 @@ func (sq *ShareQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Share,
return nil, err return nil, err
} }
} }
if query := sq.withPurchases; query != nil {
if err := sq.loadPurchases(ctx, query, nodes,
func(n *Share) { n.Edges.Purchases = []*SharePurchase{} },
func(n *Share, e *SharePurchase) { n.Edges.Purchases = append(n.Edges.Purchases, e) }); err != nil {
return nil, err
}
}
return nodes, nil return nodes, nil
} }
@ -515,6 +560,36 @@ func (sq *ShareQuery) loadFile(ctx context.Context, query *FileQuery, nodes []*S
} }
return nil return nil
} }
func (sq *ShareQuery) loadPurchases(ctx context.Context, query *SharePurchaseQuery, nodes []*Share, init func(*Share), assign func(*Share, *SharePurchase)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int]*Share)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(sharepurchase.FieldShareID)
}
query.Where(predicate.SharePurchase(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(share.PurchasesColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.ShareID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "share_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (sq *ShareQuery) sqlCount(ctx context.Context) (int, error) { func (sq *ShareQuery) sqlCount(ctx context.Context) (int, error) {
_spec := sq.querySpec() _spec := sq.querySpec()

@ -14,6 +14,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/inventory/types"
) )
@ -166,6 +167,27 @@ func (su *ShareUpdate) ClearRemainDownloads() *ShareUpdate {
return su return su
} }
// SetPricePoints sets the "price_points" field.
func (su *ShareUpdate) SetPricePoints(i int) *ShareUpdate {
su.mutation.ResetPricePoints()
su.mutation.SetPricePoints(i)
return su
}
// SetNillablePricePoints sets the "price_points" field if the given value is not nil.
func (su *ShareUpdate) SetNillablePricePoints(i *int) *ShareUpdate {
if i != nil {
su.SetPricePoints(*i)
}
return su
}
// AddPricePoints adds i to the "price_points" field.
func (su *ShareUpdate) AddPricePoints(i int) *ShareUpdate {
su.mutation.AddPricePoints(i)
return su
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (su *ShareUpdate) SetProps(tp *types.ShareProps) *ShareUpdate { func (su *ShareUpdate) SetProps(tp *types.ShareProps) *ShareUpdate {
su.mutation.SetProps(tp) su.mutation.SetProps(tp)
@ -216,6 +238,21 @@ func (su *ShareUpdate) SetFile(f *File) *ShareUpdate {
return su.SetFileID(f.ID) return su.SetFileID(f.ID)
} }
// AddPurchaseIDs adds the "purchases" edge to the SharePurchase entity by IDs.
func (su *ShareUpdate) AddPurchaseIDs(ids ...int) *ShareUpdate {
su.mutation.AddPurchaseIDs(ids...)
return su
}
// AddPurchases adds the "purchases" edges to the SharePurchase entity.
func (su *ShareUpdate) AddPurchases(s ...*SharePurchase) *ShareUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return su.AddPurchaseIDs(ids...)
}
// Mutation returns the ShareMutation object of the builder. // Mutation returns the ShareMutation object of the builder.
func (su *ShareUpdate) Mutation() *ShareMutation { func (su *ShareUpdate) Mutation() *ShareMutation {
return su.mutation return su.mutation
@ -233,6 +270,27 @@ func (su *ShareUpdate) ClearFile() *ShareUpdate {
return su return su
} }
// ClearPurchases clears all "purchases" edges to the SharePurchase entity.
func (su *ShareUpdate) ClearPurchases() *ShareUpdate {
su.mutation.ClearPurchases()
return su
}
// RemovePurchaseIDs removes the "purchases" edge to SharePurchase entities by IDs.
func (su *ShareUpdate) RemovePurchaseIDs(ids ...int) *ShareUpdate {
su.mutation.RemovePurchaseIDs(ids...)
return su
}
// RemovePurchases removes "purchases" edges to SharePurchase entities.
func (su *ShareUpdate) RemovePurchases(s ...*SharePurchase) *ShareUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return su.RemovePurchaseIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (su *ShareUpdate) Save(ctx context.Context) (int, error) { func (su *ShareUpdate) Save(ctx context.Context) (int, error) {
if err := su.defaults(); err != nil { if err := su.defaults(); err != nil {
@ -275,7 +333,20 @@ func (su *ShareUpdate) defaults() error {
return nil return nil
} }
// check runs all checks and user-defined validators on the builder.
func (su *ShareUpdate) check() error {
if v, ok := su.mutation.PricePoints(); ok {
if err := share.PricePointsValidator(v); err != nil {
return &ValidationError{Name: "price_points", err: fmt.Errorf(`ent: validator failed for field "Share.price_points": %w`, err)}
}
}
return nil
}
func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) { func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := su.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(share.Table, share.Columns, sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt)) _spec := sqlgraph.NewUpdateSpec(share.Table, share.Columns, sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt))
if ps := su.mutation.predicates; len(ps) > 0 { if ps := su.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) { _spec.Predicate = func(selector *sql.Selector) {
@ -326,6 +397,12 @@ func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) {
if su.mutation.RemainDownloadsCleared() { if su.mutation.RemainDownloadsCleared() {
_spec.ClearField(share.FieldRemainDownloads, field.TypeInt) _spec.ClearField(share.FieldRemainDownloads, field.TypeInt)
} }
if value, ok := su.mutation.PricePoints(); ok {
_spec.SetField(share.FieldPricePoints, field.TypeInt, value)
}
if value, ok := su.mutation.AddedPricePoints(); ok {
_spec.AddField(share.FieldPricePoints, field.TypeInt, value)
}
if value, ok := su.mutation.Props(); ok { if value, ok := su.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value) _spec.SetField(share.FieldProps, field.TypeJSON, value)
} }
@ -390,6 +467,51 @@ func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) {
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if su.mutation.PurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := su.mutation.RemovedPurchasesIDs(); len(nodes) > 0 && !su.mutation.PurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := su.mutation.PurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, su.driver, _spec); err != nil { if n, err = sqlgraph.UpdateNodes(ctx, su.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok { if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{share.Label} err = &NotFoundError{share.Label}
@ -545,6 +667,27 @@ func (suo *ShareUpdateOne) ClearRemainDownloads() *ShareUpdateOne {
return suo return suo
} }
// SetPricePoints sets the "price_points" field.
func (suo *ShareUpdateOne) SetPricePoints(i int) *ShareUpdateOne {
suo.mutation.ResetPricePoints()
suo.mutation.SetPricePoints(i)
return suo
}
// SetNillablePricePoints sets the "price_points" field if the given value is not nil.
func (suo *ShareUpdateOne) SetNillablePricePoints(i *int) *ShareUpdateOne {
if i != nil {
suo.SetPricePoints(*i)
}
return suo
}
// AddPricePoints adds i to the "price_points" field.
func (suo *ShareUpdateOne) AddPricePoints(i int) *ShareUpdateOne {
suo.mutation.AddPricePoints(i)
return suo
}
// SetProps sets the "props" field. // SetProps sets the "props" field.
func (suo *ShareUpdateOne) SetProps(tp *types.ShareProps) *ShareUpdateOne { func (suo *ShareUpdateOne) SetProps(tp *types.ShareProps) *ShareUpdateOne {
suo.mutation.SetProps(tp) suo.mutation.SetProps(tp)
@ -595,6 +738,21 @@ func (suo *ShareUpdateOne) SetFile(f *File) *ShareUpdateOne {
return suo.SetFileID(f.ID) return suo.SetFileID(f.ID)
} }
// AddPurchaseIDs adds the "purchases" edge to the SharePurchase entity by IDs.
func (suo *ShareUpdateOne) AddPurchaseIDs(ids ...int) *ShareUpdateOne {
suo.mutation.AddPurchaseIDs(ids...)
return suo
}
// AddPurchases adds the "purchases" edges to the SharePurchase entity.
func (suo *ShareUpdateOne) AddPurchases(s ...*SharePurchase) *ShareUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return suo.AddPurchaseIDs(ids...)
}
// Mutation returns the ShareMutation object of the builder. // Mutation returns the ShareMutation object of the builder.
func (suo *ShareUpdateOne) Mutation() *ShareMutation { func (suo *ShareUpdateOne) Mutation() *ShareMutation {
return suo.mutation return suo.mutation
@ -612,6 +770,27 @@ func (suo *ShareUpdateOne) ClearFile() *ShareUpdateOne {
return suo return suo
} }
// ClearPurchases clears all "purchases" edges to the SharePurchase entity.
func (suo *ShareUpdateOne) ClearPurchases() *ShareUpdateOne {
suo.mutation.ClearPurchases()
return suo
}
// RemovePurchaseIDs removes the "purchases" edge to SharePurchase entities by IDs.
func (suo *ShareUpdateOne) RemovePurchaseIDs(ids ...int) *ShareUpdateOne {
suo.mutation.RemovePurchaseIDs(ids...)
return suo
}
// RemovePurchases removes "purchases" edges to SharePurchase entities.
func (suo *ShareUpdateOne) RemovePurchases(s ...*SharePurchase) *ShareUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return suo.RemovePurchaseIDs(ids...)
}
// Where appends a list predicates to the ShareUpdate builder. // Where appends a list predicates to the ShareUpdate builder.
func (suo *ShareUpdateOne) Where(ps ...predicate.Share) *ShareUpdateOne { func (suo *ShareUpdateOne) Where(ps ...predicate.Share) *ShareUpdateOne {
suo.mutation.Where(ps...) suo.mutation.Where(ps...)
@ -667,7 +846,20 @@ func (suo *ShareUpdateOne) defaults() error {
return nil return nil
} }
// check runs all checks and user-defined validators on the builder.
func (suo *ShareUpdateOne) check() error {
if v, ok := suo.mutation.PricePoints(); ok {
if err := share.PricePointsValidator(v); err != nil {
return &ValidationError{Name: "price_points", err: fmt.Errorf(`ent: validator failed for field "Share.price_points": %w`, err)}
}
}
return nil
}
func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error) { func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error) {
if err := suo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(share.Table, share.Columns, sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt)) _spec := sqlgraph.NewUpdateSpec(share.Table, share.Columns, sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt))
id, ok := suo.mutation.ID() id, ok := suo.mutation.ID()
if !ok { if !ok {
@ -735,6 +927,12 @@ func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error
if suo.mutation.RemainDownloadsCleared() { if suo.mutation.RemainDownloadsCleared() {
_spec.ClearField(share.FieldRemainDownloads, field.TypeInt) _spec.ClearField(share.FieldRemainDownloads, field.TypeInt)
} }
if value, ok := suo.mutation.PricePoints(); ok {
_spec.SetField(share.FieldPricePoints, field.TypeInt, value)
}
if value, ok := suo.mutation.AddedPricePoints(); ok {
_spec.AddField(share.FieldPricePoints, field.TypeInt, value)
}
if value, ok := suo.mutation.Props(); ok { if value, ok := suo.mutation.Props(); ok {
_spec.SetField(share.FieldProps, field.TypeJSON, value) _spec.SetField(share.FieldProps, field.TypeJSON, value)
} }
@ -799,6 +997,51 @@ func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if suo.mutation.PurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := suo.mutation.RemovedPurchasesIDs(); len(nodes) > 0 && !suo.mutation.PurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := suo.mutation.PurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: share.PurchasesTable,
Columns: []string{share.PurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Share{config: suo.config} _node = &Share{config: suo.config}
_spec.Assign = _node.assignValues _spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues _spec.ScanValues = _node.scanValues

@ -0,0 +1,239 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SharePurchase is the model entity for the SharePurchase schema.
type SharePurchase struct {
config `json:"-"`
// ID of the ent.
ID int `json:"id,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// DeletedAt holds the value of the "deleted_at" field.
DeletedAt *time.Time `json:"deleted_at,omitempty"`
// ShareID holds the value of the "share_id" field.
ShareID int `json:"share_id,omitempty"`
// BuyerID holds the value of the "buyer_id" field.
BuyerID int `json:"buyer_id,omitempty"`
// Points holds the value of the "points" field.
Points int `json:"points,omitempty"`
// Ticket holds the value of the "ticket" field.
Ticket string `json:"ticket,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the SharePurchaseQuery when eager-loading is set.
Edges SharePurchaseEdges `json:"edges"`
selectValues sql.SelectValues
}
// SharePurchaseEdges holds the relations/edges for other nodes in the graph.
type SharePurchaseEdges struct {
// Share holds the value of the share edge.
Share *Share `json:"share,omitempty"`
// Buyer holds the value of the buyer edge.
Buyer *User `json:"buyer,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
}
// ShareOrErr returns the Share value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e SharePurchaseEdges) ShareOrErr() (*Share, error) {
if e.loadedTypes[0] {
if e.Share == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: share.Label}
}
return e.Share, nil
}
return nil, &NotLoadedError{edge: "share"}
}
// BuyerOrErr returns the Buyer value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e SharePurchaseEdges) BuyerOrErr() (*User, error) {
if e.loadedTypes[1] {
if e.Buyer == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: user.Label}
}
return e.Buyer, nil
}
return nil, &NotLoadedError{edge: "buyer"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*SharePurchase) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case sharepurchase.FieldID, sharepurchase.FieldShareID, sharepurchase.FieldBuyerID, sharepurchase.FieldPoints:
values[i] = new(sql.NullInt64)
case sharepurchase.FieldTicket:
values[i] = new(sql.NullString)
case sharepurchase.FieldCreatedAt, sharepurchase.FieldUpdatedAt, sharepurchase.FieldDeletedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the SharePurchase fields.
func (sp *SharePurchase) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case sharepurchase.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
sp.ID = int(value.Int64)
case sharepurchase.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
sp.CreatedAt = value.Time
}
case sharepurchase.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
sp.UpdatedAt = value.Time
}
case sharepurchase.FieldDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field deleted_at", values[i])
} else if value.Valid {
sp.DeletedAt = new(time.Time)
*sp.DeletedAt = value.Time
}
case sharepurchase.FieldShareID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field share_id", values[i])
} else if value.Valid {
sp.ShareID = int(value.Int64)
}
case sharepurchase.FieldBuyerID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field buyer_id", values[i])
} else if value.Valid {
sp.BuyerID = int(value.Int64)
}
case sharepurchase.FieldPoints:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field points", values[i])
} else if value.Valid {
sp.Points = int(value.Int64)
}
case sharepurchase.FieldTicket:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field ticket", values[i])
} else if value.Valid {
sp.Ticket = value.String
}
default:
sp.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the SharePurchase.
// This includes values selected through modifiers, order, etc.
func (sp *SharePurchase) Value(name string) (ent.Value, error) {
return sp.selectValues.Get(name)
}
// QueryShare queries the "share" edge of the SharePurchase entity.
func (sp *SharePurchase) QueryShare() *ShareQuery {
return NewSharePurchaseClient(sp.config).QueryShare(sp)
}
// QueryBuyer queries the "buyer" edge of the SharePurchase entity.
func (sp *SharePurchase) QueryBuyer() *UserQuery {
return NewSharePurchaseClient(sp.config).QueryBuyer(sp)
}
// Update returns a builder for updating this SharePurchase.
// Note that you need to call SharePurchase.Unwrap() before calling this method if this SharePurchase
// was returned from a transaction, and the transaction was committed or rolled back.
func (sp *SharePurchase) Update() *SharePurchaseUpdateOne {
return NewSharePurchaseClient(sp.config).UpdateOne(sp)
}
// Unwrap unwraps the SharePurchase entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (sp *SharePurchase) Unwrap() *SharePurchase {
_tx, ok := sp.config.driver.(*txDriver)
if !ok {
panic("ent: SharePurchase is not a transactional entity")
}
sp.config.driver = _tx.drv
return sp
}
// String implements the fmt.Stringer.
func (sp *SharePurchase) String() string {
var builder strings.Builder
builder.WriteString("SharePurchase(")
builder.WriteString(fmt.Sprintf("id=%v, ", sp.ID))
builder.WriteString("created_at=")
builder.WriteString(sp.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(sp.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := sp.DeletedAt; v != nil {
builder.WriteString("deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("share_id=")
builder.WriteString(fmt.Sprintf("%v", sp.ShareID))
builder.WriteString(", ")
builder.WriteString("buyer_id=")
builder.WriteString(fmt.Sprintf("%v", sp.BuyerID))
builder.WriteString(", ")
builder.WriteString("points=")
builder.WriteString(fmt.Sprintf("%v", sp.Points))
builder.WriteString(", ")
builder.WriteString("ticket=")
builder.WriteString(sp.Ticket)
builder.WriteByte(')')
return builder.String()
}
// SetShare manually set the edge as loaded state.
func (e *SharePurchase) SetShare(v *Share) {
e.Edges.Share = v
e.Edges.loadedTypes[0] = true
}
// SetBuyer manually set the edge as loaded state.
func (e *SharePurchase) SetBuyer(v *User) {
e.Edges.Buyer = v
e.Edges.loadedTypes[1] = true
}
// SharePurchases is a parsable slice of SharePurchase.
type SharePurchases []*SharePurchase

@ -0,0 +1,163 @@
// Code generated by ent, DO NOT EDIT.
package sharepurchase
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the sharepurchase type in the database.
Label = "share_purchase"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldDeletedAt holds the string denoting the deleted_at field in the database.
FieldDeletedAt = "deleted_at"
// FieldShareID holds the string denoting the share_id field in the database.
FieldShareID = "share_id"
// FieldBuyerID holds the string denoting the buyer_id field in the database.
FieldBuyerID = "buyer_id"
// FieldPoints holds the string denoting the points field in the database.
FieldPoints = "points"
// FieldTicket holds the string denoting the ticket field in the database.
FieldTicket = "ticket"
// EdgeShare holds the string denoting the share edge name in mutations.
EdgeShare = "share"
// EdgeBuyer holds the string denoting the buyer edge name in mutations.
EdgeBuyer = "buyer"
// Table holds the table name of the sharepurchase in the database.
Table = "share_purchases"
// ShareTable is the table that holds the share relation/edge.
ShareTable = "share_purchases"
// ShareInverseTable is the table name for the Share entity.
// It exists in this package in order to avoid circular dependency with the "share" package.
ShareInverseTable = "shares"
// ShareColumn is the table column denoting the share relation/edge.
ShareColumn = "share_id"
// BuyerTable is the table that holds the buyer relation/edge.
BuyerTable = "share_purchases"
// BuyerInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
BuyerInverseTable = "users"
// BuyerColumn is the table column denoting the buyer relation/edge.
BuyerColumn = "buyer_id"
)
// Columns holds all SQL columns for sharepurchase fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldDeletedAt,
FieldShareID,
FieldBuyerID,
FieldPoints,
FieldTicket,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
// Note that the variables below are initialized by the runtime
// package on the initialization of the application. Therefore,
// it should be imported in the main as follows:
//
// import _ "github.com/cloudreve/Cloudreve/v4/ent/runtime"
var (
Hooks [1]ent.Hook
Interceptors [1]ent.Interceptor
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// TicketValidator is a validator for the "ticket" field. It is called by the builders before save.
TicketValidator func(string) error
)
// OrderOption defines the ordering options for the SharePurchase queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByDeletedAt orders the results by the deleted_at field.
func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDeletedAt, opts...).ToFunc()
}
// ByShareID orders the results by the share_id field.
func ByShareID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldShareID, opts...).ToFunc()
}
// ByBuyerID orders the results by the buyer_id field.
func ByBuyerID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBuyerID, opts...).ToFunc()
}
// ByPoints orders the results by the points field.
func ByPoints(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPoints, opts...).ToFunc()
}
// ByTicket orders the results by the ticket field.
func ByTicket(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTicket, opts...).ToFunc()
}
// ByShareField orders the results by share field.
func ByShareField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newShareStep(), sql.OrderByField(field, opts...))
}
}
// ByBuyerField orders the results by buyer field.
func ByBuyerField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newBuyerStep(), sql.OrderByField(field, opts...))
}
}
func newShareStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ShareInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ShareTable, ShareColumn),
)
}
func newBuyerStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(BuyerInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, BuyerTable, BuyerColumn),
)
}

@ -0,0 +1,427 @@
// Code generated by ent, DO NOT EDIT.
package sharepurchase
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldUpdatedAt, v))
}
// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ.
func DeletedAt(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldDeletedAt, v))
}
// ShareID applies equality check predicate on the "share_id" field. It's identical to ShareIDEQ.
func ShareID(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldShareID, v))
}
// BuyerID applies equality check predicate on the "buyer_id" field. It's identical to BuyerIDEQ.
func BuyerID(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldBuyerID, v))
}
// Points applies equality check predicate on the "points" field. It's identical to PointsEQ.
func Points(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldPoints, v))
}
// Ticket applies equality check predicate on the "ticket" field. It's identical to TicketEQ.
func Ticket(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldTicket, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldUpdatedAt, v))
}
// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
func DeletedAtEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldDeletedAt, v))
}
// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
func DeletedAtNEQ(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldDeletedAt, v))
}
// DeletedAtIn applies the In predicate on the "deleted_at" field.
func DeletedAtIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldDeletedAt, vs...))
}
// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
func DeletedAtNotIn(vs ...time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldDeletedAt, vs...))
}
// DeletedAtGT applies the GT predicate on the "deleted_at" field.
func DeletedAtGT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldDeletedAt, v))
}
// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
func DeletedAtGTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldDeletedAt, v))
}
// DeletedAtLT applies the LT predicate on the "deleted_at" field.
func DeletedAtLT(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldDeletedAt, v))
}
// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
func DeletedAtLTE(v time.Time) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldDeletedAt, v))
}
// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
func DeletedAtIsNil() predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIsNull(FieldDeletedAt))
}
// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
func DeletedAtNotNil() predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotNull(FieldDeletedAt))
}
// ShareIDEQ applies the EQ predicate on the "share_id" field.
func ShareIDEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldShareID, v))
}
// ShareIDNEQ applies the NEQ predicate on the "share_id" field.
func ShareIDNEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldShareID, v))
}
// ShareIDIn applies the In predicate on the "share_id" field.
func ShareIDIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldShareID, vs...))
}
// ShareIDNotIn applies the NotIn predicate on the "share_id" field.
func ShareIDNotIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldShareID, vs...))
}
// BuyerIDEQ applies the EQ predicate on the "buyer_id" field.
func BuyerIDEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldBuyerID, v))
}
// BuyerIDNEQ applies the NEQ predicate on the "buyer_id" field.
func BuyerIDNEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldBuyerID, v))
}
// BuyerIDIn applies the In predicate on the "buyer_id" field.
func BuyerIDIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldBuyerID, vs...))
}
// BuyerIDNotIn applies the NotIn predicate on the "buyer_id" field.
func BuyerIDNotIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldBuyerID, vs...))
}
// PointsEQ applies the EQ predicate on the "points" field.
func PointsEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldPoints, v))
}
// PointsNEQ applies the NEQ predicate on the "points" field.
func PointsNEQ(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldPoints, v))
}
// PointsIn applies the In predicate on the "points" field.
func PointsIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldPoints, vs...))
}
// PointsNotIn applies the NotIn predicate on the "points" field.
func PointsNotIn(vs ...int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldPoints, vs...))
}
// PointsGT applies the GT predicate on the "points" field.
func PointsGT(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldPoints, v))
}
// PointsGTE applies the GTE predicate on the "points" field.
func PointsGTE(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldPoints, v))
}
// PointsLT applies the LT predicate on the "points" field.
func PointsLT(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldPoints, v))
}
// PointsLTE applies the LTE predicate on the "points" field.
func PointsLTE(v int) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldPoints, v))
}
// TicketEQ applies the EQ predicate on the "ticket" field.
func TicketEQ(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEQ(FieldTicket, v))
}
// TicketNEQ applies the NEQ predicate on the "ticket" field.
func TicketNEQ(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNEQ(FieldTicket, v))
}
// TicketIn applies the In predicate on the "ticket" field.
func TicketIn(vs ...string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldIn(FieldTicket, vs...))
}
// TicketNotIn applies the NotIn predicate on the "ticket" field.
func TicketNotIn(vs ...string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldNotIn(FieldTicket, vs...))
}
// TicketGT applies the GT predicate on the "ticket" field.
func TicketGT(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGT(FieldTicket, v))
}
// TicketGTE applies the GTE predicate on the "ticket" field.
func TicketGTE(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldGTE(FieldTicket, v))
}
// TicketLT applies the LT predicate on the "ticket" field.
func TicketLT(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLT(FieldTicket, v))
}
// TicketLTE applies the LTE predicate on the "ticket" field.
func TicketLTE(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldLTE(FieldTicket, v))
}
// TicketContains applies the Contains predicate on the "ticket" field.
func TicketContains(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldContains(FieldTicket, v))
}
// TicketHasPrefix applies the HasPrefix predicate on the "ticket" field.
func TicketHasPrefix(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldHasPrefix(FieldTicket, v))
}
// TicketHasSuffix applies the HasSuffix predicate on the "ticket" field.
func TicketHasSuffix(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldHasSuffix(FieldTicket, v))
}
// TicketEqualFold applies the EqualFold predicate on the "ticket" field.
func TicketEqualFold(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldEqualFold(FieldTicket, v))
}
// TicketContainsFold applies the ContainsFold predicate on the "ticket" field.
func TicketContainsFold(v string) predicate.SharePurchase {
return predicate.SharePurchase(sql.FieldContainsFold(FieldTicket, v))
}
// HasShare applies the HasEdge predicate on the "share" edge.
func HasShare() predicate.SharePurchase {
return predicate.SharePurchase(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ShareTable, ShareColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasShareWith applies the HasEdge predicate on the "share" edge with a given conditions (other predicates).
func HasShareWith(preds ...predicate.Share) predicate.SharePurchase {
return predicate.SharePurchase(func(s *sql.Selector) {
step := newShareStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasBuyer applies the HasEdge predicate on the "buyer" edge.
func HasBuyer() predicate.SharePurchase {
return predicate.SharePurchase(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, BuyerTable, BuyerColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasBuyerWith applies the HasEdge predicate on the "buyer" edge with a given conditions (other predicates).
func HasBuyerWith(preds ...predicate.User) predicate.SharePurchase {
return predicate.SharePurchase(func(s *sql.Selector) {
step := newBuyerStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.SharePurchase) predicate.SharePurchase {
return predicate.SharePurchase(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.SharePurchase) predicate.SharePurchase {
return predicate.SharePurchase(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.SharePurchase) predicate.SharePurchase {
return predicate.SharePurchase(sql.NotPredicates(p))
}

@ -0,0 +1,890 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SharePurchaseCreate is the builder for creating a SharePurchase entity.
type SharePurchaseCreate struct {
config
mutation *SharePurchaseMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetCreatedAt sets the "created_at" field.
func (spc *SharePurchaseCreate) SetCreatedAt(t time.Time) *SharePurchaseCreate {
spc.mutation.SetCreatedAt(t)
return spc
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (spc *SharePurchaseCreate) SetNillableCreatedAt(t *time.Time) *SharePurchaseCreate {
if t != nil {
spc.SetCreatedAt(*t)
}
return spc
}
// SetUpdatedAt sets the "updated_at" field.
func (spc *SharePurchaseCreate) SetUpdatedAt(t time.Time) *SharePurchaseCreate {
spc.mutation.SetUpdatedAt(t)
return spc
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (spc *SharePurchaseCreate) SetNillableUpdatedAt(t *time.Time) *SharePurchaseCreate {
if t != nil {
spc.SetUpdatedAt(*t)
}
return spc
}
// SetDeletedAt sets the "deleted_at" field.
func (spc *SharePurchaseCreate) SetDeletedAt(t time.Time) *SharePurchaseCreate {
spc.mutation.SetDeletedAt(t)
return spc
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (spc *SharePurchaseCreate) SetNillableDeletedAt(t *time.Time) *SharePurchaseCreate {
if t != nil {
spc.SetDeletedAt(*t)
}
return spc
}
// SetShareID sets the "share_id" field.
func (spc *SharePurchaseCreate) SetShareID(i int) *SharePurchaseCreate {
spc.mutation.SetShareID(i)
return spc
}
// SetBuyerID sets the "buyer_id" field.
func (spc *SharePurchaseCreate) SetBuyerID(i int) *SharePurchaseCreate {
spc.mutation.SetBuyerID(i)
return spc
}
// SetPoints sets the "points" field.
func (spc *SharePurchaseCreate) SetPoints(i int) *SharePurchaseCreate {
spc.mutation.SetPoints(i)
return spc
}
// SetTicket sets the "ticket" field.
func (spc *SharePurchaseCreate) SetTicket(s string) *SharePurchaseCreate {
spc.mutation.SetTicket(s)
return spc
}
// SetShare sets the "share" edge to the Share entity.
func (spc *SharePurchaseCreate) SetShare(s *Share) *SharePurchaseCreate {
return spc.SetShareID(s.ID)
}
// SetBuyer sets the "buyer" edge to the User entity.
func (spc *SharePurchaseCreate) SetBuyer(u *User) *SharePurchaseCreate {
return spc.SetBuyerID(u.ID)
}
// Mutation returns the SharePurchaseMutation object of the builder.
func (spc *SharePurchaseCreate) Mutation() *SharePurchaseMutation {
return spc.mutation
}
// Save creates the SharePurchase in the database.
func (spc *SharePurchaseCreate) Save(ctx context.Context) (*SharePurchase, error) {
if err := spc.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, spc.sqlSave, spc.mutation, spc.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (spc *SharePurchaseCreate) SaveX(ctx context.Context) *SharePurchase {
v, err := spc.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (spc *SharePurchaseCreate) Exec(ctx context.Context) error {
_, err := spc.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (spc *SharePurchaseCreate) ExecX(ctx context.Context) {
if err := spc.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (spc *SharePurchaseCreate) defaults() error {
if _, ok := spc.mutation.CreatedAt(); !ok {
if sharepurchase.DefaultCreatedAt == nil {
return fmt.Errorf("ent: uninitialized sharepurchase.DefaultCreatedAt (forgotten import ent/runtime?)")
}
v := sharepurchase.DefaultCreatedAt()
spc.mutation.SetCreatedAt(v)
}
if _, ok := spc.mutation.UpdatedAt(); !ok {
if sharepurchase.DefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized sharepurchase.DefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := sharepurchase.DefaultUpdatedAt()
spc.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (spc *SharePurchaseCreate) check() error {
if _, ok := spc.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SharePurchase.created_at"`)}
}
if _, ok := spc.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SharePurchase.updated_at"`)}
}
if _, ok := spc.mutation.ShareID(); !ok {
return &ValidationError{Name: "share_id", err: errors.New(`ent: missing required field "SharePurchase.share_id"`)}
}
if _, ok := spc.mutation.BuyerID(); !ok {
return &ValidationError{Name: "buyer_id", err: errors.New(`ent: missing required field "SharePurchase.buyer_id"`)}
}
if _, ok := spc.mutation.Points(); !ok {
return &ValidationError{Name: "points", err: errors.New(`ent: missing required field "SharePurchase.points"`)}
}
if _, ok := spc.mutation.Ticket(); !ok {
return &ValidationError{Name: "ticket", err: errors.New(`ent: missing required field "SharePurchase.ticket"`)}
}
if v, ok := spc.mutation.Ticket(); ok {
if err := sharepurchase.TicketValidator(v); err != nil {
return &ValidationError{Name: "ticket", err: fmt.Errorf(`ent: validator failed for field "SharePurchase.ticket": %w`, err)}
}
}
if _, ok := spc.mutation.ShareID(); !ok {
return &ValidationError{Name: "share", err: errors.New(`ent: missing required edge "SharePurchase.share"`)}
}
if _, ok := spc.mutation.BuyerID(); !ok {
return &ValidationError{Name: "buyer", err: errors.New(`ent: missing required edge "SharePurchase.buyer"`)}
}
return nil
}
func (spc *SharePurchaseCreate) sqlSave(ctx context.Context) (*SharePurchase, error) {
if err := spc.check(); err != nil {
return nil, err
}
_node, _spec := spc.createSpec()
if err := sqlgraph.CreateNode(ctx, spc.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
id := _spec.ID.Value.(int64)
_node.ID = int(id)
spc.mutation.id = &_node.ID
spc.mutation.done = true
return _node, nil
}
func (spc *SharePurchaseCreate) createSpec() (*SharePurchase, *sqlgraph.CreateSpec) {
var (
_node = &SharePurchase{config: spc.config}
_spec = sqlgraph.NewCreateSpec(sharepurchase.Table, sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt))
)
if id, ok := spc.mutation.ID(); ok {
_node.ID = id
id64 := int64(id)
_spec.ID.Value = id64
}
_spec.OnConflict = spc.conflict
if value, ok := spc.mutation.CreatedAt(); ok {
_spec.SetField(sharepurchase.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := spc.mutation.UpdatedAt(); ok {
_spec.SetField(sharepurchase.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := spc.mutation.DeletedAt(); ok {
_spec.SetField(sharepurchase.FieldDeletedAt, field.TypeTime, value)
_node.DeletedAt = &value
}
if value, ok := spc.mutation.Points(); ok {
_spec.SetField(sharepurchase.FieldPoints, field.TypeInt, value)
_node.Points = value
}
if value, ok := spc.mutation.Ticket(); ok {
_spec.SetField(sharepurchase.FieldTicket, field.TypeString, value)
_node.Ticket = value
}
if nodes := spc.mutation.ShareIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.ShareTable,
Columns: []string{sharepurchase.ShareColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.ShareID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := spc.mutation.BuyerIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.BuyerTable,
Columns: []string{sharepurchase.BuyerColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.BuyerID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.SharePurchase.Create().
// SetCreatedAt(v).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.SharePurchaseUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (spc *SharePurchaseCreate) OnConflict(opts ...sql.ConflictOption) *SharePurchaseUpsertOne {
spc.conflict = opts
return &SharePurchaseUpsertOne{
create: spc,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (spc *SharePurchaseCreate) OnConflictColumns(columns ...string) *SharePurchaseUpsertOne {
spc.conflict = append(spc.conflict, sql.ConflictColumns(columns...))
return &SharePurchaseUpsertOne{
create: spc,
}
}
type (
// SharePurchaseUpsertOne is the builder for "upsert"-ing
// one SharePurchase node.
SharePurchaseUpsertOne struct {
create *SharePurchaseCreate
}
// SharePurchaseUpsert is the "OnConflict" setter.
SharePurchaseUpsert struct {
*sql.UpdateSet
}
)
// SetUpdatedAt sets the "updated_at" field.
func (u *SharePurchaseUpsert) SetUpdatedAt(v time.Time) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldUpdatedAt, v)
return u
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdateUpdatedAt() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldUpdatedAt)
return u
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SharePurchaseUpsert) SetDeletedAt(v time.Time) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldDeletedAt, v)
return u
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdateDeletedAt() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldDeletedAt)
return u
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SharePurchaseUpsert) ClearDeletedAt() *SharePurchaseUpsert {
u.SetNull(sharepurchase.FieldDeletedAt)
return u
}
// SetShareID sets the "share_id" field.
func (u *SharePurchaseUpsert) SetShareID(v int) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldShareID, v)
return u
}
// UpdateShareID sets the "share_id" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdateShareID() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldShareID)
return u
}
// SetBuyerID sets the "buyer_id" field.
func (u *SharePurchaseUpsert) SetBuyerID(v int) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldBuyerID, v)
return u
}
// UpdateBuyerID sets the "buyer_id" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdateBuyerID() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldBuyerID)
return u
}
// SetPoints sets the "points" field.
func (u *SharePurchaseUpsert) SetPoints(v int) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldPoints, v)
return u
}
// UpdatePoints sets the "points" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdatePoints() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldPoints)
return u
}
// AddPoints adds v to the "points" field.
func (u *SharePurchaseUpsert) AddPoints(v int) *SharePurchaseUpsert {
u.Add(sharepurchase.FieldPoints, v)
return u
}
// SetTicket sets the "ticket" field.
func (u *SharePurchaseUpsert) SetTicket(v string) *SharePurchaseUpsert {
u.Set(sharepurchase.FieldTicket, v)
return u
}
// UpdateTicket sets the "ticket" field to the value that was provided on create.
func (u *SharePurchaseUpsert) UpdateTicket() *SharePurchaseUpsert {
u.SetExcluded(sharepurchase.FieldTicket)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *SharePurchaseUpsertOne) UpdateNewValues() *SharePurchaseUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
if _, exists := u.create.mutation.CreatedAt(); exists {
s.SetIgnore(sharepurchase.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *SharePurchaseUpsertOne) Ignore() *SharePurchaseUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *SharePurchaseUpsertOne) DoNothing() *SharePurchaseUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the SharePurchaseCreate.OnConflict
// documentation for more info.
func (u *SharePurchaseUpsertOne) Update(set func(*SharePurchaseUpsert)) *SharePurchaseUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&SharePurchaseUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *SharePurchaseUpsertOne) SetUpdatedAt(v time.Time) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdateUpdatedAt() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SharePurchaseUpsertOne) SetDeletedAt(v time.Time) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdateDeletedAt() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SharePurchaseUpsertOne) ClearDeletedAt() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.ClearDeletedAt()
})
}
// SetShareID sets the "share_id" field.
func (u *SharePurchaseUpsertOne) SetShareID(v int) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetShareID(v)
})
}
// UpdateShareID sets the "share_id" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdateShareID() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateShareID()
})
}
// SetBuyerID sets the "buyer_id" field.
func (u *SharePurchaseUpsertOne) SetBuyerID(v int) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetBuyerID(v)
})
}
// UpdateBuyerID sets the "buyer_id" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdateBuyerID() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateBuyerID()
})
}
// SetPoints sets the "points" field.
func (u *SharePurchaseUpsertOne) SetPoints(v int) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetPoints(v)
})
}
// AddPoints adds v to the "points" field.
func (u *SharePurchaseUpsertOne) AddPoints(v int) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.AddPoints(v)
})
}
// UpdatePoints sets the "points" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdatePoints() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdatePoints()
})
}
// SetTicket sets the "ticket" field.
func (u *SharePurchaseUpsertOne) SetTicket(v string) *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetTicket(v)
})
}
// UpdateTicket sets the "ticket" field to the value that was provided on create.
func (u *SharePurchaseUpsertOne) UpdateTicket() *SharePurchaseUpsertOne {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateTicket()
})
}
// Exec executes the query.
func (u *SharePurchaseUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for SharePurchaseCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *SharePurchaseUpsertOne) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
// Exec executes the UPSERT query and returns the inserted/updated ID.
func (u *SharePurchaseUpsertOne) ID(ctx context.Context) (id int, err error) {
node, err := u.create.Save(ctx)
if err != nil {
return id, err
}
return node.ID, nil
}
// IDX is like ID, but panics if an error occurs.
func (u *SharePurchaseUpsertOne) IDX(ctx context.Context) int {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
func (m *SharePurchaseCreate) SetRawID(t int) *SharePurchaseCreate {
m.mutation.SetRawID(t)
return m
}
// SharePurchaseCreateBulk is the builder for creating many SharePurchase entities in bulk.
type SharePurchaseCreateBulk struct {
config
err error
builders []*SharePurchaseCreate
conflict []sql.ConflictOption
}
// Save creates the SharePurchase entities in the database.
func (spcb *SharePurchaseCreateBulk) Save(ctx context.Context) ([]*SharePurchase, error) {
if spcb.err != nil {
return nil, spcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(spcb.builders))
nodes := make([]*SharePurchase, len(spcb.builders))
mutators := make([]Mutator, len(spcb.builders))
for i := range spcb.builders {
func(i int, root context.Context) {
builder := spcb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*SharePurchaseMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, spcb.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
spec.OnConflict = spcb.conflict
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, spcb.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
if specs[i].ID.Value != nil {
id := specs[i].ID.Value.(int64)
nodes[i].ID = int(id)
}
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, spcb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (spcb *SharePurchaseCreateBulk) SaveX(ctx context.Context) []*SharePurchase {
v, err := spcb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (spcb *SharePurchaseCreateBulk) Exec(ctx context.Context) error {
_, err := spcb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (spcb *SharePurchaseCreateBulk) ExecX(ctx context.Context) {
if err := spcb.Exec(ctx); err != nil {
panic(err)
}
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.SharePurchase.CreateBulk(builders...).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.SharePurchaseUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (spcb *SharePurchaseCreateBulk) OnConflict(opts ...sql.ConflictOption) *SharePurchaseUpsertBulk {
spcb.conflict = opts
return &SharePurchaseUpsertBulk{
create: spcb,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (spcb *SharePurchaseCreateBulk) OnConflictColumns(columns ...string) *SharePurchaseUpsertBulk {
spcb.conflict = append(spcb.conflict, sql.ConflictColumns(columns...))
return &SharePurchaseUpsertBulk{
create: spcb,
}
}
// SharePurchaseUpsertBulk is the builder for "upsert"-ing
// a bulk of SharePurchase nodes.
type SharePurchaseUpsertBulk struct {
create *SharePurchaseCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *SharePurchaseUpsertBulk) UpdateNewValues() *SharePurchaseUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
for _, b := range u.create.builders {
if _, exists := b.mutation.CreatedAt(); exists {
s.SetIgnore(sharepurchase.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.SharePurchase.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *SharePurchaseUpsertBulk) Ignore() *SharePurchaseUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *SharePurchaseUpsertBulk) DoNothing() *SharePurchaseUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the SharePurchaseCreateBulk.OnConflict
// documentation for more info.
func (u *SharePurchaseUpsertBulk) Update(set func(*SharePurchaseUpsert)) *SharePurchaseUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&SharePurchaseUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *SharePurchaseUpsertBulk) SetUpdatedAt(v time.Time) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdateUpdatedAt() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SharePurchaseUpsertBulk) SetDeletedAt(v time.Time) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdateDeletedAt() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SharePurchaseUpsertBulk) ClearDeletedAt() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.ClearDeletedAt()
})
}
// SetShareID sets the "share_id" field.
func (u *SharePurchaseUpsertBulk) SetShareID(v int) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetShareID(v)
})
}
// UpdateShareID sets the "share_id" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdateShareID() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateShareID()
})
}
// SetBuyerID sets the "buyer_id" field.
func (u *SharePurchaseUpsertBulk) SetBuyerID(v int) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetBuyerID(v)
})
}
// UpdateBuyerID sets the "buyer_id" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdateBuyerID() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateBuyerID()
})
}
// SetPoints sets the "points" field.
func (u *SharePurchaseUpsertBulk) SetPoints(v int) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetPoints(v)
})
}
// AddPoints adds v to the "points" field.
func (u *SharePurchaseUpsertBulk) AddPoints(v int) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.AddPoints(v)
})
}
// UpdatePoints sets the "points" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdatePoints() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdatePoints()
})
}
// SetTicket sets the "ticket" field.
func (u *SharePurchaseUpsertBulk) SetTicket(v string) *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.SetTicket(v)
})
}
// UpdateTicket sets the "ticket" field to the value that was provided on create.
func (u *SharePurchaseUpsertBulk) UpdateTicket() *SharePurchaseUpsertBulk {
return u.Update(func(s *SharePurchaseUpsert) {
s.UpdateTicket()
})
}
// Exec executes the query.
func (u *SharePurchaseUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
return u.create.err
}
for i, b := range u.create.builders {
if len(b.conflict) != 0 {
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the SharePurchaseCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for SharePurchaseCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *SharePurchaseUpsertBulk) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
)
// SharePurchaseDelete is the builder for deleting a SharePurchase entity.
type SharePurchaseDelete struct {
config
hooks []Hook
mutation *SharePurchaseMutation
}
// Where appends a list predicates to the SharePurchaseDelete builder.
func (spd *SharePurchaseDelete) Where(ps ...predicate.SharePurchase) *SharePurchaseDelete {
spd.mutation.Where(ps...)
return spd
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (spd *SharePurchaseDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, spd.sqlExec, spd.mutation, spd.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (spd *SharePurchaseDelete) ExecX(ctx context.Context) int {
n, err := spd.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (spd *SharePurchaseDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(sharepurchase.Table, sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt))
if ps := spd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, spd.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
spd.mutation.done = true
return affected, err
}
// SharePurchaseDeleteOne is the builder for deleting a single SharePurchase entity.
type SharePurchaseDeleteOne struct {
spd *SharePurchaseDelete
}
// Where appends a list predicates to the SharePurchaseDelete builder.
func (spdo *SharePurchaseDeleteOne) Where(ps ...predicate.SharePurchase) *SharePurchaseDeleteOne {
spdo.spd.mutation.Where(ps...)
return spdo
}
// Exec executes the deletion query.
func (spdo *SharePurchaseDeleteOne) Exec(ctx context.Context) error {
n, err := spdo.spd.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{sharepurchase.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (spdo *SharePurchaseDeleteOne) ExecX(ctx context.Context) {
if err := spdo.Exec(ctx); err != nil {
panic(err)
}
}

@ -0,0 +1,680 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SharePurchaseQuery is the builder for querying SharePurchase entities.
type SharePurchaseQuery struct {
config
ctx *QueryContext
order []sharepurchase.OrderOption
inters []Interceptor
predicates []predicate.SharePurchase
withShare *ShareQuery
withBuyer *UserQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the SharePurchaseQuery builder.
func (spq *SharePurchaseQuery) Where(ps ...predicate.SharePurchase) *SharePurchaseQuery {
spq.predicates = append(spq.predicates, ps...)
return spq
}
// Limit the number of records to be returned by this query.
func (spq *SharePurchaseQuery) Limit(limit int) *SharePurchaseQuery {
spq.ctx.Limit = &limit
return spq
}
// Offset to start from.
func (spq *SharePurchaseQuery) Offset(offset int) *SharePurchaseQuery {
spq.ctx.Offset = &offset
return spq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (spq *SharePurchaseQuery) Unique(unique bool) *SharePurchaseQuery {
spq.ctx.Unique = &unique
return spq
}
// Order specifies how the records should be ordered.
func (spq *SharePurchaseQuery) Order(o ...sharepurchase.OrderOption) *SharePurchaseQuery {
spq.order = append(spq.order, o...)
return spq
}
// QueryShare chains the current query on the "share" edge.
func (spq *SharePurchaseQuery) QueryShare() *ShareQuery {
query := (&ShareClient{config: spq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := spq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := spq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(sharepurchase.Table, sharepurchase.FieldID, selector),
sqlgraph.To(share.Table, share.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, sharepurchase.ShareTable, sharepurchase.ShareColumn),
)
fromU = sqlgraph.SetNeighbors(spq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryBuyer chains the current query on the "buyer" edge.
func (spq *SharePurchaseQuery) QueryBuyer() *UserQuery {
query := (&UserClient{config: spq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := spq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := spq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(sharepurchase.Table, sharepurchase.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, sharepurchase.BuyerTable, sharepurchase.BuyerColumn),
)
fromU = sqlgraph.SetNeighbors(spq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first SharePurchase entity from the query.
// Returns a *NotFoundError when no SharePurchase was found.
func (spq *SharePurchaseQuery) First(ctx context.Context) (*SharePurchase, error) {
nodes, err := spq.Limit(1).All(setContextOp(ctx, spq.ctx, "First"))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{sharepurchase.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (spq *SharePurchaseQuery) FirstX(ctx context.Context) *SharePurchase {
node, err := spq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first SharePurchase ID from the query.
// Returns a *NotFoundError when no SharePurchase ID was found.
func (spq *SharePurchaseQuery) FirstID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = spq.Limit(1).IDs(setContextOp(ctx, spq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{sharepurchase.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (spq *SharePurchaseQuery) FirstIDX(ctx context.Context) int {
id, err := spq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single SharePurchase entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one SharePurchase entity is found.
// Returns a *NotFoundError when no SharePurchase entities are found.
func (spq *SharePurchaseQuery) Only(ctx context.Context) (*SharePurchase, error) {
nodes, err := spq.Limit(2).All(setContextOp(ctx, spq.ctx, "Only"))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{sharepurchase.Label}
default:
return nil, &NotSingularError{sharepurchase.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (spq *SharePurchaseQuery) OnlyX(ctx context.Context) *SharePurchase {
node, err := spq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only SharePurchase ID in the query.
// Returns a *NotSingularError when more than one SharePurchase ID is found.
// Returns a *NotFoundError when no entities are found.
func (spq *SharePurchaseQuery) OnlyID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = spq.Limit(2).IDs(setContextOp(ctx, spq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{sharepurchase.Label}
default:
err = &NotSingularError{sharepurchase.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (spq *SharePurchaseQuery) OnlyIDX(ctx context.Context) int {
id, err := spq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of SharePurchases.
func (spq *SharePurchaseQuery) All(ctx context.Context) ([]*SharePurchase, error) {
ctx = setContextOp(ctx, spq.ctx, "All")
if err := spq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*SharePurchase, *SharePurchaseQuery]()
return withInterceptors[[]*SharePurchase](ctx, spq, qr, spq.inters)
}
// AllX is like All, but panics if an error occurs.
func (spq *SharePurchaseQuery) AllX(ctx context.Context) []*SharePurchase {
nodes, err := spq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of SharePurchase IDs.
func (spq *SharePurchaseQuery) IDs(ctx context.Context) (ids []int, err error) {
if spq.ctx.Unique == nil && spq.path != nil {
spq.Unique(true)
}
ctx = setContextOp(ctx, spq.ctx, "IDs")
if err = spq.Select(sharepurchase.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (spq *SharePurchaseQuery) IDsX(ctx context.Context) []int {
ids, err := spq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (spq *SharePurchaseQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, spq.ctx, "Count")
if err := spq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, spq, querierCount[*SharePurchaseQuery](), spq.inters)
}
// CountX is like Count, but panics if an error occurs.
func (spq *SharePurchaseQuery) CountX(ctx context.Context) int {
count, err := spq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (spq *SharePurchaseQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, spq.ctx, "Exist")
switch _, err := spq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (spq *SharePurchaseQuery) ExistX(ctx context.Context) bool {
exist, err := spq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the SharePurchaseQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (spq *SharePurchaseQuery) Clone() *SharePurchaseQuery {
if spq == nil {
return nil
}
return &SharePurchaseQuery{
config: spq.config,
ctx: spq.ctx.Clone(),
order: append([]sharepurchase.OrderOption{}, spq.order...),
inters: append([]Interceptor{}, spq.inters...),
predicates: append([]predicate.SharePurchase{}, spq.predicates...),
withShare: spq.withShare.Clone(),
withBuyer: spq.withBuyer.Clone(),
// clone intermediate query.
sql: spq.sql.Clone(),
path: spq.path,
}
}
// WithShare tells the query-builder to eager-load the nodes that are connected to
// the "share" edge. The optional arguments are used to configure the query builder of the edge.
func (spq *SharePurchaseQuery) WithShare(opts ...func(*ShareQuery)) *SharePurchaseQuery {
query := (&ShareClient{config: spq.config}).Query()
for _, opt := range opts {
opt(query)
}
spq.withShare = query
return spq
}
// WithBuyer tells the query-builder to eager-load the nodes that are connected to
// the "buyer" edge. The optional arguments are used to configure the query builder of the edge.
func (spq *SharePurchaseQuery) WithBuyer(opts ...func(*UserQuery)) *SharePurchaseQuery {
query := (&UserClient{config: spq.config}).Query()
for _, opt := range opts {
opt(query)
}
spq.withBuyer = query
return spq
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.SharePurchase.Query().
// GroupBy(sharepurchase.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (spq *SharePurchaseQuery) GroupBy(field string, fields ...string) *SharePurchaseGroupBy {
spq.ctx.Fields = append([]string{field}, fields...)
grbuild := &SharePurchaseGroupBy{build: spq}
grbuild.flds = &spq.ctx.Fields
grbuild.label = sharepurchase.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// }
//
// client.SharePurchase.Query().
// Select(sharepurchase.FieldCreatedAt).
// Scan(ctx, &v)
func (spq *SharePurchaseQuery) Select(fields ...string) *SharePurchaseSelect {
spq.ctx.Fields = append(spq.ctx.Fields, fields...)
sbuild := &SharePurchaseSelect{SharePurchaseQuery: spq}
sbuild.label = sharepurchase.Label
sbuild.flds, sbuild.scan = &spq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a SharePurchaseSelect configured with the given aggregations.
func (spq *SharePurchaseQuery) Aggregate(fns ...AggregateFunc) *SharePurchaseSelect {
return spq.Select().Aggregate(fns...)
}
func (spq *SharePurchaseQuery) prepareQuery(ctx context.Context) error {
for _, inter := range spq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, spq); err != nil {
return err
}
}
}
for _, f := range spq.ctx.Fields {
if !sharepurchase.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if spq.path != nil {
prev, err := spq.path(ctx)
if err != nil {
return err
}
spq.sql = prev
}
return nil
}
func (spq *SharePurchaseQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SharePurchase, error) {
var (
nodes = []*SharePurchase{}
_spec = spq.querySpec()
loadedTypes = [2]bool{
spq.withShare != nil,
spq.withBuyer != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*SharePurchase).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &SharePurchase{config: spq.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, spq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := spq.withShare; query != nil {
if err := spq.loadShare(ctx, query, nodes, nil,
func(n *SharePurchase, e *Share) { n.Edges.Share = e }); err != nil {
return nil, err
}
}
if query := spq.withBuyer; query != nil {
if err := spq.loadBuyer(ctx, query, nodes, nil,
func(n *SharePurchase, e *User) { n.Edges.Buyer = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (spq *SharePurchaseQuery) loadShare(ctx context.Context, query *ShareQuery, nodes []*SharePurchase, init func(*SharePurchase), assign func(*SharePurchase, *Share)) error {
ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*SharePurchase)
for i := range nodes {
fk := nodes[i].ShareID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(share.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "share_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (spq *SharePurchaseQuery) loadBuyer(ctx context.Context, query *UserQuery, nodes []*SharePurchase, init func(*SharePurchase), assign func(*SharePurchase, *User)) error {
ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*SharePurchase)
for i := range nodes {
fk := nodes[i].BuyerID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "buyer_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (spq *SharePurchaseQuery) sqlCount(ctx context.Context) (int, error) {
_spec := spq.querySpec()
_spec.Node.Columns = spq.ctx.Fields
if len(spq.ctx.Fields) > 0 {
_spec.Unique = spq.ctx.Unique != nil && *spq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, spq.driver, _spec)
}
func (spq *SharePurchaseQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(sharepurchase.Table, sharepurchase.Columns, sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt))
_spec.From = spq.sql
if unique := spq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if spq.path != nil {
_spec.Unique = true
}
if fields := spq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, sharepurchase.FieldID)
for i := range fields {
if fields[i] != sharepurchase.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if spq.withShare != nil {
_spec.Node.AddColumnOnce(sharepurchase.FieldShareID)
}
if spq.withBuyer != nil {
_spec.Node.AddColumnOnce(sharepurchase.FieldBuyerID)
}
}
if ps := spq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := spq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := spq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := spq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (spq *SharePurchaseQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(spq.driver.Dialect())
t1 := builder.Table(sharepurchase.Table)
columns := spq.ctx.Fields
if len(columns) == 0 {
columns = sharepurchase.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if spq.sql != nil {
selector = spq.sql
selector.Select(selector.Columns(columns...)...)
}
if spq.ctx.Unique != nil && *spq.ctx.Unique {
selector.Distinct()
}
for _, p := range spq.predicates {
p(selector)
}
for _, p := range spq.order {
p(selector)
}
if offset := spq.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := spq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// SharePurchaseGroupBy is the group-by builder for SharePurchase entities.
type SharePurchaseGroupBy struct {
selector
build *SharePurchaseQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (spgb *SharePurchaseGroupBy) Aggregate(fns ...AggregateFunc) *SharePurchaseGroupBy {
spgb.fns = append(spgb.fns, fns...)
return spgb
}
// Scan applies the selector query and scans the result into the given value.
func (spgb *SharePurchaseGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, spgb.build.ctx, "GroupBy")
if err := spgb.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SharePurchaseQuery, *SharePurchaseGroupBy](ctx, spgb.build, spgb, spgb.build.inters, v)
}
func (spgb *SharePurchaseGroupBy) sqlScan(ctx context.Context, root *SharePurchaseQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(spgb.fns))
for _, fn := range spgb.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*spgb.flds)+len(spgb.fns))
for _, f := range *spgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*spgb.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := spgb.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// SharePurchaseSelect is the builder for selecting fields of SharePurchase entities.
type SharePurchaseSelect struct {
*SharePurchaseQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (sps *SharePurchaseSelect) Aggregate(fns ...AggregateFunc) *SharePurchaseSelect {
sps.fns = append(sps.fns, fns...)
return sps
}
// Scan applies the selector query and scans the result into the given value.
func (sps *SharePurchaseSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, sps.ctx, "Select")
if err := sps.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SharePurchaseQuery, *SharePurchaseSelect](ctx, sps.SharePurchaseQuery, sps, sps.inters, v)
}
func (sps *SharePurchaseSelect) sqlScan(ctx context.Context, root *SharePurchaseQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(sps.fns))
for _, fn := range sps.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*sps.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := sps.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

@ -0,0 +1,620 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SharePurchaseUpdate is the builder for updating SharePurchase entities.
type SharePurchaseUpdate struct {
config
hooks []Hook
mutation *SharePurchaseMutation
}
// Where appends a list predicates to the SharePurchaseUpdate builder.
func (spu *SharePurchaseUpdate) Where(ps ...predicate.SharePurchase) *SharePurchaseUpdate {
spu.mutation.Where(ps...)
return spu
}
// SetUpdatedAt sets the "updated_at" field.
func (spu *SharePurchaseUpdate) SetUpdatedAt(t time.Time) *SharePurchaseUpdate {
spu.mutation.SetUpdatedAt(t)
return spu
}
// SetDeletedAt sets the "deleted_at" field.
func (spu *SharePurchaseUpdate) SetDeletedAt(t time.Time) *SharePurchaseUpdate {
spu.mutation.SetDeletedAt(t)
return spu
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (spu *SharePurchaseUpdate) SetNillableDeletedAt(t *time.Time) *SharePurchaseUpdate {
if t != nil {
spu.SetDeletedAt(*t)
}
return spu
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (spu *SharePurchaseUpdate) ClearDeletedAt() *SharePurchaseUpdate {
spu.mutation.ClearDeletedAt()
return spu
}
// SetShareID sets the "share_id" field.
func (spu *SharePurchaseUpdate) SetShareID(i int) *SharePurchaseUpdate {
spu.mutation.SetShareID(i)
return spu
}
// SetNillableShareID sets the "share_id" field if the given value is not nil.
func (spu *SharePurchaseUpdate) SetNillableShareID(i *int) *SharePurchaseUpdate {
if i != nil {
spu.SetShareID(*i)
}
return spu
}
// SetBuyerID sets the "buyer_id" field.
func (spu *SharePurchaseUpdate) SetBuyerID(i int) *SharePurchaseUpdate {
spu.mutation.SetBuyerID(i)
return spu
}
// SetNillableBuyerID sets the "buyer_id" field if the given value is not nil.
func (spu *SharePurchaseUpdate) SetNillableBuyerID(i *int) *SharePurchaseUpdate {
if i != nil {
spu.SetBuyerID(*i)
}
return spu
}
// SetPoints sets the "points" field.
func (spu *SharePurchaseUpdate) SetPoints(i int) *SharePurchaseUpdate {
spu.mutation.ResetPoints()
spu.mutation.SetPoints(i)
return spu
}
// SetNillablePoints sets the "points" field if the given value is not nil.
func (spu *SharePurchaseUpdate) SetNillablePoints(i *int) *SharePurchaseUpdate {
if i != nil {
spu.SetPoints(*i)
}
return spu
}
// AddPoints adds i to the "points" field.
func (spu *SharePurchaseUpdate) AddPoints(i int) *SharePurchaseUpdate {
spu.mutation.AddPoints(i)
return spu
}
// SetTicket sets the "ticket" field.
func (spu *SharePurchaseUpdate) SetTicket(s string) *SharePurchaseUpdate {
spu.mutation.SetTicket(s)
return spu
}
// SetNillableTicket sets the "ticket" field if the given value is not nil.
func (spu *SharePurchaseUpdate) SetNillableTicket(s *string) *SharePurchaseUpdate {
if s != nil {
spu.SetTicket(*s)
}
return spu
}
// SetShare sets the "share" edge to the Share entity.
func (spu *SharePurchaseUpdate) SetShare(s *Share) *SharePurchaseUpdate {
return spu.SetShareID(s.ID)
}
// SetBuyer sets the "buyer" edge to the User entity.
func (spu *SharePurchaseUpdate) SetBuyer(u *User) *SharePurchaseUpdate {
return spu.SetBuyerID(u.ID)
}
// Mutation returns the SharePurchaseMutation object of the builder.
func (spu *SharePurchaseUpdate) Mutation() *SharePurchaseMutation {
return spu.mutation
}
// ClearShare clears the "share" edge to the Share entity.
func (spu *SharePurchaseUpdate) ClearShare() *SharePurchaseUpdate {
spu.mutation.ClearShare()
return spu
}
// ClearBuyer clears the "buyer" edge to the User entity.
func (spu *SharePurchaseUpdate) ClearBuyer() *SharePurchaseUpdate {
spu.mutation.ClearBuyer()
return spu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (spu *SharePurchaseUpdate) Save(ctx context.Context) (int, error) {
if err := spu.defaults(); err != nil {
return 0, err
}
return withHooks(ctx, spu.sqlSave, spu.mutation, spu.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (spu *SharePurchaseUpdate) SaveX(ctx context.Context) int {
affected, err := spu.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (spu *SharePurchaseUpdate) Exec(ctx context.Context) error {
_, err := spu.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (spu *SharePurchaseUpdate) ExecX(ctx context.Context) {
if err := spu.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (spu *SharePurchaseUpdate) defaults() error {
if _, ok := spu.mutation.UpdatedAt(); !ok {
if sharepurchase.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized sharepurchase.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := sharepurchase.UpdateDefaultUpdatedAt()
spu.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (spu *SharePurchaseUpdate) check() error {
if v, ok := spu.mutation.Ticket(); ok {
if err := sharepurchase.TicketValidator(v); err != nil {
return &ValidationError{Name: "ticket", err: fmt.Errorf(`ent: validator failed for field "SharePurchase.ticket": %w`, err)}
}
}
if _, ok := spu.mutation.ShareID(); spu.mutation.ShareCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SharePurchase.share"`)
}
if _, ok := spu.mutation.BuyerID(); spu.mutation.BuyerCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SharePurchase.buyer"`)
}
return nil
}
func (spu *SharePurchaseUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := spu.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(sharepurchase.Table, sharepurchase.Columns, sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt))
if ps := spu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := spu.mutation.UpdatedAt(); ok {
_spec.SetField(sharepurchase.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := spu.mutation.DeletedAt(); ok {
_spec.SetField(sharepurchase.FieldDeletedAt, field.TypeTime, value)
}
if spu.mutation.DeletedAtCleared() {
_spec.ClearField(sharepurchase.FieldDeletedAt, field.TypeTime)
}
if value, ok := spu.mutation.Points(); ok {
_spec.SetField(sharepurchase.FieldPoints, field.TypeInt, value)
}
if value, ok := spu.mutation.AddedPoints(); ok {
_spec.AddField(sharepurchase.FieldPoints, field.TypeInt, value)
}
if value, ok := spu.mutation.Ticket(); ok {
_spec.SetField(sharepurchase.FieldTicket, field.TypeString, value)
}
if spu.mutation.ShareCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.ShareTable,
Columns: []string{sharepurchase.ShareColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := spu.mutation.ShareIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.ShareTable,
Columns: []string{sharepurchase.ShareColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if spu.mutation.BuyerCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.BuyerTable,
Columns: []string{sharepurchase.BuyerColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := spu.mutation.BuyerIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.BuyerTable,
Columns: []string{sharepurchase.BuyerColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, spu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{sharepurchase.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
spu.mutation.done = true
return n, nil
}
// SharePurchaseUpdateOne is the builder for updating a single SharePurchase entity.
type SharePurchaseUpdateOne struct {
config
fields []string
hooks []Hook
mutation *SharePurchaseMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (spuo *SharePurchaseUpdateOne) SetUpdatedAt(t time.Time) *SharePurchaseUpdateOne {
spuo.mutation.SetUpdatedAt(t)
return spuo
}
// SetDeletedAt sets the "deleted_at" field.
func (spuo *SharePurchaseUpdateOne) SetDeletedAt(t time.Time) *SharePurchaseUpdateOne {
spuo.mutation.SetDeletedAt(t)
return spuo
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (spuo *SharePurchaseUpdateOne) SetNillableDeletedAt(t *time.Time) *SharePurchaseUpdateOne {
if t != nil {
spuo.SetDeletedAt(*t)
}
return spuo
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (spuo *SharePurchaseUpdateOne) ClearDeletedAt() *SharePurchaseUpdateOne {
spuo.mutation.ClearDeletedAt()
return spuo
}
// SetShareID sets the "share_id" field.
func (spuo *SharePurchaseUpdateOne) SetShareID(i int) *SharePurchaseUpdateOne {
spuo.mutation.SetShareID(i)
return spuo
}
// SetNillableShareID sets the "share_id" field if the given value is not nil.
func (spuo *SharePurchaseUpdateOne) SetNillableShareID(i *int) *SharePurchaseUpdateOne {
if i != nil {
spuo.SetShareID(*i)
}
return spuo
}
// SetBuyerID sets the "buyer_id" field.
func (spuo *SharePurchaseUpdateOne) SetBuyerID(i int) *SharePurchaseUpdateOne {
spuo.mutation.SetBuyerID(i)
return spuo
}
// SetNillableBuyerID sets the "buyer_id" field if the given value is not nil.
func (spuo *SharePurchaseUpdateOne) SetNillableBuyerID(i *int) *SharePurchaseUpdateOne {
if i != nil {
spuo.SetBuyerID(*i)
}
return spuo
}
// SetPoints sets the "points" field.
func (spuo *SharePurchaseUpdateOne) SetPoints(i int) *SharePurchaseUpdateOne {
spuo.mutation.ResetPoints()
spuo.mutation.SetPoints(i)
return spuo
}
// SetNillablePoints sets the "points" field if the given value is not nil.
func (spuo *SharePurchaseUpdateOne) SetNillablePoints(i *int) *SharePurchaseUpdateOne {
if i != nil {
spuo.SetPoints(*i)
}
return spuo
}
// AddPoints adds i to the "points" field.
func (spuo *SharePurchaseUpdateOne) AddPoints(i int) *SharePurchaseUpdateOne {
spuo.mutation.AddPoints(i)
return spuo
}
// SetTicket sets the "ticket" field.
func (spuo *SharePurchaseUpdateOne) SetTicket(s string) *SharePurchaseUpdateOne {
spuo.mutation.SetTicket(s)
return spuo
}
// SetNillableTicket sets the "ticket" field if the given value is not nil.
func (spuo *SharePurchaseUpdateOne) SetNillableTicket(s *string) *SharePurchaseUpdateOne {
if s != nil {
spuo.SetTicket(*s)
}
return spuo
}
// SetShare sets the "share" edge to the Share entity.
func (spuo *SharePurchaseUpdateOne) SetShare(s *Share) *SharePurchaseUpdateOne {
return spuo.SetShareID(s.ID)
}
// SetBuyer sets the "buyer" edge to the User entity.
func (spuo *SharePurchaseUpdateOne) SetBuyer(u *User) *SharePurchaseUpdateOne {
return spuo.SetBuyerID(u.ID)
}
// Mutation returns the SharePurchaseMutation object of the builder.
func (spuo *SharePurchaseUpdateOne) Mutation() *SharePurchaseMutation {
return spuo.mutation
}
// ClearShare clears the "share" edge to the Share entity.
func (spuo *SharePurchaseUpdateOne) ClearShare() *SharePurchaseUpdateOne {
spuo.mutation.ClearShare()
return spuo
}
// ClearBuyer clears the "buyer" edge to the User entity.
func (spuo *SharePurchaseUpdateOne) ClearBuyer() *SharePurchaseUpdateOne {
spuo.mutation.ClearBuyer()
return spuo
}
// Where appends a list predicates to the SharePurchaseUpdate builder.
func (spuo *SharePurchaseUpdateOne) Where(ps ...predicate.SharePurchase) *SharePurchaseUpdateOne {
spuo.mutation.Where(ps...)
return spuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (spuo *SharePurchaseUpdateOne) Select(field string, fields ...string) *SharePurchaseUpdateOne {
spuo.fields = append([]string{field}, fields...)
return spuo
}
// Save executes the query and returns the updated SharePurchase entity.
func (spuo *SharePurchaseUpdateOne) Save(ctx context.Context) (*SharePurchase, error) {
if err := spuo.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, spuo.sqlSave, spuo.mutation, spuo.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (spuo *SharePurchaseUpdateOne) SaveX(ctx context.Context) *SharePurchase {
node, err := spuo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (spuo *SharePurchaseUpdateOne) Exec(ctx context.Context) error {
_, err := spuo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (spuo *SharePurchaseUpdateOne) ExecX(ctx context.Context) {
if err := spuo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (spuo *SharePurchaseUpdateOne) defaults() error {
if _, ok := spuo.mutation.UpdatedAt(); !ok {
if sharepurchase.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized sharepurchase.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := sharepurchase.UpdateDefaultUpdatedAt()
spuo.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (spuo *SharePurchaseUpdateOne) check() error {
if v, ok := spuo.mutation.Ticket(); ok {
if err := sharepurchase.TicketValidator(v); err != nil {
return &ValidationError{Name: "ticket", err: fmt.Errorf(`ent: validator failed for field "SharePurchase.ticket": %w`, err)}
}
}
if _, ok := spuo.mutation.ShareID(); spuo.mutation.ShareCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SharePurchase.share"`)
}
if _, ok := spuo.mutation.BuyerID(); spuo.mutation.BuyerCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SharePurchase.buyer"`)
}
return nil
}
func (spuo *SharePurchaseUpdateOne) sqlSave(ctx context.Context) (_node *SharePurchase, err error) {
if err := spuo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(sharepurchase.Table, sharepurchase.Columns, sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt))
id, ok := spuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SharePurchase.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := spuo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, sharepurchase.FieldID)
for _, f := range fields {
if !sharepurchase.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != sharepurchase.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := spuo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := spuo.mutation.UpdatedAt(); ok {
_spec.SetField(sharepurchase.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := spuo.mutation.DeletedAt(); ok {
_spec.SetField(sharepurchase.FieldDeletedAt, field.TypeTime, value)
}
if spuo.mutation.DeletedAtCleared() {
_spec.ClearField(sharepurchase.FieldDeletedAt, field.TypeTime)
}
if value, ok := spuo.mutation.Points(); ok {
_spec.SetField(sharepurchase.FieldPoints, field.TypeInt, value)
}
if value, ok := spuo.mutation.AddedPoints(); ok {
_spec.AddField(sharepurchase.FieldPoints, field.TypeInt, value)
}
if value, ok := spuo.mutation.Ticket(); ok {
_spec.SetField(sharepurchase.FieldTicket, field.TypeString, value)
}
if spuo.mutation.ShareCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.ShareTable,
Columns: []string{sharepurchase.ShareColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := spuo.mutation.ShareIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.ShareTable,
Columns: []string{sharepurchase.ShareColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(share.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if spuo.mutation.BuyerCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.BuyerTable,
Columns: []string{sharepurchase.BuyerColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := spuo.mutation.BuyerIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: sharepurchase.BuyerTable,
Columns: []string{sharepurchase.BuyerColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &SharePurchase{config: spuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, spuo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{sharepurchase.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
spuo.mutation.done = true
return _node, nil
}

@ -52,6 +52,8 @@ type Tx struct {
Setting *SettingClient Setting *SettingClient
// Share is the client for interacting with the Share builders. // Share is the client for interacting with the Share builders.
Share *ShareClient Share *ShareClient
// SharePurchase is the client for interacting with the SharePurchase builders.
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders. // Sku is the client for interacting with the Sku builders.
Sku *SkuClient Sku *SkuClient
// StoragePolicy is the client for interacting with the StoragePolicy builders. // StoragePolicy is the client for interacting with the StoragePolicy builders.
@ -212,6 +214,7 @@ func (tx *Tx) init() {
tx.Passkey = NewPasskeyClient(tx.config) tx.Passkey = NewPasskeyClient(tx.config)
tx.Setting = NewSettingClient(tx.config) tx.Setting = NewSettingClient(tx.config)
tx.Share = NewShareClient(tx.config) tx.Share = NewShareClient(tx.config)
tx.SharePurchase = NewSharePurchaseClient(tx.config)
tx.Sku = NewSkuClient(tx.config) tx.Sku = NewSkuClient(tx.config)
tx.StoragePolicy = NewStoragePolicyClient(tx.config) tx.StoragePolicy = NewStoragePolicyClient(tx.config)
tx.Task = NewTaskClient(tx.config) tx.Task = NewTaskClient(tx.config)

@ -84,9 +84,11 @@ type UserEdges struct {
RedeemedCodes []*GiftCode `json:"redeemed_codes,omitempty"` RedeemedCodes []*GiftCode `json:"redeemed_codes,omitempty"`
// Grants holds the value of the grants edge. // Grants holds the value of the grants edge.
Grants []*UserGrant `json:"grants,omitempty"` Grants []*UserGrant `json:"grants,omitempty"`
// SharePurchases holds the value of the share_purchases edge.
SharePurchases []*SharePurchase `json:"share_purchases,omitempty"`
// loadedTypes holds the information for reporting if a // loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not. // type was loaded (or requested) in eager-loading or not.
loadedTypes [12]bool loadedTypes [13]bool
} }
// GroupOrErr returns the Group value or an error if the edge // GroupOrErr returns the Group value or an error if the edge
@ -201,6 +203,15 @@ func (e UserEdges) GrantsOrErr() ([]*UserGrant, error) {
return nil, &NotLoadedError{edge: "grants"} return nil, &NotLoadedError{edge: "grants"}
} }
// SharePurchasesOrErr returns the SharePurchases value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) SharePurchasesOrErr() ([]*SharePurchase, error) {
if e.loadedTypes[12] {
return e.SharePurchases, nil
}
return nil, &NotLoadedError{edge: "share_purchases"}
}
// scanValues returns the types for scanning values from sql.Rows. // scanValues returns the types for scanning values from sql.Rows.
func (*User) scanValues(columns []string) ([]any, error) { func (*User) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns)) values := make([]any, len(columns))
@ -409,6 +420,11 @@ func (u *User) QueryGrants() *UserGrantQuery {
return NewUserClient(u.config).QueryGrants(u) return NewUserClient(u.config).QueryGrants(u)
} }
// QuerySharePurchases queries the "share_purchases" edge of the User entity.
func (u *User) QuerySharePurchases() *SharePurchaseQuery {
return NewUserClient(u.config).QuerySharePurchases(u)
}
// Update returns a builder for updating this User. // Update returns a builder for updating this User.
// Note that you need to call User.Unwrap() before calling this method if this User // Note that you need to call User.Unwrap() before calling this method if this User
// was returned from a transaction, and the transaction was committed or rolled back. // was returned from a transaction, and the transaction was committed or rolled back.
@ -559,5 +575,11 @@ func (e *User) SetGrants(v []*UserGrant) {
e.Edges.loadedTypes[11] = true e.Edges.loadedTypes[11] = true
} }
// SetSharePurchases manually set the edge as loaded state.
func (e *User) SetSharePurchases(v []*SharePurchase) {
e.Edges.SharePurchases = v
e.Edges.loadedTypes[12] = true
}
// Users is a parsable slice of User. // Users is a parsable slice of User.
type Users []*User type Users []*User

@ -73,6 +73,8 @@ const (
EdgeRedeemedCodes = "redeemed_codes" EdgeRedeemedCodes = "redeemed_codes"
// EdgeGrants holds the string denoting the grants edge name in mutations. // EdgeGrants holds the string denoting the grants edge name in mutations.
EdgeGrants = "grants" EdgeGrants = "grants"
// EdgeSharePurchases holds the string denoting the share_purchases edge name in mutations.
EdgeSharePurchases = "share_purchases"
// Table holds the table name of the user in the database. // Table holds the table name of the user in the database.
Table = "users" Table = "users"
// GroupTable is the table that holds the group relation/edge. // GroupTable is the table that holds the group relation/edge.
@ -159,6 +161,13 @@ const (
GrantsInverseTable = "user_grants" GrantsInverseTable = "user_grants"
// GrantsColumn is the table column denoting the grants relation/edge. // GrantsColumn is the table column denoting the grants relation/edge.
GrantsColumn = "user_id" GrantsColumn = "user_id"
// SharePurchasesTable is the table that holds the share_purchases relation/edge.
SharePurchasesTable = "share_purchases"
// SharePurchasesInverseTable is the table name for the SharePurchase entity.
// It exists in this package in order to avoid circular dependency with the "sharepurchase" package.
SharePurchasesInverseTable = "share_purchases"
// SharePurchasesColumn is the table column denoting the share_purchases relation/edge.
SharePurchasesColumn = "buyer_id"
) )
// Columns holds all SQL columns for user fields. // Columns holds all SQL columns for user fields.
@ -489,6 +498,20 @@ func ByGrants(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newGrantsStep(), append([]sql.OrderTerm{term}, terms...)...) sqlgraph.OrderByNeighborTerms(s, newGrantsStep(), append([]sql.OrderTerm{term}, terms...)...)
} }
} }
// BySharePurchasesCount orders the results by share_purchases count.
func BySharePurchasesCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newSharePurchasesStep(), opts...)
}
}
// BySharePurchases orders the results by share_purchases terms.
func BySharePurchases(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newSharePurchasesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newGroupStep() *sqlgraph.Step { func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep( return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID), sqlgraph.From(Table, FieldID),
@ -573,3 +596,10 @@ func newGrantsStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, GrantsTable, GrantsColumn), sqlgraph.Edge(sqlgraph.O2M, false, GrantsTable, GrantsColumn),
) )
} }
func newSharePurchasesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(SharePurchasesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, SharePurchasesTable, SharePurchasesColumn),
)
}

@ -1191,6 +1191,29 @@ func HasGrantsWith(preds ...predicate.UserGrant) predicate.User {
}) })
} }
// HasSharePurchases applies the HasEdge predicate on the "share_purchases" edge.
func HasSharePurchases() predicate.User {
return predicate.User(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, SharePurchasesTable, SharePurchasesColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasSharePurchasesWith applies the HasEdge predicate on the "share_purchases" edge with a given conditions (other predicates).
func HasSharePurchasesWith(preds ...predicate.SharePurchase) predicate.User {
return predicate.User(func(s *sql.Selector) {
step := newSharePurchasesStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
func And(predicates ...predicate.User) predicate.User { func And(predicates ...predicate.User) predicate.User {
return predicate.User(sql.AndPredicates(predicates...)) return predicate.User(sql.AndPredicates(predicates...))

@ -21,6 +21,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/oauthgrant" "github.com/cloudreve/Cloudreve/v4/ent/oauthgrant"
"github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant" "github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -403,6 +404,21 @@ func (uc *UserCreate) AddGrants(u ...*UserGrant) *UserCreate {
return uc.AddGrantIDs(ids...) return uc.AddGrantIDs(ids...)
} }
// AddSharePurchaseIDs adds the "share_purchases" edge to the SharePurchase entity by IDs.
func (uc *UserCreate) AddSharePurchaseIDs(ids ...int) *UserCreate {
uc.mutation.AddSharePurchaseIDs(ids...)
return uc
}
// AddSharePurchases adds the "share_purchases" edges to the SharePurchase entity.
func (uc *UserCreate) AddSharePurchases(s ...*SharePurchase) *UserCreate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uc.AddSharePurchaseIDs(ids...)
}
// Mutation returns the UserMutation object of the builder. // Mutation returns the UserMutation object of the builder.
func (uc *UserCreate) Mutation() *UserMutation { func (uc *UserCreate) Mutation() *UserMutation {
return uc.mutation return uc.mutation
@ -804,6 +820,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
} }
_spec.Edges = append(_spec.Edges, edge) _spec.Edges = append(_spec.Edges, edge)
} }
if nodes := uc.mutation.SharePurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec return _node, _spec
} }

@ -22,6 +22,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant" "github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -30,22 +31,23 @@ import (
// UserQuery is the builder for querying User entities. // UserQuery is the builder for querying User entities.
type UserQuery struct { type UserQuery struct {
config config
ctx *QueryContext ctx *QueryContext
order []user.OrderOption order []user.OrderOption
inters []Interceptor inters []Interceptor
predicates []predicate.User predicates []predicate.User
withGroup *GroupQuery withGroup *GroupQuery
withFiles *FileQuery withFiles *FileQuery
withDavAccounts *DavAccountQuery withDavAccounts *DavAccountQuery
withShares *ShareQuery withShares *ShareQuery
withPasskey *PasskeyQuery withPasskey *PasskeyQuery
withTasks *TaskQuery withTasks *TaskQuery
withFsevents *FsEventQuery withFsevents *FsEventQuery
withEntities *EntityQuery withEntities *EntityQuery
withOauthGrants *OAuthGrantQuery withOauthGrants *OAuthGrantQuery
withCreditTxns *CreditTxnQuery withCreditTxns *CreditTxnQuery
withRedeemedCodes *GiftCodeQuery withRedeemedCodes *GiftCodeQuery
withGrants *UserGrantQuery withGrants *UserGrantQuery
withSharePurchases *SharePurchaseQuery
// intermediate query (i.e. traversal path). // intermediate query (i.e. traversal path).
sql *sql.Selector sql *sql.Selector
path func(context.Context) (*sql.Selector, error) path func(context.Context) (*sql.Selector, error)
@ -346,6 +348,28 @@ func (uq *UserQuery) QueryGrants() *UserGrantQuery {
return query return query
} }
// QuerySharePurchases chains the current query on the "share_purchases" edge.
func (uq *UserQuery) QuerySharePurchases() *SharePurchaseQuery {
query := (&SharePurchaseClient{config: uq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := uq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := uq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, selector),
sqlgraph.To(sharepurchase.Table, sharepurchase.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.SharePurchasesTable, user.SharePurchasesColumn),
)
fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first User entity from the query. // First returns the first User entity from the query.
// Returns a *NotFoundError when no User was found. // Returns a *NotFoundError when no User was found.
func (uq *UserQuery) First(ctx context.Context) (*User, error) { func (uq *UserQuery) First(ctx context.Context) (*User, error) {
@ -533,23 +557,24 @@ func (uq *UserQuery) Clone() *UserQuery {
return nil return nil
} }
return &UserQuery{ return &UserQuery{
config: uq.config, config: uq.config,
ctx: uq.ctx.Clone(), ctx: uq.ctx.Clone(),
order: append([]user.OrderOption{}, uq.order...), order: append([]user.OrderOption{}, uq.order...),
inters: append([]Interceptor{}, uq.inters...), inters: append([]Interceptor{}, uq.inters...),
predicates: append([]predicate.User{}, uq.predicates...), predicates: append([]predicate.User{}, uq.predicates...),
withGroup: uq.withGroup.Clone(), withGroup: uq.withGroup.Clone(),
withFiles: uq.withFiles.Clone(), withFiles: uq.withFiles.Clone(),
withDavAccounts: uq.withDavAccounts.Clone(), withDavAccounts: uq.withDavAccounts.Clone(),
withShares: uq.withShares.Clone(), withShares: uq.withShares.Clone(),
withPasskey: uq.withPasskey.Clone(), withPasskey: uq.withPasskey.Clone(),
withTasks: uq.withTasks.Clone(), withTasks: uq.withTasks.Clone(),
withFsevents: uq.withFsevents.Clone(), withFsevents: uq.withFsevents.Clone(),
withEntities: uq.withEntities.Clone(), withEntities: uq.withEntities.Clone(),
withOauthGrants: uq.withOauthGrants.Clone(), withOauthGrants: uq.withOauthGrants.Clone(),
withCreditTxns: uq.withCreditTxns.Clone(), withCreditTxns: uq.withCreditTxns.Clone(),
withRedeemedCodes: uq.withRedeemedCodes.Clone(), withRedeemedCodes: uq.withRedeemedCodes.Clone(),
withGrants: uq.withGrants.Clone(), withGrants: uq.withGrants.Clone(),
withSharePurchases: uq.withSharePurchases.Clone(),
// clone intermediate query. // clone intermediate query.
sql: uq.sql.Clone(), sql: uq.sql.Clone(),
path: uq.path, path: uq.path,
@ -688,6 +713,17 @@ func (uq *UserQuery) WithGrants(opts ...func(*UserGrantQuery)) *UserQuery {
return uq return uq
} }
// WithSharePurchases tells the query-builder to eager-load the nodes that are connected to
// the "share_purchases" edge. The optional arguments are used to configure the query builder of the edge.
func (uq *UserQuery) WithSharePurchases(opts ...func(*SharePurchaseQuery)) *UserQuery {
query := (&SharePurchaseClient{config: uq.config}).Query()
for _, opt := range opts {
opt(query)
}
uq.withSharePurchases = query
return uq
}
// GroupBy is used to group vertices by one or more fields/columns. // GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum. // It is often used with aggregate functions, like: count, max, mean, min, sum.
// //
@ -766,7 +802,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
var ( var (
nodes = []*User{} nodes = []*User{}
_spec = uq.querySpec() _spec = uq.querySpec()
loadedTypes = [12]bool{ loadedTypes = [13]bool{
uq.withGroup != nil, uq.withGroup != nil,
uq.withFiles != nil, uq.withFiles != nil,
uq.withDavAccounts != nil, uq.withDavAccounts != nil,
@ -779,6 +815,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
uq.withCreditTxns != nil, uq.withCreditTxns != nil,
uq.withRedeemedCodes != nil, uq.withRedeemedCodes != nil,
uq.withGrants != nil, uq.withGrants != nil,
uq.withSharePurchases != nil,
} }
) )
_spec.ScanValues = func(columns []string) ([]any, error) { _spec.ScanValues = func(columns []string) ([]any, error) {
@ -882,6 +919,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err return nil, err
} }
} }
if query := uq.withSharePurchases; query != nil {
if err := uq.loadSharePurchases(ctx, query, nodes,
func(n *User) { n.Edges.SharePurchases = []*SharePurchase{} },
func(n *User, e *SharePurchase) { n.Edges.SharePurchases = append(n.Edges.SharePurchases, e) }); err != nil {
return nil, err
}
}
return nodes, nil return nodes, nil
} }
@ -1245,6 +1289,36 @@ func (uq *UserQuery) loadGrants(ctx context.Context, query *UserGrantQuery, node
} }
return nil return nil
} }
func (uq *UserQuery) loadSharePurchases(ctx context.Context, query *SharePurchaseQuery, nodes []*User, init func(*User), assign func(*User, *SharePurchase)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int]*User)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(sharepurchase.FieldBuyerID)
}
query.Where(predicate.SharePurchase(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(user.SharePurchasesColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.BuyerID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "buyer_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) {
_spec := uq.querySpec() _spec := uq.querySpec()

@ -22,6 +22,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/task" "github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant" "github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -473,6 +474,21 @@ func (uu *UserUpdate) AddGrants(u ...*UserGrant) *UserUpdate {
return uu.AddGrantIDs(ids...) return uu.AddGrantIDs(ids...)
} }
// AddSharePurchaseIDs adds the "share_purchases" edge to the SharePurchase entity by IDs.
func (uu *UserUpdate) AddSharePurchaseIDs(ids ...int) *UserUpdate {
uu.mutation.AddSharePurchaseIDs(ids...)
return uu
}
// AddSharePurchases adds the "share_purchases" edges to the SharePurchase entity.
func (uu *UserUpdate) AddSharePurchases(s ...*SharePurchase) *UserUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uu.AddSharePurchaseIDs(ids...)
}
// Mutation returns the UserMutation object of the builder. // Mutation returns the UserMutation object of the builder.
func (uu *UserUpdate) Mutation() *UserMutation { func (uu *UserUpdate) Mutation() *UserMutation {
return uu.mutation return uu.mutation
@ -715,6 +731,27 @@ func (uu *UserUpdate) RemoveGrants(u ...*UserGrant) *UserUpdate {
return uu.RemoveGrantIDs(ids...) return uu.RemoveGrantIDs(ids...)
} }
// ClearSharePurchases clears all "share_purchases" edges to the SharePurchase entity.
func (uu *UserUpdate) ClearSharePurchases() *UserUpdate {
uu.mutation.ClearSharePurchases()
return uu
}
// RemoveSharePurchaseIDs removes the "share_purchases" edge to SharePurchase entities by IDs.
func (uu *UserUpdate) RemoveSharePurchaseIDs(ids ...int) *UserUpdate {
uu.mutation.RemoveSharePurchaseIDs(ids...)
return uu
}
// RemoveSharePurchases removes "share_purchases" edges to SharePurchase entities.
func (uu *UserUpdate) RemoveSharePurchases(s ...*SharePurchase) *UserUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uu.RemoveSharePurchaseIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation. // Save executes the query and returns the number of nodes affected by the update operation.
func (uu *UserUpdate) Save(ctx context.Context) (int, error) { func (uu *UserUpdate) Save(ctx context.Context) (int, error) {
if err := uu.defaults(); err != nil { if err := uu.defaults(); err != nil {
@ -1388,6 +1425,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if uu.mutation.SharePurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uu.mutation.RemovedSharePurchasesIDs(); len(nodes) > 0 && !uu.mutation.SharePurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uu.mutation.SharePurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok { if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{user.Label} err = &NotFoundError{user.Label}
@ -1840,6 +1922,21 @@ func (uuo *UserUpdateOne) AddGrants(u ...*UserGrant) *UserUpdateOne {
return uuo.AddGrantIDs(ids...) return uuo.AddGrantIDs(ids...)
} }
// AddSharePurchaseIDs adds the "share_purchases" edge to the SharePurchase entity by IDs.
func (uuo *UserUpdateOne) AddSharePurchaseIDs(ids ...int) *UserUpdateOne {
uuo.mutation.AddSharePurchaseIDs(ids...)
return uuo
}
// AddSharePurchases adds the "share_purchases" edges to the SharePurchase entity.
func (uuo *UserUpdateOne) AddSharePurchases(s ...*SharePurchase) *UserUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uuo.AddSharePurchaseIDs(ids...)
}
// Mutation returns the UserMutation object of the builder. // Mutation returns the UserMutation object of the builder.
func (uuo *UserUpdateOne) Mutation() *UserMutation { func (uuo *UserUpdateOne) Mutation() *UserMutation {
return uuo.mutation return uuo.mutation
@ -2082,6 +2179,27 @@ func (uuo *UserUpdateOne) RemoveGrants(u ...*UserGrant) *UserUpdateOne {
return uuo.RemoveGrantIDs(ids...) return uuo.RemoveGrantIDs(ids...)
} }
// ClearSharePurchases clears all "share_purchases" edges to the SharePurchase entity.
func (uuo *UserUpdateOne) ClearSharePurchases() *UserUpdateOne {
uuo.mutation.ClearSharePurchases()
return uuo
}
// RemoveSharePurchaseIDs removes the "share_purchases" edge to SharePurchase entities by IDs.
func (uuo *UserUpdateOne) RemoveSharePurchaseIDs(ids ...int) *UserUpdateOne {
uuo.mutation.RemoveSharePurchaseIDs(ids...)
return uuo
}
// RemoveSharePurchases removes "share_purchases" edges to SharePurchase entities.
func (uuo *UserUpdateOne) RemoveSharePurchases(s ...*SharePurchase) *UserUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uuo.RemoveSharePurchaseIDs(ids...)
}
// Where appends a list predicates to the UserUpdate builder. // Where appends a list predicates to the UserUpdate builder.
func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
uuo.mutation.Where(ps...) uuo.mutation.Where(ps...)
@ -2785,6 +2903,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if uuo.mutation.SharePurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uuo.mutation.RemovedSharePurchasesIDs(); len(nodes) > 0 && !uuo.mutation.SharePurchasesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uuo.mutation.SharePurchasesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SharePurchasesTable,
Columns: []string{user.SharePurchasesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(sharepurchase.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &User{config: uuo.config} _node = &User{config: uuo.config}
_spec.Assign = _node.assignValues _spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues _spec.ScanValues = _node.scanValues

@ -502,6 +502,10 @@
"policyGroupDefault": "Group default" "policyGroupDefault": "Group default"
}, },
"modals": { "modals": {
"paidShare": "Paid share",
"paidSharePrice": "{{price}} points",
"paidShareDes": "Visitors must purchase this share with credit points before downloading. Set 0 for a free share.",
"paidSharePriceLabel": "Price (points)",
"includePasswordInShareLink": "Include password in share link", "includePasswordInShareLink": "Include password in share link",
"includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.", "includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.",
"showFileName": "Show file name", "showFileName": "Show file name",
@ -806,7 +810,11 @@
"preview": "Preview", "preview": "Preview",
"incorrectPassword": "Password incorrect.", "incorrectPassword": "Password incorrect.",
"shareNotExist": "Share link is invalid or expired.", "shareNotExist": "Share link is invalid or expired.",
"copyLinkToClipboard": "Copy link to clipboard" "copyLinkToClipboard": "Copy link to clipboard",
"paidShareDes": "This is a paid share. Purchasing costs {{price}} points and unlocks downloads.",
"purchaseForPoints": "Purchase for {{price}} pts",
"signInToPurchase": "Sign in to purchase",
"yourBalance": "Balance: {{credits}} points"
}, },
"download": { "download": {
"noFilesFound": "No files found", "noFilesFound": "No files found",

@ -1316,6 +1316,8 @@
"allowCreateShareLinkDes": "If disabled, users cannot create sharing links.", "allowCreateShareLinkDes": "If disabled, users cannot create sharing links.",
"shareFree": "Free share link", "shareFree": "Free share link",
"shareFreeDes": "When enabled, users can access all paid sharing links without purchasing.", "shareFreeDes": "When enabled, users can access all paid sharing links without purchasing.",
"shareSell": "Sell share links",
"shareSellDes": "When enabled, users can set a points price on share links they create.",
"fileManagement": "File management", "fileManagement": "File management",
"allowWabDAV": "WebDAV", "allowWabDAV": "WebDAV",
"allowWabDAVDes": "If disabled, users cannot connect to the storage via the WebDAV protocol", "allowWabDAVDes": "If disabled, users cannot connect to the storage via the WebDAV protocol",

@ -502,6 +502,10 @@
"policyGroupDefault": "用户组默认" "policyGroupDefault": "用户组默认"
}, },
"modals": { "modals": {
"paidShare": "付费分享",
"paidSharePrice": "{{price}} 积分",
"paidShareDes": "访客需使用积分购买此分享后才能下载。设置为 0 表示免费分享。",
"paidSharePriceLabel": "价格(积分)",
"includePasswordInShareLink": "在链接中包含密码", "includePasswordInShareLink": "在链接中包含密码",
"includePasswordInShareLinkDes": "勾选后,分享链接中会包含密码,通过此链接访问时不需要再输入密码。", "includePasswordInShareLinkDes": "勾选后,分享链接中会包含密码,通过此链接访问时不需要再输入密码。",
"showFileName": "显示文件名", "showFileName": "显示文件名",
@ -806,7 +810,11 @@
"preview": "预览", "preview": "预览",
"incorrectPassword": "密码不正确", "incorrectPassword": "密码不正确",
"shareNotExist": "分享不存在或已过期", "shareNotExist": "分享不存在或已过期",
"copyLinkToClipboard": "复制链接到剪切板" "copyLinkToClipboard": "复制链接到剪切板",
"paidShareDes": "此分享为付费分享,需支付 {{price}} 积分购买后才能下载。",
"purchaseForPoints": "支付 {{price}} 积分购买",
"signInToPurchase": "登录后购买",
"yourBalance": "余额:{{credits}} 积分"
}, },
"download": { "download": {
"noFilesFound": "没有找到任何文件", "noFilesFound": "没有找到任何文件",

@ -1316,6 +1316,8 @@
"allowCreateShareLinkDes": "关闭后,用户无法创建分享链接。", "allowCreateShareLinkDes": "关闭后,用户无法创建分享链接。",
"shareFree": "无需购买分享链接", "shareFree": "无需购买分享链接",
"shareFreeDes": "开启后,用户无需购买即可访问所有付费分享链接。", "shareFreeDes": "开启后,用户无需购买即可访问所有付费分享链接。",
"shareSell": "出售分享链接",
"shareSellDes": "开启后,用户可以为其创建的分享链接设置积分价格。",
"fileManagement": "文件管理", "fileManagement": "文件管理",
"allowWabDAV": "WebDAV", "allowWabDAV": "WebDAV",
"allowWabDAVDes": "关闭后,用户无法通过 WebDAV 协议连接至网盘。", "allowWabDAVDes": "关闭后,用户无法通过 WebDAV 协议连接至网盘。",

@ -120,6 +120,7 @@ import {
User, User,
UserSettings, UserSettings,
} from "./user.ts"; } from "./user.ts";
import CrUri, { Filesystem } from "../util/uri.ts";
import { import {
ArchiveWorkflowService, ArchiveWorkflowService,
DownloadWorkflowService, DownloadWorkflowService,
@ -319,11 +320,23 @@ export function getFileList(req: ListFileService, skipSnackbar = true): ThunkRes
export function getFileThumb(path: string, contextHint?: string): ThunkResponse<FileThumbResponse> { export function getFileThumb(path: string, contextHint?: string): ThunkResponse<FileThumbResponse> {
return async (dispatch, _getState) => { return async (dispatch, _getState) => {
const params: Record<string, string> = { uri: path };
try {
const uri = new CrUri(path);
if (uri.fs() == Filesystem.share) {
const ticket = getSharePurchaseTicket(uri.id());
if (ticket) {
params.purchase_ticket = ticket;
}
}
} catch {
// non-CrUri inputs fall through unchanged
}
return await dispatch( return await dispatch(
send( send(
"/file/thumb", "/file/thumb",
{ {
params: { uri: path }, params,
method: "GET", method: "GET",
headers: contextHint headers: contextHint
? { ? {
@ -675,6 +688,48 @@ export function sendDeleteShares(ids: string[]): ThunkResponse<void> {
}; };
} }
const shareTicketKey = (shareId: string) => `cloudreve.share_ticket.${shareId}`;
export const getSharePurchaseTicket = (shareId: string): string | undefined => {
try {
return localStorage.getItem(shareTicketKey(shareId)) ?? undefined;
} catch {
return undefined;
}
};
export const setSharePurchaseTicket = (shareId: string, ticket: string) => {
try {
localStorage.setItem(shareTicketKey(shareId), ticket);
} catch {
// storage unavailable; resume simply won't survive a reload
}
};
export interface SharePurchaseResponse {
ticket: string;
}
export function purchaseShare(id: string): ThunkResponse<SharePurchaseResponse> {
return async (dispatch, _getState) => {
const res = await dispatch(
send(
"/share/purchase/" + id,
{
method: "POST",
},
{
...defaultOpts,
},
),
);
if (res?.ticket) {
setSharePurchaseTicket(id, res.ticket);
}
return res;
};
}
export function getShareInfo( export function getShareInfo(
id: string, id: string,
password?: string, password?: string,
@ -687,6 +742,10 @@ export function getShareInfo(
if (password && password != "") { if (password && password != "") {
query.set("password", password); query.set("password", password);
} }
const purchaseTicket = getSharePurchaseTicket(id);
if (purchaseTicket) {
query.set("purchase_ticket", purchaseTicket);
}
if (count_views) { if (count_views) {
query.set("count_views", "true"); query.set("count_views", "true");
} }
@ -696,7 +755,7 @@ export function getShareInfo(
if (query.toString() != "") { if (query.toString() != "") {
uri += "?" + query.toString(); uri += "?" + query.toString();
} }
return await dispatch( const res = await dispatch(
send( send(
uri, uri,
{ {
@ -708,6 +767,12 @@ export function getShareInfo(
}, },
), ),
); );
// Persist the server-issued resume ticket so entity/thumb downloads keep
// working for buyers who purchased on another device or browser.
if (res?.purchase_ticket) {
setSharePurchaseTicket(id, res.purchase_ticket);
}
return res;
}; };
} }
@ -730,6 +795,21 @@ export function sendCreateFile(req: CreateFileService): ThunkResponse<FileRespon
export function getFileEntityUrl(req: FileURLService): ThunkResponse<FileURLResponse> { export function getFileEntityUrl(req: FileURLService): ThunkResponse<FileURLResponse> {
return async (dispatch, _getState) => { return async (dispatch, _getState) => {
// Attach the stored purchase ticket for share URIs so paid-share
// downloads resume without a session.
if (!req.purchase_ticket && req.uris.length > 0) {
try {
const uri = new CrUri(req.uris[0]);
if (uri.fs() == Filesystem.share) {
const ticket = getSharePurchaseTicket(uri.id());
if (ticket) {
req = { ...req, purchase_ticket: ticket };
}
}
} catch {
// non-CrUri inputs fall through unchanged
}
}
return await dispatch( return await dispatch(
send( send(
"/file/url", "/file/url",

@ -95,6 +95,9 @@ export interface Share {
preview_only?: boolean; preview_only?: boolean;
upload_only?: boolean; upload_only?: boolean;
note?: string; note?: string;
price?: number;
paid?: boolean;
purchase_ticket?: string;
} }
export enum PolicyType { export enum PolicyType {
@ -347,6 +350,7 @@ export interface ShareCreateService {
preview_only?: boolean; preview_only?: boolean;
upload_only?: boolean; upload_only?: boolean;
note?: string; note?: string;
price_points?: number;
} }
export interface CreateFileService { export interface CreateFileService {
@ -366,6 +370,7 @@ export interface FileURLService extends MultipleUriService {
skip_error?: boolean; skip_error?: boolean;
use_primary_site_url?: boolean; use_primary_site_url?: boolean;
archive?: boolean; archive?: boolean;
purchase_ticket?: string;
} }
export interface FileURLResponse { export interface FileURLResponse {

@ -93,6 +93,7 @@ export const GroupPermission = {
archive_task: 5, archive_task: 5,
webdav_proxy: 6, webdav_proxy: 6,
share_download: 7, share_download: 7,
share_free: 8,
remote_download: 9, remote_download: 9,
redirected_source: 11, redirected_source: 11,
advance_delete: 12, advance_delete: 12,
@ -109,6 +110,7 @@ export const GroupPermission = {
admin_payment: 26, admin_payment: 26,
admin_events: 27, admin_events: 27,
admin_reports: 28, admin_reports: 28,
share_sell: 29,
}; };
// Delegated admin section bits — is_admin implies all of them. // Delegated admin section bits — is_admin implies all of them.

@ -37,6 +37,26 @@ const ShareSection = () => {
[setGroup], [setGroup],
); );
const onShareSellChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setGroup((p: GroupEnt) => ({
...p,
permissions: new Boolset(p.permissions).set(GroupPermission.share_sell, e.target.checked).toString(),
}));
},
[setGroup],
);
const onShareFreeChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setGroup((p: GroupEnt) => ({
...p,
permissions: new Boolset(p.permissions).set(GroupPermission.share_free, e.target.checked).toString(),
}));
},
[setGroup],
);
const onSetExplicitUserChange = useCallback( const onSetExplicitUserChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
setGroup((p: GroupEnt) => ({ setGroup((p: GroupEnt) => ({
@ -68,10 +88,25 @@ const ShareSection = () => {
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}
{values?.id != AnonymousGroupID && (
<SettingForm lgWidth={5}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch checked={permission.enabled(GroupPermission.share_sell)} onChange={onShareSellChange} />
}
label={t("group.shareSell")}
/>
<NoMarginHelperText>{t("group.shareSellDes")}</NoMarginHelperText>
</FormControl>
</SettingForm>
)}
<SettingForm lgWidth={5}> <SettingForm lgWidth={5}>
<FormControl fullWidth> <FormControl fullWidth>
<FormControlLabel <FormControlLabel
control={<Switch checked={false} />} control={
<Switch checked={permission.enabled(GroupPermission.share_free)} onChange={onShareFreeChange} />
}
label={ label={
<Box sx={{ display: "flex", alignItems: "center" }}> <Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.shareFree")} {t("group.shareFree")}

@ -22,7 +22,7 @@ import PaymentProviders from "./PaymentProviders.tsx";
import SkuTable from "./SkuTable.tsx"; import SkuTable from "./SkuTable.tsx";
const VAS = () => { const VAS = () => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const { formRef } = useContext(SettingContext); const { formRef, setSettings, values } = useContext(SettingContext);
const currencyPopupState = usePopupState({ const currencyPopupState = usePopupState({
variant: "popover", variant: "popover",
popupId: "currencySelector", popupId: "currencySelector",
@ -52,7 +52,15 @@ const VAS = () => {
<SettingForm title={t("settings.shareScoreRate")} lgWidth={5}> <SettingForm title={t("settings.shareScoreRate")} lgWidth={5}>
<FormControl fullWidth> <FormControl fullWidth>
<DenseFilledTextField type="number" value={80} slotProps={{ input: { readOnly: true } }} /> <DenseFilledTextField
type="number"
value={values.share_score_rate ?? "100"}
slotProps={{ htmlInput: { min: 0, max: 100 } }}
onChange={(e) => {
const v = Math.max(0, Math.min(100, Math.floor(Number(e.target.value) || 0)));
setSettings({ share_score_rate: v.toString() });
}}
/>
<NoMarginHelperText>{t("settings.shareScoreRateDes")}</NoMarginHelperText> <NoMarginHelperText>{t("settings.shareScoreRateDes")}</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>

@ -66,6 +66,7 @@ const shareToSetting = (share: ShareModel, t: TFunction): ShareSetting => {
preview_only: share.preview_only, preview_only: share.preview_only,
upload_only: share.upload_only, upload_only: share.upload_only,
note: share.note, note: share.note,
price_points: share.price && share.price > 0 ? share.price : undefined,
downloads: share.remain_downloads != undefined && share.remain_downloads > 0, downloads: share.remain_downloads != undefined && share.remain_downloads > 0,
expires_val: expireOptions[2], expires_val: expireOptions[2],

@ -24,6 +24,7 @@ import { Code } from "../../../Common/Code.tsx";
import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx"; import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx";
import BookInformation from "../../../Icons/BookInformation.tsx"; import BookInformation from "../../../Icons/BookInformation.tsx";
import ClockArrowDownload from "../../../Icons/ClockArrowDownload.tsx"; import ClockArrowDownload from "../../../Icons/ClockArrowDownload.tsx";
import CoinStack from "../../../Icons/CoinStack.tsx";
import Edit from "../../../Icons/Edit.tsx"; import Edit from "../../../Icons/Edit.tsx";
import Eye from "../../../Icons/Eye.tsx"; import Eye from "../../../Icons/Eye.tsx";
import EyeOff from "../../../Icons/EyeOff.tsx"; import EyeOff from "../../../Icons/EyeOff.tsx";
@ -91,6 +92,7 @@ export interface ShareSetting {
note?: string; note?: string;
downloads?: boolean; downloads?: boolean;
expires?: boolean; expires?: boolean;
price_points?: number;
downloads_val: valueOption; downloads_val: valueOption;
expires_val: valueOption; expires_val: valueOption;
@ -243,6 +245,40 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS
</FormControl> </FormControl>
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
<Accordion expanded={expanded === "price"} onChange={handleExpand("price")}>
<AccordionSummary aria-controls="panel-price-content" id="panel-price-header">
<StyledListItemButton>
<ListItemIcon>
<CoinStack />
</ListItemIcon>
<ListItemText
primary={t("application:modals.paidShare")}
secondary={setting.price_points ? t("application:modals.paidSharePrice", { price: setting.price_points }) : undefined}
/>
</StyledListItemButton>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" sx={{ mb: 1 }}>
{t("application:modals.paidShareDes")}
</Typography>
<FormControl variant="standard" fullWidth>
<FilledTextField
label={t("application:modals.paidSharePriceLabel")}
type="number"
slotProps={{
htmlInput: {
min: 0,
},
}}
value={setting.price_points ?? 0}
onChange={(e) => {
const v = Math.max(0, Math.floor(Number(e.target.value) || 0));
onSettingChange({ ...setting, price_points: v > 0 ? v : undefined });
}}
/>
</FormControl>
</AccordionDetails>
</Accordion>
<Accordion expanded={expanded === "preview_only"} onChange={handleExpand("preview_only")}> <Accordion expanded={expanded === "preview_only"} onChange={handleExpand("preview_only")}>
<AccordionSummary aria-controls="panel1a-content" id="panel1a-header"> <AccordionSummary aria-controls="panel1a-content" id="panel1a-header">
<StyledListItemButton> <StyledListItemButton>

@ -1,19 +1,22 @@
import { Box, useMediaQuery, useTheme } from "@mui/material"; import { Box, useMediaQuery, useTheme } from "@mui/material";
import React, { RefCallback, useCallback, useContext, useEffect, useMemo } from "react"; import React, { RefCallback, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { CSSTransition, SwitchTransition } from "react-transition-group"; import { CSSTransition, SwitchTransition } from "react-transition-group";
import { useAreaSelection } from "../../../hooks/areaSelection.ts"; import { useAreaSelection } from "../../../hooks/areaSelection.ts";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts"; import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts";
import { openEmptyContextMenu } from "../../../redux/thunks/filemanager.ts"; import { openEmptyContextMenu } from "../../../redux/thunks/filemanager.ts";
import { loadSiteConfig } from "../../../redux/thunks/site.ts"; import { loadSiteConfig } from "../../../redux/thunks/site.ts";
import { queueLoadShareInfo } from "../../../redux/thunks/share.ts";
import { Share } from "../../../api/explorer.ts";
import CircularProgress from "../../Common/CircularProgress.tsx"; import CircularProgress from "../../Common/CircularProgress.tsx";
import "../../Common/FadeTransition.css"; import "../../Common/FadeTransition.css";
import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; import { RadiusFrame } from "../../Frame/RadiusFrame.tsx";
import PaidShareGate from "../PaidShareGate.tsx";
import ExplorerError from "./ExplorerError.tsx"; import ExplorerError from "./ExplorerError.tsx";
import GridView, { FmFile } from "./GridView/GridView.tsx"; import GridView, { FmFile } from "./GridView/GridView.tsx";
import { Layouts } from "../../../redux/fileManagerSlice.ts"; import { Layouts } from "../../../redux/fileManagerSlice.ts";
import { SearchParam } from "../../../util/uri.ts"; import CrUri, { Filesystem, SearchParam } from "../../../util/uri.ts";
import { FileManagerIndex } from "../FileManager.tsx"; import { FileManagerIndex } from "../FileManager.tsx";
import { FmIndexContext } from "../FmIndexContext.tsx"; import { FmIndexContext } from "../FmIndexContext.tsx";
import EmptyFileList, { SearchLimitReached } from "./EmptyFileList.tsx"; import EmptyFileList, { SearchLimitReached } from "./EmptyFileList.tsx";
@ -58,6 +61,22 @@ const Explorer = () => {
const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files); const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files);
const recursion_limit_reached = useAppSelector((state) => state.fileManager[fmIndex].list?.recursion_limit_reached); const recursion_limit_reached = useAppSelector((state) => state.fileManager[fmIndex].list?.recursion_limit_reached);
const layout = useAppSelector((state) => state.fileManager[fmIndex].layout); const layout = useAppSelector((state) => state.fileManager[fmIndex].layout);
const currentFs = useAppSelector((state) => state.fileManager[fmIndex].current_fs);
const purePath = useAppSelector((state) => state.fileManager[fmIndex].pure_path);
const [shareInfo, setShareInfo] = useState<Share | null>(null);
useEffect(() => {
setShareInfo(null);
if (currentFs == Filesystem.share && purePath) {
try {
dispatch(queueLoadShareInfo(new CrUri(purePath)))
.then(setShareInfo)
.catch(() => setShareInfo(null));
} catch {
setShareInfo(null);
}
}
}, [currentFs, purePath, dispatch]);
const selectContainerRef = React.useRef<HTMLElement | null>(null); const selectContainerRef = React.useRef<HTMLElement | null>(null);
@ -119,6 +138,11 @@ const Explorer = () => {
onMouseUp={isMobile || isTouch ? undefined : handleMouseUp} onMouseUp={isMobile || isTouch ? undefined : handleMouseUp}
onMouseMove={isMobile || isTouch ? undefined : handleMouseMove} onMouseMove={isMobile || isTouch ? undefined : handleMouseMove}
> >
{fmIndex == FileManagerIndex.main && shareInfo && (shareInfo.price ?? 0) > 0 && !shareInfo.paid && (
<Box sx={{ px: 2, pt: 1 }}>
<PaidShareGate shareInfo={shareInfo} onPurchased={() => window.location.reload()} />
</Box>
)}
<SwitchTransition> <SwitchTransition>
<CSSTransition <CSSTransition
timeout={500} timeout={500}

@ -37,6 +37,7 @@ import Timer from "../../Icons/Timer.tsx";
import useActionDisplayOpt from "../ContextMenu/useActionDisplayOpt.ts"; import useActionDisplayOpt from "../ContextMenu/useActionDisplayOpt.ts";
import { FmIndexContext } from "../FmIndexContext.tsx"; import { FmIndexContext } from "../FmIndexContext.tsx";
import { PropTypography, ShareExpires, ShareStatistics } from "../TopBar/ShareInfoPopover.tsx"; import { PropTypography, ShareExpires, ShareStatistics } from "../TopBar/ShareInfoPopover.tsx";
import PaidShareGate from "../PaidShareGate.tsx";
import FileIcon from "./FileIcon.tsx"; import FileIcon from "./FileIcon.tsx";
import FileTagSummary from "./FileTagSummary.tsx"; import FileTagSummary from "./FileTagSummary.tsx";
import { useFileBlockState } from "./GridView/GridFile.tsx"; import { useFileBlockState } from "./GridView/GridFile.tsx";
@ -228,6 +229,7 @@ const SingleFileView = forwardRef((_props, ref: React.Ref<any>) => {
<ShareExpires expires={shareInfo.expires} remain_downloads={shareInfo.remain_downloads} /> <ShareExpires expires={shareInfo.expires} remain_downloads={shareInfo.remain_downloads} />
</Alert> </Alert>
)} )}
<PaidShareGate shareInfo={shareInfo} onPurchased={() => window.location.reload()} />
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -251,7 +253,7 @@ const SingleFileView = forwardRef((_props, ref: React.Ref<any>) => {
{t("application:fileManager.save")} {t("application:fileManager.save")}
</SecondaryButton> </SecondaryButton>
)} )}
{displayOpt.showOpen && file && ( {displayOpt.showOpen && file && !(shareInfo.price && !shareInfo.paid) && (
<SecondaryButton <SecondaryButton
variant="contained" variant="contained"
onClick={() => dispatch(openViewers(0, file))} onClick={() => dispatch(openViewers(0, file))}
@ -262,7 +264,7 @@ const SingleFileView = forwardRef((_props, ref: React.Ref<any>) => {
</SecondaryButton> </SecondaryButton>
)} )}
<ButtonGroup disableElevation variant="contained"> <ButtonGroup disableElevation variant="contained">
{!shareInfo.preview_only && ( {!shareInfo.preview_only && !(shareInfo.price && !shareInfo.paid) && (
<Button onClick={download} disabled={loading} startIcon={<Download />}> <Button onClick={download} disabled={loading} startIcon={<Download />}>
{t("application:fileManager.download")} {t("application:fileManager.download")}
</Button> </Button>

@ -0,0 +1,89 @@
import { Alert, Box, Button, Typography } from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { getCredit, purchaseShare } from "../../api/api.ts";
import { CreditInfo } from "../../api/user.ts";
import { Share } from "../../api/explorer.ts";
import { useAppDispatch } from "../../redux/hooks.ts";
import SessionManager from "../../session/index.ts";
import CoinStack from "../Icons/CoinStack.tsx";
interface PaidShareGateProps {
shareInfo: Share;
// Called after a successful purchase; the caller typically reloads state.
onPurchased?: () => void;
}
// PaidShareGate renders the purchase CTA for a priced share the visitor has
// not paid for. Anonymous visitors get a sign-in prompt; buyers resume via
// the ticket stored by purchaseShare.
const PaidShareGate = ({ shareInfo, onPurchased }: PaidShareGateProps) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(false);
const [credit, setCredit] = useState<CreditInfo | null>(null);
const user = SessionManager.currentLoginOrNull();
const price = shareInfo.price ?? 0;
const paid = shareInfo.paid ?? false;
useEffect(() => {
if (user && price > 0 && !paid) {
dispatch(getCredit())
.then(setCredit)
.catch(() => setCredit(null));
}
}, [user, price, paid, dispatch]);
const purchase = useCallback(async () => {
setLoading(true);
try {
await dispatch(purchaseShare(shareInfo.id));
onPurchased?.();
} catch (_e) {
// snackbar already reported by the request layer
} finally {
setLoading(false);
}
}, [dispatch, shareInfo.id, onPurchased]);
if (price <= 0 || paid || (user && shareInfo.owner.id == user.user.id)) {
return null;
}
return (
<Alert
severity="info"
icon={<CoinStack />}
sx={{ alignItems: "center" }}
action={
user ? (
<Button
variant="contained"
size="small"
disabled={loading || (credit != null && credit.credits < price)}
onClick={purchase}
>
{t("application:share.purchaseForPoints", { price })}
</Button>
) : (
<Button variant="contained" size="small" component={RouterLink} to="/session">
{t("application:share.signInToPurchase")}
</Button>
)
}
>
<Box>
<Typography variant="body2">{t("application:share.paidShareDes", { price })}</Typography>
{user && credit != null && (
<Typography variant="caption" color="text.secondary">
{t("application:share.yourBalance", { credits: credit.credits })}
</Typography>
)}
</Box>
</Alert>
);
};
export default PaidShareGate;

@ -35,6 +35,7 @@ export function createOrUpdateShareLink(
preview_only: setting.preview_only, preview_only: setting.preview_only,
upload_only: setting.upload_only, upload_only: setting.upload_only,
note: setting.note?.trim() || undefined, note: setting.note?.trim() || undefined,
price_points: setting.price_points ?? undefined,
downloads: setting.downloads && setting.downloads_val.value > 0 ? setting.downloads_val.value : undefined, downloads: setting.downloads && setting.downloads_val.value > 0 ? setting.downloads_val.value : undefined,
expire: setting.expires && setting.expires_val.value > 0 ? setting.expires_val.value : undefined, expire: setting.expires && setting.expires_val.value > 0 ? setting.expires_val.value : undefined,
}; };

@ -69,6 +69,7 @@ type (
OwnerID int OwnerID int
FileID int FileID int
Props *types.ShareProps Props *types.ShareProps
PricePoints int
} }
ListShareArgs struct { ListShareArgs struct {
@ -133,6 +134,7 @@ func (c *shareClient) Upsert(ctx context.Context, params *CreateShareParams) (*e
createQuery.SetProps(params.Props) createQuery.SetProps(params.Props)
} }
createQuery.SetPricePoints(params.PricePoints)
return createQuery.Save(ctx) return createQuery.Save(ctx)
} }
@ -152,6 +154,9 @@ func (c *shareClient) Upsert(ctx context.Context, params *CreateShareParams) (*e
if params.Props != nil { if params.Props != nil {
query.SetProps(params.Props) query.SetProps(params.Props)
} }
if params.PricePoints > 0 {
query.SetPricePoints(params.PricePoints)
}
return query.Save(ctx) return query.Save(ctx)
} }

@ -324,7 +324,9 @@ const (
GroupPermissionArchiveTask GroupPermissionArchiveTask
GroupPermissionWebDAVProxy GroupPermissionWebDAVProxy
GroupPermissionShareDownload GroupPermissionShareDownload
GroupPermission_CommunityPlaceholder1 // GroupPermissionShareFree lets members access paid shares without
// purchasing (e.g. staff or VIP groups).
GroupPermissionShareFree
GroupPermissionRemoteDownload GroupPermissionRemoteDownload
GroupPermission_CommunityPlaceholder2 GroupPermission_CommunityPlaceholder2
GroupPermissionRedirectedSource // not used GroupPermissionRedirectedSource // not used
@ -354,6 +356,9 @@ const (
GroupPermissionAdminPayment GroupPermissionAdminPayment
GroupPermissionAdminEvents GroupPermissionAdminEvents
GroupPermissionAdminReports GroupPermissionAdminReports
// GroupPermissionShareSell allows members to set a points price on
// shares they create (paid shares).
GroupPermissionShareSell
) )
// AclPermission is a bit position in an ACL entry's permission bitmask. // AclPermission is a bit position in an ACL entry's permission bitmask.

@ -10,6 +10,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/credittxn" "github.com/cloudreve/Cloudreve/v4/ent/credittxn"
"github.com/cloudreve/Cloudreve/v4/ent/giftcode" "github.com/cloudreve/Cloudreve/v4/ent/giftcode"
"github.com/cloudreve/Cloudreve/v4/ent/schema" "github.com/cloudreve/Cloudreve/v4/ent/schema"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant" "github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -66,6 +67,17 @@ type (
// grant. Fails with ErrInsufficientPoints when the balance cannot // grant. Fails with ErrInsufficientPoints when the balance cannot
// cover the price, leaving the grant unapplied. // cover the price, leaving the grant unapplied.
PurchaseSku(ctx context.Context, userID int, sku *ent.Sku) error PurchaseSku(ctx context.Context, userID int, sku *ent.Sku) error
// PurchaseShare atomically debits the share's points price from the
// buyer, credits the owner income (price times scoreRate fraction),
// and records the purchase row. Idempotent: an existing purchase for
// the same (share, buyer) is returned without debiting again.
PurchaseShare(ctx context.Context, share *ent.Share, buyerID int, scoreRate float64) (*ent.SharePurchase, error)
// SharePurchase returns the purchase row for (share, buyer), or
// ent.NotFound when the buyer has not purchased.
SharePurchase(ctx context.Context, shareID, buyerID int) (*ent.SharePurchase, error)
// SharePurchaseByTicket resolves a resume ticket to its purchase row,
// scoped to the given share so tickets cannot cross shares.
SharePurchaseByTicket(ctx context.Context, shareID int, ticket string) (*ent.SharePurchase, error)
} }
CreateGiftCodeParams struct { CreateGiftCodeParams struct {
@ -417,6 +429,76 @@ func (c *vasClient) PurchaseSku(ctx context.Context, userID int, s *ent.Sku) err
return Commit(tx) return Commit(tx)
} }
func (c *vasClient) PurchaseShare(ctx context.Context, s *ent.Share, buyerID int, scoreRate float64) (*ent.SharePurchase, error) {
if existing, err := c.SharePurchase(ctx, s.ID, buyerID); err == nil {
return existing, nil
} else if !ent.IsNotFound(err) {
return nil, err
}
price := int64(s.PricePoints)
if scoreRate < 0 {
scoreRate = 0
}
if scoreRate > 1 {
scoreRate = 1
}
income := int64(float64(price) * scoreRate)
txVc, tx, txCtx, err := WithTx(ctx, c)
if err != nil {
return nil, err
}
if err := txVc.CreditAdjust(txCtx, buyerID, -price, credittxn.TypePurchase,
strconv.Itoa(s.ID), "share purchase"); err != nil {
_ = Rollback(tx)
return nil, err
}
purchase, err := txVc.client.SharePurchase.Create().
SetShareID(s.ID).
SetBuyerID(buyerID).
SetPoints(s.PricePoints).
SetTicket(newGiftCodeString()).
Save(txCtx)
if err != nil {
_ = Rollback(tx)
// Concurrent buyer already committed — their purchase wins.
if existing, qErr := c.SharePurchase(ctx, s.ID, buyerID); qErr == nil {
return existing, nil
}
return nil, err
}
if income > 0 && s.Edges.User != nil && s.Edges.User.ID != buyerID {
if err := txVc.CreditAdjust(txCtx, s.Edges.User.ID, income, credittxn.TypeShareIncome,
strconv.Itoa(s.ID), "share sale"); err != nil {
return nil, Rollback(tx)
}
}
if err := Commit(tx); err != nil {
return nil, err
}
return purchase, nil
}
func (c *vasClient) SharePurchase(ctx context.Context, shareID, buyerID int) (*ent.SharePurchase, error) {
return c.client.SharePurchase.Query().
Where(sharepurchase.ShareID(shareID), sharepurchase.BuyerID(buyerID)).
Only(ctx)
}
func (c *vasClient) SharePurchaseByTicket(ctx context.Context, shareID int, ticket string) (*ent.SharePurchase, error) {
if ticket == "" {
return nil, &ent.NotFoundError{}
}
return c.client.SharePurchase.Query().
Where(sharepurchase.ShareID(shareID), sharepurchase.Ticket(ticket)).
Only(ctx)
}
func newGiftCodeString() string { func newGiftCodeString() string {
return uuid.Must(uuid.NewV4()).String() return uuid.Must(uuid.NewV4()).String()
} }

@ -2,6 +2,7 @@ package inventory
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -12,6 +13,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/giftcode" "github.com/cloudreve/Cloudreve/v4/ent/giftcode"
"github.com/cloudreve/Cloudreve/v4/ent/sku" "github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant" "github.com/cloudreve/Cloudreve/v4/ent/usergrant"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset" "github.com/cloudreve/Cloudreve/v4/pkg/boolset"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -239,3 +241,85 @@ func TestPurchaseSku(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(1024), bonus) require.Equal(t, int64(1024), bonus)
} }
func paidShareFixture(t *testing.T, client *ent.Client, price int) (*ent.User, *ent.User, *ent.Share) {
return paidShareFixtureN(t, client, price, 0)
}
func paidShareFixtureN(t *testing.T, client *ent.Client, price, n int) (*ent.User, *ent.User, *ent.Share) {
ctx := context.Background()
suffix := fmt.Sprintf("%s-%d", t.Name(), n)
group := client.Group.Create().SetName("g" + suffix).SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
owner := client.User.Create().SetEmail("owner-" + suffix + "@example.com").SetNick("o" + suffix).SetGroup(group).SaveX(ctx)
buyer := client.User.Create().SetEmail("buyer-" + suffix + "@example.com").SetNick("b" + suffix).SetGroup(group).SaveX(ctx)
root := client.File.Create().SetName(RootFolderName).SetType(int(types.FileTypeFolder)).SetOwner(owner).SaveX(ctx)
file := client.File.Create().SetName("paid" + suffix + ".txt").SetType(int(types.FileTypeFile)).SetOwner(owner).SetParent(root).SaveX(ctx)
share := client.Share.Create().SetUser(owner).SetFile(file).SetPricePoints(price).SaveX(ctx)
share.Edges.User = owner
return owner, buyer, share
}
func TestPurchaseShare(t *testing.T) {
ctx := context.Background()
client, c := newVasClient(t)
owner, buyer, share := paidShareFixture(t, client, 100)
require.NoError(t, c.CreditAdjust(ctx, buyer.ID, 250, credittxn.TypeAdjust, "", "grant"))
// 80% commission: buyer pays 100, owner earns 80.
purchase, err := c.PurchaseShare(ctx, share, buyer.ID, 0.8)
require.NoError(t, err)
require.NotEmpty(t, purchase.Ticket)
require.Equal(t, 100, purchase.Points)
require.Equal(t, int64(150), client.User.GetX(ctx, buyer.ID).Credits)
require.Equal(t, int64(80), client.User.GetX(ctx, owner.ID).Credits)
// Idempotent: second purchase returns the same row without re-debiting.
again, err := c.PurchaseShare(ctx, share, buyer.ID, 0.8)
require.NoError(t, err)
require.Equal(t, purchase.ID, again.ID)
require.Equal(t, purchase.Ticket, again.Ticket)
require.Equal(t, int64(150), client.User.GetX(ctx, buyer.ID).Credits)
// Ticket is scoped to its own share.
_, err = c.SharePurchaseByTicket(ctx, share.ID, purchase.Ticket)
require.NoError(t, err)
_, _, otherShare := paidShareFixtureN(t, client, 100, 1)
_, err = c.SharePurchaseByTicket(ctx, otherShare.ID, purchase.Ticket)
require.Error(t, err)
_, err = c.SharePurchaseByTicket(ctx, share.ID, "bogus-ticket")
require.Error(t, err)
}
func TestPurchaseShareInsufficient(t *testing.T) {
ctx := context.Background()
client, c := newVasClient(t)
owner, buyer, share := paidShareFixture(t, client, 100)
require.NoError(t, c.CreditAdjust(ctx, buyer.ID, 50, credittxn.TypeAdjust, "", "grant"))
_, err := c.PurchaseShare(ctx, share, buyer.ID, 1)
require.ErrorIs(t, err, ErrInsufficientPoints)
require.Equal(t, int64(50), client.User.GetX(ctx, buyer.ID).Credits)
require.Equal(t, int64(0), client.User.GetX(ctx, owner.ID).Credits)
_, err = c.SharePurchase(ctx, share.ID, buyer.ID)
require.Error(t, err)
}
func TestPurchaseShareIncomeRates(t *testing.T) {
ctx := context.Background()
client, c := newVasClient(t)
// Zero commission: owner earns nothing, purchase still recorded.
owner, buyer, share := paidShareFixture(t, client, 40)
require.NoError(t, c.CreditAdjust(ctx, buyer.ID, 100, credittxn.TypeAdjust, "", "grant"))
p, err := c.PurchaseShare(ctx, share, buyer.ID, 0)
require.NoError(t, err)
require.NotNil(t, p)
require.Equal(t, int64(0), client.User.GetX(ctx, owner.ID).Credits)
// Rate above 1 clamps to full price.
owner2, buyer2, share2 := paidShareFixtureN(t, client, 30, 1)
require.NoError(t, c.CreditAdjust(ctx, buyer2.ID, 100, credittxn.TypeAdjust, "", "grant"))
_, err = c.PurchaseShare(ctx, share2, buyer2.ID, 1.5)
require.NoError(t, err)
require.Equal(t, int64(30), client.User.GetX(ctx, owner2.ID).Credits)
}

@ -43,6 +43,9 @@ type (
// IsDownloadCtxKey marks the request as an explicit file download (as // IsDownloadCtxKey marks the request as an explicit file download (as
// opposed to an inline preview fetch). Navigator hooks consult it. // opposed to an inline preview fetch). Navigator hooks consult it.
IsDownloadCtxKey struct{} IsDownloadCtxKey struct{}
// PurchaseTicketCtxKey carries a paid-share resume ticket used to
// restore download access after session loss.
PurchaseTicketCtxKey struct{}
// ExpectedSourceIDsCtxKey carries source-file identity preconditions // ExpectedSourceIDsCtxKey carries source-file identity preconditions
// for move/copy/rename: a []int positionally aligned with the source // for move/copy/rename: a []int positionally aligned with the source
// URI list. An entry of 0 disables the check for that position (#3565). // URI list. An entry of 0 disables the check for that position (#3565).
@ -947,7 +950,7 @@ func (f *DBFS) getNavigator(ctx context.Context, path *fs.URI, requiredCapabilit
case constants.FileSystemMy: case constants.FileSystemMy:
n = NewMyNavigator(f.user, f.fileClient, f.userClient, f.l, config, f.hasher) n = NewMyNavigator(f.user, f.fileClient, f.userClient, f.l, config, f.hasher)
case constants.FileSystemShare: case constants.FileSystemShare:
n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.aclClient, f.l, config, f.hasher) n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.aclClient, f.vasClient, f.l, config, f.hasher)
case constants.FileSystemTrash: case constants.FileSystemTrash:
n = NewTrashNavigator(f.user, f.fileClient, f.l, config, f.hasher) n = NewTrashNavigator(f.user, f.fileClient, f.l, config, f.hasher)
case constants.FileSystemSharedWithMe: case constants.FileSystemSharedWithMe:

@ -38,13 +38,14 @@ var shareNavigatorCapability = &boolset.BooleanSet{}
// NewShareNavigator creates a navigator for user's "shared" file system. // NewShareNavigator creates a navigator for user's "shared" file system.
func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient, func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient,
aclClient inventory.AclClient, l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator { aclClient inventory.AclClient, vasClient inventory.VasClient, l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator {
n := &shareNavigator{ n := &shareNavigator{
user: u, user: u,
l: l, l: l,
fileClient: fileClient, fileClient: fileClient,
shareClient: shareClient, shareClient: shareClient,
aclClient: aclClient, aclClient: aclClient,
vasClient: vasClient,
config: config, config: config,
} }
n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config) n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config)
@ -70,8 +71,12 @@ type (
// shared file matching the acting user. nil means no entry matched — // shared file matching the acting user. nil means no entry matched —
// capabilities then fall back to share props. // capabilities then fall back to share props.
aclCaps *boolset.BooleanSet aclCaps *boolset.BooleanSet
vasClient inventory.VasClient
disableRecycle bool disableRecycle bool
persist func() persist func()
// sharePaid is resolved at Root() and persisted with the navigator
// state: free shares, the owner, and buyers/ticket holders are true.
sharePaid bool
} }
shareNavigatorState struct { shareNavigatorState struct {
@ -81,6 +86,7 @@ type (
Share *ent.Share Share *ent.Share
Owner *ent.User Owner *ent.User
AclCaps *boolset.BooleanSet AclCaps *boolset.BooleanSet
SharePaid bool
} }
) )
@ -94,6 +100,7 @@ func (n *shareNavigator) PersistState(kv cache.Driver, key string) {
Share: n.share, Share: n.share,
Owner: n.owner, Owner: n.owner,
AclCaps: n.aclCaps, AclCaps: n.aclCaps,
SharePaid: n.sharePaid,
}, ContextHintTTL) }, ContextHintTTL)
} }
} }
@ -107,6 +114,7 @@ func (n *shareNavigator) RestoreState(s State) error {
n.share = state.Share n.share = state.Share
n.aclCaps = state.AclCaps n.aclCaps = state.AclCaps
n.owner = state.Owner n.owner = state.Owner
n.sharePaid = state.SharePaid
return nil return nil
} }
@ -150,6 +158,7 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error)
// Share must be assigned before capabilities are derived from its props. // Share must be assigned before capabilities are derived from its props.
n.share = share n.share = share
n.sharePaid = n.checkSharePaid(ctx, share)
// Resolve per-file ACL entries for non-owner visitors; matched rows // Resolve per-file ACL entries for non-owner visitors; matched rows
// replace the share-props capability set for this user. // replace the share-props capability set for this user.
@ -316,7 +325,9 @@ func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet {
// Matched ACL entries fully define a non-owner visitor's capabilities; // Matched ACL entries fully define a non-owner visitor's capabilities;
// when no entry matched (nil) share props apply as the link default. // when no entry matched (nil) share props apply as the link default.
if n.aclCaps != nil && n.owner != nil && n.user.ID != n.owner.ID { if n.aclCaps != nil && n.owner != nil && n.user.ID != n.owner.ID {
return aclPermsToCapabilities(n.aclCaps) bs := aclPermsToCapabilities(n.aclCaps)
n.stripUnpaid(bs)
return bs
} }
bs := &boolset.BooleanSet{} bs := &boolset.BooleanSet{}
@ -365,9 +376,19 @@ func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet {
boolset.Set(int(NavigatorCapabilitySoftDelete), true, bs) boolset.Set(int(NavigatorCapabilitySoftDelete), true, bs)
} }
n.stripUnpaid(bs)
return bs return bs
} }
// stripUnpaid removes download/thumbnail capabilities for visitors who have
// not paid for a priced share; listing stays so the paywall can render.
func (n *shareNavigator) stripUnpaid(bs *boolset.BooleanSet) {
if n.share != nil && n.share.PricePoints > 0 && !n.sharePaid {
boolset.Set(int(NavigatorCapabilityDownloadFile), false, bs)
boolset.Set(int(NavigatorCapabilityGenerateThumb), false, bs)
}
}
// aclPermsToCapabilities maps ACL permission bits (read/create/update/delete) // aclPermsToCapabilities maps ACL permission bits (read/create/update/delete)
// to the navigator capability set granted through a share link. // to the navigator capability set granted through a share link.
func aclPermsToCapabilities(perms *boolset.BooleanSet) *boolset.BooleanSet { func aclPermsToCapabilities(perms *boolset.BooleanSet) *boolset.BooleanSet {
@ -401,7 +422,7 @@ func aclPermsToCapabilities(perms *boolset.BooleanSet) *boolset.BooleanSet {
if perms.Enabled(int(types.AclPermDelete)) { if perms.Enabled(int(types.AclPermDelete)) {
boolset.Sets(map[NavigatorCapability]bool{ boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityDeleteFile: true, NavigatorCapabilityDeleteFile: true,
NavigatorCapabilitySoftDelete: true, NavigatorCapabilitySoftDelete: true,
}, bs) }, bs)
} }
return bs return bs
@ -431,6 +452,11 @@ func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) {
func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {
switch hookType { switch hookType {
case fs.HookTypeBeforeDownload: case fs.HookTypeBeforeDownload:
// Priced shares deny every entity fetch — previews included — until
// the visitor holds a purchase or a valid resume ticket.
if n.share != nil && n.share.PricePoints > 0 && !n.sharePaid {
return ErrNotPurchased
}
// Preview-only shares deny explicit downloads but still allow // Preview-only shares deny explicit downloads but still allow
// entity fetches for inline viewers. // entity fetches for inline viewers.
if n.share != nil && n.share.Props != nil && n.share.Props.PreviewOnly { if n.share != nil && n.share.Props != nil && n.share.Props.PreviewOnly {
@ -445,6 +471,36 @@ func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType,
return nil return nil
} }
// checkSharePaid resolves whether the acting user may fetch entities of a
// priced share. Free shares, the owner, existing buyers, and holders of a
// valid resume ticket pass; everyone else — including anonymous users
// without a ticket — is denied.
func (n *shareNavigator) checkSharePaid(ctx context.Context, share *ent.Share) bool {
if share.PricePoints <= 0 || n.user.ID == share.Edges.User.ID {
return true
}
// Groups with the share-free bit (staff/VIP) bypass the paywall.
if n.user.Edges.Group != nil &&
n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareFree)) {
return true
}
if n.vasClient == nil {
return false
}
if ticket, ok := ctx.Value(PurchaseTicketCtxKey{}).(string); ok && ticket != "" {
if _, err := n.vasClient.SharePurchaseByTicket(ctx, share.ID, ticket); err == nil {
return true
}
}
if inventory.IsAnonymousUser(n.user) {
return false
}
if _, err := n.vasClient.SharePurchase(ctx, share.ID, n.user.ID); err == nil {
return true
}
return false
}
func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error { func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error {
return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f) return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f)
} }

@ -128,6 +128,7 @@ type (
PreviewOnly bool PreviewOnly bool
UploadOnly bool UploadOnly bool
Note string Note string
PricePoints int
} }
FullTextSearchResults struct { FullTextSearchResults struct {

@ -366,6 +366,7 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C
RemainDownloads: args.RemainDownloads, RemainDownloads: args.RemainDownloads,
Existed: existed, Existed: existed,
Props: props, Props: props,
PricePoints: args.PricePoints,
}) })
if err != nil { if err != nil {

@ -268,6 +268,9 @@ type (
// Announcement returns the site-wide markdown announcement shown to // Announcement returns the site-wide markdown announcement shown to
// logged-in users. "" disables the announcement modal. // logged-in users. "" disables the announcement modal.
Announcement(ctx context.Context) string Announcement(ctx context.Context) string
// ShareScoreRate returns the fraction (0..1) of a paid share's
// points price the owner receives as income.
ShareScoreRate(ctx context.Context) float64
} }
UseFirstSiteUrlCtxKey = struct{} UseFirstSiteUrlCtxKey = struct{}
) )
@ -932,6 +935,10 @@ func (s *settingProvider) AbuseCaptchaEnabled(ctx context.Context) bool {
return s.getBoolean(ctx, "abuse_captcha", true) return s.getBoolean(ctx, "abuse_captcha", true)
} }
func (s *settingProvider) ShareScoreRate(ctx context.Context) float64 {
return s.getFloat64(ctx, "share_score_rate", 100) / 100
}
func (s *settingProvider) AuthnEnabled(ctx context.Context) bool { func (s *settingProvider) AuthnEnabled(ctx context.Context) bool {
return s.getBoolean(ctx, "authn_enabled", false) return s.getBoolean(ctx, "authn_enabled", false)
} }

@ -42,6 +42,17 @@ func GetShare(c *gin.Context) {
c.JSON(200, serializer.Response{Data: info}) c.JSON(200, serializer.Response{Data: info})
} }
// PurchaseShare purchases a paid share with the caller's credit balance.
func PurchaseShare(c *gin.Context) {
service := ParametersFromContext[*share.SharePurchaseService](c, share.SharePurchaseParamCtx{})
res, err := service.Purchase(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{Data: res})
}
// ListShare 列出分享 // ListShare 列出分享
func ListShare(c *gin.Context) { func ListShare(c *gin.Context) {
service := ParametersFromContext[*share.ListShareService](c, share.ListShareParamCtx{}) service := ParametersFromContext[*share.ListShareService](c, share.ListShareParamCtx{})

@ -939,6 +939,12 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
controllers.FromQuery[sharesvc.ShareInfoService](sharesvc.ShareInfoParamCtx{}), controllers.FromQuery[sharesvc.ShareInfoService](sharesvc.ShareInfoParamCtx{}),
controllers.GetShare, controllers.GetShare,
) )
// Purchase a paid share with credits
share.POST("purchase/:id",
middleware.LoginRequired(),
middleware.HashID(hashid.ShareID),
controllers.PurchaseShare,
)
// List my shares // List my shares
share.GET("", share.GET("",
middleware.LoginRequired(), middleware.LoginRequired(),

@ -51,6 +51,7 @@ func TestMasterRouteWiring(t *testing.T) {
"GET /api/v4/user/info/:id", "GET /api/v4/user/info/:id",
"PUT /api/v4/file/upload", "PUT /api/v4/file/upload",
"POST /api/v4/file/upload/:sessionId/:index", "POST /api/v4/file/upload/:sessionId/:index",
"POST /api/v4/share/purchase/:id",
"GET /f/:id/:name", "GET /f/:id/:name",
} }
for _, e := range expected { for _, e := range expected {

@ -415,6 +415,8 @@ type (
SkipError bool `json:"skip_error"` SkipError bool `json:"skip_error"`
Archive bool `json:"archive"` Archive bool `json:"archive"`
NoCache bool `json:"no_cache"` NoCache bool `json:"no_cache"`
// PurchaseTicket restores paid-share access after session loss.
PurchaseTicket string `json:"purchase_ticket"`
} }
FileURLResponse struct { FileURLResponse struct {
Urls []manager.EntityUrl `json:"urls"` Urls []manager.EntityUrl `json:"urls"`
@ -503,6 +505,9 @@ func (s *FileURLService) Get(c *gin.Context) (*FileURLResponse, error) {
if s.UsePrimarySiteURL { if s.UsePrimarySiteURL {
ctx = setting.UseFirstSiteUrl(ctx) ctx = setting.UseFirstSiteUrl(ctx)
} }
if s.PurchaseTicket != "" {
ctx = context.WithValue(ctx, dbfs.PurchaseTicketCtxKey{}, s.PurchaseTicket)
}
res, earliestExpire, err := m.GetEntityUrls(ctx, urlReq, res, earliestExpire, err := m.GetEntityUrls(ctx, urlReq,
fs.WithDownloadSpeed(int64(user.Edges.Group.SpeedLimit)), fs.WithDownloadSpeed(int64(user.Edges.Group.SpeedLimit)),
@ -541,7 +546,8 @@ func (s *FileURLService) Get(c *gin.Context) (*FileURLResponse, error) {
type ( type (
FileThumbParameterCtx struct{} FileThumbParameterCtx struct{}
FileThumbService struct { FileThumbService struct {
Uri string `form:"uri" binding:"required"` Uri string `form:"uri" binding:"required"`
PurchaseTicket string `form:"purchase_ticket"`
} }
FileThumbResponse struct { FileThumbResponse struct {
Url string `json:"url"` Url string `json:"url"`
@ -562,7 +568,11 @@ func (s *FileThumbService) Get(c *gin.Context) (*FileThumbResponse, error) {
} }
// Get thumbnail // Get thumbnail
thumb, err := m.Thumbnail(c, uri) var ctx context.Context = c
if s.PurchaseTicket != "" {
ctx = context.WithValue(c, dbfs.PurchaseTicketCtxKey{}, s.PurchaseTicket)
}
thumb, err := m.Thumbnail(ctx, uri)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get thumbnail: %w", err) return nil, fmt.Errorf("failed to get thumbnail: %w", err)
} }

@ -346,6 +346,16 @@ type Share struct {
// Only viewable if explicitly unlocked by owner // Only viewable if explicitly unlocked by owner
SourceUri string `json:"source_uri,omitempty"` SourceUri string `json:"source_uri,omitempty"`
// Points price for paid shares. Always visible so visitors can render
// the purchase gate.
Price int `json:"price,omitempty"`
// Paid is true when the requester is the owner, has purchased the
// share, or presented a valid resume ticket. Omitted for free shares.
Paid *bool `json:"paid,omitempty"`
// PurchaseTicket is the requester's resume credential; only set for
// the purchasing user, never for the owner or other visitors.
PurchaseTicket string `json:"purchase_ticket,omitempty"`
} }
func BuildShare(ctx context.Context, s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *ent.User, owner *ent.User, func BuildShare(ctx context.Context, s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *ent.User, owner *ent.User,
@ -365,6 +375,7 @@ func BuildShare(ctx context.Context, s *ent.Share, base *url.URL, hasher hashid.
Visited: s.Views, Visited: s.Views,
SourceType: util.ToPtr(t), SourceType: util.ToPtr(t),
PasswordProtected: s.Password != "", PasswordProtected: s.Password != "",
Price: s.PricePoints,
} }
if unlocked { if unlocked {

@ -34,6 +34,8 @@ type (
UploadOnly bool `json:"upload_only"` UploadOnly bool `json:"upload_only"`
// Optional owner-defined note shown on My Shares (#3570). // Optional owner-defined note shown on My Shares (#3570).
Note string `json:"note" binding:"omitempty,max=255"` Note string `json:"note" binding:"omitempty,max=255"`
// Points price visitors must pay before downloading. 0 = free share.
PricePoints int `json:"price_points" binding:"omitempty,min=0"`
} }
ShareCreateParamCtx struct{} ShareCreateParamCtx struct{}
@ -81,6 +83,10 @@ func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string,
return "", serializer.NewError(serializer.CodeGroupNotAllowed, "Group permission denied", nil) return "", serializer.NewError(serializer.CodeGroupNotAllowed, "Group permission denied", nil)
} }
if service.PricePoints > 0 && !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareSell)) {
return "", serializer.NewError(serializer.CodeGroupNotAllowed, "Group permission denied for paid share", nil)
}
uri, err := fs.NewUriFromString(service.Uri) uri, err := fs.NewUriFromString(service.Uri)
if err != nil { if err != nil {
return "", serializer.NewError(serializer.CodeParamErr, "unknown uri", err) return "", serializer.NewError(serializer.CodeParamErr, "unknown uri", err)
@ -105,6 +111,7 @@ func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string,
PreviewOnly: service.PreviewOnly, PreviewOnly: service.PreviewOnly,
UploadOnly: service.UploadOnly, UploadOnly: service.UploadOnly,
Note: service.Note, Note: service.Note,
PricePoints: service.PricePoints,
}) })
if err != nil { if err != nil {
return "", err return "", err

@ -0,0 +1,68 @@
package share
import (
"context"
"errors"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/activity"
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/gin-gonic/gin"
)
type (
// SharePurchaseService purchases a paid share with the caller's credit
// balance and returns the resume ticket.
SharePurchaseService struct{}
SharePurchaseParamCtx struct{}
SharePurchaseResponse struct {
Ticket string `json:"ticket"`
}
)
func (s *SharePurchaseService) Purchase(c *gin.Context) (*SharePurchaseResponse, error) {
dep := dependency.FromContext(c)
u := inventory.UserFromContext(c)
ctx := context.WithValue(c, inventory.LoadShareUser{}, true)
ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true)
share, err := dep.ShareClient().GetByID(ctx, hashid.FromContext(c))
if err != nil {
return nil, serializer.NewError(serializer.CodeNotFound, "Share not found", err)
}
if err := inventory.IsValidShare(share); err != nil {
return nil, serializer.NewError(serializer.CodeNotFound, "Share link expired", err)
}
if share.PricePoints <= 0 {
return nil, serializer.NewError(serializer.CodeParamErr, "Share is free", nil)
}
if share.Edges.User.ID == u.ID {
return nil, serializer.NewError(serializer.CodeParamErr, "Cannot purchase own share", nil)
}
if !u.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareDownload)) {
return nil, serializer.NewError(serializer.CodeNoPermissionErr, "You don't have permission to access share links", nil)
}
purchase, err := dep.VasClient().PurchaseShare(ctx, share, u.ID, dep.SettingProvider().ShareScoreRate(c))
if err != nil {
switch {
case errors.Is(err, inventory.ErrInsufficientPoints):
return nil, serializer.NewError(serializer.CodeParamErr, "Insufficient credit balance", err)
default:
return nil, serializer.NewError(serializer.CodeDBError, "Failed to purchase share", err)
}
}
activity.Record(c, dep.SettingProvider(), dep.ActivityClient(), types.EventPaymentFulfilled,
activity.Share(share.ID), activity.File(share.Edges.File.ID),
activity.Extra(map[string]any{
"points": purchase.Points,
"seller": share.Edges.User.ID,
}))
return &SharePurchaseResponse{Ticket: purchase.Ticket}, nil
}

@ -51,9 +51,10 @@ func (s *ShortLinkRedirectService) RedirectTo(c *gin.Context) string {
type ( type (
ShareInfoService struct { ShareInfoService struct {
Password string `form:"password"` Password string `form:"password"`
CountViews bool `form:"count_views"` CountViews bool `form:"count_views"`
OwnerExtended bool `form:"owner_extended"` OwnerExtended bool `form:"owner_extended"`
PurchaseTicket string `form:"purchase_ticket"`
} }
ShareInfoParamCtx struct{} ShareInfoParamCtx struct{}
) )
@ -93,6 +94,31 @@ func (s *ShareInfoService) Get(c *gin.Context) (*explorer.Share, error) {
res := explorer.BuildShare(c, share, base, dep.HashIDEncoder(), u, share.Edges.User, share.Edges.File.Name, res := explorer.BuildShare(c, share, base, dep.HashIDEncoder(), u, share.Edges.User, share.Edges.File.Name,
types.FileType(share.Edges.File.Type), unlocked, false) types.FileType(share.Edges.File.Type), unlocked, false)
// Priced shares resolve the requester's payment state: owner, existing
// buyer, or bearer of a valid resume ticket.
if share.PricePoints > 0 {
paid := share.Edges.User.ID == u.ID ||
(u.Edges.Group != nil && u.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareFree)))
purchaseTicket := ""
if !paid {
vasClient := dep.VasClient()
if s.PurchaseTicket != "" {
if p, err := vasClient.SharePurchaseByTicket(ctx, share.ID, s.PurchaseTicket); err == nil {
paid = true
purchaseTicket = p.Ticket
}
}
if !paid && !inventory.IsAnonymousUser(u) {
if p, err := vasClient.SharePurchase(ctx, share.ID, u.ID); err == nil {
paid = true
purchaseTicket = p.Ticket
}
}
}
res.Paid = &paid
res.PurchaseTicket = purchaseTicket
}
if s.OwnerExtended && share.Edges.User.ID == u.ID { if s.OwnerExtended && share.Edges.User.ID == u.ID {
// Add more information about the shared file // Add more information about the shared file
m := manager.NewFileManager(dep, u) m := manager.NewFileManager(dep, u)

Loading…
Cancel
Save