From fd86e28e878dc81a17c0b53da3e0877da9d050bf Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sun, 20 Sep 2026 02:07:03 +0200 Subject: [PATCH 1/2] feat: QQ Connect sign-in and external account linking Adds a second third-party sign-in provider alongside generic OIDC SSO: - sso_binding entity + inventory client: persistent (provider, subject) identity bindings, one binding per (user, provider), conflict-safe re-bind. - QQ Connect OAuth2 flow (graph.qq.com): authorize redirect, code exchange, JSONP openid resolution with client_id echo check, optional get_user_info nickname for provisioning. - link=1 mode binds the QQ identity to the signed-in user and returns to /settings?tab=security; login mode reuses the one-time SSO ticket handoff. Provisioning uses synthetic @connect.qq.local addresses and is gated by qq_connect_register_enabled. - DELETE /user/setting/sso_binding/:provider under ScopeUserSecurityInfoWrite, with a lockout guard refusing to remove the last viable sign-in method (no password, no passkey, no other binding). - Server-side single-use state in KV, SSRF validation of the provider endpoints, link/unlink activity events, qq_connect_app_secret in RedactedSettings. - Admin: QQ Connect accordion in user/session settings; user: linked accounts section with unlink, QQ button on the sign-in page; qq_connect_enabled in the login site config. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- application/dependency/dependency.go | 12 + ent/client.go | 191 ++++- ent/ent.go | 2 + ent/hook/hook.go | 12 + ent/intercept/intercept.go | 30 + ent/internal/schema.go | 2 +- ent/migrate/schema.go | 38 + ent/mutation.go | 766 ++++++++++++++++- ent/mutationhelper.go | 6 + ent/predicate/predicate.go | 3 + ent/runtime/runtime.go | 56 ++ ent/schema/ssobinding.go | 54 ++ ent/schema/user.go | 1 + ent/ssobinding.go | 201 +++++ ent/ssobinding/ssobinding.go | 134 +++ ent/ssobinding/where.go | 404 +++++++++ ent/ssobinding_create.go | 800 ++++++++++++++++++ ent/ssobinding_delete.go | 88 ++ ent/ssobinding_query.go | 605 +++++++++++++ ent/ssobinding_update.go | 495 +++++++++++ ent/tx.go | 3 + ent/user.go | 24 +- ent/user/user.go | 30 + ent/user/where.go | 23 + ent/user_create.go | 32 + ent/user_query.go | 76 +- ent/user_update.go | 163 ++++ .../public/locales/en-US/application.json | 6 + frontend/public/locales/en-US/dashboard.json | 8 + .../public/locales/zh-CN/application.json | 6 + frontend/public/locales/zh-CN/dashboard.json | 8 + frontend/src/api/api.ts | 16 + frontend/src/api/site.ts | 1 + frontend/src/api/user.ts | 6 + .../src/component/Admin/Settings/Settings.tsx | 4 + .../UserSession/QQConnectSettings.tsx | 104 +++ .../Settings/UserSession/UserSession.tsx | 4 + .../Pages/Login/Phases/PhaseCollectEmail.tsx | 8 +- .../Pages/Login/Signin/QQLoginButton.tsx | 31 + .../Setting/Security/SecuritySetting.tsx | 102 ++- inventory/setting.go | 5 + inventory/ssobinding.go | 113 +++ inventory/ssobinding_test.go | 80 ++ pkg/setting/provider.go | 10 + pkg/setting/types.go | 10 + routers/controllers/user.go | 24 + routers/router.go | 19 + routers/router_test.go | 3 + service/basic/site.go | 3 + service/user/qq.go | 435 ++++++++++ service/user/qq_test.go | 49 ++ service/user/response.go | 13 +- service/user/setting.go | 7 +- service/user/sso.go | 3 + 54 files changed, 5309 insertions(+), 20 deletions(-) create mode 100644 ent/schema/ssobinding.go create mode 100644 ent/ssobinding.go create mode 100644 ent/ssobinding/ssobinding.go create mode 100644 ent/ssobinding/where.go create mode 100644 ent/ssobinding_create.go create mode 100644 ent/ssobinding_delete.go create mode 100644 ent/ssobinding_query.go create mode 100644 ent/ssobinding_update.go create mode 100644 frontend/src/component/Admin/Settings/UserSession/QQConnectSettings.tsx create mode 100644 frontend/src/component/Pages/Login/Signin/QQLoginButton.tsx create mode 100644 inventory/ssobinding.go create mode 100644 inventory/ssobinding_test.go create mode 100644 service/user/qq.go create mode 100644 service/user/qq_test.go 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>", "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>", + "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=1 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}}", + "qqConnect": "QQ Connect", + "qqAppID": "App ID", + "qqAppIDDes": "The App ID of the website application created in the <0>QQ Connect console (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}}", + "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>", "revokeOAuthGrant": "撤销授权", "revokeOAuthGrantConfirm": "确定要撤销此应用的授权吗?撤销后,该应用将无法再访问你的账户。", + "linkedAccounts": "已绑定账号", + "providerQQ": "QQ", + "linkedAt": "绑定于 <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=1 可进入密码登录表单(例如管理员账户恢复)。", "ssoCallbackUrl": "回调地址", "ssoCallbackUrlDes": "请在身份提供方中将此 URL 注册为 Redirect/Callback URL:<0>{{url}}", + "qqConnect": "QQ 互联", + "qqAppID": "App ID", + "qqAppIDDes": "在 <0>QQ 互联开放平台(connect.qq.com)创建的网站应用的 App ID。", + "qqAppSecret": "App Key", + "qqCallbackUrl": "回调地址", + "qqCallbackUrlDes": "请在 QQ 互联控制台中将此 URL 注册为网站回调域/回调地址:<0>{{url}}", + "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 ( + + ); +} 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 From 59cdb7a2d0be184948dae175b3c5607fe075d329 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sun, 20 Sep 2026 02:07:57 +0200 Subject: [PATCH 2/2] docs: mark account-switcher and QQ Connect items in roadmap --- ROADMAP.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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