diff --git a/ROADMAP.md b/ROADMAP.md
index e2685cbd..a9791f93 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -180,8 +180,8 @@ Order = user-visible value first; each ships with backend + UI + tests.
- [x] User-facing relocate — `POST /file/relocate` from FM More-actions dialog, entity expansion + same-policy skip + dedup, restricted to group-allowed policies (#182, fixes #2262)
3. **SSO** — generic OIDC, Logto, multi-account switching, sign-up email filtering (fixes #3464, #3056, #3505). See §1.3a.
- [x] PR #141 — inbound OIDC consumer (auth-code + nonce, JWKS-verified RS256 id_tokens, userinfo fallback, auto-provisioning, one-time ticket handoff, SSRF-validated endpoints, redacted secret); covers Keycloak/Authentik/Logto/generic IdPs; sign-up email domain filtering (whitelist/blacklist + sub-address block) enforced at registration and SSO provisioning; multi-account lands via existing session `upsert` (fixes #3464, #3056)
- - [ ] Multi-account switcher UI — N-token session store, avatar-menu switch + signed-out badges (frontend-only, backend already supports)
- - [ ] QQ Connect (non-OIDC protocol, separate integration), account linking UI for existing local accounts, group/role claim mapping
+ - [x] Multi-account switcher UI — N-token session store, avatar-menu switch + signed-out badges (#189)
+ - [x] QQ Connect (non-OIDC protocol, separate integration), account linking UI for existing local accounts — `sso_binding` entity, link/unlink with lockout guard, admin accordion, sign-in button (#190); remainder: group/role claim mapping
4. **VAS/monetization-free** — credits + redemption codes as *free* features (gift codes for admin use), storage/membership SKU definitions; payment processors stay out of scope (fixes #3231). See §1.3a for the SKU/credits/gift-code spec.
- [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)
@@ -191,7 +191,8 @@ Order = user-visible value first; each ships with backend + UI + tests.
- [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] Coverage wave 2: email/user-activated/token-refresh/share-viewed/version/metadata/view/thumb/live-photo/copy-from/webdav/profile+security/oauth/admin-ops/import (1bbaddf)
- - [ ] Event coverage remainder (needs unbuilt features): payment_*, link/unlink_account, membership_unsubscribe, mount, quota-notify
+ - [x] link/unlink_account events wired via `sso_binding` flows (#190)
+ - [ ] Event coverage remainder (needs unbuilt features): payment_*, membership_unsubscribe, mount, quota-notify
- [x] site announcement: `announcement` setting (markdown) + post-login modal + per-user dismissal re-triggering on content change (#184)
- [x] `abuse_report` entity + public `POST /abuse/report` (IP rate-limit + `abuse_captcha` gate) + admin `/admin/abuse` queue (resolve/dismiss + reversible share block) + share-menu Report entry (#185)
- [x] group `allowed_nodes` pool + `allow_select_node` + task `target_node` dispatch (persisted in task state, weighted LB within pool); group admin multi-select + task-dialog node picker
diff --git a/application/dependency/dependency.go b/application/dependency/dependency.go
index d1f1ebf9..32733023 100644
--- a/application/dependency/dependency.go
+++ b/application/dependency/dependency.go
@@ -114,6 +114,8 @@ type Dep interface {
ActivityClient() inventory.ActivityClient
// AbuseReportClient Get a singleton inventory.AbuseReportClient instance for the abuse review queue.
AbuseReportClient() inventory.AbuseReportClient
+ // SsoBindingClient Get a singleton inventory.SsoBindingClient instance for external identity bindings.
+ SsoBindingClient() inventory.SsoBindingClient
// TaskClient Get a singleton inventory.TaskClient instance for access DB task store.
TaskClient() inventory.TaskClient
// ForkWithLogger create a shallow copy of dependency with a new correlated logger, used as per-request dep.
@@ -174,6 +176,7 @@ type dependency struct {
vasClient inventory.VasClient
activityClient inventory.ActivityClient
abuseReportClient inventory.AbuseReportClient
+ ssoBindingClient inventory.SsoBindingClient
settingProvider setting.Provider
userClient inventory.UserClient
groupClient inventory.GroupClient
@@ -870,6 +873,15 @@ func (d *dependency) AbuseReportClient() inventory.AbuseReportClient {
return d.abuseReportClient
}
+func (d *dependency) SsoBindingClient() inventory.SsoBindingClient {
+ if d.ssoBindingClient != nil {
+ return d.ssoBindingClient
+ }
+
+ d.ssoBindingClient = inventory.NewSsoBindingClient(d.DBClient(), d.ConfigProvider().Database().Type)
+ return d.ssoBindingClient
+}
+
func (d *dependency) TaskClient() inventory.TaskClient {
if d.taskClient != nil {
return d.taskClient
diff --git a/ent/client.go b/ent/client.go
index 4c0a0940..b1168cfb 100644
--- a/ent/client.go
+++ b/ent/client.go
@@ -36,6 +36,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@@ -91,6 +92,8 @@ type Client struct {
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders.
Sku *SkuClient
+ // SsoBinding is the client for interacting with the SsoBinding builders.
+ SsoBinding *SsoBindingClient
// StoragePolicy is the client for interacting with the StoragePolicy builders.
StoragePolicy *StoragePolicyClient
// Task is the client for interacting with the Task builders.
@@ -131,6 +134,7 @@ func (c *Client) init() {
c.Share = NewShareClient(c.config)
c.SharePurchase = NewSharePurchaseClient(c.config)
c.Sku = NewSkuClient(c.config)
+ c.SsoBinding = NewSsoBindingClient(c.config)
c.StoragePolicy = NewStoragePolicyClient(c.config)
c.Task = NewTaskClient(c.config)
c.User = NewUserClient(c.config)
@@ -248,6 +252,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg),
+ SsoBinding: NewSsoBindingClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg),
User: NewUserClient(cfg),
@@ -292,6 +297,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg),
+ SsoBinding: NewSsoBindingClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg),
User: NewUserClient(cfg),
@@ -328,8 +334,8 @@ func (c *Client) Use(hooks ...Hook) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
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.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
- c.UserGrant,
+ c.Setting, c.Share, c.SharePurchase, c.Sku, c.SsoBinding, c.StoragePolicy,
+ c.Task, c.User, c.UserGrant,
} {
n.Use(hooks...)
}
@@ -342,8 +348,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
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.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
- c.UserGrant,
+ c.Setting, c.Share, c.SharePurchase, c.Sku, c.SsoBinding, c.StoragePolicy,
+ c.Task, c.User, c.UserGrant,
} {
n.Intercept(interceptors...)
}
@@ -394,6 +400,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.SharePurchase.mutate(ctx, m)
case *SkuMutation:
return c.Sku.mutate(ctx, m)
+ case *SsoBindingMutation:
+ return c.SsoBinding.mutate(ctx, m)
case *StoragePolicyMutation:
return c.StoragePolicy.mutate(ctx, m)
case *TaskMutation:
@@ -3753,6 +3761,157 @@ func (c *SkuClient) mutate(ctx context.Context, m *SkuMutation) (Value, error) {
}
}
+// SsoBindingClient is a client for the SsoBinding schema.
+type SsoBindingClient struct {
+ config
+}
+
+// NewSsoBindingClient returns a client for the SsoBinding from the given config.
+func NewSsoBindingClient(c config) *SsoBindingClient {
+ return &SsoBindingClient{config: c}
+}
+
+// Use adds a list of mutation hooks to the hooks stack.
+// A call to `Use(f, g, h)` equals to `ssobinding.Hooks(f(g(h())))`.
+func (c *SsoBindingClient) Use(hooks ...Hook) {
+ c.hooks.SsoBinding = append(c.hooks.SsoBinding, hooks...)
+}
+
+// Intercept adds a list of query interceptors to the interceptors stack.
+// A call to `Intercept(f, g, h)` equals to `ssobinding.Intercept(f(g(h())))`.
+func (c *SsoBindingClient) Intercept(interceptors ...Interceptor) {
+ c.inters.SsoBinding = append(c.inters.SsoBinding, interceptors...)
+}
+
+// Create returns a builder for creating a SsoBinding entity.
+func (c *SsoBindingClient) Create() *SsoBindingCreate {
+ mutation := newSsoBindingMutation(c.config, OpCreate)
+ return &SsoBindingCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// CreateBulk returns a builder for creating a bulk of SsoBinding entities.
+func (c *SsoBindingClient) CreateBulk(builders ...*SsoBindingCreate) *SsoBindingCreateBulk {
+ return &SsoBindingCreateBulk{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 *SsoBindingClient) MapCreateBulk(slice any, setFunc func(*SsoBindingCreate, int)) *SsoBindingCreateBulk {
+ rv := reflect.ValueOf(slice)
+ if rv.Kind() != reflect.Slice {
+ return &SsoBindingCreateBulk{err: fmt.Errorf("calling to SsoBindingClient.MapCreateBulk with wrong type %T, need slice", slice)}
+ }
+ builders := make([]*SsoBindingCreate, rv.Len())
+ for i := 0; i < rv.Len(); i++ {
+ builders[i] = c.Create()
+ setFunc(builders[i], i)
+ }
+ return &SsoBindingCreateBulk{config: c.config, builders: builders}
+}
+
+// Update returns an update builder for SsoBinding.
+func (c *SsoBindingClient) Update() *SsoBindingUpdate {
+ mutation := newSsoBindingMutation(c.config, OpUpdate)
+ return &SsoBindingUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *SsoBindingClient) UpdateOne(sb *SsoBinding) *SsoBindingUpdateOne {
+ mutation := newSsoBindingMutation(c.config, OpUpdateOne, withSsoBinding(sb))
+ return &SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *SsoBindingClient) UpdateOneID(id int) *SsoBindingUpdateOne {
+ mutation := newSsoBindingMutation(c.config, OpUpdateOne, withSsoBindingID(id))
+ return &SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// Delete returns a delete builder for SsoBinding.
+func (c *SsoBindingClient) Delete() *SsoBindingDelete {
+ mutation := newSsoBindingMutation(c.config, OpDelete)
+ return &SsoBindingDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// DeleteOne returns a builder for deleting the given entity.
+func (c *SsoBindingClient) DeleteOne(sb *SsoBinding) *SsoBindingDeleteOne {
+ return c.DeleteOneID(sb.ID)
+}
+
+// DeleteOneID returns a builder for deleting the given entity by its id.
+func (c *SsoBindingClient) DeleteOneID(id int) *SsoBindingDeleteOne {
+ builder := c.Delete().Where(ssobinding.ID(id))
+ builder.mutation.id = &id
+ builder.mutation.op = OpDeleteOne
+ return &SsoBindingDeleteOne{builder}
+}
+
+// Query returns a query builder for SsoBinding.
+func (c *SsoBindingClient) Query() *SsoBindingQuery {
+ return &SsoBindingQuery{
+ config: c.config,
+ ctx: &QueryContext{Type: TypeSsoBinding},
+ inters: c.Interceptors(),
+ }
+}
+
+// Get returns a SsoBinding entity by its id.
+func (c *SsoBindingClient) Get(ctx context.Context, id int) (*SsoBinding, error) {
+ return c.Query().Where(ssobinding.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *SsoBindingClient) GetX(ctx context.Context, id int) *SsoBinding {
+ obj, err := c.Get(ctx, id)
+ if err != nil {
+ panic(err)
+ }
+ return obj
+}
+
+// QueryUser queries the user edge of a SsoBinding.
+func (c *SsoBindingClient) QueryUser(sb *SsoBinding) *UserQuery {
+ query := (&UserClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := sb.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(ssobinding.Table, ssobinding.FieldID, id),
+ sqlgraph.To(user.Table, user.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, ssobinding.UserTable, ssobinding.UserColumn),
+ )
+ fromV = sqlgraph.Neighbors(sb.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
+// Hooks returns the client hooks.
+func (c *SsoBindingClient) Hooks() []Hook {
+ hooks := c.hooks.SsoBinding
+ return append(hooks[:len(hooks):len(hooks)], ssobinding.Hooks[:]...)
+}
+
+// Interceptors returns the client interceptors.
+func (c *SsoBindingClient) Interceptors() []Interceptor {
+ inters := c.inters.SsoBinding
+ return append(inters[:len(inters):len(inters)], ssobinding.Interceptors[:]...)
+}
+
+func (c *SsoBindingClient) mutate(ctx context.Context, m *SsoBindingMutation) (Value, error) {
+ switch m.Op() {
+ case OpCreate:
+ return (&SsoBindingCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdate:
+ return (&SsoBindingUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdateOne:
+ return (&SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpDelete, OpDeleteOne:
+ return (&SsoBindingDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
+ default:
+ return nil, fmt.Errorf("ent: unknown SsoBinding mutation op: %q", m.Op())
+ }
+}
+
// StoragePolicyClient is a client for the StoragePolicy schema.
type StoragePolicyClient struct {
config
@@ -4435,6 +4594,22 @@ func (c *UserClient) QuerySharePurchases(u *User) *SharePurchaseQuery {
return query
}
+// QuerySSOBindings queries the sso_bindings edge of a User.
+func (c *UserClient) QuerySSOBindings(u *User) *SsoBindingQuery {
+ query := (&SsoBindingClient{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(ssobinding.Table, ssobinding.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, user.SSOBindingsTable, user.SSOBindingsColumn),
+ )
+ fromV = sqlgraph.Neighbors(u.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
// Hooks returns the client hooks.
func (c *UserClient) Hooks() []Hook {
hooks := c.hooks.User
@@ -4618,14 +4793,14 @@ type (
hooks struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
- OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
- User, UserGrant []ent.Hook
+ OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, SsoBinding,
+ StoragePolicy, Task, User, UserGrant []ent.Hook
}
inters struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
- OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
- User, UserGrant []ent.Interceptor
+ OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, SsoBinding,
+ StoragePolicy, Task, User, UserGrant []ent.Interceptor
}
)
diff --git a/ent/ent.go b/ent/ent.go
index d508f642..823a1129 100644
--- a/ent/ent.go
+++ b/ent/ent.go
@@ -33,6 +33,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@@ -118,6 +119,7 @@ func checkColumn(table, column string) error {
share.Table: share.ValidColumn,
sharepurchase.Table: sharepurchase.ValidColumn,
sku.Table: sku.ValidColumn,
+ ssobinding.Table: ssobinding.ValidColumn,
storagepolicy.Table: storagepolicy.ValidColumn,
task.Table: task.ValidColumn,
user.Table: user.ValidColumn,
diff --git a/ent/hook/hook.go b/ent/hook/hook.go
index e596a4e4..63686ef1 100644
--- a/ent/hook/hook.go
+++ b/ent/hook/hook.go
@@ -261,6 +261,18 @@ func (f SkuFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error)
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SkuMutation", m)
}
+// The SsoBindingFunc type is an adapter to allow the use of ordinary
+// function as SsoBinding mutator.
+type SsoBindingFunc func(context.Context, *ent.SsoBindingMutation) (ent.Value, error)
+
+// Mutate calls f(ctx, m).
+func (f SsoBindingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
+ if mv, ok := m.(*ent.SsoBindingMutation); ok {
+ return f(ctx, mv)
+ }
+ return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SsoBindingMutation", m)
+}
+
// The StoragePolicyFunc type is an adapter to allow the use of ordinary
// function as StoragePolicy mutator.
type StoragePolicyFunc func(context.Context, *ent.StoragePolicyMutation) (ent.Value, error)
diff --git a/ent/intercept/intercept.go b/ent/intercept/intercept.go
index 741de4a6..cc5f0a2c 100644
--- a/ent/intercept/intercept.go
+++ b/ent/intercept/intercept.go
@@ -30,6 +30,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@@ -659,6 +660,33 @@ func (f TraverseSku) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.SkuQuery", q)
}
+// The SsoBindingFunc type is an adapter to allow the use of ordinary function as a Querier.
+type SsoBindingFunc func(context.Context, *ent.SsoBindingQuery) (ent.Value, error)
+
+// Query calls f(ctx, q).
+func (f SsoBindingFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
+ if q, ok := q.(*ent.SsoBindingQuery); ok {
+ return f(ctx, q)
+ }
+ return nil, fmt.Errorf("unexpected query type %T. expect *ent.SsoBindingQuery", q)
+}
+
+// The TraverseSsoBinding type is an adapter to allow the use of ordinary function as Traverser.
+type TraverseSsoBinding func(context.Context, *ent.SsoBindingQuery) error
+
+// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
+func (f TraverseSsoBinding) Intercept(next ent.Querier) ent.Querier {
+ return next
+}
+
+// Traverse calls f(ctx, q).
+func (f TraverseSsoBinding) Traverse(ctx context.Context, q ent.Query) error {
+ if q, ok := q.(*ent.SsoBindingQuery); ok {
+ return f(ctx, q)
+ }
+ return fmt.Errorf("unexpected query type %T. expect *ent.SsoBindingQuery", q)
+}
+
// The StoragePolicyFunc type is an adapter to allow the use of ordinary function as a Querier.
type StoragePolicyFunc func(context.Context, *ent.StoragePolicyQuery) (ent.Value, error)
@@ -812,6 +840,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.SharePurchaseQuery, predicate.SharePurchase, sharepurchase.OrderOption]{typ: ent.TypeSharePurchase, tq: q}, nil
case *ent.SkuQuery:
return &query[*ent.SkuQuery, predicate.Sku, sku.OrderOption]{typ: ent.TypeSku, tq: q}, nil
+ case *ent.SsoBindingQuery:
+ return &query[*ent.SsoBindingQuery, predicate.SsoBinding, ssobinding.OrderOption]{typ: ent.TypeSsoBinding, tq: q}, nil
case *ent.StoragePolicyQuery:
return &query[*ent.StoragePolicyQuery, predicate.StoragePolicy, storagepolicy.OrderOption]{typ: ent.TypeStoragePolicy, tq: q}, nil
case *ent.TaskQuery:
diff --git a/ent/internal/schema.go b/ent/internal/schema.go
index 11422325..2a85ffdf 100644
--- a/ent/internal/schema.go
+++ b/ent/internal/schema.go
@@ -6,4 +6,4 @@
// Package internal holds a loadable version of the latest schema.
package internal
-const Schema = "{\"Schema\":\"github.com/cloudreve/Cloudreve/v4/ent/schema\",\"Package\":\"github.com/cloudreve/Cloudreve/v4/ent\",\"Schemas\":[{\"name\":\"AbuseReport\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"reporter_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reporter_email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"target_type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"target_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reason\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"open\",\"default_kind\":24,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"admin_note\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"status\",\"created_at\"]},{\"fields\":[\"target_type\",\"target_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"AclEntry\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"acl_entries\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subject_type\",\"type\":{\"Type\":6,\"Ident\":\"aclentry.SubjectType\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"user\",\"V\":\"user\"},{\"N\":\"group\",\"V\":\"group\"},{\"N\":\"anonymous\",\"V\":\"anonymous\"},{\"N\":\"everyone\",\"V\":\"everyone\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subject_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"permissions\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_id\",\"subject_type\",\"subject_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"ActivityEvent\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"actor_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"cid\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"share_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"extra\",\"type\":{\"Type\":3,\"Ident\":\"map[string]interface {}\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"map[string]interface {}\",\"Kind\":21,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"created_at\"]},{\"fields\":[\"type\",\"created_at\"]},{\"fields\":[\"file_id\",\"created_at\"]},{\"fields\":[\"actor_id\",\"created_at\"]},{\"fields\":[\"share_id\",\"created_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"CreditTxn\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"credit_txns\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"credittxn.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"purchase\",\"V\":\"purchase\"},{\"N\":\"gift\",\"V\":\"gift\"},{\"N\":\"share_income\",\"V\":\"share_income\"},{\"N\":\"adjust\",\"V\":\"adjust\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ref\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"user_id\",\"created_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"DavAccount\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"field\":\"owner_id\",\"ref_name\":\"dav_accounts\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"uri\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"options\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.DavAccountProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"DavAccountProps\",\"Ident\":\"types.DavAccountProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"owner_id\",\"password\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"DirectLink\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"direct_links\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"speed\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Entity\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"ref_name\":\"entities\",\"inverse\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"created_by\",\"ref_name\":\"entities\",\"unique\":true,\"inverse\":true},{\"name\":\"storage_policy\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_entities\",\"ref_name\":\"entities\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"source\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reference_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_entities\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_by\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"upload_session_id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.EntityProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"EntityProps\",\"Ident\":\"types.EntityProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"storage_key\":\"recycle_options\",\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"File\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"field\":\"owner_id\",\"ref_name\":\"files\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_files\",\"ref_name\":\"files\",\"unique\":true,\"inverse\":true},{\"name\":\"parent\",\"type\":\"File\",\"field\":\"file_children\",\"ref\":{\"name\":\"children\",\"type\":\"File\"},\"unique\":true,\"inverse\":true},{\"name\":\"metadata\",\"type\":\"Metadata\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"shares\",\"type\":\"Share\"},{\"name\":\"acl_entries\",\"type\":\"AclEntry\"},{\"name\":\"direct_links\",\"type\":\"DirectLink\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"primary_entity\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_children\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_symbolic\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.FileProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"FileProps\",\"Ident\":\"types.FileProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_files\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_children\",\"name\"]},{\"fields\":[\"file_children\",\"type\",\"updated_at\"]},{\"fields\":[\"file_children\",\"type\",\"created_at\"]},{\"fields\":[\"file_children\",\"type\",\"size\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"FsEvent\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_fsevent\",\"ref_name\":\"fsevents\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"event\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subscriber\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_fsevent\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"GiftCode\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"redeemer\",\"type\":\"User\",\"field\":\"used_by_id\",\"ref_name\":\"redeemed_codes\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"code\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"giftcode.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"points\",\"V\":\"points\"},{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"duration\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_by_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\"},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_id\",\"ref_name\":\"groups\",\"unique\":true,\"inverse\":true},{\"name\":\"allowed_policies\",\"type\":\"StoragePolicy\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"speed_limit\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"permissions\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.GroupSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"GroupSetting\",\"Ident\":\"types.GroupSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"InvitationCode\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"code\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":64,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_uses\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Metadata\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"metadata\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_public\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_id\",\"name\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Node\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"storage_policy\",\"type\":\"StoragePolicy\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"node.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"suspended\",\"V\":\"suspended\"}],\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"node.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"master\",\"V\":\"master\"},{\"N\":\"slave\",\"V\":\"slave\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"slave_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"capabilities\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.NodeSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"NodeSetting\",\"Ident\":\"types.NodeSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"OAuthClient\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"grants\",\"type\":\"OAuthGrant\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"guid\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"validators\":1,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"homepage_url\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2048,\"optional\":true,\"validators\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"redirect_uris\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.OAuthClientProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"OAuthClientProps\",\"Ident\":\"types.OAuthClientProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_enabled\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"OAuthGrant\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"oauth_grants\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"client\",\"type\":\"OAuthClient\",\"field\":\"client_id\",\"ref_name\":\"grants\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"client_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"client_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Passkey\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"passkey\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credential_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credential\",\"type\":{\"Type\":3,\"Ident\":\"*webauthn.Credential\",\"PkgPath\":\"github.com/go-webauthn/webauthn/webauthn\",\"PkgName\":\"webauthn\",\"Nillable\":true,\"RType\":{\"Name\":\"Credential\",\"Ident\":\"webauthn.Credential\",\"Kind\":22,\"PkgPath\":\"github.com/go-webauthn/webauthn/webauthn\",\"Methods\":{\"Descriptor\":{\"In\":[],\"Out\":[{\"Name\":\"CredentialDescriptor\",\"Ident\":\"protocol.CredentialDescriptor\",\"Kind\":25,\"PkgPath\":\"github.com/go-webauthn/webauthn/protocol\",\"Methods\":null}]},\"Verify\":{\"In\":[{\"Name\":\"Provider\",\"Ident\":\"metadata.Provider\",\"Kind\":20,\"PkgPath\":\"github.com/go-webauthn/webauthn/metadata\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"credential_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Setting\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Share\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"shares\",\"unique\":true,\"inverse\":true},{\"name\":\"file\",\"type\":\"File\",\"ref_name\":\"shares\",\"unique\":true,\"inverse\":true},{\"name\":\"purchases\",\"type\":\"SharePurchase\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"views\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"remain_downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price_points\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"validators\":1,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.ShareProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"ShareProps\",\"Ident\":\"types.ShareProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"SharePurchase\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"share\",\"type\":\"Share\",\"field\":\"share_id\",\"ref_name\":\"purchases\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"buyer\",\"type\":\"User\",\"field\":\"buyer_id\",\"ref_name\":\"share_purchases\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"share_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"buyer_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"points\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ticket\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"validators\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"share_id\",\"buyer_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Sku\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"sku.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"duration\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"points\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"label\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"enabled\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"StoragePolicy\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"groups\",\"type\":\"Group\"},{\"name\":\"files\",\"type\":\"File\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"allowed_groups\",\"type\":\"Group\",\"ref_name\":\"allowed_policies\",\"inverse\":true},{\"name\":\"node\",\"type\":\"Node\",\"field\":\"node_id\",\"ref_name\":\"storage_policy\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"storagepolicy.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"suspended\",\"V\":\"suspended\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"bucket_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_private\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"access_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"dir_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.PolicySetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"PolicySetting\",\"Ident\":\"types.PolicySetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"file_type\":null,\"native_media_processing\":false,\"s3_path_style\":false,\"token\":\"\"},\"default_kind\":22,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"node_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Task\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_tasks\",\"ref_name\":\"tasks\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"task.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"queued\",\"V\":\"queued\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"suspending\",\"V\":\"suspending\"},{\"N\":\"error\",\"V\":\"error\"},{\"N\":\"canceled\",\"V\":\"canceled\"},{\"N\":\"completed\",\"V\":\"completed\"}],\"default\":true,\"default_value\":\"queued\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"public_state\",\"type\":{\"Type\":3,\"Ident\":\"*types.TaskPublicState\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"TaskPublicState\",\"Ident\":\"types.TaskPublicState\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"private_state\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"correlation_id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_tasks\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"creator_ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":45,\"optional\":true,\"validators\":1,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"hidden\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_users\",\"ref_name\":\"users\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"files\",\"type\":\"File\"},{\"name\":\"dav_accounts\",\"type\":\"DavAccount\"},{\"name\":\"shares\",\"type\":\"Share\"},{\"name\":\"passkey\",\"type\":\"Passkey\"},{\"name\":\"tasks\",\"type\":\"Task\"},{\"name\":\"fsevents\",\"type\":\"FsEvent\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"oauth_grants\",\"type\":\"OAuthGrant\"},{\"name\":\"credit_txns\",\"type\":\"CreditTxn\"},{\"name\":\"redeemed_codes\",\"type\":\"GiftCode\"},{\"name\":\"grants\",\"type\":\"UserGrant\"},{\"name\":\"share_purchases\",\"type\":\"SharePurchase\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":100,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"nick\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":100,\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"user.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"inactive\",\"V\":\"inactive\"},{\"N\":\"manual_banned\",\"V\":\"manual_banned\"},{\"N\":\"sys_banned\",\"V\":\"sys_banned\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ban_expires\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ban_reason\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_login\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Time of the last successful sign-in\"},{\"name\":\"storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credits\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.UserSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"UserSetting\",\"Ident\":\"types.UserSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"UserGrant\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"grants\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"usergrant.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"prev_group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"user_id\",\"type\"]},{\"fields\":[\"expires_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]}],\"Features\":[\"intercept\",\"schema/snapshot\",\"sql/upsert\",\"sql/upsert\",\"sql/execquery\"]}"
+const Schema = "{\"Schema\":\"github.com/cloudreve/Cloudreve/v4/ent/schema\",\"Package\":\"github.com/cloudreve/Cloudreve/v4/ent\",\"Schemas\":[{\"name\":\"AbuseReport\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"reporter_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reporter_email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"target_type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"target_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reason\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"open\",\"default_kind\":24,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"admin_note\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"status\",\"created_at\"]},{\"fields\":[\"target_type\",\"target_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"AclEntry\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"acl_entries\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subject_type\",\"type\":{\"Type\":6,\"Ident\":\"aclentry.SubjectType\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"user\",\"V\":\"user\"},{\"N\":\"group\",\"V\":\"group\"},{\"N\":\"anonymous\",\"V\":\"anonymous\"},{\"N\":\"everyone\",\"V\":\"everyone\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subject_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"permissions\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_id\",\"subject_type\",\"subject_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"ActivityEvent\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"actor_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"cid\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"share_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"extra\",\"type\":{\"Type\":3,\"Ident\":\"map[string]interface {}\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"map[string]interface {}\",\"Kind\":21,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"created_at\"]},{\"fields\":[\"type\",\"created_at\"]},{\"fields\":[\"file_id\",\"created_at\"]},{\"fields\":[\"actor_id\",\"created_at\"]},{\"fields\":[\"share_id\",\"created_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"CreditTxn\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"credit_txns\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"credittxn.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"purchase\",\"V\":\"purchase\"},{\"N\":\"gift\",\"V\":\"gift\"},{\"N\":\"share_income\",\"V\":\"share_income\"},{\"N\":\"adjust\",\"V\":\"adjust\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ref\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"user_id\",\"created_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"DavAccount\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"field\":\"owner_id\",\"ref_name\":\"dav_accounts\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"uri\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"options\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.DavAccountProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"DavAccountProps\",\"Ident\":\"types.DavAccountProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"owner_id\",\"password\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"DirectLink\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"direct_links\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"speed\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Entity\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"ref_name\":\"entities\",\"inverse\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"created_by\",\"ref_name\":\"entities\",\"unique\":true,\"inverse\":true},{\"name\":\"storage_policy\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_entities\",\"ref_name\":\"entities\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"source\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"reference_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_entities\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_by\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"upload_session_id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.EntityProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"EntityProps\",\"Ident\":\"types.EntityProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"storage_key\":\"recycle_options\",\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"File\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"field\":\"owner_id\",\"ref_name\":\"files\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_files\",\"ref_name\":\"files\",\"unique\":true,\"inverse\":true},{\"name\":\"parent\",\"type\":\"File\",\"field\":\"file_children\",\"ref\":{\"name\":\"children\",\"type\":\"File\"},\"unique\":true,\"inverse\":true},{\"name\":\"metadata\",\"type\":\"Metadata\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"shares\",\"type\":\"Share\"},{\"name\":\"acl_entries\",\"type\":\"AclEntry\"},{\"name\":\"direct_links\",\"type\":\"DirectLink\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"primary_entity\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_children\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_symbolic\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.FileProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"FileProps\",\"Ident\":\"types.FileProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_files\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_children\",\"name\"]},{\"fields\":[\"file_children\",\"type\",\"updated_at\"]},{\"fields\":[\"file_children\",\"type\",\"created_at\"]},{\"fields\":[\"file_children\",\"type\",\"size\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"FsEvent\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_fsevent\",\"ref_name\":\"fsevents\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"event\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subscriber\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_fsevent\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"GiftCode\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"redeemer\",\"type\":\"User\",\"field\":\"used_by_id\",\"ref_name\":\"redeemed_codes\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"code\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"giftcode.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"points\",\"V\":\"points\"},{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"duration\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_by_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\"},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_id\",\"ref_name\":\"groups\",\"unique\":true,\"inverse\":true},{\"name\":\"allowed_policies\",\"type\":\"StoragePolicy\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"speed_limit\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"permissions\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.GroupSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"GroupSetting\",\"Ident\":\"types.GroupSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"InvitationCode\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"code\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":64,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_uses\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"used_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Metadata\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"file\",\"type\":\"File\",\"field\":\"file_id\",\"ref_name\":\"metadata\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_public\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_id\",\"name\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Node\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"storage_policy\",\"type\":\"StoragePolicy\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"node.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"suspended\",\"V\":\"suspended\"}],\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"node.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"master\",\"V\":\"master\"},{\"N\":\"slave\",\"V\":\"slave\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"slave_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"capabilities\",\"type\":{\"Type\":5,\"Ident\":\"*boolset.BooleanSet\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"PkgName\":\"boolset\",\"Nillable\":true,\"RType\":{\"Name\":\"BooleanSet\",\"Ident\":\"boolset.BooleanSet\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/pkg/boolset\",\"Methods\":{\"Enabled\":{\"In\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.NodeSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"NodeSetting\",\"Ident\":\"types.NodeSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"OAuthClient\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"grants\",\"type\":\"OAuthGrant\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"guid\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":255,\"validators\":1,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"homepage_url\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2048,\"optional\":true,\"validators\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"redirect_uris\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.OAuthClientProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"OAuthClientProps\",\"Ident\":\"types.OAuthClientProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_enabled\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"OAuthGrant\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"oauth_grants\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"client\",\"type\":\"OAuthClient\",\"field\":\"client_id\",\"ref_name\":\"grants\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"client_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"client_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Passkey\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"passkey\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credential_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credential\",\"type\":{\"Type\":3,\"Ident\":\"*webauthn.Credential\",\"PkgPath\":\"github.com/go-webauthn/webauthn/webauthn\",\"PkgName\":\"webauthn\",\"Nillable\":true,\"RType\":{\"Name\":\"Credential\",\"Ident\":\"webauthn.Credential\",\"Kind\":22,\"PkgPath\":\"github.com/go-webauthn/webauthn/webauthn\",\"Methods\":{\"Descriptor\":{\"In\":[],\"Out\":[{\"Name\":\"CredentialDescriptor\",\"Ident\":\"protocol.CredentialDescriptor\",\"Kind\":25,\"PkgPath\":\"github.com/go-webauthn/webauthn/protocol\",\"Methods\":null}]},\"Verify\":{\"In\":[{\"Name\":\"Provider\",\"Ident\":\"metadata.Provider\",\"Kind\":20,\"PkgPath\":\"github.com/go-webauthn/webauthn/metadata\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"used_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"credential_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Setting\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Share\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"shares\",\"unique\":true,\"inverse\":true},{\"name\":\"file\",\"type\":\"File\",\"ref_name\":\"shares\",\"unique\":true,\"inverse\":true},{\"name\":\"purchases\",\"type\":\"SharePurchase\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"views\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"remain_downloads\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price_points\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"validators\":1,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"props\",\"type\":{\"Type\":3,\"Ident\":\"*types.ShareProps\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"ShareProps\",\"Ident\":\"types.ShareProps\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"SharePurchase\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"share\",\"type\":\"Share\",\"field\":\"share_id\",\"ref_name\":\"purchases\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"buyer\",\"type\":\"User\",\"field\":\"buyer_id\",\"ref_name\":\"share_purchases\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"share_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"buyer_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"points\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ticket\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"validators\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"share_id\",\"buyer_id\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Sku\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"sku.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"duration\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"points\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"label\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"des\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"enabled\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"SsoBinding\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"sso_bindings\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"provider\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":32,\"validators\":2,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"subject\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":128,\"validators\":2,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"provider\",\"subject\"]},{\"unique\":true,\"fields\":[\"user_id\",\"provider\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"StoragePolicy\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"groups\",\"type\":\"Group\"},{\"name\":\"files\",\"type\":\"File\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"allowed_groups\",\"type\":\"Group\",\"ref_name\":\"allowed_policies\",\"inverse\":true},{\"name\":\"node\",\"type\":\"Node\",\"field\":\"node_id\",\"ref_name\":\"storage_policy\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"storagepolicy.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"suspended\",\"V\":\"suspended\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"bucket_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_private\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"access_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"dir_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.PolicySetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"PolicySetting\",\"Ident\":\"types.PolicySetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"file_type\":null,\"native_media_processing\":false,\"s3_path_style\":false,\"token\":\"\"},\"default_kind\":22,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"node_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"Task\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_tasks\",\"ref_name\":\"tasks\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"type\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"task.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"queued\",\"V\":\"queued\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"suspending\",\"V\":\"suspending\"},{\"N\":\"error\",\"V\":\"error\"},{\"N\":\"canceled\",\"V\":\"canceled\"},{\"N\":\"completed\",\"V\":\"completed\"}],\"default\":true,\"default_value\":\"queued\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"public_state\",\"type\":{\"Type\":3,\"Ident\":\"*types.TaskPublicState\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"TaskPublicState\",\"Ident\":\"types.TaskPublicState\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"private_state\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"correlation_id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/gofrs/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/gofrs/uuid\",\"Methods\":{\"Bytes\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Format\":{\"In\":[{\"Name\":\"State\",\"Ident\":\"fmt.State\",\"Kind\":20,\"PkgPath\":\"fmt\",\"Methods\":null},{\"Name\":\"int32\",\"Ident\":\"int32\",\"Kind\":5,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"SetVariant\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"SetVersion\":{\"In\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"uint8\",\"Ident\":\"uint8\",\"Kind\":8,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_tasks\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"creator_ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":45,\"optional\":true,\"validators\":1,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"hidden\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_users\",\"ref_name\":\"users\",\"unique\":true,\"inverse\":true,\"required\":true},{\"name\":\"files\",\"type\":\"File\"},{\"name\":\"dav_accounts\",\"type\":\"DavAccount\"},{\"name\":\"shares\",\"type\":\"Share\"},{\"name\":\"passkey\",\"type\":\"Passkey\"},{\"name\":\"tasks\",\"type\":\"Task\"},{\"name\":\"fsevents\",\"type\":\"FsEvent\"},{\"name\":\"entities\",\"type\":\"Entity\"},{\"name\":\"oauth_grants\",\"type\":\"OAuthGrant\"},{\"name\":\"credit_txns\",\"type\":\"CreditTxn\"},{\"name\":\"redeemed_codes\",\"type\":\"GiftCode\"},{\"name\":\"grants\",\"type\":\"UserGrant\"},{\"name\":\"share_purchases\",\"type\":\"SharePurchase\"},{\"name\":\"sso_bindings\",\"type\":\"SsoBinding\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":100,\"unique\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"nick\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":100,\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"user.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"inactive\",\"V\":\"inactive\"},{\"N\":\"manual_banned\",\"V\":\"manual_banned\"},{\"N\":\"sys_banned\",\"V\":\"sys_banned\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ban_expires\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ban_reason\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_login\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Time of the last successful sign-in\"},{\"name\":\"storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"credits\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*types.UserSetting\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"UserSetting\",\"Ident\":\"types.UserSetting\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{},\"default_kind\":22,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"UserGrant\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"grants\",\"unique\":true,\"inverse\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"deleted_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":true,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"datetime\"}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"usergrant.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"storage\",\"V\":\"storage\"},{\"N\":\"group\",\"V\":\"group\"}],\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"amount\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"prev_group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"expires_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"user_id\",\"type\"]},{\"fields\":[\"expires_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]}],\"Features\":[\"intercept\",\"schema/snapshot\",\"sql/upsert\",\"sql/upsert\",\"sql/execquery\"]}"
diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go
index d365397e..f266c56b 100644
--- a/ent/migrate/schema.go
+++ b/ent/migrate/schema.go
@@ -664,6 +664,42 @@ var (
Columns: SkusColumns,
PrimaryKey: []*schema.Column{SkusColumns[0]},
}
+ // SSOBindingsColumns holds the columns for the "sso_bindings" table.
+ SSOBindingsColumns = []*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: "provider", Type: field.TypeString, Size: 32},
+ {Name: "subject", Type: field.TypeString, Size: 128},
+ {Name: "user_id", Type: field.TypeInt},
+ }
+ // SSOBindingsTable holds the schema information for the "sso_bindings" table.
+ SSOBindingsTable = &schema.Table{
+ Name: "sso_bindings",
+ Columns: SSOBindingsColumns,
+ PrimaryKey: []*schema.Column{SSOBindingsColumns[0]},
+ ForeignKeys: []*schema.ForeignKey{
+ {
+ Symbol: "sso_bindings_users_sso_bindings",
+ Columns: []*schema.Column{SSOBindingsColumns[6]},
+ RefColumns: []*schema.Column{UsersColumns[0]},
+ OnDelete: schema.NoAction,
+ },
+ },
+ Indexes: []*schema.Index{
+ {
+ Name: "ssobinding_provider_subject",
+ Unique: true,
+ Columns: []*schema.Column{SSOBindingsColumns[4], SSOBindingsColumns[5]},
+ },
+ {
+ Name: "ssobinding_user_id_provider",
+ Unique: true,
+ Columns: []*schema.Column{SSOBindingsColumns[6], SSOBindingsColumns[4]},
+ },
+ },
+ }
// StoragePoliciesColumns holds the columns for the "storage_policies" table.
StoragePoliciesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
@@ -872,6 +908,7 @@ var (
SharesTable,
SharePurchasesTable,
SkusTable,
+ SSOBindingsTable,
StoragePoliciesTable,
TasksTable,
UsersTable,
@@ -902,6 +939,7 @@ func init() {
SharesTable.ForeignKeys[1].RefTable = UsersTable
SharePurchasesTable.ForeignKeys[0].RefTable = SharesTable
SharePurchasesTable.ForeignKeys[1].RefTable = UsersTable
+ SSOBindingsTable.ForeignKeys[0].RefTable = UsersTable
StoragePoliciesTable.ForeignKeys[0].RefTable = NodesTable
TasksTable.ForeignKeys[0].RefTable = UsersTable
UsersTable.ForeignKeys[0].RefTable = GroupsTable
diff --git a/ent/mutation.go b/ent/mutation.go
index 141943e7..8283cb95 100644
--- a/ent/mutation.go
+++ b/ent/mutation.go
@@ -33,6 +33,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@@ -73,6 +74,7 @@ const (
TypeShare = "Share"
TypeSharePurchase = "SharePurchase"
TypeSku = "Sku"
+ TypeSsoBinding = "SsoBinding"
TypeStoragePolicy = "StoragePolicy"
TypeTask = "Task"
TypeUser = "User"
@@ -20617,6 +20619,681 @@ func (m *SkuMutation) ResetEdge(name string) error {
return fmt.Errorf("unknown Sku edge %s", name)
}
+// SsoBindingMutation represents an operation that mutates the SsoBinding nodes in the graph.
+type SsoBindingMutation struct {
+ config
+ op Op
+ typ string
+ id *int
+ created_at *time.Time
+ updated_at *time.Time
+ deleted_at *time.Time
+ provider *string
+ subject *string
+ clearedFields map[string]struct{}
+ user *int
+ cleareduser bool
+ done bool
+ oldValue func(context.Context) (*SsoBinding, error)
+ predicates []predicate.SsoBinding
+}
+
+var _ ent.Mutation = (*SsoBindingMutation)(nil)
+
+// ssobindingOption allows management of the mutation configuration using functional options.
+type ssobindingOption func(*SsoBindingMutation)
+
+// newSsoBindingMutation creates new mutation for the SsoBinding entity.
+func newSsoBindingMutation(c config, op Op, opts ...ssobindingOption) *SsoBindingMutation {
+ m := &SsoBindingMutation{
+ config: c,
+ op: op,
+ typ: TypeSsoBinding,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withSsoBindingID sets the ID field of the mutation.
+func withSsoBindingID(id int) ssobindingOption {
+ return func(m *SsoBindingMutation) {
+ var (
+ err error
+ once sync.Once
+ value *SsoBinding
+ )
+ m.oldValue = func(ctx context.Context) (*SsoBinding, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().SsoBinding.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withSsoBinding sets the old SsoBinding of the mutation.
+func withSsoBinding(node *SsoBinding) ssobindingOption {
+ return func(m *SsoBindingMutation) {
+ m.oldValue = func(context.Context) (*SsoBinding, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m SsoBindingMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m SsoBindingMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *SsoBindingMutation) ID() (id int, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *SsoBindingMutation) IDs(ctx context.Context) ([]int, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().SsoBinding.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *SsoBindingMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *SsoBindingMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ }
+ return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *SsoBindingMutation) ResetCreatedAt() {
+ m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *SsoBindingMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *SsoBindingMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *SsoBindingMutation) ResetUpdatedAt() {
+ m.updated_at = nil
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (m *SsoBindingMutation) SetDeletedAt(t time.Time) {
+ m.deleted_at = &t
+}
+
+// DeletedAt returns the value of the "deleted_at" field in the mutation.
+func (m *SsoBindingMutation) DeletedAt() (r time.Time, exists bool) {
+ v := m.deleted_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldDeletedAt returns the old "deleted_at" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldDeletedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err)
+ }
+ return oldValue.DeletedAt, nil
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (m *SsoBindingMutation) ClearDeletedAt() {
+ m.deleted_at = nil
+ m.clearedFields[ssobinding.FieldDeletedAt] = struct{}{}
+}
+
+// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
+func (m *SsoBindingMutation) DeletedAtCleared() bool {
+ _, ok := m.clearedFields[ssobinding.FieldDeletedAt]
+ return ok
+}
+
+// ResetDeletedAt resets all changes to the "deleted_at" field.
+func (m *SsoBindingMutation) ResetDeletedAt() {
+ m.deleted_at = nil
+ delete(m.clearedFields, ssobinding.FieldDeletedAt)
+}
+
+// SetProvider sets the "provider" field.
+func (m *SsoBindingMutation) SetProvider(s string) {
+ m.provider = &s
+}
+
+// Provider returns the value of the "provider" field in the mutation.
+func (m *SsoBindingMutation) Provider() (r string, exists bool) {
+ v := m.provider
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldProvider returns the old "provider" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldProvider(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldProvider is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldProvider requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldProvider: %w", err)
+ }
+ return oldValue.Provider, nil
+}
+
+// ResetProvider resets all changes to the "provider" field.
+func (m *SsoBindingMutation) ResetProvider() {
+ m.provider = nil
+}
+
+// SetSubject sets the "subject" field.
+func (m *SsoBindingMutation) SetSubject(s string) {
+ m.subject = &s
+}
+
+// Subject returns the value of the "subject" field in the mutation.
+func (m *SsoBindingMutation) Subject() (r string, exists bool) {
+ v := m.subject
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSubject returns the old "subject" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldSubject(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSubject is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSubject requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSubject: %w", err)
+ }
+ return oldValue.Subject, nil
+}
+
+// ResetSubject resets all changes to the "subject" field.
+func (m *SsoBindingMutation) ResetSubject() {
+ m.subject = nil
+}
+
+// SetUserID sets the "user_id" field.
+func (m *SsoBindingMutation) SetUserID(i int) {
+ m.user = &i
+}
+
+// UserID returns the value of the "user_id" field in the mutation.
+func (m *SsoBindingMutation) UserID() (r int, exists bool) {
+ v := m.user
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUserID returns the old "user_id" field's value of the SsoBinding entity.
+// If the SsoBinding object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SsoBindingMutation) OldUserID(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserID: %w", err)
+ }
+ return oldValue.UserID, nil
+}
+
+// ResetUserID resets all changes to the "user_id" field.
+func (m *SsoBindingMutation) ResetUserID() {
+ m.user = nil
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (m *SsoBindingMutation) ClearUser() {
+ m.cleareduser = true
+ m.clearedFields[ssobinding.FieldUserID] = struct{}{}
+}
+
+// UserCleared reports if the "user" edge to the User entity was cleared.
+func (m *SsoBindingMutation) UserCleared() bool {
+ return m.cleareduser
+}
+
+// UserIDs returns the "user" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// UserID instead. It exists only for internal usage by the builders.
+func (m *SsoBindingMutation) UserIDs() (ids []int) {
+ if id := m.user; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetUser resets all changes to the "user" edge.
+func (m *SsoBindingMutation) ResetUser() {
+ m.user = nil
+ m.cleareduser = false
+}
+
+// Where appends a list predicates to the SsoBindingMutation builder.
+func (m *SsoBindingMutation) Where(ps ...predicate.SsoBinding) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the SsoBindingMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *SsoBindingMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.SsoBinding, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *SsoBindingMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *SsoBindingMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (SsoBinding).
+func (m *SsoBindingMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *SsoBindingMutation) Fields() []string {
+ fields := make([]string, 0, 6)
+ if m.created_at != nil {
+ fields = append(fields, ssobinding.FieldCreatedAt)
+ }
+ if m.updated_at != nil {
+ fields = append(fields, ssobinding.FieldUpdatedAt)
+ }
+ if m.deleted_at != nil {
+ fields = append(fields, ssobinding.FieldDeletedAt)
+ }
+ if m.provider != nil {
+ fields = append(fields, ssobinding.FieldProvider)
+ }
+ if m.subject != nil {
+ fields = append(fields, ssobinding.FieldSubject)
+ }
+ if m.user != nil {
+ fields = append(fields, ssobinding.FieldUserID)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *SsoBindingMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case ssobinding.FieldCreatedAt:
+ return m.CreatedAt()
+ case ssobinding.FieldUpdatedAt:
+ return m.UpdatedAt()
+ case ssobinding.FieldDeletedAt:
+ return m.DeletedAt()
+ case ssobinding.FieldProvider:
+ return m.Provider()
+ case ssobinding.FieldSubject:
+ return m.Subject()
+ case ssobinding.FieldUserID:
+ return m.UserID()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *SsoBindingMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case ssobinding.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case ssobinding.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ case ssobinding.FieldDeletedAt:
+ return m.OldDeletedAt(ctx)
+ case ssobinding.FieldProvider:
+ return m.OldProvider(ctx)
+ case ssobinding.FieldSubject:
+ return m.OldSubject(ctx)
+ case ssobinding.FieldUserID:
+ return m.OldUserID(ctx)
+ }
+ return nil, fmt.Errorf("unknown SsoBinding field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *SsoBindingMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case ssobinding.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case ssobinding.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ case ssobinding.FieldDeletedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetDeletedAt(v)
+ return nil
+ case ssobinding.FieldProvider:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetProvider(v)
+ return nil
+ case ssobinding.FieldSubject:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSubject(v)
+ return nil
+ case ssobinding.FieldUserID:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserID(v)
+ return nil
+ }
+ return fmt.Errorf("unknown SsoBinding field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *SsoBindingMutation) AddedFields() []string {
+ var fields []string
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *SsoBindingMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *SsoBindingMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ }
+ return fmt.Errorf("unknown SsoBinding numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *SsoBindingMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(ssobinding.FieldDeletedAt) {
+ fields = append(fields, ssobinding.FieldDeletedAt)
+ }
+ return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *SsoBindingMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *SsoBindingMutation) ClearField(name string) error {
+ switch name {
+ case ssobinding.FieldDeletedAt:
+ m.ClearDeletedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown SsoBinding nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *SsoBindingMutation) ResetField(name string) error {
+ switch name {
+ case ssobinding.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case ssobinding.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ case ssobinding.FieldDeletedAt:
+ m.ResetDeletedAt()
+ return nil
+ case ssobinding.FieldProvider:
+ m.ResetProvider()
+ return nil
+ case ssobinding.FieldSubject:
+ m.ResetSubject()
+ return nil
+ case ssobinding.FieldUserID:
+ m.ResetUserID()
+ return nil
+ }
+ return fmt.Errorf("unknown SsoBinding field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *SsoBindingMutation) AddedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.user != nil {
+ edges = append(edges, ssobinding.EdgeUser)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *SsoBindingMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case ssobinding.EdgeUser:
+ if id := m.user; id != nil {
+ return []ent.Value{*id}
+ }
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *SsoBindingMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 1)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *SsoBindingMutation) RemovedIDs(name string) []ent.Value {
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *SsoBindingMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.cleareduser {
+ edges = append(edges, ssobinding.EdgeUser)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *SsoBindingMutation) EdgeCleared(name string) bool {
+ switch name {
+ case ssobinding.EdgeUser:
+ return m.cleareduser
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *SsoBindingMutation) ClearEdge(name string) error {
+ switch name {
+ case ssobinding.EdgeUser:
+ m.ClearUser()
+ return nil
+ }
+ return fmt.Errorf("unknown SsoBinding unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *SsoBindingMutation) ResetEdge(name string) error {
+ switch name {
+ case ssobinding.EdgeUser:
+ m.ResetUser()
+ return nil
+ }
+ return fmt.Errorf("unknown SsoBinding edge %s", name)
+}
+
// StoragePolicyMutation represents an operation that mutates the StoragePolicy nodes in the graph.
type StoragePolicyMutation struct {
config
@@ -23486,6 +24163,9 @@ type UserMutation struct {
share_purchases map[int]struct{}
removedshare_purchases map[int]struct{}
clearedshare_purchases bool
+ sso_bindings map[int]struct{}
+ removedsso_bindings map[int]struct{}
+ clearedsso_bindings bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
@@ -24997,6 +25677,60 @@ func (m *UserMutation) ResetSharePurchases() {
m.removedshare_purchases = nil
}
+// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by ids.
+func (m *UserMutation) AddSSOBindingIDs(ids ...int) {
+ if m.sso_bindings == nil {
+ m.sso_bindings = make(map[int]struct{})
+ }
+ for i := range ids {
+ m.sso_bindings[ids[i]] = struct{}{}
+ }
+}
+
+// ClearSSOBindings clears the "sso_bindings" edge to the SsoBinding entity.
+func (m *UserMutation) ClearSSOBindings() {
+ m.clearedsso_bindings = true
+}
+
+// SSOBindingsCleared reports if the "sso_bindings" edge to the SsoBinding entity was cleared.
+func (m *UserMutation) SSOBindingsCleared() bool {
+ return m.clearedsso_bindings
+}
+
+// RemoveSSOBindingIDs removes the "sso_bindings" edge to the SsoBinding entity by IDs.
+func (m *UserMutation) RemoveSSOBindingIDs(ids ...int) {
+ if m.removedsso_bindings == nil {
+ m.removedsso_bindings = make(map[int]struct{})
+ }
+ for i := range ids {
+ delete(m.sso_bindings, ids[i])
+ m.removedsso_bindings[ids[i]] = struct{}{}
+ }
+}
+
+// RemovedSSOBindings returns the removed IDs of the "sso_bindings" edge to the SsoBinding entity.
+func (m *UserMutation) RemovedSSOBindingsIDs() (ids []int) {
+ for id := range m.removedsso_bindings {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// SSOBindingsIDs returns the "sso_bindings" edge IDs in the mutation.
+func (m *UserMutation) SSOBindingsIDs() (ids []int) {
+ for id := range m.sso_bindings {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// ResetSSOBindings resets all changes to the "sso_bindings" edge.
+func (m *UserMutation) ResetSSOBindings() {
+ m.sso_bindings = nil
+ m.clearedsso_bindings = false
+ m.removedsso_bindings = nil
+}
+
// Where appends a list predicates to the UserMutation builder.
func (m *UserMutation) Where(ps ...predicate.User) {
m.predicates = append(m.predicates, ps...)
@@ -25463,7 +26197,7 @@ func (m *UserMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *UserMutation) AddedEdges() []string {
- edges := make([]string, 0, 13)
+ edges := make([]string, 0, 14)
if m.group != nil {
edges = append(edges, user.EdgeGroup)
}
@@ -25503,6 +26237,9 @@ func (m *UserMutation) AddedEdges() []string {
if m.share_purchases != nil {
edges = append(edges, user.EdgeSharePurchases)
}
+ if m.sso_bindings != nil {
+ edges = append(edges, user.EdgeSSOBindings)
+ }
return edges
}
@@ -25586,13 +26323,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgeSSOBindings:
+ ids := make([]ent.Value, 0, len(m.sso_bindings))
+ for id := range m.sso_bindings {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *UserMutation) RemovedEdges() []string {
- edges := make([]string, 0, 13)
+ edges := make([]string, 0, 14)
if m.removedfiles != nil {
edges = append(edges, user.EdgeFiles)
}
@@ -25629,6 +26372,9 @@ func (m *UserMutation) RemovedEdges() []string {
if m.removedshare_purchases != nil {
edges = append(edges, user.EdgeSharePurchases)
}
+ if m.removedsso_bindings != nil {
+ edges = append(edges, user.EdgeSSOBindings)
+ }
return edges
}
@@ -25708,13 +26454,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgeSSOBindings:
+ ids := make([]ent.Value, 0, len(m.removedsso_bindings))
+ for id := range m.removedsso_bindings {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *UserMutation) ClearedEdges() []string {
- edges := make([]string, 0, 13)
+ edges := make([]string, 0, 14)
if m.clearedgroup {
edges = append(edges, user.EdgeGroup)
}
@@ -25754,6 +26506,9 @@ func (m *UserMutation) ClearedEdges() []string {
if m.clearedshare_purchases {
edges = append(edges, user.EdgeSharePurchases)
}
+ if m.clearedsso_bindings {
+ edges = append(edges, user.EdgeSSOBindings)
+ }
return edges
}
@@ -25787,6 +26542,8 @@ func (m *UserMutation) EdgeCleared(name string) bool {
return m.clearedgrants
case user.EdgeSharePurchases:
return m.clearedshare_purchases
+ case user.EdgeSSOBindings:
+ return m.clearedsso_bindings
}
return false
}
@@ -25845,6 +26602,9 @@ func (m *UserMutation) ResetEdge(name string) error {
case user.EdgeSharePurchases:
m.ResetSharePurchases()
return nil
+ case user.EdgeSSOBindings:
+ m.ResetSSOBindings()
+ return nil
}
return fmt.Errorf("unknown User edge %s", name)
}
diff --git a/ent/mutationhelper.go b/ent/mutationhelper.go
index 867d4525..1ccf4d97 100644
--- a/ent/mutationhelper.go
+++ b/ent/mutationhelper.go
@@ -130,6 +130,12 @@ func (m *SkuMutation) SetRawID(t int) {
// SetUpdatedAt sets the "updated_at" field.
+func (m *SsoBindingMutation) SetRawID(t int) {
+ m.id = &t
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+
func (m *StoragePolicyMutation) SetRawID(t int) {
m.id = &t
}
diff --git a/ent/predicate/predicate.go b/ent/predicate/predicate.go
index c72b6ec4..fc9e812b 100644
--- a/ent/predicate/predicate.go
+++ b/ent/predicate/predicate.go
@@ -69,6 +69,9 @@ type SharePurchase func(*sql.Selector)
// Sku is the predicate function for sku builders.
type Sku func(*sql.Selector)
+// SsoBinding is the predicate function for ssobinding builders.
+type SsoBinding func(*sql.Selector)
+
// StoragePolicy is the predicate function for storagepolicy builders.
type StoragePolicy func(*sql.Selector)
diff --git a/ent/runtime/runtime.go b/ent/runtime/runtime.go
index 3acb4555..a52ff773 100644
--- a/ent/runtime/runtime.go
+++ b/ent/runtime/runtime.go
@@ -27,6 +27,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@@ -540,6 +541,61 @@ func init() {
skuDescWeight := skuFields[9].Descriptor()
// sku.DefaultWeight holds the default value on creation for the weight field.
sku.DefaultWeight = skuDescWeight.Default.(int)
+ ssobindingMixin := schema.SsoBinding{}.Mixin()
+ ssobindingMixinHooks0 := ssobindingMixin[0].Hooks()
+ ssobinding.Hooks[0] = ssobindingMixinHooks0[0]
+ ssobindingMixinInters0 := ssobindingMixin[0].Interceptors()
+ ssobinding.Interceptors[0] = ssobindingMixinInters0[0]
+ ssobindingMixinFields0 := ssobindingMixin[0].Fields()
+ _ = ssobindingMixinFields0
+ ssobindingFields := schema.SsoBinding{}.Fields()
+ _ = ssobindingFields
+ // ssobindingDescCreatedAt is the schema descriptor for created_at field.
+ ssobindingDescCreatedAt := ssobindingMixinFields0[0].Descriptor()
+ // ssobinding.DefaultCreatedAt holds the default value on creation for the created_at field.
+ ssobinding.DefaultCreatedAt = ssobindingDescCreatedAt.Default.(func() time.Time)
+ // ssobindingDescUpdatedAt is the schema descriptor for updated_at field.
+ ssobindingDescUpdatedAt := ssobindingMixinFields0[1].Descriptor()
+ // ssobinding.DefaultUpdatedAt holds the default value on creation for the updated_at field.
+ ssobinding.DefaultUpdatedAt = ssobindingDescUpdatedAt.Default.(func() time.Time)
+ // ssobinding.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
+ ssobinding.UpdateDefaultUpdatedAt = ssobindingDescUpdatedAt.UpdateDefault.(func() time.Time)
+ // ssobindingDescProvider is the schema descriptor for provider field.
+ ssobindingDescProvider := ssobindingFields[0].Descriptor()
+ // ssobinding.ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
+ ssobinding.ProviderValidator = func() func(string) error {
+ validators := ssobindingDescProvider.Validators
+ fns := [...]func(string) error{
+ validators[0].(func(string) error),
+ validators[1].(func(string) error),
+ }
+ return func(provider string) error {
+ for _, fn := range fns {
+ if err := fn(provider); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ }()
+ // ssobindingDescSubject is the schema descriptor for subject field.
+ ssobindingDescSubject := ssobindingFields[1].Descriptor()
+ // ssobinding.SubjectValidator is a validator for the "subject" field. It is called by the builders before save.
+ ssobinding.SubjectValidator = func() func(string) error {
+ validators := ssobindingDescSubject.Validators
+ fns := [...]func(string) error{
+ validators[0].(func(string) error),
+ validators[1].(func(string) error),
+ }
+ return func(subject string) error {
+ for _, fn := range fns {
+ if err := fn(subject); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ }()
storagepolicyMixin := schema.StoragePolicy{}.Mixin()
storagepolicyMixinHooks0 := storagepolicyMixin[0].Hooks()
storagepolicy.Hooks[0] = storagepolicyMixinHooks0[0]
diff --git a/ent/schema/ssobinding.go b/ent/schema/ssobinding.go
new file mode 100644
index 00000000..54358076
--- /dev/null
+++ b/ent/schema/ssobinding.go
@@ -0,0 +1,54 @@
+package schema
+
+import (
+ "entgo.io/ent"
+ "entgo.io/ent/schema/edge"
+ "entgo.io/ent/schema/field"
+ "entgo.io/ent/schema/index"
+)
+
+// SsoBinding holds the schema definition for external identity bindings.
+// Each row links a local user to a subject at an external sign-in provider
+// (e.g. a QQ Connect openid). One binding per (provider, subject) and one
+// binding per (user, provider).
+type SsoBinding struct {
+ ent.Schema
+}
+
+// Fields of the SsoBinding.
+func (SsoBinding) Fields() []ent.Field {
+ return []ent.Field{
+ field.String("provider").
+ MaxLen(32).
+ NotEmpty(),
+ field.String("subject").
+ MaxLen(128).
+ NotEmpty(),
+ field.Int("user_id"),
+ }
+}
+
+// Edges of the SsoBinding.
+func (SsoBinding) Edges() []ent.Edge {
+ return []ent.Edge{
+ edge.From("user", User.Type).
+ Ref("sso_bindings").
+ Field("user_id").
+ Unique().
+ Required(),
+ }
+}
+
+// Indexes of the SsoBinding.
+func (SsoBinding) Indexes() []ent.Index {
+ return []ent.Index{
+ index.Fields("provider", "subject").Unique(),
+ index.Fields("user_id", "provider").Unique(),
+ }
+}
+
+func (SsoBinding) Mixin() []ent.Mixin {
+ return []ent.Mixin{
+ CommonMixin{},
+ }
+}
diff --git a/ent/schema/user.go b/ent/schema/user.go
index de6a487d..aecd156b 100644
--- a/ent/schema/user.go
+++ b/ent/schema/user.go
@@ -73,6 +73,7 @@ func (User) Edges() []ent.Edge {
edge.To("redeemed_codes", GiftCode.Type),
edge.To("grants", UserGrant.Type),
edge.To("share_purchases", SharePurchase.Type),
+ edge.To("sso_bindings", SsoBinding.Type),
}
}
diff --git a/ent/ssobinding.go b/ent/ssobinding.go
new file mode 100644
index 00000000..c9418a16
--- /dev/null
+++ b/ent/ssobinding.go
@@ -0,0 +1,201 @@
+// 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/ssobinding"
+ "github.com/cloudreve/Cloudreve/v4/ent/user"
+)
+
+// SsoBinding is the model entity for the SsoBinding schema.
+type SsoBinding 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"`
+ // Provider holds the value of the "provider" field.
+ Provider string `json:"provider,omitempty"`
+ // Subject holds the value of the "subject" field.
+ Subject string `json:"subject,omitempty"`
+ // UserID holds the value of the "user_id" field.
+ UserID int `json:"user_id,omitempty"`
+ // Edges holds the relations/edges for other nodes in the graph.
+ // The values are being populated by the SsoBindingQuery when eager-loading is set.
+ Edges SsoBindingEdges `json:"edges"`
+ selectValues sql.SelectValues
+}
+
+// SsoBindingEdges holds the relations/edges for other nodes in the graph.
+type SsoBindingEdges struct {
+ // User holds the value of the user edge.
+ User *User `json:"user,omitempty"`
+ // loadedTypes holds the information for reporting if a
+ // type was loaded (or requested) in eager-loading or not.
+ loadedTypes [1]bool
+}
+
+// UserOrErr returns the User value or an error if the edge
+// was not loaded in eager-loading, or loaded but was not found.
+func (e SsoBindingEdges) UserOrErr() (*User, error) {
+ if e.loadedTypes[0] {
+ if e.User == nil {
+ // Edge was loaded but was not found.
+ return nil, &NotFoundError{label: user.Label}
+ }
+ return e.User, nil
+ }
+ return nil, &NotLoadedError{edge: "user"}
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*SsoBinding) scanValues(columns []string) ([]any, error) {
+ values := make([]any, len(columns))
+ for i := range columns {
+ switch columns[i] {
+ case ssobinding.FieldID, ssobinding.FieldUserID:
+ values[i] = new(sql.NullInt64)
+ case ssobinding.FieldProvider, ssobinding.FieldSubject:
+ values[i] = new(sql.NullString)
+ case ssobinding.FieldCreatedAt, ssobinding.FieldUpdatedAt, ssobinding.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 SsoBinding fields.
+func (sb *SsoBinding) 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 ssobinding.FieldID:
+ value, ok := values[i].(*sql.NullInt64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field id", value)
+ }
+ sb.ID = int(value.Int64)
+ case ssobinding.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 {
+ sb.CreatedAt = value.Time
+ }
+ case ssobinding.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 {
+ sb.UpdatedAt = value.Time
+ }
+ case ssobinding.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 {
+ sb.DeletedAt = new(time.Time)
+ *sb.DeletedAt = value.Time
+ }
+ case ssobinding.FieldProvider:
+ if value, ok := values[i].(*sql.NullString); !ok {
+ return fmt.Errorf("unexpected type %T for field provider", values[i])
+ } else if value.Valid {
+ sb.Provider = value.String
+ }
+ case ssobinding.FieldSubject:
+ if value, ok := values[i].(*sql.NullString); !ok {
+ return fmt.Errorf("unexpected type %T for field subject", values[i])
+ } else if value.Valid {
+ sb.Subject = value.String
+ }
+ case ssobinding.FieldUserID:
+ if value, ok := values[i].(*sql.NullInt64); !ok {
+ return fmt.Errorf("unexpected type %T for field user_id", values[i])
+ } else if value.Valid {
+ sb.UserID = int(value.Int64)
+ }
+ default:
+ sb.selectValues.Set(columns[i], values[i])
+ }
+ }
+ return nil
+}
+
+// Value returns the ent.Value that was dynamically selected and assigned to the SsoBinding.
+// This includes values selected through modifiers, order, etc.
+func (sb *SsoBinding) Value(name string) (ent.Value, error) {
+ return sb.selectValues.Get(name)
+}
+
+// QueryUser queries the "user" edge of the SsoBinding entity.
+func (sb *SsoBinding) QueryUser() *UserQuery {
+ return NewSsoBindingClient(sb.config).QueryUser(sb)
+}
+
+// Update returns a builder for updating this SsoBinding.
+// Note that you need to call SsoBinding.Unwrap() before calling this method if this SsoBinding
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (sb *SsoBinding) Update() *SsoBindingUpdateOne {
+ return NewSsoBindingClient(sb.config).UpdateOne(sb)
+}
+
+// Unwrap unwraps the SsoBinding 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 (sb *SsoBinding) Unwrap() *SsoBinding {
+ _tx, ok := sb.config.driver.(*txDriver)
+ if !ok {
+ panic("ent: SsoBinding is not a transactional entity")
+ }
+ sb.config.driver = _tx.drv
+ return sb
+}
+
+// String implements the fmt.Stringer.
+func (sb *SsoBinding) String() string {
+ var builder strings.Builder
+ builder.WriteString("SsoBinding(")
+ builder.WriteString(fmt.Sprintf("id=%v, ", sb.ID))
+ builder.WriteString("created_at=")
+ builder.WriteString(sb.CreatedAt.Format(time.ANSIC))
+ builder.WriteString(", ")
+ builder.WriteString("updated_at=")
+ builder.WriteString(sb.UpdatedAt.Format(time.ANSIC))
+ builder.WriteString(", ")
+ if v := sb.DeletedAt; v != nil {
+ builder.WriteString("deleted_at=")
+ builder.WriteString(v.Format(time.ANSIC))
+ }
+ builder.WriteString(", ")
+ builder.WriteString("provider=")
+ builder.WriteString(sb.Provider)
+ builder.WriteString(", ")
+ builder.WriteString("subject=")
+ builder.WriteString(sb.Subject)
+ builder.WriteString(", ")
+ builder.WriteString("user_id=")
+ builder.WriteString(fmt.Sprintf("%v", sb.UserID))
+ builder.WriteByte(')')
+ return builder.String()
+}
+
+// SetUser manually set the edge as loaded state.
+func (e *SsoBinding) SetUser(v *User) {
+ e.Edges.User = v
+ e.Edges.loadedTypes[0] = true
+}
+
+// SsoBindings is a parsable slice of SsoBinding.
+type SsoBindings []*SsoBinding
diff --git a/ent/ssobinding/ssobinding.go b/ent/ssobinding/ssobinding.go
new file mode 100644
index 00000000..a7006126
--- /dev/null
+++ b/ent/ssobinding/ssobinding.go
@@ -0,0 +1,134 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ssobinding
+
+import (
+ "time"
+
+ "entgo.io/ent"
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+)
+
+const (
+ // Label holds the string label denoting the ssobinding type in the database.
+ Label = "sso_binding"
+ // 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"
+ // FieldProvider holds the string denoting the provider field in the database.
+ FieldProvider = "provider"
+ // FieldSubject holds the string denoting the subject field in the database.
+ FieldSubject = "subject"
+ // FieldUserID holds the string denoting the user_id field in the database.
+ FieldUserID = "user_id"
+ // EdgeUser holds the string denoting the user edge name in mutations.
+ EdgeUser = "user"
+ // Table holds the table name of the ssobinding in the database.
+ Table = "sso_bindings"
+ // UserTable is the table that holds the user relation/edge.
+ UserTable = "sso_bindings"
+ // UserInverseTable is the table name for the User entity.
+ // It exists in this package in order to avoid circular dependency with the "user" package.
+ UserInverseTable = "users"
+ // UserColumn is the table column denoting the user relation/edge.
+ UserColumn = "user_id"
+)
+
+// Columns holds all SQL columns for ssobinding fields.
+var Columns = []string{
+ FieldID,
+ FieldCreatedAt,
+ FieldUpdatedAt,
+ FieldDeletedAt,
+ FieldProvider,
+ FieldSubject,
+ FieldUserID,
+}
+
+// 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
+ // ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
+ ProviderValidator func(string) error
+ // SubjectValidator is a validator for the "subject" field. It is called by the builders before save.
+ SubjectValidator func(string) error
+)
+
+// OrderOption defines the ordering options for the SsoBinding 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()
+}
+
+// ByProvider orders the results by the provider field.
+func ByProvider(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldProvider, opts...).ToFunc()
+}
+
+// BySubject orders the results by the subject field.
+func BySubject(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldSubject, opts...).ToFunc()
+}
+
+// ByUserID orders the results by the user_id field.
+func ByUserID(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldUserID, opts...).ToFunc()
+}
+
+// ByUserField orders the results by user field.
+func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
+ }
+}
+func newUserStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(UserInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
+ )
+}
diff --git a/ent/ssobinding/where.go b/ent/ssobinding/where.go
new file mode 100644
index 00000000..b1c45ce6
--- /dev/null
+++ b/ent/ssobinding/where.go
@@ -0,0 +1,404 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ssobinding
+
+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.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id int) predicate.SsoBinding {
+ return predicate.SsoBinding(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.SsoBinding {
+ return predicate.SsoBinding(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.SsoBinding {
+ return predicate.SsoBinding(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.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldDeletedAt, v))
+}
+
+// Provider applies equality check predicate on the "provider" field. It's identical to ProviderEQ.
+func Provider(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldProvider, v))
+}
+
+// Subject applies equality check predicate on the "subject" field. It's identical to SubjectEQ.
+func Subject(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldSubject, v))
+}
+
+// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
+func UserID(v int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldUserID, v))
+}
+
+// CreatedAtEQ applies the EQ predicate on the "created_at" field.
+func CreatedAtEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
+func CreatedAtNEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtIn applies the In predicate on the "created_at" field.
+func CreatedAtIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
+func CreatedAtNotIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtGT applies the GT predicate on the "created_at" field.
+func CreatedAtGT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldCreatedAt, v))
+}
+
+// CreatedAtGTE applies the GTE predicate on the "created_at" field.
+func CreatedAtGTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldCreatedAt, v))
+}
+
+// CreatedAtLT applies the LT predicate on the "created_at" field.
+func CreatedAtLT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldCreatedAt, v))
+}
+
+// CreatedAtLTE applies the LTE predicate on the "created_at" field.
+func CreatedAtLTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLTE(FieldCreatedAt, v))
+}
+
+// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
+func UpdatedAtEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
+func UpdatedAtNEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtIn applies the In predicate on the "updated_at" field.
+func UpdatedAtIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
+func UpdatedAtNotIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtGT applies the GT predicate on the "updated_at" field.
+func UpdatedAtGT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
+func UpdatedAtGTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLT applies the LT predicate on the "updated_at" field.
+func UpdatedAtLT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
+func UpdatedAtLTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLTE(FieldUpdatedAt, v))
+}
+
+// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
+func DeletedAtEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldDeletedAt, v))
+}
+
+// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
+func DeletedAtNEQ(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldDeletedAt, v))
+}
+
+// DeletedAtIn applies the In predicate on the "deleted_at" field.
+func DeletedAtIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldDeletedAt, vs...))
+}
+
+// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
+func DeletedAtNotIn(vs ...time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldDeletedAt, vs...))
+}
+
+// DeletedAtGT applies the GT predicate on the "deleted_at" field.
+func DeletedAtGT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldDeletedAt, v))
+}
+
+// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
+func DeletedAtGTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldDeletedAt, v))
+}
+
+// DeletedAtLT applies the LT predicate on the "deleted_at" field.
+func DeletedAtLT(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldDeletedAt, v))
+}
+
+// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
+func DeletedAtLTE(v time.Time) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLTE(FieldDeletedAt, v))
+}
+
+// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
+func DeletedAtIsNil() predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIsNull(FieldDeletedAt))
+}
+
+// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
+func DeletedAtNotNil() predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotNull(FieldDeletedAt))
+}
+
+// ProviderEQ applies the EQ predicate on the "provider" field.
+func ProviderEQ(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldProvider, v))
+}
+
+// ProviderNEQ applies the NEQ predicate on the "provider" field.
+func ProviderNEQ(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldProvider, v))
+}
+
+// ProviderIn applies the In predicate on the "provider" field.
+func ProviderIn(vs ...string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldProvider, vs...))
+}
+
+// ProviderNotIn applies the NotIn predicate on the "provider" field.
+func ProviderNotIn(vs ...string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldProvider, vs...))
+}
+
+// ProviderGT applies the GT predicate on the "provider" field.
+func ProviderGT(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldProvider, v))
+}
+
+// ProviderGTE applies the GTE predicate on the "provider" field.
+func ProviderGTE(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldProvider, v))
+}
+
+// ProviderLT applies the LT predicate on the "provider" field.
+func ProviderLT(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldProvider, v))
+}
+
+// ProviderLTE applies the LTE predicate on the "provider" field.
+func ProviderLTE(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLTE(FieldProvider, v))
+}
+
+// ProviderContains applies the Contains predicate on the "provider" field.
+func ProviderContains(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldContains(FieldProvider, v))
+}
+
+// ProviderHasPrefix applies the HasPrefix predicate on the "provider" field.
+func ProviderHasPrefix(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldHasPrefix(FieldProvider, v))
+}
+
+// ProviderHasSuffix applies the HasSuffix predicate on the "provider" field.
+func ProviderHasSuffix(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldHasSuffix(FieldProvider, v))
+}
+
+// ProviderEqualFold applies the EqualFold predicate on the "provider" field.
+func ProviderEqualFold(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEqualFold(FieldProvider, v))
+}
+
+// ProviderContainsFold applies the ContainsFold predicate on the "provider" field.
+func ProviderContainsFold(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldContainsFold(FieldProvider, v))
+}
+
+// SubjectEQ applies the EQ predicate on the "subject" field.
+func SubjectEQ(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldSubject, v))
+}
+
+// SubjectNEQ applies the NEQ predicate on the "subject" field.
+func SubjectNEQ(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldSubject, v))
+}
+
+// SubjectIn applies the In predicate on the "subject" field.
+func SubjectIn(vs ...string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldSubject, vs...))
+}
+
+// SubjectNotIn applies the NotIn predicate on the "subject" field.
+func SubjectNotIn(vs ...string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldSubject, vs...))
+}
+
+// SubjectGT applies the GT predicate on the "subject" field.
+func SubjectGT(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGT(FieldSubject, v))
+}
+
+// SubjectGTE applies the GTE predicate on the "subject" field.
+func SubjectGTE(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldGTE(FieldSubject, v))
+}
+
+// SubjectLT applies the LT predicate on the "subject" field.
+func SubjectLT(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLT(FieldSubject, v))
+}
+
+// SubjectLTE applies the LTE predicate on the "subject" field.
+func SubjectLTE(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldLTE(FieldSubject, v))
+}
+
+// SubjectContains applies the Contains predicate on the "subject" field.
+func SubjectContains(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldContains(FieldSubject, v))
+}
+
+// SubjectHasPrefix applies the HasPrefix predicate on the "subject" field.
+func SubjectHasPrefix(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldHasPrefix(FieldSubject, v))
+}
+
+// SubjectHasSuffix applies the HasSuffix predicate on the "subject" field.
+func SubjectHasSuffix(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldHasSuffix(FieldSubject, v))
+}
+
+// SubjectEqualFold applies the EqualFold predicate on the "subject" field.
+func SubjectEqualFold(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEqualFold(FieldSubject, v))
+}
+
+// SubjectContainsFold applies the ContainsFold predicate on the "subject" field.
+func SubjectContainsFold(v string) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldContainsFold(FieldSubject, v))
+}
+
+// UserIDEQ applies the EQ predicate on the "user_id" field.
+func UserIDEQ(v int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldEQ(FieldUserID, v))
+}
+
+// UserIDNEQ applies the NEQ predicate on the "user_id" field.
+func UserIDNEQ(v int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNEQ(FieldUserID, v))
+}
+
+// UserIDIn applies the In predicate on the "user_id" field.
+func UserIDIn(vs ...int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldIn(FieldUserID, vs...))
+}
+
+// UserIDNotIn applies the NotIn predicate on the "user_id" field.
+func UserIDNotIn(vs ...int) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.FieldNotIn(FieldUserID, vs...))
+}
+
+// HasUser applies the HasEdge predicate on the "user" edge.
+func HasUser() predicate.SsoBinding {
+ return predicate.SsoBinding(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
+func HasUserWith(preds ...predicate.User) predicate.SsoBinding {
+ return predicate.SsoBinding(func(s *sql.Selector) {
+ step := newUserStep()
+ 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.SsoBinding) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.SsoBinding) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.SsoBinding) predicate.SsoBinding {
+ return predicate.SsoBinding(sql.NotPredicates(p))
+}
diff --git a/ent/ssobinding_create.go b/ent/ssobinding_create.go
new file mode 100644
index 00000000..67a7fbe3
--- /dev/null
+++ b/ent/ssobinding_create.go
@@ -0,0 +1,800 @@
+// 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/ssobinding"
+ "github.com/cloudreve/Cloudreve/v4/ent/user"
+)
+
+// SsoBindingCreate is the builder for creating a SsoBinding entity.
+type SsoBindingCreate struct {
+ config
+ mutation *SsoBindingMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (sbc *SsoBindingCreate) SetCreatedAt(t time.Time) *SsoBindingCreate {
+ sbc.mutation.SetCreatedAt(t)
+ return sbc
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (sbc *SsoBindingCreate) SetNillableCreatedAt(t *time.Time) *SsoBindingCreate {
+ if t != nil {
+ sbc.SetCreatedAt(*t)
+ }
+ return sbc
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (sbc *SsoBindingCreate) SetUpdatedAt(t time.Time) *SsoBindingCreate {
+ sbc.mutation.SetUpdatedAt(t)
+ return sbc
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (sbc *SsoBindingCreate) SetNillableUpdatedAt(t *time.Time) *SsoBindingCreate {
+ if t != nil {
+ sbc.SetUpdatedAt(*t)
+ }
+ return sbc
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (sbc *SsoBindingCreate) SetDeletedAt(t time.Time) *SsoBindingCreate {
+ sbc.mutation.SetDeletedAt(t)
+ return sbc
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (sbc *SsoBindingCreate) SetNillableDeletedAt(t *time.Time) *SsoBindingCreate {
+ if t != nil {
+ sbc.SetDeletedAt(*t)
+ }
+ return sbc
+}
+
+// SetProvider sets the "provider" field.
+func (sbc *SsoBindingCreate) SetProvider(s string) *SsoBindingCreate {
+ sbc.mutation.SetProvider(s)
+ return sbc
+}
+
+// SetSubject sets the "subject" field.
+func (sbc *SsoBindingCreate) SetSubject(s string) *SsoBindingCreate {
+ sbc.mutation.SetSubject(s)
+ return sbc
+}
+
+// SetUserID sets the "user_id" field.
+func (sbc *SsoBindingCreate) SetUserID(i int) *SsoBindingCreate {
+ sbc.mutation.SetUserID(i)
+ return sbc
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (sbc *SsoBindingCreate) SetUser(u *User) *SsoBindingCreate {
+ return sbc.SetUserID(u.ID)
+}
+
+// Mutation returns the SsoBindingMutation object of the builder.
+func (sbc *SsoBindingCreate) Mutation() *SsoBindingMutation {
+ return sbc.mutation
+}
+
+// Save creates the SsoBinding in the database.
+func (sbc *SsoBindingCreate) Save(ctx context.Context) (*SsoBinding, error) {
+ if err := sbc.defaults(); err != nil {
+ return nil, err
+ }
+ return withHooks(ctx, sbc.sqlSave, sbc.mutation, sbc.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (sbc *SsoBindingCreate) SaveX(ctx context.Context) *SsoBinding {
+ v, err := sbc.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (sbc *SsoBindingCreate) Exec(ctx context.Context) error {
+ _, err := sbc.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbc *SsoBindingCreate) ExecX(ctx context.Context) {
+ if err := sbc.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (sbc *SsoBindingCreate) defaults() error {
+ if _, ok := sbc.mutation.CreatedAt(); !ok {
+ if ssobinding.DefaultCreatedAt == nil {
+ return fmt.Errorf("ent: uninitialized ssobinding.DefaultCreatedAt (forgotten import ent/runtime?)")
+ }
+ v := ssobinding.DefaultCreatedAt()
+ sbc.mutation.SetCreatedAt(v)
+ }
+ if _, ok := sbc.mutation.UpdatedAt(); !ok {
+ if ssobinding.DefaultUpdatedAt == nil {
+ return fmt.Errorf("ent: uninitialized ssobinding.DefaultUpdatedAt (forgotten import ent/runtime?)")
+ }
+ v := ssobinding.DefaultUpdatedAt()
+ sbc.mutation.SetUpdatedAt(v)
+ }
+ return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (sbc *SsoBindingCreate) check() error {
+ if _, ok := sbc.mutation.CreatedAt(); !ok {
+ return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SsoBinding.created_at"`)}
+ }
+ if _, ok := sbc.mutation.UpdatedAt(); !ok {
+ return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SsoBinding.updated_at"`)}
+ }
+ if _, ok := sbc.mutation.Provider(); !ok {
+ return &ValidationError{Name: "provider", err: errors.New(`ent: missing required field "SsoBinding.provider"`)}
+ }
+ if v, ok := sbc.mutation.Provider(); ok {
+ if err := ssobinding.ProviderValidator(v); err != nil {
+ return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
+ }
+ }
+ if _, ok := sbc.mutation.Subject(); !ok {
+ return &ValidationError{Name: "subject", err: errors.New(`ent: missing required field "SsoBinding.subject"`)}
+ }
+ if v, ok := sbc.mutation.Subject(); ok {
+ if err := ssobinding.SubjectValidator(v); err != nil {
+ return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
+ }
+ }
+ if _, ok := sbc.mutation.UserID(); !ok {
+ return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "SsoBinding.user_id"`)}
+ }
+ if _, ok := sbc.mutation.UserID(); !ok {
+ return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "SsoBinding.user"`)}
+ }
+ return nil
+}
+
+func (sbc *SsoBindingCreate) sqlSave(ctx context.Context) (*SsoBinding, error) {
+ if err := sbc.check(); err != nil {
+ return nil, err
+ }
+ _node, _spec := sbc.createSpec()
+ if err := sqlgraph.CreateNode(ctx, sbc.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)
+ sbc.mutation.id = &_node.ID
+ sbc.mutation.done = true
+ return _node, nil
+}
+
+func (sbc *SsoBindingCreate) createSpec() (*SsoBinding, *sqlgraph.CreateSpec) {
+ var (
+ _node = &SsoBinding{config: sbc.config}
+ _spec = sqlgraph.NewCreateSpec(ssobinding.Table, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
+ )
+
+ if id, ok := sbc.mutation.ID(); ok {
+ _node.ID = id
+ id64 := int64(id)
+ _spec.ID.Value = id64
+ }
+
+ _spec.OnConflict = sbc.conflict
+ if value, ok := sbc.mutation.CreatedAt(); ok {
+ _spec.SetField(ssobinding.FieldCreatedAt, field.TypeTime, value)
+ _node.CreatedAt = value
+ }
+ if value, ok := sbc.mutation.UpdatedAt(); ok {
+ _spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
+ _node.UpdatedAt = value
+ }
+ if value, ok := sbc.mutation.DeletedAt(); ok {
+ _spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
+ _node.DeletedAt = &value
+ }
+ if value, ok := sbc.mutation.Provider(); ok {
+ _spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
+ _node.Provider = value
+ }
+ if value, ok := sbc.mutation.Subject(); ok {
+ _spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
+ _node.Subject = value
+ }
+ if nodes := sbc.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: ssobinding.UserTable,
+ Columns: []string{ssobinding.UserColumn},
+ 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.UserID = 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.SsoBinding.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.SsoBindingUpsert) {
+// SetCreatedAt(v+v).
+// }).
+// Exec(ctx)
+func (sbc *SsoBindingCreate) OnConflict(opts ...sql.ConflictOption) *SsoBindingUpsertOne {
+ sbc.conflict = opts
+ return &SsoBindingUpsertOne{
+ create: sbc,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (sbc *SsoBindingCreate) OnConflictColumns(columns ...string) *SsoBindingUpsertOne {
+ sbc.conflict = append(sbc.conflict, sql.ConflictColumns(columns...))
+ return &SsoBindingUpsertOne{
+ create: sbc,
+ }
+}
+
+type (
+ // SsoBindingUpsertOne is the builder for "upsert"-ing
+ // one SsoBinding node.
+ SsoBindingUpsertOne struct {
+ create *SsoBindingCreate
+ }
+
+ // SsoBindingUpsert is the "OnConflict" setter.
+ SsoBindingUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SsoBindingUpsert) SetUpdatedAt(v time.Time) *SsoBindingUpsert {
+ u.Set(ssobinding.FieldUpdatedAt, v)
+ return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SsoBindingUpsert) UpdateUpdatedAt() *SsoBindingUpsert {
+ u.SetExcluded(ssobinding.FieldUpdatedAt)
+ return u
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *SsoBindingUpsert) SetDeletedAt(v time.Time) *SsoBindingUpsert {
+ u.Set(ssobinding.FieldDeletedAt, v)
+ return u
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *SsoBindingUpsert) UpdateDeletedAt() *SsoBindingUpsert {
+ u.SetExcluded(ssobinding.FieldDeletedAt)
+ return u
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *SsoBindingUpsert) ClearDeletedAt() *SsoBindingUpsert {
+ u.SetNull(ssobinding.FieldDeletedAt)
+ return u
+}
+
+// SetProvider sets the "provider" field.
+func (u *SsoBindingUpsert) SetProvider(v string) *SsoBindingUpsert {
+ u.Set(ssobinding.FieldProvider, v)
+ return u
+}
+
+// UpdateProvider sets the "provider" field to the value that was provided on create.
+func (u *SsoBindingUpsert) UpdateProvider() *SsoBindingUpsert {
+ u.SetExcluded(ssobinding.FieldProvider)
+ return u
+}
+
+// SetSubject sets the "subject" field.
+func (u *SsoBindingUpsert) SetSubject(v string) *SsoBindingUpsert {
+ u.Set(ssobinding.FieldSubject, v)
+ return u
+}
+
+// UpdateSubject sets the "subject" field to the value that was provided on create.
+func (u *SsoBindingUpsert) UpdateSubject() *SsoBindingUpsert {
+ u.SetExcluded(ssobinding.FieldSubject)
+ return u
+}
+
+// SetUserID sets the "user_id" field.
+func (u *SsoBindingUpsert) SetUserID(v int) *SsoBindingUpsert {
+ u.Set(ssobinding.FieldUserID, v)
+ return u
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *SsoBindingUpsert) UpdateUserID() *SsoBindingUpsert {
+ u.SetExcluded(ssobinding.FieldUserID)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *SsoBindingUpsertOne) UpdateNewValues() *SsoBindingUpsertOne {
+ 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(ssobinding.FieldCreatedAt)
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *SsoBindingUpsertOne) Ignore() *SsoBindingUpsertOne {
+ 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 *SsoBindingUpsertOne) DoNothing() *SsoBindingUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the SsoBindingCreate.OnConflict
+// documentation for more info.
+func (u *SsoBindingUpsertOne) Update(set func(*SsoBindingUpsert)) *SsoBindingUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&SsoBindingUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SsoBindingUpsertOne) SetUpdatedAt(v time.Time) *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SsoBindingUpsertOne) UpdateUpdatedAt() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *SsoBindingUpsertOne) SetDeletedAt(v time.Time) *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetDeletedAt(v)
+ })
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *SsoBindingUpsertOne) UpdateDeletedAt() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateDeletedAt()
+ })
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *SsoBindingUpsertOne) ClearDeletedAt() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.ClearDeletedAt()
+ })
+}
+
+// SetProvider sets the "provider" field.
+func (u *SsoBindingUpsertOne) SetProvider(v string) *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetProvider(v)
+ })
+}
+
+// UpdateProvider sets the "provider" field to the value that was provided on create.
+func (u *SsoBindingUpsertOne) UpdateProvider() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateProvider()
+ })
+}
+
+// SetSubject sets the "subject" field.
+func (u *SsoBindingUpsertOne) SetSubject(v string) *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetSubject(v)
+ })
+}
+
+// UpdateSubject sets the "subject" field to the value that was provided on create.
+func (u *SsoBindingUpsertOne) UpdateSubject() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateSubject()
+ })
+}
+
+// SetUserID sets the "user_id" field.
+func (u *SsoBindingUpsertOne) SetUserID(v int) *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *SsoBindingUpsertOne) UpdateUserID() *SsoBindingUpsertOne {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// Exec executes the query.
+func (u *SsoBindingUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for SsoBindingCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *SsoBindingUpsertOne) 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 *SsoBindingUpsertOne) 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 *SsoBindingUpsertOne) IDX(ctx context.Context) int {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+func (m *SsoBindingCreate) SetRawID(t int) *SsoBindingCreate {
+ m.mutation.SetRawID(t)
+ return m
+}
+
+// SsoBindingCreateBulk is the builder for creating many SsoBinding entities in bulk.
+type SsoBindingCreateBulk struct {
+ config
+ err error
+ builders []*SsoBindingCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the SsoBinding entities in the database.
+func (sbcb *SsoBindingCreateBulk) Save(ctx context.Context) ([]*SsoBinding, error) {
+ if sbcb.err != nil {
+ return nil, sbcb.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(sbcb.builders))
+ nodes := make([]*SsoBinding, len(sbcb.builders))
+ mutators := make([]Mutator, len(sbcb.builders))
+ for i := range sbcb.builders {
+ func(i int, root context.Context) {
+ builder := sbcb.builders[i]
+ builder.defaults()
+ var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
+ mutation, ok := m.(*SsoBindingMutation)
+ 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, sbcb.builders[i+1].mutation)
+ } else {
+ spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
+ spec.OnConflict = sbcb.conflict
+ // Invoke the actual operation on the latest mutation in the chain.
+ if err = sqlgraph.BatchCreate(ctx, sbcb.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, sbcb.builders[0].mutation); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (sbcb *SsoBindingCreateBulk) SaveX(ctx context.Context) []*SsoBinding {
+ v, err := sbcb.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (sbcb *SsoBindingCreateBulk) Exec(ctx context.Context) error {
+ _, err := sbcb.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbcb *SsoBindingCreateBulk) ExecX(ctx context.Context) {
+ if err := sbcb.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.SsoBinding.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.SsoBindingUpsert) {
+// SetCreatedAt(v+v).
+// }).
+// Exec(ctx)
+func (sbcb *SsoBindingCreateBulk) OnConflict(opts ...sql.ConflictOption) *SsoBindingUpsertBulk {
+ sbcb.conflict = opts
+ return &SsoBindingUpsertBulk{
+ create: sbcb,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (sbcb *SsoBindingCreateBulk) OnConflictColumns(columns ...string) *SsoBindingUpsertBulk {
+ sbcb.conflict = append(sbcb.conflict, sql.ConflictColumns(columns...))
+ return &SsoBindingUpsertBulk{
+ create: sbcb,
+ }
+}
+
+// SsoBindingUpsertBulk is the builder for "upsert"-ing
+// a bulk of SsoBinding nodes.
+type SsoBindingUpsertBulk struct {
+ create *SsoBindingCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *SsoBindingUpsertBulk) UpdateNewValues() *SsoBindingUpsertBulk {
+ 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(ssobinding.FieldCreatedAt)
+ }
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.SsoBinding.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *SsoBindingUpsertBulk) Ignore() *SsoBindingUpsertBulk {
+ 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 *SsoBindingUpsertBulk) DoNothing() *SsoBindingUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the SsoBindingCreateBulk.OnConflict
+// documentation for more info.
+func (u *SsoBindingUpsertBulk) Update(set func(*SsoBindingUpsert)) *SsoBindingUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&SsoBindingUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SsoBindingUpsertBulk) SetUpdatedAt(v time.Time) *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SsoBindingUpsertBulk) UpdateUpdatedAt() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *SsoBindingUpsertBulk) SetDeletedAt(v time.Time) *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetDeletedAt(v)
+ })
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *SsoBindingUpsertBulk) UpdateDeletedAt() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateDeletedAt()
+ })
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *SsoBindingUpsertBulk) ClearDeletedAt() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.ClearDeletedAt()
+ })
+}
+
+// SetProvider sets the "provider" field.
+func (u *SsoBindingUpsertBulk) SetProvider(v string) *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetProvider(v)
+ })
+}
+
+// UpdateProvider sets the "provider" field to the value that was provided on create.
+func (u *SsoBindingUpsertBulk) UpdateProvider() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateProvider()
+ })
+}
+
+// SetSubject sets the "subject" field.
+func (u *SsoBindingUpsertBulk) SetSubject(v string) *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetSubject(v)
+ })
+}
+
+// UpdateSubject sets the "subject" field to the value that was provided on create.
+func (u *SsoBindingUpsertBulk) UpdateSubject() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateSubject()
+ })
+}
+
+// SetUserID sets the "user_id" field.
+func (u *SsoBindingUpsertBulk) SetUserID(v int) *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *SsoBindingUpsertBulk) UpdateUserID() *SsoBindingUpsertBulk {
+ return u.Update(func(s *SsoBindingUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// Exec executes the query.
+func (u *SsoBindingUpsertBulk) 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 SsoBindingCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for SsoBindingCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *SsoBindingUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/ent/ssobinding_delete.go b/ent/ssobinding_delete.go
new file mode 100644
index 00000000..ff5f2dbb
--- /dev/null
+++ b/ent/ssobinding_delete.go
@@ -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/ssobinding"
+)
+
+// SsoBindingDelete is the builder for deleting a SsoBinding entity.
+type SsoBindingDelete struct {
+ config
+ hooks []Hook
+ mutation *SsoBindingMutation
+}
+
+// Where appends a list predicates to the SsoBindingDelete builder.
+func (sbd *SsoBindingDelete) Where(ps ...predicate.SsoBinding) *SsoBindingDelete {
+ sbd.mutation.Where(ps...)
+ return sbd
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (sbd *SsoBindingDelete) Exec(ctx context.Context) (int, error) {
+ return withHooks(ctx, sbd.sqlExec, sbd.mutation, sbd.hooks)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbd *SsoBindingDelete) ExecX(ctx context.Context) int {
+ n, err := sbd.Exec(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func (sbd *SsoBindingDelete) sqlExec(ctx context.Context) (int, error) {
+ _spec := sqlgraph.NewDeleteSpec(ssobinding.Table, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
+ if ps := sbd.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ affected, err := sqlgraph.DeleteNodes(ctx, sbd.driver, _spec)
+ if err != nil && sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ sbd.mutation.done = true
+ return affected, err
+}
+
+// SsoBindingDeleteOne is the builder for deleting a single SsoBinding entity.
+type SsoBindingDeleteOne struct {
+ sbd *SsoBindingDelete
+}
+
+// Where appends a list predicates to the SsoBindingDelete builder.
+func (sbdo *SsoBindingDeleteOne) Where(ps ...predicate.SsoBinding) *SsoBindingDeleteOne {
+ sbdo.sbd.mutation.Where(ps...)
+ return sbdo
+}
+
+// Exec executes the deletion query.
+func (sbdo *SsoBindingDeleteOne) Exec(ctx context.Context) error {
+ n, err := sbdo.sbd.Exec(ctx)
+ switch {
+ case err != nil:
+ return err
+ case n == 0:
+ return &NotFoundError{ssobinding.Label}
+ default:
+ return nil
+ }
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbdo *SsoBindingDeleteOne) ExecX(ctx context.Context) {
+ if err := sbdo.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/ent/ssobinding_query.go b/ent/ssobinding_query.go
new file mode 100644
index 00000000..f60311ce
--- /dev/null
+++ b/ent/ssobinding_query.go
@@ -0,0 +1,605 @@
+// 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/ssobinding"
+ "github.com/cloudreve/Cloudreve/v4/ent/user"
+)
+
+// SsoBindingQuery is the builder for querying SsoBinding entities.
+type SsoBindingQuery struct {
+ config
+ ctx *QueryContext
+ order []ssobinding.OrderOption
+ inters []Interceptor
+ predicates []predicate.SsoBinding
+ withUser *UserQuery
+ // intermediate query (i.e. traversal path).
+ sql *sql.Selector
+ path func(context.Context) (*sql.Selector, error)
+}
+
+// Where adds a new predicate for the SsoBindingQuery builder.
+func (sbq *SsoBindingQuery) Where(ps ...predicate.SsoBinding) *SsoBindingQuery {
+ sbq.predicates = append(sbq.predicates, ps...)
+ return sbq
+}
+
+// Limit the number of records to be returned by this query.
+func (sbq *SsoBindingQuery) Limit(limit int) *SsoBindingQuery {
+ sbq.ctx.Limit = &limit
+ return sbq
+}
+
+// Offset to start from.
+func (sbq *SsoBindingQuery) Offset(offset int) *SsoBindingQuery {
+ sbq.ctx.Offset = &offset
+ return sbq
+}
+
+// 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 (sbq *SsoBindingQuery) Unique(unique bool) *SsoBindingQuery {
+ sbq.ctx.Unique = &unique
+ return sbq
+}
+
+// Order specifies how the records should be ordered.
+func (sbq *SsoBindingQuery) Order(o ...ssobinding.OrderOption) *SsoBindingQuery {
+ sbq.order = append(sbq.order, o...)
+ return sbq
+}
+
+// QueryUser chains the current query on the "user" edge.
+func (sbq *SsoBindingQuery) QueryUser() *UserQuery {
+ query := (&UserClient{config: sbq.config}).Query()
+ query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
+ if err := sbq.prepareQuery(ctx); err != nil {
+ return nil, err
+ }
+ selector := sbq.sqlQuery(ctx)
+ if err := selector.Err(); err != nil {
+ return nil, err
+ }
+ step := sqlgraph.NewStep(
+ sqlgraph.From(ssobinding.Table, ssobinding.FieldID, selector),
+ sqlgraph.To(user.Table, user.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, ssobinding.UserTable, ssobinding.UserColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(sbq.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
+// First returns the first SsoBinding entity from the query.
+// Returns a *NotFoundError when no SsoBinding was found.
+func (sbq *SsoBindingQuery) First(ctx context.Context) (*SsoBinding, error) {
+ nodes, err := sbq.Limit(1).All(setContextOp(ctx, sbq.ctx, "First"))
+ if err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nil, &NotFoundError{ssobinding.Label}
+ }
+ return nodes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (sbq *SsoBindingQuery) FirstX(ctx context.Context) *SsoBinding {
+ node, err := sbq.First(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return node
+}
+
+// FirstID returns the first SsoBinding ID from the query.
+// Returns a *NotFoundError when no SsoBinding ID was found.
+func (sbq *SsoBindingQuery) FirstID(ctx context.Context) (id int, err error) {
+ var ids []int
+ if ids, err = sbq.Limit(1).IDs(setContextOp(ctx, sbq.ctx, "FirstID")); err != nil {
+ return
+ }
+ if len(ids) == 0 {
+ err = &NotFoundError{ssobinding.Label}
+ return
+ }
+ return ids[0], nil
+}
+
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (sbq *SsoBindingQuery) FirstIDX(ctx context.Context) int {
+ id, err := sbq.FirstID(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return id
+}
+
+// Only returns a single SsoBinding entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when more than one SsoBinding entity is found.
+// Returns a *NotFoundError when no SsoBinding entities are found.
+func (sbq *SsoBindingQuery) Only(ctx context.Context) (*SsoBinding, error) {
+ nodes, err := sbq.Limit(2).All(setContextOp(ctx, sbq.ctx, "Only"))
+ if err != nil {
+ return nil, err
+ }
+ switch len(nodes) {
+ case 1:
+ return nodes[0], nil
+ case 0:
+ return nil, &NotFoundError{ssobinding.Label}
+ default:
+ return nil, &NotSingularError{ssobinding.Label}
+ }
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (sbq *SsoBindingQuery) OnlyX(ctx context.Context) *SsoBinding {
+ node, err := sbq.Only(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// OnlyID is like Only, but returns the only SsoBinding ID in the query.
+// Returns a *NotSingularError when more than one SsoBinding ID is found.
+// Returns a *NotFoundError when no entities are found.
+func (sbq *SsoBindingQuery) OnlyID(ctx context.Context) (id int, err error) {
+ var ids []int
+ if ids, err = sbq.Limit(2).IDs(setContextOp(ctx, sbq.ctx, "OnlyID")); err != nil {
+ return
+ }
+ switch len(ids) {
+ case 1:
+ id = ids[0]
+ case 0:
+ err = &NotFoundError{ssobinding.Label}
+ default:
+ err = &NotSingularError{ssobinding.Label}
+ }
+ return
+}
+
+// OnlyIDX is like OnlyID, but panics if an error occurs.
+func (sbq *SsoBindingQuery) OnlyIDX(ctx context.Context) int {
+ id, err := sbq.OnlyID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// All executes the query and returns a list of SsoBindings.
+func (sbq *SsoBindingQuery) All(ctx context.Context) ([]*SsoBinding, error) {
+ ctx = setContextOp(ctx, sbq.ctx, "All")
+ if err := sbq.prepareQuery(ctx); err != nil {
+ return nil, err
+ }
+ qr := querierAll[[]*SsoBinding, *SsoBindingQuery]()
+ return withInterceptors[[]*SsoBinding](ctx, sbq, qr, sbq.inters)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (sbq *SsoBindingQuery) AllX(ctx context.Context) []*SsoBinding {
+ nodes, err := sbq.All(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return nodes
+}
+
+// IDs executes the query and returns a list of SsoBinding IDs.
+func (sbq *SsoBindingQuery) IDs(ctx context.Context) (ids []int, err error) {
+ if sbq.ctx.Unique == nil && sbq.path != nil {
+ sbq.Unique(true)
+ }
+ ctx = setContextOp(ctx, sbq.ctx, "IDs")
+ if err = sbq.Select(ssobinding.FieldID).Scan(ctx, &ids); err != nil {
+ return nil, err
+ }
+ return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (sbq *SsoBindingQuery) IDsX(ctx context.Context) []int {
+ ids, err := sbq.IDs(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return ids
+}
+
+// Count returns the count of the given query.
+func (sbq *SsoBindingQuery) Count(ctx context.Context) (int, error) {
+ ctx = setContextOp(ctx, sbq.ctx, "Count")
+ if err := sbq.prepareQuery(ctx); err != nil {
+ return 0, err
+ }
+ return withInterceptors[int](ctx, sbq, querierCount[*SsoBindingQuery](), sbq.inters)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (sbq *SsoBindingQuery) CountX(ctx context.Context) int {
+ count, err := sbq.Count(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (sbq *SsoBindingQuery) Exist(ctx context.Context) (bool, error) {
+ ctx = setContextOp(ctx, sbq.ctx, "Exist")
+ switch _, err := sbq.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 (sbq *SsoBindingQuery) ExistX(ctx context.Context) bool {
+ exist, err := sbq.Exist(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return exist
+}
+
+// Clone returns a duplicate of the SsoBindingQuery builder, including all associated steps. It can be
+// used to prepare common query builders and use them differently after the clone is made.
+func (sbq *SsoBindingQuery) Clone() *SsoBindingQuery {
+ if sbq == nil {
+ return nil
+ }
+ return &SsoBindingQuery{
+ config: sbq.config,
+ ctx: sbq.ctx.Clone(),
+ order: append([]ssobinding.OrderOption{}, sbq.order...),
+ inters: append([]Interceptor{}, sbq.inters...),
+ predicates: append([]predicate.SsoBinding{}, sbq.predicates...),
+ withUser: sbq.withUser.Clone(),
+ // clone intermediate query.
+ sql: sbq.sql.Clone(),
+ path: sbq.path,
+ }
+}
+
+// WithUser tells the query-builder to eager-load the nodes that are connected to
+// the "user" edge. The optional arguments are used to configure the query builder of the edge.
+func (sbq *SsoBindingQuery) WithUser(opts ...func(*UserQuery)) *SsoBindingQuery {
+ query := (&UserClient{config: sbq.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ sbq.withUser = query
+ return sbq
+}
+
+// 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.SsoBinding.Query().
+// GroupBy(ssobinding.FieldCreatedAt).
+// Aggregate(ent.Count()).
+// Scan(ctx, &v)
+func (sbq *SsoBindingQuery) GroupBy(field string, fields ...string) *SsoBindingGroupBy {
+ sbq.ctx.Fields = append([]string{field}, fields...)
+ grbuild := &SsoBindingGroupBy{build: sbq}
+ grbuild.flds = &sbq.ctx.Fields
+ grbuild.label = ssobinding.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.SsoBinding.Query().
+// Select(ssobinding.FieldCreatedAt).
+// Scan(ctx, &v)
+func (sbq *SsoBindingQuery) Select(fields ...string) *SsoBindingSelect {
+ sbq.ctx.Fields = append(sbq.ctx.Fields, fields...)
+ sbuild := &SsoBindingSelect{SsoBindingQuery: sbq}
+ sbuild.label = ssobinding.Label
+ sbuild.flds, sbuild.scan = &sbq.ctx.Fields, sbuild.Scan
+ return sbuild
+}
+
+// Aggregate returns a SsoBindingSelect configured with the given aggregations.
+func (sbq *SsoBindingQuery) Aggregate(fns ...AggregateFunc) *SsoBindingSelect {
+ return sbq.Select().Aggregate(fns...)
+}
+
+func (sbq *SsoBindingQuery) prepareQuery(ctx context.Context) error {
+ for _, inter := range sbq.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, sbq); err != nil {
+ return err
+ }
+ }
+ }
+ for _, f := range sbq.ctx.Fields {
+ if !ssobinding.ValidColumn(f) {
+ return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ }
+ if sbq.path != nil {
+ prev, err := sbq.path(ctx)
+ if err != nil {
+ return err
+ }
+ sbq.sql = prev
+ }
+ return nil
+}
+
+func (sbq *SsoBindingQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SsoBinding, error) {
+ var (
+ nodes = []*SsoBinding{}
+ _spec = sbq.querySpec()
+ loadedTypes = [1]bool{
+ sbq.withUser != nil,
+ }
+ )
+ _spec.ScanValues = func(columns []string) ([]any, error) {
+ return (*SsoBinding).scanValues(nil, columns)
+ }
+ _spec.Assign = func(columns []string, values []any) error {
+ node := &SsoBinding{config: sbq.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, sbq.driver, _spec); err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nodes, nil
+ }
+ if query := sbq.withUser; query != nil {
+ if err := sbq.loadUser(ctx, query, nodes, nil,
+ func(n *SsoBinding, e *User) { n.Edges.User = e }); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+func (sbq *SsoBindingQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*SsoBinding, init func(*SsoBinding), assign func(*SsoBinding, *User)) error {
+ ids := make([]int, 0, len(nodes))
+ nodeids := make(map[int][]*SsoBinding)
+ for i := range nodes {
+ fk := nodes[i].UserID
+ 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 "user_id" returned %v`, n.ID)
+ }
+ for i := range nodes {
+ assign(nodes[i], n)
+ }
+ }
+ return nil
+}
+
+func (sbq *SsoBindingQuery) sqlCount(ctx context.Context) (int, error) {
+ _spec := sbq.querySpec()
+ _spec.Node.Columns = sbq.ctx.Fields
+ if len(sbq.ctx.Fields) > 0 {
+ _spec.Unique = sbq.ctx.Unique != nil && *sbq.ctx.Unique
+ }
+ return sqlgraph.CountNodes(ctx, sbq.driver, _spec)
+}
+
+func (sbq *SsoBindingQuery) querySpec() *sqlgraph.QuerySpec {
+ _spec := sqlgraph.NewQuerySpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
+ _spec.From = sbq.sql
+ if unique := sbq.ctx.Unique; unique != nil {
+ _spec.Unique = *unique
+ } else if sbq.path != nil {
+ _spec.Unique = true
+ }
+ if fields := sbq.ctx.Fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, ssobinding.FieldID)
+ for i := range fields {
+ if fields[i] != ssobinding.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+ }
+ }
+ if sbq.withUser != nil {
+ _spec.Node.AddColumnOnce(ssobinding.FieldUserID)
+ }
+ }
+ if ps := sbq.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if limit := sbq.ctx.Limit; limit != nil {
+ _spec.Limit = *limit
+ }
+ if offset := sbq.ctx.Offset; offset != nil {
+ _spec.Offset = *offset
+ }
+ if ps := sbq.order; len(ps) > 0 {
+ _spec.Order = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ return _spec
+}
+
+func (sbq *SsoBindingQuery) sqlQuery(ctx context.Context) *sql.Selector {
+ builder := sql.Dialect(sbq.driver.Dialect())
+ t1 := builder.Table(ssobinding.Table)
+ columns := sbq.ctx.Fields
+ if len(columns) == 0 {
+ columns = ssobinding.Columns
+ }
+ selector := builder.Select(t1.Columns(columns...)...).From(t1)
+ if sbq.sql != nil {
+ selector = sbq.sql
+ selector.Select(selector.Columns(columns...)...)
+ }
+ if sbq.ctx.Unique != nil && *sbq.ctx.Unique {
+ selector.Distinct()
+ }
+ for _, p := range sbq.predicates {
+ p(selector)
+ }
+ for _, p := range sbq.order {
+ p(selector)
+ }
+ if offset := sbq.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 := sbq.ctx.Limit; limit != nil {
+ selector.Limit(*limit)
+ }
+ return selector
+}
+
+// SsoBindingGroupBy is the group-by builder for SsoBinding entities.
+type SsoBindingGroupBy struct {
+ selector
+ build *SsoBindingQuery
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (sbgb *SsoBindingGroupBy) Aggregate(fns ...AggregateFunc) *SsoBindingGroupBy {
+ sbgb.fns = append(sbgb.fns, fns...)
+ return sbgb
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (sbgb *SsoBindingGroupBy) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, sbgb.build.ctx, "GroupBy")
+ if err := sbgb.build.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*SsoBindingQuery, *SsoBindingGroupBy](ctx, sbgb.build, sbgb, sbgb.build.inters, v)
+}
+
+func (sbgb *SsoBindingGroupBy) sqlScan(ctx context.Context, root *SsoBindingQuery, v any) error {
+ selector := root.sqlQuery(ctx).Select()
+ aggregation := make([]string, 0, len(sbgb.fns))
+ for _, fn := range sbgb.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ if len(selector.SelectedColumns()) == 0 {
+ columns := make([]string, 0, len(*sbgb.flds)+len(sbgb.fns))
+ for _, f := range *sbgb.flds {
+ columns = append(columns, selector.C(f))
+ }
+ columns = append(columns, aggregation...)
+ selector.Select(columns...)
+ }
+ selector.GroupBy(selector.Columns(*sbgb.flds...)...)
+ if err := selector.Err(); err != nil {
+ return err
+ }
+ rows := &sql.Rows{}
+ query, args := selector.Query()
+ if err := sbgb.build.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
+
+// SsoBindingSelect is the builder for selecting fields of SsoBinding entities.
+type SsoBindingSelect struct {
+ *SsoBindingQuery
+ selector
+}
+
+// Aggregate adds the given aggregation functions to the selector query.
+func (sbs *SsoBindingSelect) Aggregate(fns ...AggregateFunc) *SsoBindingSelect {
+ sbs.fns = append(sbs.fns, fns...)
+ return sbs
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (sbs *SsoBindingSelect) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, sbs.ctx, "Select")
+ if err := sbs.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*SsoBindingQuery, *SsoBindingSelect](ctx, sbs.SsoBindingQuery, sbs, sbs.inters, v)
+}
+
+func (sbs *SsoBindingSelect) sqlScan(ctx context.Context, root *SsoBindingQuery, v any) error {
+ selector := root.sqlQuery(ctx)
+ aggregation := make([]string, 0, len(sbs.fns))
+ for _, fn := range sbs.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ switch n := len(*sbs.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 := sbs.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
diff --git a/ent/ssobinding_update.go b/ent/ssobinding_update.go
new file mode 100644
index 00000000..8128b7cc
--- /dev/null
+++ b/ent/ssobinding_update.go
@@ -0,0 +1,495 @@
+// 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/ssobinding"
+ "github.com/cloudreve/Cloudreve/v4/ent/user"
+)
+
+// SsoBindingUpdate is the builder for updating SsoBinding entities.
+type SsoBindingUpdate struct {
+ config
+ hooks []Hook
+ mutation *SsoBindingMutation
+}
+
+// Where appends a list predicates to the SsoBindingUpdate builder.
+func (sbu *SsoBindingUpdate) Where(ps ...predicate.SsoBinding) *SsoBindingUpdate {
+ sbu.mutation.Where(ps...)
+ return sbu
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (sbu *SsoBindingUpdate) SetUpdatedAt(t time.Time) *SsoBindingUpdate {
+ sbu.mutation.SetUpdatedAt(t)
+ return sbu
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (sbu *SsoBindingUpdate) SetDeletedAt(t time.Time) *SsoBindingUpdate {
+ sbu.mutation.SetDeletedAt(t)
+ return sbu
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (sbu *SsoBindingUpdate) SetNillableDeletedAt(t *time.Time) *SsoBindingUpdate {
+ if t != nil {
+ sbu.SetDeletedAt(*t)
+ }
+ return sbu
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (sbu *SsoBindingUpdate) ClearDeletedAt() *SsoBindingUpdate {
+ sbu.mutation.ClearDeletedAt()
+ return sbu
+}
+
+// SetProvider sets the "provider" field.
+func (sbu *SsoBindingUpdate) SetProvider(s string) *SsoBindingUpdate {
+ sbu.mutation.SetProvider(s)
+ return sbu
+}
+
+// SetNillableProvider sets the "provider" field if the given value is not nil.
+func (sbu *SsoBindingUpdate) SetNillableProvider(s *string) *SsoBindingUpdate {
+ if s != nil {
+ sbu.SetProvider(*s)
+ }
+ return sbu
+}
+
+// SetSubject sets the "subject" field.
+func (sbu *SsoBindingUpdate) SetSubject(s string) *SsoBindingUpdate {
+ sbu.mutation.SetSubject(s)
+ return sbu
+}
+
+// SetNillableSubject sets the "subject" field if the given value is not nil.
+func (sbu *SsoBindingUpdate) SetNillableSubject(s *string) *SsoBindingUpdate {
+ if s != nil {
+ sbu.SetSubject(*s)
+ }
+ return sbu
+}
+
+// SetUserID sets the "user_id" field.
+func (sbu *SsoBindingUpdate) SetUserID(i int) *SsoBindingUpdate {
+ sbu.mutation.SetUserID(i)
+ return sbu
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (sbu *SsoBindingUpdate) SetNillableUserID(i *int) *SsoBindingUpdate {
+ if i != nil {
+ sbu.SetUserID(*i)
+ }
+ return sbu
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (sbu *SsoBindingUpdate) SetUser(u *User) *SsoBindingUpdate {
+ return sbu.SetUserID(u.ID)
+}
+
+// Mutation returns the SsoBindingMutation object of the builder.
+func (sbu *SsoBindingUpdate) Mutation() *SsoBindingMutation {
+ return sbu.mutation
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (sbu *SsoBindingUpdate) ClearUser() *SsoBindingUpdate {
+ sbu.mutation.ClearUser()
+ return sbu
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (sbu *SsoBindingUpdate) Save(ctx context.Context) (int, error) {
+ if err := sbu.defaults(); err != nil {
+ return 0, err
+ }
+ return withHooks(ctx, sbu.sqlSave, sbu.mutation, sbu.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (sbu *SsoBindingUpdate) SaveX(ctx context.Context) int {
+ affected, err := sbu.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return affected
+}
+
+// Exec executes the query.
+func (sbu *SsoBindingUpdate) Exec(ctx context.Context) error {
+ _, err := sbu.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbu *SsoBindingUpdate) ExecX(ctx context.Context) {
+ if err := sbu.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (sbu *SsoBindingUpdate) defaults() error {
+ if _, ok := sbu.mutation.UpdatedAt(); !ok {
+ if ssobinding.UpdateDefaultUpdatedAt == nil {
+ return fmt.Errorf("ent: uninitialized ssobinding.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
+ }
+ v := ssobinding.UpdateDefaultUpdatedAt()
+ sbu.mutation.SetUpdatedAt(v)
+ }
+ return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (sbu *SsoBindingUpdate) check() error {
+ if v, ok := sbu.mutation.Provider(); ok {
+ if err := ssobinding.ProviderValidator(v); err != nil {
+ return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
+ }
+ }
+ if v, ok := sbu.mutation.Subject(); ok {
+ if err := ssobinding.SubjectValidator(v); err != nil {
+ return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
+ }
+ }
+ if _, ok := sbu.mutation.UserID(); sbu.mutation.UserCleared() && !ok {
+ return errors.New(`ent: clearing a required unique edge "SsoBinding.user"`)
+ }
+ return nil
+}
+
+func (sbu *SsoBindingUpdate) sqlSave(ctx context.Context) (n int, err error) {
+ if err := sbu.check(); err != nil {
+ return n, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
+ if ps := sbu.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := sbu.mutation.UpdatedAt(); ok {
+ _spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if value, ok := sbu.mutation.DeletedAt(); ok {
+ _spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
+ }
+ if sbu.mutation.DeletedAtCleared() {
+ _spec.ClearField(ssobinding.FieldDeletedAt, field.TypeTime)
+ }
+ if value, ok := sbu.mutation.Provider(); ok {
+ _spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
+ }
+ if value, ok := sbu.mutation.Subject(); ok {
+ _spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
+ }
+ if sbu.mutation.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: ssobinding.UserTable,
+ Columns: []string{ssobinding.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := sbu.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: ssobinding.UserTable,
+ Columns: []string{ssobinding.UserColumn},
+ 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, sbu.driver, _spec); err != nil {
+ if _, ok := err.(*sqlgraph.NotFoundError); ok {
+ err = &NotFoundError{ssobinding.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return 0, err
+ }
+ sbu.mutation.done = true
+ return n, nil
+}
+
+// SsoBindingUpdateOne is the builder for updating a single SsoBinding entity.
+type SsoBindingUpdateOne struct {
+ config
+ fields []string
+ hooks []Hook
+ mutation *SsoBindingMutation
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (sbuo *SsoBindingUpdateOne) SetUpdatedAt(t time.Time) *SsoBindingUpdateOne {
+ sbuo.mutation.SetUpdatedAt(t)
+ return sbuo
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (sbuo *SsoBindingUpdateOne) SetDeletedAt(t time.Time) *SsoBindingUpdateOne {
+ sbuo.mutation.SetDeletedAt(t)
+ return sbuo
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (sbuo *SsoBindingUpdateOne) SetNillableDeletedAt(t *time.Time) *SsoBindingUpdateOne {
+ if t != nil {
+ sbuo.SetDeletedAt(*t)
+ }
+ return sbuo
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (sbuo *SsoBindingUpdateOne) ClearDeletedAt() *SsoBindingUpdateOne {
+ sbuo.mutation.ClearDeletedAt()
+ return sbuo
+}
+
+// SetProvider sets the "provider" field.
+func (sbuo *SsoBindingUpdateOne) SetProvider(s string) *SsoBindingUpdateOne {
+ sbuo.mutation.SetProvider(s)
+ return sbuo
+}
+
+// SetNillableProvider sets the "provider" field if the given value is not nil.
+func (sbuo *SsoBindingUpdateOne) SetNillableProvider(s *string) *SsoBindingUpdateOne {
+ if s != nil {
+ sbuo.SetProvider(*s)
+ }
+ return sbuo
+}
+
+// SetSubject sets the "subject" field.
+func (sbuo *SsoBindingUpdateOne) SetSubject(s string) *SsoBindingUpdateOne {
+ sbuo.mutation.SetSubject(s)
+ return sbuo
+}
+
+// SetNillableSubject sets the "subject" field if the given value is not nil.
+func (sbuo *SsoBindingUpdateOne) SetNillableSubject(s *string) *SsoBindingUpdateOne {
+ if s != nil {
+ sbuo.SetSubject(*s)
+ }
+ return sbuo
+}
+
+// SetUserID sets the "user_id" field.
+func (sbuo *SsoBindingUpdateOne) SetUserID(i int) *SsoBindingUpdateOne {
+ sbuo.mutation.SetUserID(i)
+ return sbuo
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (sbuo *SsoBindingUpdateOne) SetNillableUserID(i *int) *SsoBindingUpdateOne {
+ if i != nil {
+ sbuo.SetUserID(*i)
+ }
+ return sbuo
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (sbuo *SsoBindingUpdateOne) SetUser(u *User) *SsoBindingUpdateOne {
+ return sbuo.SetUserID(u.ID)
+}
+
+// Mutation returns the SsoBindingMutation object of the builder.
+func (sbuo *SsoBindingUpdateOne) Mutation() *SsoBindingMutation {
+ return sbuo.mutation
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (sbuo *SsoBindingUpdateOne) ClearUser() *SsoBindingUpdateOne {
+ sbuo.mutation.ClearUser()
+ return sbuo
+}
+
+// Where appends a list predicates to the SsoBindingUpdate builder.
+func (sbuo *SsoBindingUpdateOne) Where(ps ...predicate.SsoBinding) *SsoBindingUpdateOne {
+ sbuo.mutation.Where(ps...)
+ return sbuo
+}
+
+// Select allows selecting one or more fields (columns) of the returned entity.
+// The default is selecting all fields defined in the entity schema.
+func (sbuo *SsoBindingUpdateOne) Select(field string, fields ...string) *SsoBindingUpdateOne {
+ sbuo.fields = append([]string{field}, fields...)
+ return sbuo
+}
+
+// Save executes the query and returns the updated SsoBinding entity.
+func (sbuo *SsoBindingUpdateOne) Save(ctx context.Context) (*SsoBinding, error) {
+ if err := sbuo.defaults(); err != nil {
+ return nil, err
+ }
+ return withHooks(ctx, sbuo.sqlSave, sbuo.mutation, sbuo.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (sbuo *SsoBindingUpdateOne) SaveX(ctx context.Context) *SsoBinding {
+ node, err := sbuo.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// Exec executes the query on the entity.
+func (sbuo *SsoBindingUpdateOne) Exec(ctx context.Context) error {
+ _, err := sbuo.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (sbuo *SsoBindingUpdateOne) ExecX(ctx context.Context) {
+ if err := sbuo.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (sbuo *SsoBindingUpdateOne) defaults() error {
+ if _, ok := sbuo.mutation.UpdatedAt(); !ok {
+ if ssobinding.UpdateDefaultUpdatedAt == nil {
+ return fmt.Errorf("ent: uninitialized ssobinding.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
+ }
+ v := ssobinding.UpdateDefaultUpdatedAt()
+ sbuo.mutation.SetUpdatedAt(v)
+ }
+ return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (sbuo *SsoBindingUpdateOne) check() error {
+ if v, ok := sbuo.mutation.Provider(); ok {
+ if err := ssobinding.ProviderValidator(v); err != nil {
+ return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
+ }
+ }
+ if v, ok := sbuo.mutation.Subject(); ok {
+ if err := ssobinding.SubjectValidator(v); err != nil {
+ return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
+ }
+ }
+ if _, ok := sbuo.mutation.UserID(); sbuo.mutation.UserCleared() && !ok {
+ return errors.New(`ent: clearing a required unique edge "SsoBinding.user"`)
+ }
+ return nil
+}
+
+func (sbuo *SsoBindingUpdateOne) sqlSave(ctx context.Context) (_node *SsoBinding, err error) {
+ if err := sbuo.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
+ id, ok := sbuo.mutation.ID()
+ if !ok {
+ return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SsoBinding.id" for update`)}
+ }
+ _spec.Node.ID.Value = id
+ if fields := sbuo.fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, ssobinding.FieldID)
+ for _, f := range fields {
+ if !ssobinding.ValidColumn(f) {
+ return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ if f != ssobinding.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, f)
+ }
+ }
+ }
+ if ps := sbuo.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := sbuo.mutation.UpdatedAt(); ok {
+ _spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if value, ok := sbuo.mutation.DeletedAt(); ok {
+ _spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
+ }
+ if sbuo.mutation.DeletedAtCleared() {
+ _spec.ClearField(ssobinding.FieldDeletedAt, field.TypeTime)
+ }
+ if value, ok := sbuo.mutation.Provider(); ok {
+ _spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
+ }
+ if value, ok := sbuo.mutation.Subject(); ok {
+ _spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
+ }
+ if sbuo.mutation.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: ssobinding.UserTable,
+ Columns: []string{ssobinding.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := sbuo.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: ssobinding.UserTable,
+ Columns: []string{ssobinding.UserColumn},
+ 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 = &SsoBinding{config: sbuo.config}
+ _spec.Assign = _node.assignValues
+ _spec.ScanValues = _node.scanValues
+ if err = sqlgraph.UpdateNode(ctx, sbuo.driver, _spec); err != nil {
+ if _, ok := err.(*sqlgraph.NotFoundError); ok {
+ err = &NotFoundError{ssobinding.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ sbuo.mutation.done = true
+ return _node, nil
+}
diff --git a/ent/tx.go b/ent/tx.go
index 02120c97..1755047a 100644
--- a/ent/tx.go
+++ b/ent/tx.go
@@ -56,6 +56,8 @@ type Tx struct {
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders.
Sku *SkuClient
+ // SsoBinding is the client for interacting with the SsoBinding builders.
+ SsoBinding *SsoBindingClient
// StoragePolicy is the client for interacting with the StoragePolicy builders.
StoragePolicy *StoragePolicyClient
// Task is the client for interacting with the Task builders.
@@ -216,6 +218,7 @@ func (tx *Tx) init() {
tx.Share = NewShareClient(tx.config)
tx.SharePurchase = NewSharePurchaseClient(tx.config)
tx.Sku = NewSkuClient(tx.config)
+ tx.SsoBinding = NewSsoBindingClient(tx.config)
tx.StoragePolicy = NewStoragePolicyClient(tx.config)
tx.Task = NewTaskClient(tx.config)
tx.User = NewUserClient(tx.config)
diff --git a/ent/user.go b/ent/user.go
index 03734db4..2e85db1e 100644
--- a/ent/user.go
+++ b/ent/user.go
@@ -86,9 +86,11 @@ type UserEdges struct {
Grants []*UserGrant `json:"grants,omitempty"`
// SharePurchases holds the value of the share_purchases edge.
SharePurchases []*SharePurchase `json:"share_purchases,omitempty"`
+ // SSOBindings holds the value of the sso_bindings edge.
+ SSOBindings []*SsoBinding `json:"sso_bindings,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
- loadedTypes [13]bool
+ loadedTypes [14]bool
}
// GroupOrErr returns the Group value or an error if the edge
@@ -212,6 +214,15 @@ func (e UserEdges) SharePurchasesOrErr() ([]*SharePurchase, error) {
return nil, &NotLoadedError{edge: "share_purchases"}
}
+// SSOBindingsOrErr returns the SSOBindings value or an error if the edge
+// was not loaded in eager-loading.
+func (e UserEdges) SSOBindingsOrErr() ([]*SsoBinding, error) {
+ if e.loadedTypes[13] {
+ return e.SSOBindings, nil
+ }
+ return nil, &NotLoadedError{edge: "sso_bindings"}
+}
+
// scanValues returns the types for scanning values from sql.Rows.
func (*User) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@@ -425,6 +436,11 @@ func (u *User) QuerySharePurchases() *SharePurchaseQuery {
return NewUserClient(u.config).QuerySharePurchases(u)
}
+// QuerySSOBindings queries the "sso_bindings" edge of the User entity.
+func (u *User) QuerySSOBindings() *SsoBindingQuery {
+ return NewUserClient(u.config).QuerySSOBindings(u)
+}
+
// Update returns a builder for updating 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.
@@ -581,5 +597,11 @@ func (e *User) SetSharePurchases(v []*SharePurchase) {
e.Edges.loadedTypes[12] = true
}
+// SetSSOBindings manually set the edge as loaded state.
+func (e *User) SetSSOBindings(v []*SsoBinding) {
+ e.Edges.SSOBindings = v
+ e.Edges.loadedTypes[13] = true
+}
+
// Users is a parsable slice of User.
type Users []*User
diff --git a/ent/user/user.go b/ent/user/user.go
index bb88d66e..2b35f980 100644
--- a/ent/user/user.go
+++ b/ent/user/user.go
@@ -75,6 +75,8 @@ const (
EdgeGrants = "grants"
// EdgeSharePurchases holds the string denoting the share_purchases edge name in mutations.
EdgeSharePurchases = "share_purchases"
+ // EdgeSSOBindings holds the string denoting the sso_bindings edge name in mutations.
+ EdgeSSOBindings = "sso_bindings"
// Table holds the table name of the user in the database.
Table = "users"
// GroupTable is the table that holds the group relation/edge.
@@ -168,6 +170,13 @@ const (
SharePurchasesInverseTable = "share_purchases"
// SharePurchasesColumn is the table column denoting the share_purchases relation/edge.
SharePurchasesColumn = "buyer_id"
+ // SSOBindingsTable is the table that holds the sso_bindings relation/edge.
+ SSOBindingsTable = "sso_bindings"
+ // SSOBindingsInverseTable is the table name for the SsoBinding entity.
+ // It exists in this package in order to avoid circular dependency with the "ssobinding" package.
+ SSOBindingsInverseTable = "sso_bindings"
+ // SSOBindingsColumn is the table column denoting the sso_bindings relation/edge.
+ SSOBindingsColumn = "user_id"
)
// Columns holds all SQL columns for user fields.
@@ -512,6 +521,20 @@ func BySharePurchases(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newSharePurchasesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
+
+// BySSOBindingsCount orders the results by sso_bindings count.
+func BySSOBindingsCount(opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborsCount(s, newSSOBindingsStep(), opts...)
+ }
+}
+
+// BySSOBindings orders the results by sso_bindings terms.
+func BySSOBindings(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newSSOBindingsStep(), append([]sql.OrderTerm{term}, terms...)...)
+ }
+}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
@@ -603,3 +626,10 @@ func newSharePurchasesStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, SharePurchasesTable, SharePurchasesColumn),
)
}
+func newSSOBindingsStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(SSOBindingsInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, SSOBindingsTable, SSOBindingsColumn),
+ )
+}
diff --git a/ent/user/where.go b/ent/user/where.go
index f908fc65..6f1962bf 100644
--- a/ent/user/where.go
+++ b/ent/user/where.go
@@ -1214,6 +1214,29 @@ func HasSharePurchasesWith(preds ...predicate.SharePurchase) predicate.User {
})
}
+// HasSSOBindings applies the HasEdge predicate on the "sso_bindings" edge.
+func HasSSOBindings() predicate.User {
+ return predicate.User(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, SSOBindingsTable, SSOBindingsColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasSSOBindingsWith applies the HasEdge predicate on the "sso_bindings" edge with a given conditions (other predicates).
+func HasSSOBindingsWith(preds ...predicate.SsoBinding) predicate.User {
+ return predicate.User(func(s *sql.Selector) {
+ step := newSSOBindingsStep()
+ 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.User) predicate.User {
return predicate.User(sql.AndPredicates(predicates...))
diff --git a/ent/user_create.go b/ent/user_create.go
index 76c00469..b55c14b3 100644
--- a/ent/user_create.go
+++ b/ent/user_create.go
@@ -22,6 +22,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
+ "github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@@ -419,6 +420,21 @@ func (uc *UserCreate) AddSharePurchases(s ...*SharePurchase) *UserCreate {
return uc.AddSharePurchaseIDs(ids...)
}
+// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
+func (uc *UserCreate) AddSSOBindingIDs(ids ...int) *UserCreate {
+ uc.mutation.AddSSOBindingIDs(ids...)
+ return uc
+}
+
+// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
+func (uc *UserCreate) AddSSOBindings(s ...*SsoBinding) *UserCreate {
+ ids := make([]int, len(s))
+ for i := range s {
+ ids[i] = s[i].ID
+ }
+ return uc.AddSSOBindingIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (uc *UserCreate) Mutation() *UserMutation {
return uc.mutation
@@ -836,6 +852,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
+ if nodes := uc.mutation.SSOBindingsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges = append(_spec.Edges, edge)
+ }
return _node, _spec
}
diff --git a/ent/user_query.go b/ent/user_query.go
index a6c45738..d00aaff9 100644
--- a/ent/user_query.go
+++ b/ent/user_query.go
@@ -23,6 +23,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@@ -48,6 +49,7 @@ type UserQuery struct {
withRedeemedCodes *GiftCodeQuery
withGrants *UserGrantQuery
withSharePurchases *SharePurchaseQuery
+ withSSOBindings *SsoBindingQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -370,6 +372,28 @@ func (uq *UserQuery) QuerySharePurchases() *SharePurchaseQuery {
return query
}
+// QuerySSOBindings chains the current query on the "sso_bindings" edge.
+func (uq *UserQuery) QuerySSOBindings() *SsoBindingQuery {
+ query := (&SsoBindingClient{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(ssobinding.Table, ssobinding.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, user.SSOBindingsTable, user.SSOBindingsColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
// First returns the first User entity from the query.
// Returns a *NotFoundError when no User was found.
func (uq *UserQuery) First(ctx context.Context) (*User, error) {
@@ -575,6 +599,7 @@ func (uq *UserQuery) Clone() *UserQuery {
withRedeemedCodes: uq.withRedeemedCodes.Clone(),
withGrants: uq.withGrants.Clone(),
withSharePurchases: uq.withSharePurchases.Clone(),
+ withSSOBindings: uq.withSSOBindings.Clone(),
// clone intermediate query.
sql: uq.sql.Clone(),
path: uq.path,
@@ -724,6 +749,17 @@ func (uq *UserQuery) WithSharePurchases(opts ...func(*SharePurchaseQuery)) *User
return uq
}
+// WithSSOBindings tells the query-builder to eager-load the nodes that are connected to
+// the "sso_bindings" edge. The optional arguments are used to configure the query builder of the edge.
+func (uq *UserQuery) WithSSOBindings(opts ...func(*SsoBindingQuery)) *UserQuery {
+ query := (&SsoBindingClient{config: uq.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ uq.withSSOBindings = query
+ return uq
+}
+
// 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.
//
@@ -802,7 +838,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
var (
nodes = []*User{}
_spec = uq.querySpec()
- loadedTypes = [13]bool{
+ loadedTypes = [14]bool{
uq.withGroup != nil,
uq.withFiles != nil,
uq.withDavAccounts != nil,
@@ -816,6 +852,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
uq.withRedeemedCodes != nil,
uq.withGrants != nil,
uq.withSharePurchases != nil,
+ uq.withSSOBindings != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
@@ -926,6 +963,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err
}
}
+ if query := uq.withSSOBindings; query != nil {
+ if err := uq.loadSSOBindings(ctx, query, nodes,
+ func(n *User) { n.Edges.SSOBindings = []*SsoBinding{} },
+ func(n *User, e *SsoBinding) { n.Edges.SSOBindings = append(n.Edges.SSOBindings, e) }); err != nil {
+ return nil, err
+ }
+ }
return nodes, nil
}
@@ -1319,6 +1363,36 @@ func (uq *UserQuery) loadSharePurchases(ctx context.Context, query *SharePurchas
}
return nil
}
+func (uq *UserQuery) loadSSOBindings(ctx context.Context, query *SsoBindingQuery, nodes []*User, init func(*User), assign func(*User, *SsoBinding)) 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(ssobinding.FieldUserID)
+ }
+ query.Where(predicate.SsoBinding(func(s *sql.Selector) {
+ s.Where(sql.InValues(s.C(user.SSOBindingsColumn), fks...))
+ }))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ fk := n.UserID
+ node, ok := nodeids[fk]
+ if !ok {
+ return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
+ }
+ assign(node, n)
+ }
+ return nil
+}
func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) {
_spec := uq.querySpec()
diff --git a/ent/user_update.go b/ent/user_update.go
index f395fdb6..fdb60337 100644
--- a/ent/user_update.go
+++ b/ent/user_update.go
@@ -23,6 +23,7 @@ import (
"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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@@ -489,6 +490,21 @@ func (uu *UserUpdate) AddSharePurchases(s ...*SharePurchase) *UserUpdate {
return uu.AddSharePurchaseIDs(ids...)
}
+// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
+func (uu *UserUpdate) AddSSOBindingIDs(ids ...int) *UserUpdate {
+ uu.mutation.AddSSOBindingIDs(ids...)
+ return uu
+}
+
+// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
+func (uu *UserUpdate) AddSSOBindings(s ...*SsoBinding) *UserUpdate {
+ ids := make([]int, len(s))
+ for i := range s {
+ ids[i] = s[i].ID
+ }
+ return uu.AddSSOBindingIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (uu *UserUpdate) Mutation() *UserMutation {
return uu.mutation
@@ -752,6 +768,27 @@ func (uu *UserUpdate) RemoveSharePurchases(s ...*SharePurchase) *UserUpdate {
return uu.RemoveSharePurchaseIDs(ids...)
}
+// ClearSSOBindings clears all "sso_bindings" edges to the SsoBinding entity.
+func (uu *UserUpdate) ClearSSOBindings() *UserUpdate {
+ uu.mutation.ClearSSOBindings()
+ return uu
+}
+
+// RemoveSSOBindingIDs removes the "sso_bindings" edge to SsoBinding entities by IDs.
+func (uu *UserUpdate) RemoveSSOBindingIDs(ids ...int) *UserUpdate {
+ uu.mutation.RemoveSSOBindingIDs(ids...)
+ return uu
+}
+
+// RemoveSSOBindings removes "sso_bindings" edges to SsoBinding entities.
+func (uu *UserUpdate) RemoveSSOBindings(s ...*SsoBinding) *UserUpdate {
+ ids := make([]int, len(s))
+ for i := range s {
+ ids[i] = s[i].ID
+ }
+ return uu.RemoveSSOBindingIDs(ids...)
+}
+
// Save executes the query and returns the number of nodes affected by the update operation.
func (uu *UserUpdate) Save(ctx context.Context) (int, error) {
if err := uu.defaults(); err != nil {
@@ -1470,6 +1507,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if uu.mutation.SSOBindingsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := uu.mutation.RemovedSSOBindingsIDs(); len(nodes) > 0 && !uu.mutation.SSOBindingsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.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.SSOBindingsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.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 _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{user.Label}
@@ -1937,6 +2019,21 @@ func (uuo *UserUpdateOne) AddSharePurchases(s ...*SharePurchase) *UserUpdateOne
return uuo.AddSharePurchaseIDs(ids...)
}
+// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
+func (uuo *UserUpdateOne) AddSSOBindingIDs(ids ...int) *UserUpdateOne {
+ uuo.mutation.AddSSOBindingIDs(ids...)
+ return uuo
+}
+
+// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
+func (uuo *UserUpdateOne) AddSSOBindings(s ...*SsoBinding) *UserUpdateOne {
+ ids := make([]int, len(s))
+ for i := range s {
+ ids[i] = s[i].ID
+ }
+ return uuo.AddSSOBindingIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (uuo *UserUpdateOne) Mutation() *UserMutation {
return uuo.mutation
@@ -2200,6 +2297,27 @@ func (uuo *UserUpdateOne) RemoveSharePurchases(s ...*SharePurchase) *UserUpdateO
return uuo.RemoveSharePurchaseIDs(ids...)
}
+// ClearSSOBindings clears all "sso_bindings" edges to the SsoBinding entity.
+func (uuo *UserUpdateOne) ClearSSOBindings() *UserUpdateOne {
+ uuo.mutation.ClearSSOBindings()
+ return uuo
+}
+
+// RemoveSSOBindingIDs removes the "sso_bindings" edge to SsoBinding entities by IDs.
+func (uuo *UserUpdateOne) RemoveSSOBindingIDs(ids ...int) *UserUpdateOne {
+ uuo.mutation.RemoveSSOBindingIDs(ids...)
+ return uuo
+}
+
+// RemoveSSOBindings removes "sso_bindings" edges to SsoBinding entities.
+func (uuo *UserUpdateOne) RemoveSSOBindings(s ...*SsoBinding) *UserUpdateOne {
+ ids := make([]int, len(s))
+ for i := range s {
+ ids[i] = s[i].ID
+ }
+ return uuo.RemoveSSOBindingIDs(ids...)
+}
+
// Where appends a list predicates to the UserUpdate builder.
func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
uuo.mutation.Where(ps...)
@@ -2948,6 +3066,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if uuo.mutation.SSOBindingsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := uuo.mutation.RemovedSSOBindingsIDs(); len(nodes) > 0 && !uuo.mutation.SSOBindingsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.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.SSOBindingsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.SSOBindingsTable,
+ Columns: []string{user.SSOBindingsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(ssobinding.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}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json
index fb0fd61e..567338fe 100644
--- a/frontend/public/locales/en-US/application.json
+++ b/frontend/public/locales/en-US/application.json
@@ -894,6 +894,12 @@
"oauthGrantLastUsed": "Last used at <0>0>",
"revokeOAuthGrant": "Revoke Authorization",
"revokeOAuthGrantConfirm": "Are you sure you want to revoke this app's authorization? Once revoked, this app will no longer be able to access your account.",
+ "linkedAccounts": "Linked accounts",
+ "providerQQ": "QQ",
+ "linkedAt": "Linked at <0>0>",
+ "unlinkAccount": "Unlink",
+ "unlinkAccountConfirm": "Are you sure you want to unlink this account? You will no longer be able to sign in with it.",
+ "linkQQAccount": "Link QQ account",
"nickNameDes": "This is your public display name. It can be your real name or a pseudonym.",
"changeEmail": "Change",
"changeEmailDes": "A confirmation link will be sent to the new address. Your current email ({{email}}) stays active until confirmed.",
diff --git a/frontend/public/locales/en-US/dashboard.json b/frontend/public/locales/en-US/dashboard.json
index 83b9e2ae..e9de254c 100644
--- a/frontend/public/locales/en-US/dashboard.json
+++ b/frontend/public/locales/en-US/dashboard.json
@@ -850,6 +850,14 @@
"ssoAutoRedirectDes": "Skip the login form and send visitors straight to the identity provider. Append <0>?nosso=10> to the login URL to reach the password form (e.g. for admin recovery).",
"ssoCallbackUrl": "Callback URL",
"ssoCallbackUrlDes": "Register this URL as the redirect/callback URL in your identity provider: <0>{{url}}0>",
+ "qqConnect": "QQ Connect",
+ "qqAppID": "App ID",
+ "qqAppIDDes": "The App ID of the website application created in the <0>QQ Connect console0> (connect.qq.com).",
+ "qqAppSecret": "App Key",
+ "qqCallbackUrl": "Callback URL",
+ "qqCallbackUrlDes": "Register this URL as the website callback domain/URL in the QQ Connect console: <0>{{url}}0>",
+ "qqRegisterEnabled": "Allow automatic registration",
+ "qqRegisterEnabledDes": "Automatically create a local account when a user signs in via QQ for the first time. QQ supplies no email address, so provisioned accounts use a synthetic @connect.qq.local address.",
"themeVisible": "Visible",
"shareDefaultPrivate": "Private share by default",
"shareDefaultPrivateDes": "New shares default to private (password protected). Users can override this in their personal settings.",
diff --git a/frontend/public/locales/zh-CN/application.json b/frontend/public/locales/zh-CN/application.json
index cf64d42f..3573afb5 100644
--- a/frontend/public/locales/zh-CN/application.json
+++ b/frontend/public/locales/zh-CN/application.json
@@ -894,6 +894,12 @@
"oauthGrantLastUsed": "上次使用于 <0>0>",
"revokeOAuthGrant": "撤销授权",
"revokeOAuthGrantConfirm": "确定要撤销此应用的授权吗?撤销后,该应用将无法再访问你的账户。",
+ "linkedAccounts": "已绑定账号",
+ "providerQQ": "QQ",
+ "linkedAt": "绑定于 <0>0>",
+ "unlinkAccount": "解绑",
+ "unlinkAccountConfirm": "确定要解绑此账号吗?解绑后将无法再使用该账号登录。",
+ "linkQQAccount": "绑定 QQ 账号",
"nickNameDes": "用于公开展示的名字,可使用真实姓名或昵称",
"changeEmail": "更换",
"changeEmailDes": "确认链接将发送到新邮箱。在确认之前,当前邮箱({{email}})仍然有效。",
diff --git a/frontend/public/locales/zh-CN/dashboard.json b/frontend/public/locales/zh-CN/dashboard.json
index 2f2477fb..755b38dc 100644
--- a/frontend/public/locales/zh-CN/dashboard.json
+++ b/frontend/public/locales/zh-CN/dashboard.json
@@ -850,6 +850,14 @@
"ssoAutoRedirectDes": "跳过登录表单,直接跳转至身份提供商。在登录地址后附加 <0>?nosso=10> 可进入密码登录表单(例如管理员账户恢复)。",
"ssoCallbackUrl": "回调地址",
"ssoCallbackUrlDes": "请在身份提供方中将此 URL 注册为 Redirect/Callback URL:<0>{{url}}0>",
+ "qqConnect": "QQ 互联",
+ "qqAppID": "App ID",
+ "qqAppIDDes": "在 <0>QQ 互联开放平台0>(connect.qq.com)创建的网站应用的 App ID。",
+ "qqAppSecret": "App Key",
+ "qqCallbackUrl": "回调地址",
+ "qqCallbackUrlDes": "请在 QQ 互联控制台中将此 URL 注册为网站回调域/回调地址:<0>{{url}}0>",
+ "qqRegisterEnabled": "允许自动注册",
+ "qqRegisterEnabledDes": "用户首次通过 QQ 登录时自动创建本地账号。QQ 不提供邮箱,自动注册的账号使用 @connect.qq.local 合成邮箱。",
"themeVisible": "可见",
"shareDefaultPrivate": "默认私密分享",
"shareDefaultPrivateDes": "新建分享默认启用私密分享(密码保护)。用户仍可在个人设置中覆盖此默认值。",
diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts
index 71a32e44..4ec39493 100644
--- a/frontend/src/api/api.ts
+++ b/frontend/src/api/api.ts
@@ -2606,6 +2606,22 @@ export function sendRevokeOAuthGrant(grant_id: string): ThunkResponse {
};
}
+export function sendUnbindSso(provider: string): ThunkResponse {
+ return async (dispatch, _getState) => {
+ return await dispatch(
+ send(
+ `/user/setting/sso_binding/${provider}`,
+ {
+ method: "DELETE",
+ },
+ {
+ ...defaultOpts,
+ },
+ ),
+ );
+ };
+}
+
export function sendFullTextSearch(query: string, offset?: number): ThunkResponse {
const params = new URLSearchParams();
params.set("query", query);
diff --git a/frontend/src/api/site.ts b/frontend/src/api/site.ts
index e227484d..31feea24 100644
--- a/frontend/src/api/site.ts
+++ b/frontend/src/api/site.ts
@@ -32,6 +32,7 @@ export interface SiteConfig {
sso_enabled?: boolean;
sso_display_name?: string;
sso_auto_redirect?: boolean;
+ qq_connect_enabled?: boolean;
download_cdn_routes?: { name: string; url: string }[];
abuse_captcha?: boolean;
allow_select_node?: boolean;
diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts
index 563e050a..984c7371 100644
--- a/frontend/src/api/user.ts
+++ b/frontend/src/api/user.ts
@@ -150,6 +150,12 @@ export interface UserSettings {
trash_retention?: number;
preferred_policy?: string;
oauth_grants?: OAuthGrant[];
+ linked_accounts?: LinkedAccount[];
+}
+
+export interface LinkedAccount {
+ provider: string;
+ created_at: string;
}
export interface OAuthGrant {
diff --git a/frontend/src/component/Admin/Settings/Settings.tsx b/frontend/src/component/Admin/Settings/Settings.tsx
index 377632f1..f6389bb9 100644
--- a/frontend/src/component/Admin/Settings/Settings.tsx
+++ b/frontend/src/component/Admin/Settings/Settings.tsx
@@ -201,6 +201,10 @@ const Settings = () => {
"sso_scopes",
"sso_register_enabled",
"sso_auto_redirect",
+ "qq_connect_enabled",
+ "qq_connect_app_id",
+ "qq_connect_app_secret",
+ "qq_connect_register_enabled",
"email_filter_mode",
"email_filter_list",
"email_disable_subaddress",
diff --git a/frontend/src/component/Admin/Settings/UserSession/QQConnectSettings.tsx b/frontend/src/component/Admin/Settings/UserSession/QQConnectSettings.tsx
new file mode 100644
index 00000000..b90f2c0a
--- /dev/null
+++ b/frontend/src/component/Admin/Settings/UserSession/QQConnectSettings.tsx
@@ -0,0 +1,104 @@
+import { ExpandMoreRounded } from "@mui/icons-material";
+import { AccordionDetails, FormControl, FormControlLabel, Switch, Typography } from "@mui/material";
+import { useContext, useMemo } from "react";
+import { Trans, useTranslation } from "react-i18next";
+import { isTrueVal } from "../../../../session/utils.ts";
+import { Code } from "../../../Common/Code.tsx";
+import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx";
+import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
+import { SettingContext } from "../SettingWrapper.tsx";
+import { AccordionSummary, StyledAccordion } from "./SSOSettings.tsx";
+
+const QQConnectSettings = () => {
+ const { t } = useTranslation("dashboard");
+ const { setSettings, values } = useContext(SettingContext);
+
+ const callbackURL = useMemo(() => {
+ const primary = (values.siteURL ?? "").split(",")[0]?.trim().replace(/\/+$/, "");
+ return primary ? `${primary}/api/v4/session/qq/callback` : "";
+ }, [values.siteURL]);
+
+ const enabled = isTrueVal(values.qq_connect_enabled);
+
+ return (
+
+ }>
+
+ setSettings({
+ qq_connect_enabled: e.target.checked ? "1" : "0",
+ })
+ }
+ onClick={(e) => e.stopPropagation()}
+ />
+ }
+ label={t("settings.qqConnect")}
+ />
+
+
+
+
+ setSettings({ qq_connect_app_id: e.target.value })}
+ required={enabled}
+ />
+
+ ]} />
+
+
+
+ setSettings({ qq_connect_app_secret: e.target.value })}
+ type="password"
+ placeholder={t("oauth.secretRedactedPlaceholder")}
+ />
+ {t("oauth.clientSecretDesExisting")}
+
+ {callbackURL && (
+
+
+
+ ]}
+ />
+
+
+ )}
+
+
+ setSettings({
+ qq_connect_register_enabled: e.target.checked ? "1" : "0",
+ })
+ }
+ />
+ }
+ label={{t("settings.qqRegisterEnabled")}}
+ />
+ {t("settings.qqRegisterEnabledDes")}
+
+
+
+
+ );
+};
+
+export default QQConnectSettings;
diff --git a/frontend/src/component/Admin/Settings/UserSession/UserSession.tsx b/frontend/src/component/Admin/Settings/UserSession/UserSession.tsx
index 01770ad2..5fc5698b 100644
--- a/frontend/src/component/Admin/Settings/UserSession/UserSession.tsx
+++ b/frontend/src/component/Admin/Settings/UserSession/UserSession.tsx
@@ -12,6 +12,7 @@ import GroupSelectionInput from "../../Common/GroupSelectionInput.tsx";
import SharesInput from "../../Common/SharesInput.tsx";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
import { SettingContext } from "../SettingWrapper.tsx";
+import QQConnectSettings from "./QQConnectSettings.tsx";
import SSOSettings from "./SSOSettings.tsx";
const UserSession = () => {
@@ -303,6 +304,9 @@ const UserSession = () => {
+
+
+
diff --git a/frontend/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx b/frontend/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx
index 71e342d3..2f6042bb 100644
--- a/frontend/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx
+++ b/frontend/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx
@@ -8,6 +8,7 @@ import { useQuery } from "../../../../util";
import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField.tsx";
import MailOutlined from "../../../Icons/MailOutlined.tsx";
import PasskeyLoginButton from "../Signin/PasskeyLoginButton.tsx";
+import QQLoginButton from "../Signin/QQLoginButton.tsx";
import SSOLoginButton from "../Signin/SSOLoginButton.tsx";
import { Control } from "../Signin/SignIn.tsx";
@@ -53,11 +54,13 @@ interface PhaseCollectEmailProps {
const PhaseCollectEmail = ({ email, setEmail, control, onOAuthPasskeyLogin }: PhaseCollectEmailProps) => {
const { t } = useTranslation();
const query = useQuery();
- const { register_enabled, authn, sso_enabled } = useAppSelector((state) => state.siteConfig.login.config);
+ const { register_enabled, authn, sso_enabled, qq_connect_enabled } = useAppSelector(
+ (state) => state.siteConfig.login.config,
+ );
const tos = useAppSelector((state) => state.siteConfig.login.config.tos_url);
const privacyPolicy = useAppSelector((state) => state.siteConfig.login.config.privacy_policy_url);
- const showFooter = tos || privacyPolicy || authn || sso_enabled;
+ const showFooter = tos || privacyPolicy || authn || sso_enabled || qq_connect_enabled;
useEffect(() => {
if (!!query.get("email")) {
@@ -101,6 +104,7 @@ const PhaseCollectEmail = ({ email, setEmail, control, onOAuthPasskeyLogin }: Ph
{authn && }
+
>
diff --git a/frontend/src/component/Pages/Login/Signin/QQLoginButton.tsx b/frontend/src/component/Pages/Login/Signin/QQLoginButton.tsx
new file mode 100644
index 00000000..750cf9cd
--- /dev/null
+++ b/frontend/src/component/Pages/Login/Signin/QQLoginButton.tsx
@@ -0,0 +1,31 @@
+import { Icon } from "@iconify/react";
+import { Button, ButtonProps } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { ApiPrefix } from "../../../../api/request.ts";
+import { useAppSelector } from "../../../../redux/hooks.ts";
+import { useQuery } from "../../../../util";
+
+export default function QQLoginButton(props: ButtonProps) {
+ const { t } = useTranslation();
+ const query = useQuery();
+ const { qq_connect_enabled } = useAppSelector((state) => state.siteConfig.login.config);
+
+ if (!qq_connect_enabled) {
+ return null;
+ }
+
+ const startLogin = () => {
+ const redirect = query.get("redirect");
+ const target = new URL(ApiPrefix + "/session/qq/login", window.location.origin);
+ if (redirect) {
+ target.searchParams.set("redirect", redirect);
+ }
+ window.location.assign(target.toString());
+ };
+
+ return (
+ } onClick={startLogin} {...props}>
+ {t("login.signInWith", { name: "QQ" })}
+
+ );
+}
diff --git a/frontend/src/component/Pages/Setting/Security/SecuritySetting.tsx b/frontend/src/component/Pages/Setting/Security/SecuritySetting.tsx
index d2203bff..760b9686 100644
--- a/frontend/src/component/Pages/Setting/Security/SecuritySetting.tsx
+++ b/frontend/src/component/Pages/Setting/Security/SecuritySetting.tsx
@@ -17,8 +17,8 @@ import { useSnackbar } from "notistack";
import React, { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
-import { sendRevokeOAuthGrant, sendUpdateUserSetting } from "../../../../api/api.ts";
-import { OAuthGrant, Passkey, UserSettings } from "../../../../api/user.ts";
+import { sendRevokeOAuthGrant, sendUnbindSso, sendUpdateUserSetting } from "../../../../api/api.ts";
+import { LinkedAccount, OAuthGrant, Passkey, UserSettings } from "../../../../api/user.ts";
import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts";
import { confirmOperation } from "../../../../redux/thunks/dialog.ts";
import SessionManager from "../../../../session";
@@ -161,6 +161,75 @@ const OAuthGrantItem = ({ grant, onRevoked }: { grant: OAuthGrant; onRevoked: (c
);
};
+const linkedAccountProviderName = (t: (key: string) => string, provider: string): string => {
+ switch (provider) {
+ case "qq":
+ return t("setting.providerQQ");
+ default:
+ return provider;
+ }
+};
+
+const LinkedAccountItem = ({
+ account,
+ onUnlinked,
+}: {
+ account: LinkedAccount;
+ onUnlinked: (provider: string) => void;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const dispatch = useAppDispatch();
+ const [loading, setLoading] = useState(false);
+
+ const onUnbind = () => {
+ dispatch(confirmOperation(t("setting.unlinkAccountConfirm"))).then(() => {
+ setLoading(true);
+ dispatch(sendUnbindSso(account.provider))
+ .then(() => {
+ onUnlinked(account.provider);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ });
+ };
+
+ return (
+
+
+
+ {account.provider === "qq" ? : }
+
+
+ ]}
+ />
+ }
+ />
+
+ }
+ color={"error"}
+ >
+ {t("setting.unlinkAccount")}
+
+
+
+ );
+};
+
const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
const { t } = useTranslation();
const theme = useTheme();
@@ -170,6 +239,7 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
const navigate = useNavigate();
const authEnabled = useAppSelector((s) => s.siteConfig.login.config.authn);
+ const qqConnectEnabled = useAppSelector((s) => s.siteConfig.login.config.qq_connect_enabled);
const resetPwdFormRef = React.createRef();
const [showResetPassword, setShowResetPassword] = useState(false);
@@ -248,6 +318,13 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
});
};
+ const onAccountUnlinked = (provider: string) => {
+ setSetting({
+ ...setting,
+ linked_accounts: setting.linked_accounts?.filter((x) => x.provider != provider),
+ });
+ };
+
return (
@@ -353,6 +430,27 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
)}
+ {(qqConnectEnabled || (setting.linked_accounts && setting.linked_accounts.length > 0)) && (
+
+
+ {setting.linked_accounts?.map((account) => (
+
+ ))}
+
+ {qqConnectEnabled && !setting.linked_accounts?.some((a) => a.provider === "qq") && (
+ }
+ onClick={() => {
+ window.location.href = "/api/v4/session/qq/login?link=1";
+ }}
+ >
+ {t("setting.linkQQAccount")}
+
+ )}
+
+ )}
setEnable2FAOpen(false)} on2FAEnabled={on2FAChange(true)} />
EmailFilterBlacklist {
diff --git a/pkg/setting/types.go b/pkg/setting/types.go
index b3191df3..03cb2a59 100644
--- a/pkg/setting/types.go
+++ b/pkg/setting/types.go
@@ -85,6 +85,16 @@ type SSO struct {
AutoRedirect bool
}
+// QQConnect holds the QQ互联 (connect.qq.com) OAuth2 application config.
+// Unlike the generic OIDC consumer, QQ Connect has no discovery document or
+// id_token; identity resolves through /oauth2.0/me openid.
+type QQConnect struct {
+ Enabled bool
+ AppID string
+ AppSecret string
+ RegisterEnabled bool
+}
+
type EmailFilterMode int
const (
diff --git a/routers/controllers/user.go b/routers/controllers/user.go
index ebc79d13..cac9b6aa 100644
--- a/routers/controllers/user.go
+++ b/routers/controllers/user.go
@@ -358,6 +358,30 @@ func UserSSOCallback(c *gin.Context) {
service.SSOCallback(c)
}
+// UserQQLogin redirects the browser to the QQ Connect authorization page.
+func UserQQLogin(c *gin.Context) {
+ service := ParametersFromContext[*user.QQLoginService](c, user.QQLoginParameterCtx{})
+ service.Login(c)
+}
+
+// UserQQCallback completes the QQ Connect flow and redirects either to the
+// SPA ticket handoff or, for link mode, back to the security settings tab.
+func UserQQCallback(c *gin.Context) {
+ service := ParametersFromContext[*user.QQCallbackService](c, user.QQCallbackParameterCtx{})
+ service.Callback(c)
+}
+
+// UserUnbindSso removes the caller's external-account binding at a provider.
+func UserUnbindSso(c *gin.Context) {
+ service := ParametersFromContext[*user.SsoUnbindService](c, user.SsoUnbindParameterCtx{})
+ err := service.Delete(c)
+ if respondErr(c, err) {
+ return
+ }
+
+ c.JSON(200, serializer.Response{})
+}
+
// UserSSOExchange trades the one-time ticket for a session token pair.
func UserSSOExchange(c *gin.Context) {
service := ParametersFromContext[*user.SSOExchangeService](c, user.SSOExchangeParameterCtx{})
diff --git a/routers/router.go b/routers/router.go
index 293cbe45..695dad86 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -342,6 +342,19 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
)
}
+ // QQ Connect (non-OIDC OAuth2 provider)
+ qqRouter := session.Group("qq")
+ {
+ qqRouter.GET("login",
+ controllers.FromQuery[usersvc.QQLoginService](usersvc.QQLoginParameterCtx{}),
+ controllers.UserQQLogin,
+ )
+ qqRouter.GET("callback",
+ controllers.FromQuery[usersvc.QQCallbackService](usersvc.QQCallbackParameterCtx{}),
+ controllers.UserQQCallback,
+ )
+ }
+
oauthRouter := session.Group("oauth")
{
oauthRouter.GET("app/:app_id",
@@ -1520,6 +1533,12 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
controllers.FromJSON[usersvc.RequestEmailChangeService](usersvc.RequestEmailChangeParamCtx{}),
controllers.UserRequestEmailChange,
)
+ // 解除外部账号绑定(QQ Connect 等)
+ setting.DELETE("sso_binding/:provider",
+ middleware.RequiredScopes(types.ScopeUserSecurityInfoWrite),
+ controllers.FromUri[usersvc.SsoUnbindService](usersvc.SsoUnbindParameterCtx{}),
+ controllers.UserUnbindSso,
+ )
}
// 积分与兑换
diff --git a/routers/router_test.go b/routers/router_test.go
index 5c0b6408..fbcff16e 100644
--- a/routers/router_test.go
+++ b/routers/router_test.go
@@ -52,6 +52,9 @@ func TestMasterRouteWiring(t *testing.T) {
"PUT /api/v4/file/upload",
"POST /api/v4/file/upload/:sessionId/:index",
"POST /api/v4/share/purchase/:id",
+ "GET /api/v4/session/qq/login",
+ "GET /api/v4/session/qq/callback",
+ "DELETE /api/v4/user/setting/sso_binding/:provider",
"GET /f/:id/:name",
}
for _, e := range expected {
diff --git a/service/basic/site.go b/service/basic/site.go
index 2d58e4f5..827299b1 100644
--- a/service/basic/site.go
+++ b/service/basic/site.go
@@ -52,6 +52,7 @@ type SiteConfig struct {
SSOEnabled bool `json:"sso_enabled,omitempty"`
SSODisplayName string `json:"sso_display_name,omitempty"`
SSOAutoRedirect bool `json:"sso_auto_redirect,omitempty"`
+ QQConnectEnabled bool `json:"qq_connect_enabled,omitempty"`
// DownloadCDNRoutes exposes configured CDN mirror endpoints so clients
// can offer a download-route picker (#2987).
@@ -124,6 +125,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
case "login":
legalDocs := settings.LegalDocuments(c)
sso := settings.SSO(c)
+ qq := settings.QQConnect(c)
return &SiteConfig{
LoginCaptcha: settings.LoginCaptchaEnabled(c),
RegCaptcha: settings.RegCaptchaEnabled(c),
@@ -136,6 +138,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
SSOEnabled: sso.Enabled && sso.Issuer != "" && sso.ClientID != "",
SSODisplayName: sso.DisplayName,
SSOAutoRedirect: sso.AutoRedirect,
+ QQConnectEnabled: qq.Enabled && qq.AppID != "",
}, nil
case "explorer":
explorerSettings := settings.ExplorerFrontendSettings(c)
diff --git a/service/user/qq.go b/service/user/qq.go
new file mode 100644
index 00000000..c67db160
--- /dev/null
+++ b/service/user/qq.go
@@ -0,0 +1,435 @@
+package user
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/cloudreve/Cloudreve/v4/application/dependency"
+ "github.com/cloudreve/Cloudreve/v4/ent"
+ "github.com/cloudreve/Cloudreve/v4/ent/user"
+ "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/request"
+ "github.com/cloudreve/Cloudreve/v4/pkg/serializer"
+ "github.com/cloudreve/Cloudreve/v4/pkg/setting"
+ "github.com/gin-gonic/gin"
+)
+
+type (
+ // QQLoginParameterCtx marks the QQ Connect authorization start route.
+ QQLoginParameterCtx struct{}
+ // QQCallbackParameterCtx marks the QQ Connect redirect target.
+ QQCallbackParameterCtx struct{}
+ // SsoUnbindParameterCtx marks the external-binding removal route.
+ SsoUnbindParameterCtx struct{}
+
+ // QQLoginService starts the QQ Connect OAuth2 flow. `link=1` binds the
+ // QQ identity to the currently signed-in user instead of signing in.
+ QQLoginService struct {
+ Redirect string `form:"redirect"`
+ Link bool `form:"link"`
+ }
+
+ // QQCallbackService completes the QQ Connect flow.
+ QQCallbackService struct {
+ Code string `form:"code"`
+ State string `form:"state"`
+ }
+
+ // SsoUnbindService removes the caller's binding at a provider.
+ SsoUnbindService struct {
+ Provider string `uri:"provider" binding:"required"`
+ }
+)
+
+const (
+ qqAuthorizeEndpoint = "https://graph.qq.com/oauth2.0/authorize"
+ qqTokenEndpoint = "https://graph.qq.com/oauth2.0/token"
+ qqOpenIDEndpoint = "https://graph.qq.com/oauth2.0/me"
+ qqUserInfoEndpoint = "https://graph.qq.com/user/get_user_info"
+ // qqConnectMailDomain is the synthetic domain used for provisioned
+ // accounts; QQ exposes no email claim so a real address cannot exist.
+ qqConnectMailDomain = "connect.qq.local"
+)
+
+// qqOpenIDResponse is the JSONP payload of /oauth2.0/me.
+type qqOpenIDResponse struct {
+ ClientID string `json:"client_id"`
+ OpenID string `json:"openid"`
+}
+
+// qqUserInfoResponse is the profile document from get_user_info.
+type qqUserInfoResponse struct {
+ Ret int `json:"ret"`
+ Nickname string `json:"nickname"`
+}
+
+func validateQQConfig(qq *setting.QQConnect) error {
+ if !qq.Enabled || qq.AppID == "" || qq.AppSecret == "" {
+ return errors.New("qq connect not enabled or not configured")
+ }
+ return nil
+}
+
+func qqCallbackURL(settings setting.Provider, c *gin.Context) string {
+ return settings.SiteURL(c).ResolveReference(&url.URL{Path: "api/v4/session/qq/callback"}).String()
+}
+
+// QQLogin redirects the browser to the QQ authorization page.
+func (service *QQLoginService) Login(c *gin.Context) {
+ dep := dependency.FromContext(c)
+ settings := dep.SettingProvider()
+ qq := settings.QQConnect(c)
+
+ if err := validateQQConfig(qq); err != nil {
+ redirectToSigninWithError(c, settings, "sso_not_configured")
+ return
+ }
+
+ state := ssoState{
+ Redirect: sanitizeSSORedirect(service.Redirect),
+ }
+ if service.Link {
+ u := inventory.UserFromContext(c)
+ if inventory.IsAnonymousUser(u) {
+ redirectToSigninWithError(c, settings, "sso_state_failed")
+ return
+ }
+ state.LinkUserID = u.ID
+ }
+
+ stateKey := ssoStateKey()
+ if err := dep.KV().Set(stateKey, state, ssoStateTTL); err != nil {
+ dep.Logger().Warning("Failed to persist QQ state: %s", err)
+ redirectToSigninWithError(c, settings, "sso_state_failed")
+ return
+ }
+
+ authorize, _ := url.Parse(qqAuthorizeEndpoint)
+ q := authorize.Query()
+ q.Set("response_type", "code")
+ q.Set("client_id", qq.AppID)
+ q.Set("redirect_uri", qqCallbackURL(settings, c))
+ q.Set("state", stateKey)
+ q.Set("scope", "get_user_info")
+ authorize.RawQuery = q.Encode()
+
+ c.Redirect(http.StatusFound, authorize.String())
+}
+
+// QQCallback exchanges the code, resolves the QQ openid, then either binds
+// it to the linking user or signs the bound account in.
+func (service *QQCallbackService) Callback(c *gin.Context) {
+ dep := dependency.FromContext(c)
+ settings := dep.SettingProvider()
+
+ fail := func(code string) {
+ redirectToSigninWithError(c, settings, code)
+ }
+
+ qq := settings.QQConnect(c)
+ if err := validateQQConfig(qq); err != nil {
+ fail("sso_not_configured")
+ return
+ }
+ if service.State == "" || service.Code == "" {
+ fail("sso_invalid_response")
+ return
+ }
+
+ rawState, ok := dep.KV().Get(service.State)
+ if !ok {
+ fail("sso_state_expired")
+ return
+ }
+ _ = dep.KV().Delete("", service.State)
+ state, ok := rawState.(ssoState)
+ if !ok {
+ fail("sso_state_expired")
+ return
+ }
+
+ // The endpoints are constants, but DNS resolution is not: reject a
+ // graph.qq.com that resolves to a private address before issuing
+ // credentialed requests.
+ for _, endpoint := range []string{qqTokenEndpoint, qqOpenIDEndpoint, qqUserInfoEndpoint} {
+ if err := request.ValidateExternalURL(c, endpoint, request.SSRFOptions{}); err != nil {
+ dep.Logger().Warning("QQ endpoint rejected by SSRF check: %s", err)
+ fail("sso_exchange_failed")
+ return
+ }
+ }
+
+ httpClient := dep.RequestClient()
+ token, err := qqExchangeCode(c, httpClient, service.Code, qqCallbackURL(settings, c), qq)
+ if err != nil {
+ dep.Logger().Warning("QQ token exchange failed: %s", err)
+ fail("sso_exchange_failed")
+ return
+ }
+
+ openID, err := qqFetchOpenID(c, httpClient, token, qq.AppID)
+ if err != nil {
+ dep.Logger().Warning("QQ openid request failed: %s", err)
+ fail("sso_token_invalid")
+ return
+ }
+
+ bindings := dep.SsoBindingClient()
+
+ // Link mode: attach the openid to the signed-in user and return to the
+ // security settings tab.
+ if state.LinkUserID != 0 {
+ if _, err := bindings.Bind(c, state.LinkUserID, inventory.SsoProviderQQ, openID); err != nil {
+ dep.Logger().Info("QQ link rejected: %s", err)
+ if errors.Is(err, inventory.ErrSsoBindingConflict) {
+ fail("sso_account_unavailable")
+ return
+ }
+ fail("sso_state_failed")
+ return
+ }
+ recordUserEvent(c, dep, state.LinkUserID, types.EventLinkAccount, map[string]any{"provider": inventory.SsoProviderQQ})
+ dest := settings.SiteURL(c).ResolveReference(&url.URL{Path: "settings", RawQuery: "tab=security"})
+ c.Redirect(http.StatusFound, dest.String())
+ return
+ }
+
+ var targetUser *ent.User
+ binding, err := bindings.Get(c, inventory.SsoProviderQQ, openID)
+ switch {
+ case err == nil:
+ ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
+ targetUser, err = dep.UserClient().GetByID(ctx, binding.UserID)
+ if err != nil {
+ dep.Logger().Warning("QQ binding resolved to missing user %d: %s", binding.UserID, err)
+ fail("sso_account_unavailable")
+ return
+ }
+ case ent.IsNotFound(err):
+ if !qq.RegisterEnabled {
+ dep.Logger().Info("QQ login rejected: provisioning disabled")
+ fail("sso_account_unavailable")
+ return
+ }
+ targetUser, err = qqProvisionUser(c, dep, httpClient, token, openID, qq)
+ if err != nil {
+ dep.Logger().Warning("QQ provisioning failed: %s", err)
+ fail("sso_account_unavailable")
+ return
+ }
+ if _, err := bindings.Bind(c, targetUser.ID, inventory.SsoProviderQQ, openID); err != nil {
+ dep.Logger().Warning("QQ binding failed: %s", err)
+ fail("sso_state_failed")
+ return
+ }
+ default:
+ dep.Logger().Warning("QQ binding lookup failed: %s", err)
+ fail("sso_state_failed")
+ return
+ }
+
+ if targetUser, err = dep.UserClient().LiftExpiredBan(c, targetUser); err != nil {
+ fail("sso_account_unavailable")
+ return
+ }
+ if err := checkUserStatus(c, targetUser); err != nil {
+ dep.Logger().Info("QQ login rejected: %s", err)
+ fail("sso_account_unavailable")
+ return
+ }
+
+ ticket := ssoTicketKey()
+ if err := dep.KV().Set(ticket, targetUser.ID, ssoTicketTTL); err != nil {
+ fail("sso_state_failed")
+ return
+ }
+
+ callback := settings.SiteURL(c).ResolveReference(&url.URL{Path: "callback/sso"})
+ q := callback.Query()
+ q.Set("ticket", ticket)
+ if state.Redirect != "" {
+ q.Set("redirect", state.Redirect)
+ }
+ callback.RawQuery = q.Encode()
+ c.Redirect(http.StatusFound, callback.String())
+}
+
+// Delete removes the caller's binding at the given provider. Removing the
+// last viable sign-in method (no password, no passkey, no other binding)
+// is refused so an SSO-provisioned account cannot lock itself out.
+func (service *SsoUnbindService) Delete(c *gin.Context) error {
+ dep := dependency.FromContext(c)
+ u := inventory.UserFromContext(c)
+ if service.Provider != inventory.SsoProviderQQ {
+ return serializer.NewError(serializer.CodeParamErr, "Unknown provider", nil)
+ }
+
+ bindings := dep.SsoBindingClient()
+ if u.Password == "" {
+ passkeys, err := dep.UserClient().ListPasskeys(c, u.ID)
+ if err != nil {
+ return serializer.NewError(serializer.CodeDBError, "Failed to get user passkey", err)
+ }
+ others, err := bindings.ListByUser(c, u.ID)
+ if err != nil {
+ return serializer.NewError(serializer.CodeDBError, "Failed to get user linked accounts", err)
+ }
+ otherProviders := 0
+ for _, b := range others {
+ if b.Provider != service.Provider {
+ otherProviders++
+ }
+ }
+ if len(passkeys) == 0 && otherProviders == 0 {
+ return serializer.NewError(serializer.CodeParamErr, "Cannot unlink the only sign-in method", nil)
+ }
+ }
+
+ if err := bindings.Unbind(c, u.ID, service.Provider); err != nil {
+ return serializer.NewError(serializer.CodeDBError, "Failed to unlink account", err)
+ }
+ activity.Record(c, dep.SettingProvider(), dep.ActivityClient(), types.EventUnlinkAccount,
+ activity.Extra(map[string]any{"provider": service.Provider}))
+ return nil
+}
+
+// qqExchangeCode trades the authorization code for an access token. QQ
+// answers with a form-encoded body (access_token=...&expires_in=...), not
+// JSON.
+func qqExchangeCode(c *gin.Context, client request.Client, code, redirectURI string, qq *setting.QQConnect) (string, error) {
+ form := url.Values{
+ "grant_type": {"authorization_code"},
+ "client_id": {qq.AppID},
+ "client_secret": {qq.AppSecret},
+ "code": {code},
+ "redirect_uri": {redirectURI},
+ }
+
+ resp, err := client.
+ Request(http.MethodPost, qqTokenEndpoint, strings.NewReader(form.Encode()),
+ request.WithContext(c),
+ request.WithTimeout(15*time.Second),
+ request.WithHeader(http.Header{"Content-Type": {"application/x-www-form-urlencoded"}}),
+ ).
+ CheckHTTPResponse(http.StatusOK).
+ GetResponse()
+ if err != nil {
+ return "", fmt.Errorf("token request failed: %w", err)
+ }
+
+ return parseQQTokenResponse(resp)
+}
+
+// parseQQTokenResponse extracts the access token from QQ's form-encoded
+// token reply. Error responses arrive as JSONP callback({"error":...})
+// and fail the lookup naturally.
+func parseQQTokenResponse(body string) (string, error) {
+ if values, err := url.ParseQuery(strings.TrimSpace(body)); err == nil {
+ if token := values.Get("access_token"); token != "" {
+ return token, nil
+ }
+ }
+ return "", errors.New("unexpected token response")
+}
+
+// qqFetchOpenID unwraps the JSONP response of /oauth2.0/me and verifies the
+// echoed client_id to guard against token substitution.
+func qqFetchOpenID(c *gin.Context, client request.Client, accessToken, appID string) (string, error) {
+ endpoint, _ := url.Parse(qqOpenIDEndpoint)
+ q := endpoint.Query()
+ q.Set("access_token", accessToken)
+ endpoint.RawQuery = q.Encode()
+
+ resp, err := client.
+ Request(http.MethodGet, endpoint.String(), nil,
+ request.WithContext(c),
+ request.WithTimeout(10*time.Second),
+ ).
+ CheckHTTPResponse(http.StatusOK).
+ GetResponse()
+ if err != nil {
+ return "", fmt.Errorf("openid request failed: %w", err)
+ }
+
+ return parseQQOpenIDResponse(resp, appID)
+}
+
+// parseQQOpenIDResponse unwraps the JSONP reply of /oauth2.0/me and
+// verifies the echoed client_id to guard against token substitution.
+func parseQQOpenIDResponse(body, appID string) (string, error) {
+ body = strings.TrimSpace(body)
+ start := strings.Index(body, "(")
+ end := strings.LastIndex(body, ")")
+ if start < 0 || end <= start {
+ return "", errors.New("malformed openid response")
+ }
+
+ var payload qqOpenIDResponse
+ if err := json.Unmarshal([]byte(body[start+1:end]), &payload); err != nil {
+ return "", fmt.Errorf("malformed openid payload: %w", err)
+ }
+ if payload.ClientID != appID || payload.OpenID == "" {
+ return "", errors.New("openid client mismatch")
+ }
+ return payload.OpenID, nil
+}
+
+// qqProvisionUser creates the local account for a new QQ identity. QQ has
+// no email claim, so a synthetic address under connect.qq.local is used;
+// the nickname falls back to an openid suffix.
+func qqProvisionUser(c *gin.Context, dep dependency.Dep, client request.Client, accessToken, openID string, qq *setting.QQConnect) (*ent.User, error) {
+ nick := ""
+ endpoint, _ := url.Parse(qqUserInfoEndpoint)
+ q := endpoint.Query()
+ q.Set("access_token", accessToken)
+ q.Set("oauth_consumer_key", qq.AppID)
+ q.Set("openid", openID)
+ endpoint.RawQuery = q.Encode()
+
+ if resp, err := client.
+ Request(http.MethodGet, endpoint.String(), nil,
+ request.WithContext(c),
+ request.WithTimeout(10*time.Second),
+ ).
+ CheckHTTPResponse(http.StatusOK).
+ GetResponse(); err == nil {
+ var info qqUserInfoResponse
+ if err := json.Unmarshal([]byte(resp), &info); err == nil && info.Ret == 0 {
+ nick = strings.TrimSpace(info.Nickname)
+ }
+ } else {
+ dep.Logger().Warning("QQ userinfo request failed: %s", err)
+ }
+
+ if nick == "" {
+ nick = "QQ user " + openID[len(openID)-min(6, len(openID)):]
+ }
+ if len(nick) > 100 {
+ nick = nick[:100]
+ }
+
+ return dep.UserClient().Create(c, &inventory.NewUserArgs{
+ Email: fmt.Sprintf("qq_%s@%s", openID, qqConnectMailDomain),
+ Nick: nick,
+ Status: user.StatusActive,
+ GroupID: dep.SettingProvider().DefaultGroup(c),
+ })
+}
+
+// recordUserEvent logs an account event attributed to a user ID rather
+// than the request actor (link-mode callbacks run for the linking user).
+func recordUserEvent(c *gin.Context, dep dependency.Dep, uid int, event int, extra map[string]any) {
+ opts := []activity.Opt{activity.Actor(uid)}
+ if extra != nil {
+ opts = append(opts, activity.Extra(extra))
+ }
+ activity.Record(c, dep.SettingProvider(), dep.ActivityClient(), event, opts...)
+}
diff --git a/service/user/qq_test.go b/service/user/qq_test.go
new file mode 100644
index 00000000..bddc1331
--- /dev/null
+++ b/service/user/qq_test.go
@@ -0,0 +1,49 @@
+package user
+
+import (
+ "testing"
+
+ "github.com/cloudreve/Cloudreve/v4/pkg/setting"
+ "github.com/stretchr/testify/require"
+)
+
+func TestValidateQQConfig(t *testing.T) {
+ require.Error(t, validateQQConfig(&setting.QQConnect{}))
+ require.Error(t, validateQQConfig(&setting.QQConnect{Enabled: true}))
+ require.Error(t, validateQQConfig(&setting.QQConnect{Enabled: true, AppID: "id"}))
+ require.NoError(t, validateQQConfig(&setting.QQConnect{Enabled: true, AppID: "id", AppSecret: "secret"}))
+}
+
+func TestParseQQTokenResponse(t *testing.T) {
+ token, err := parseQQTokenResponse("access_token=ABC123&expires_in=7776000&refresh_token=DEF")
+ require.NoError(t, err)
+ require.Equal(t, "ABC123", token)
+
+ // JSONP error reply carries no access_token.
+ _, err = parseQQTokenResponse(`callback({"error":100016,"error_description":"app secret error"});`)
+ require.Error(t, err)
+
+ _, err = parseQQTokenResponse("")
+ require.Error(t, err)
+ _, err = parseQQTokenResponse("expires_in=7776000")
+ require.Error(t, err)
+}
+
+func TestParseQQOpenIDResponse(t *testing.T) {
+ openID, err := parseQQOpenIDResponse(`callback( {"client_id":"APPID","openid":"OPENIDXYZ"} );`, "APPID")
+ require.NoError(t, err)
+ require.Equal(t, "OPENIDXYZ", openID)
+
+ // A token minted for a different app must not be accepted.
+ _, err = parseQQOpenIDResponse(`callback( {"client_id":"OTHER","openid":"OPENIDXYZ"} );`, "APPID")
+ require.Error(t, err)
+
+ _, err = parseQQOpenIDResponse(`callback( {"client_id":"APPID"} );`, "APPID")
+ require.Error(t, err)
+ _, err = parseQQOpenIDResponse(`callback( not-json );`, "APPID")
+ require.Error(t, err)
+ _, err = parseQQOpenIDResponse(`{"client_id":"APPID","openid":"X"}`, "APPID")
+ require.Error(t, err)
+ _, err = parseQQOpenIDResponse("", "APPID")
+ require.Error(t, err)
+}
diff --git a/service/user/response.go b/service/user/response.go
index 415d114f..54f0bed0 100644
--- a/service/user/response.go
+++ b/service/user/response.go
@@ -37,9 +37,17 @@ type UserSettings struct {
TrashRetention int `json:"trash_retention,omitempty"`
PreferredPolicy string `json:"preferred_policy,omitempty"`
OAuthGrants []OauthGrant `json:"oauth_grants,omitempty"`
+ LinkedAccounts []LinkedAccount `json:"linked_accounts,omitempty"`
}
-func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser, grants []*ent.OAuthGrant) *UserSettings {
+// LinkedAccount is an external identity bound to the local account
+// (e.g. a QQ Connect openid). Subject is masked in the response.
+type LinkedAccount struct {
+ Provider string `json:"provider"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser, grants []*ent.OAuthGrant, bindings []*ent.SsoBinding) *UserSettings {
return &UserSettings{
VersionRetentionEnabled: u.Settings.VersionRetention,
VersionRetentionExt: u.Settings.VersionRetentionExt,
@@ -57,6 +65,9 @@ func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Pa
OAuthGrants: lo.Map(grants, func(item *ent.OAuthGrant, index int) OauthGrant {
return BuildOauthGrant(item)
}),
+ LinkedAccounts: lo.Map(bindings, func(item *ent.SsoBinding, index int) LinkedAccount {
+ return LinkedAccount{Provider: item.Provider, CreatedAt: item.CreatedAt}
+ }),
}
}
diff --git a/service/user/setting.go b/service/user/setting.go
index 3c4c4923..6cedfb9f 100644
--- a/service/user/setting.go
+++ b/service/user/setting.go
@@ -140,7 +140,12 @@ func GetUserSettings(c *gin.Context) (*UserSettings, error) {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user OAuth grants", err)
}
- res := BuildUserSettings(u, passkeys, dep.UAParser(), grants)
+ bindings, err := dep.SsoBindingClient().ListByUser(c, u.ID)
+ if err != nil {
+ return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user linked accounts", err)
+ }
+
+ res := BuildUserSettings(u, passkeys, dep.UAParser(), grants, bindings)
if u.Settings.PreferredPolicy > 0 {
res.PreferredPolicy = hashid.EncodePolicyID(dep.HashIDEncoder(), u.Settings.PreferredPolicy)
}
diff --git a/service/user/sso.go b/service/user/sso.go
index 4eebf411..4a80afe7 100644
--- a/service/user/sso.go
+++ b/service/user/sso.go
@@ -45,6 +45,9 @@ const (
type ssoState struct {
Nonce string
Redirect string
+ // LinkUserID is non-zero for account-link flows (QQ Connect): the
+ // resolved external identity binds to this user instead of signing in.
+ LinkUserID int
}
// SSOLoginService starts an inbound OIDC flow by redirecting the browser to