From 0128ed8fd52c222af30ad8b83e76d44c66567b2a Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 19:46:23 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(acl):=20per-file/dir=20ACL=20entries?= =?UTF-8?q?=20=E2=80=94=20subjects=20=C3=97=20R/C/U/D,=20share-navigator?= =?UTF-8?q?=20enforcement,=20Permissions=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-file and per-directory access control matching the Pro parity spec: AclEntry rows grant read/create/update/delete bitmasks to a user, a group, anonymous visitors, or everyone else, keyed uniquely per (file, subject). - inventory.AclClient: upsert via unique (file_id, subject_type, subject_id) conflict, list, delete, EffectivePermissions unioning matching rows (anonymous visitors match anonymous rows; authenticated users match their user + group + everyone rows). No match returns nil so share-link props stay the fallback; an explicit empty entry revokes everything. - shareNavigator: resolves ACL once at Root() for non-owner visitors and persists it in navigator state; matched entries fully replace the share-props capability set via aclPermsToCapabilities. Owner bypasses. - API: GET/PUT/DELETE /file/acl + GET /file/acl/subjects under the authenticated file group; write ops scoped to files:write. Owner-only management gated by the new GroupPermissionSetExplicitUser bit (15). - File hard-delete now cascades to ACL rows alongside shares/metadata. - Frontend: Permissions dialog under More actions — subject autocomplete (exact-email user search, group search, anonymous/everyone tiers), R/C/U/D checkboxes saved per toggle, entry removal; admin group toggle for bit 15 replacing the dead esclateAnonymity scaffold. 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/aclentry.go | 215 +++++ ent/aclentry/aclentry.go | 159 ++++ ent/aclentry/where.go | 385 ++++++++ ent/aclentry_create.go | 894 ++++++++++++++++++ ent/aclentry_delete.go | 88 ++ ent/aclentry_query.go | 605 ++++++++++++ ent/aclentry_update.go | 542 +++++++++++ ent/client.go | 193 +++- ent/ent.go | 2 + ent/file.go | 28 +- ent/file/file.go | 30 + ent/file/where.go | 23 + ent/file_create.go | 32 + ent/file_query.go | 76 +- ent/file_update.go | 163 ++++ ent/hook/hook.go | 12 + ent/intercept/intercept.go | 30 + ent/internal/schema.go | 2 +- ent/migrate/schema.go | 34 + ent/mutation.go | 873 ++++++++++++++++- ent/mutationhelper.go | 6 + ent/predicate/predicate.go | 3 + ent/runtime/runtime.go | 20 + ent/schema/aclentry.go | 54 ++ ent/schema/file.go | 1 + ent/tx.go | 5 +- .../public/locales/en-US/application.json | 9 + frontend/public/locales/en-US/dashboard.json | 2 + .../public/locales/zh-CN/application.json | 9 + frontend/public/locales/zh-CN/dashboard.json | 2 + frontend/src/api/api.ts | 72 ++ frontend/src/api/explorer.ts | 25 + frontend/src/api/user.ts | 1 + .../Admin/Group/EditGroup/ShareSection.tsx | 23 +- .../FileManager/ContextMenu/MoreMenuItems.tsx | 19 + .../ContextMenu/useActionDisplayOpt.ts | 8 + .../FileManager/Dialogs/Acl/AclDialog.tsx | 300 ++++++ .../component/FileManager/Dialogs/Dialogs.tsx | 2 + frontend/src/redux/globalStateSlice.ts | 14 + inventory/acl.go | 156 +++ inventory/acl_test.go | 154 +++ inventory/file.go | 5 + inventory/types/types.go | 23 +- pkg/filemanager/fs/dbfs/dbfs.go | 5 +- .../fs/dbfs/share_collaboration_test.go | 74 ++ pkg/filemanager/fs/dbfs/share_navigator.go | 68 +- pkg/filemanager/manager/manager.go | 1 + routers/controllers/file.go | 51 + routers/router.go | 22 + service/explorer/acl.go | 240 +++++ 51 files changed, 5746 insertions(+), 26 deletions(-) create mode 100644 ent/aclentry.go create mode 100644 ent/aclentry/aclentry.go create mode 100644 ent/aclentry/where.go create mode 100644 ent/aclentry_create.go create mode 100644 ent/aclentry_delete.go create mode 100644 ent/aclentry_query.go create mode 100644 ent/aclentry_update.go create mode 100644 ent/schema/aclentry.go create mode 100644 frontend/src/component/FileManager/Dialogs/Acl/AclDialog.tsx create mode 100644 inventory/acl.go create mode 100644 inventory/acl_test.go create mode 100644 service/explorer/acl.go diff --git a/application/dependency/dependency.go b/application/dependency/dependency.go index ebe5e478..df3025df 100644 --- a/application/dependency/dependency.go +++ b/application/dependency/dependency.go @@ -106,6 +106,8 @@ type Dep interface { RequestClient(opts ...request.Option) request.Client // ShareClient Get a singleton inventory.ShareClient instance for access DB share store. ShareClient() inventory.ShareClient + // AclClient Get a singleton inventory.AclClient instance for access DB ACL entry store. + AclClient() inventory.AclClient // 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. @@ -162,6 +164,7 @@ type dependency struct { settingClient inventory.SettingClient fileClient inventory.FileClient shareClient inventory.ShareClient + aclClient inventory.AclClient settingProvider setting.Provider userClient inventory.UserClient groupClient inventory.GroupClient @@ -822,6 +825,15 @@ func (d *dependency) ShareClient() inventory.ShareClient { return d.shareClient } +func (d *dependency) AclClient() inventory.AclClient { + if d.aclClient != nil { + return d.aclClient + } + + d.aclClient = inventory.NewAclClient(d.DBClient(), d.ConfigProvider().Database().Type) + return d.aclClient +} + func (d *dependency) TaskClient() inventory.TaskClient { if d.taskClient != nil { return d.taskClient diff --git a/ent/aclentry.go b/ent/aclentry.go new file mode 100644 index 00000000..e9b10dad --- /dev/null +++ b/ent/aclentry.go @@ -0,0 +1,215 @@ +// 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/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/file" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" +) + +// AclEntry is the model entity for the AclEntry schema. +type AclEntry 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"` + // FileID holds the value of the "file_id" field. + FileID int `json:"file_id,omitempty"` + // SubjectType holds the value of the "subject_type" field. + SubjectType aclentry.SubjectType `json:"subject_type,omitempty"` + // SubjectID holds the value of the "subject_id" field. + SubjectID int `json:"subject_id,omitempty"` + // Permissions holds the value of the "permissions" field. + Permissions *boolset.BooleanSet `json:"permissions,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AclEntryQuery when eager-loading is set. + Edges AclEntryEdges `json:"edges"` + selectValues sql.SelectValues +} + +// AclEntryEdges holds the relations/edges for other nodes in the graph. +type AclEntryEdges struct { + // File holds the value of the file edge. + File *File `json:"file,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// FileOrErr returns the File value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AclEntryEdges) FileOrErr() (*File, error) { + if e.loadedTypes[0] { + if e.File == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: file.Label} + } + return e.File, nil + } + return nil, &NotLoadedError{edge: "file"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AclEntry) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case aclentry.FieldPermissions: + values[i] = new(boolset.BooleanSet) + case aclentry.FieldID, aclentry.FieldFileID, aclentry.FieldSubjectID: + values[i] = new(sql.NullInt64) + case aclentry.FieldSubjectType: + values[i] = new(sql.NullString) + case aclentry.FieldCreatedAt, aclentry.FieldUpdatedAt, aclentry.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 AclEntry fields. +func (ae *AclEntry) 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 aclentry.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + ae.ID = int(value.Int64) + case aclentry.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 { + ae.CreatedAt = value.Time + } + case aclentry.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 { + ae.UpdatedAt = value.Time + } + case aclentry.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 { + ae.DeletedAt = new(time.Time) + *ae.DeletedAt = value.Time + } + case aclentry.FieldFileID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field file_id", values[i]) + } else if value.Valid { + ae.FileID = int(value.Int64) + } + case aclentry.FieldSubjectType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field subject_type", values[i]) + } else if value.Valid { + ae.SubjectType = aclentry.SubjectType(value.String) + } + case aclentry.FieldSubjectID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field subject_id", values[i]) + } else if value.Valid { + ae.SubjectID = int(value.Int64) + } + case aclentry.FieldPermissions: + if value, ok := values[i].(*boolset.BooleanSet); !ok { + return fmt.Errorf("unexpected type %T for field permissions", values[i]) + } else if value != nil { + ae.Permissions = value + } + default: + ae.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the AclEntry. +// This includes values selected through modifiers, order, etc. +func (ae *AclEntry) Value(name string) (ent.Value, error) { + return ae.selectValues.Get(name) +} + +// QueryFile queries the "file" edge of the AclEntry entity. +func (ae *AclEntry) QueryFile() *FileQuery { + return NewAclEntryClient(ae.config).QueryFile(ae) +} + +// Update returns a builder for updating this AclEntry. +// Note that you need to call AclEntry.Unwrap() before calling this method if this AclEntry +// was returned from a transaction, and the transaction was committed or rolled back. +func (ae *AclEntry) Update() *AclEntryUpdateOne { + return NewAclEntryClient(ae.config).UpdateOne(ae) +} + +// Unwrap unwraps the AclEntry 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 (ae *AclEntry) Unwrap() *AclEntry { + _tx, ok := ae.config.driver.(*txDriver) + if !ok { + panic("ent: AclEntry is not a transactional entity") + } + ae.config.driver = _tx.drv + return ae +} + +// String implements the fmt.Stringer. +func (ae *AclEntry) String() string { + var builder strings.Builder + builder.WriteString("AclEntry(") + builder.WriteString(fmt.Sprintf("id=%v, ", ae.ID)) + builder.WriteString("created_at=") + builder.WriteString(ae.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(ae.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + if v := ae.DeletedAt; v != nil { + builder.WriteString("deleted_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + builder.WriteString("file_id=") + builder.WriteString(fmt.Sprintf("%v", ae.FileID)) + builder.WriteString(", ") + builder.WriteString("subject_type=") + builder.WriteString(fmt.Sprintf("%v", ae.SubjectType)) + builder.WriteString(", ") + builder.WriteString("subject_id=") + builder.WriteString(fmt.Sprintf("%v", ae.SubjectID)) + builder.WriteString(", ") + builder.WriteString("permissions=") + builder.WriteString(fmt.Sprintf("%v", ae.Permissions)) + builder.WriteByte(')') + return builder.String() +} + +// SetFile manually set the edge as loaded state. +func (e *AclEntry) SetFile(v *File) { + e.Edges.File = v + e.Edges.loadedTypes[0] = true +} + +// AclEntries is a parsable slice of AclEntry. +type AclEntries []*AclEntry diff --git a/ent/aclentry/aclentry.go b/ent/aclentry/aclentry.go new file mode 100644 index 00000000..b6416348 --- /dev/null +++ b/ent/aclentry/aclentry.go @@ -0,0 +1,159 @@ +// Code generated by ent, DO NOT EDIT. + +package aclentry + +import ( + "fmt" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the aclentry type in the database. + Label = "acl_entry" + // 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" + // FieldFileID holds the string denoting the file_id field in the database. + FieldFileID = "file_id" + // FieldSubjectType holds the string denoting the subject_type field in the database. + FieldSubjectType = "subject_type" + // FieldSubjectID holds the string denoting the subject_id field in the database. + FieldSubjectID = "subject_id" + // FieldPermissions holds the string denoting the permissions field in the database. + FieldPermissions = "permissions" + // EdgeFile holds the string denoting the file edge name in mutations. + EdgeFile = "file" + // Table holds the table name of the aclentry in the database. + Table = "acl_entries" + // FileTable is the table that holds the file relation/edge. + FileTable = "acl_entries" + // FileInverseTable is the table name for the File entity. + // It exists in this package in order to avoid circular dependency with the "file" package. + FileInverseTable = "files" + // FileColumn is the table column denoting the file relation/edge. + FileColumn = "file_id" +) + +// Columns holds all SQL columns for aclentry fields. +var Columns = []string{ + FieldID, + FieldCreatedAt, + FieldUpdatedAt, + FieldDeletedAt, + FieldFileID, + FieldSubjectType, + FieldSubjectID, + FieldPermissions, +} + +// 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 +) + +// SubjectType defines the type for the "subject_type" enum field. +type SubjectType string + +// SubjectType values. +const ( + SubjectTypeUser SubjectType = "user" + SubjectTypeGroup SubjectType = "group" + SubjectTypeAnonymous SubjectType = "anonymous" + SubjectTypeEveryone SubjectType = "everyone" +) + +func (st SubjectType) String() string { + return string(st) +} + +// SubjectTypeValidator is a validator for the "subject_type" field enum values. It is called by the builders before save. +func SubjectTypeValidator(st SubjectType) error { + switch st { + case SubjectTypeUser, SubjectTypeGroup, SubjectTypeAnonymous, SubjectTypeEveryone: + return nil + default: + return fmt.Errorf("aclentry: invalid enum value for subject_type field: %q", st) + } +} + +// OrderOption defines the ordering options for the AclEntry 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() +} + +// ByFileID orders the results by the file_id field. +func ByFileID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFileID, opts...).ToFunc() +} + +// BySubjectType orders the results by the subject_type field. +func BySubjectType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubjectType, opts...).ToFunc() +} + +// BySubjectID orders the results by the subject_id field. +func BySubjectID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubjectID, opts...).ToFunc() +} + +// ByFileField orders the results by file field. +func ByFileField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newFileStep(), sql.OrderByField(field, opts...)) + } +} +func newFileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(FileInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn), + ) +} diff --git a/ent/aclentry/where.go b/ent/aclentry/where.go new file mode 100644 index 00000000..5261960b --- /dev/null +++ b/ent/aclentry/where.go @@ -0,0 +1,385 @@ +// Code generated by ent, DO NOT EDIT. + +package aclentry + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cloudreve/Cloudreve/v4/ent/predicate" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.AclEntry { + return predicate.AclEntry(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.AclEntry { + return predicate.AclEntry(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.AclEntry { + return predicate.AclEntry(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.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldDeletedAt, v)) +} + +// FileID applies equality check predicate on the "file_id" field. It's identical to FileIDEQ. +func FileID(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldFileID, v)) +} + +// SubjectID applies equality check predicate on the "subject_id" field. It's identical to SubjectIDEQ. +func SubjectID(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldSubjectID, v)) +} + +// Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ. +func Permissions(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldPermissions, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// DeletedAtEQ applies the EQ predicate on the "deleted_at" field. +func DeletedAtEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldDeletedAt, v)) +} + +// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field. +func DeletedAtNEQ(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldDeletedAt, v)) +} + +// DeletedAtIn applies the In predicate on the "deleted_at" field. +func DeletedAtIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldDeletedAt, vs...)) +} + +// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field. +func DeletedAtNotIn(vs ...time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldDeletedAt, vs...)) +} + +// DeletedAtGT applies the GT predicate on the "deleted_at" field. +func DeletedAtGT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldDeletedAt, v)) +} + +// DeletedAtGTE applies the GTE predicate on the "deleted_at" field. +func DeletedAtGTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldDeletedAt, v)) +} + +// DeletedAtLT applies the LT predicate on the "deleted_at" field. +func DeletedAtLT(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldDeletedAt, v)) +} + +// DeletedAtLTE applies the LTE predicate on the "deleted_at" field. +func DeletedAtLTE(v time.Time) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLTE(FieldDeletedAt, v)) +} + +// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field. +func DeletedAtIsNil() predicate.AclEntry { + return predicate.AclEntry(sql.FieldIsNull(FieldDeletedAt)) +} + +// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field. +func DeletedAtNotNil() predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotNull(FieldDeletedAt)) +} + +// FileIDEQ applies the EQ predicate on the "file_id" field. +func FileIDEQ(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldFileID, v)) +} + +// FileIDNEQ applies the NEQ predicate on the "file_id" field. +func FileIDNEQ(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldFileID, v)) +} + +// FileIDIn applies the In predicate on the "file_id" field. +func FileIDIn(vs ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldFileID, vs...)) +} + +// FileIDNotIn applies the NotIn predicate on the "file_id" field. +func FileIDNotIn(vs ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldFileID, vs...)) +} + +// SubjectTypeEQ applies the EQ predicate on the "subject_type" field. +func SubjectTypeEQ(v SubjectType) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldSubjectType, v)) +} + +// SubjectTypeNEQ applies the NEQ predicate on the "subject_type" field. +func SubjectTypeNEQ(v SubjectType) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldSubjectType, v)) +} + +// SubjectTypeIn applies the In predicate on the "subject_type" field. +func SubjectTypeIn(vs ...SubjectType) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldSubjectType, vs...)) +} + +// SubjectTypeNotIn applies the NotIn predicate on the "subject_type" field. +func SubjectTypeNotIn(vs ...SubjectType) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldSubjectType, vs...)) +} + +// SubjectIDEQ applies the EQ predicate on the "subject_id" field. +func SubjectIDEQ(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldSubjectID, v)) +} + +// SubjectIDNEQ applies the NEQ predicate on the "subject_id" field. +func SubjectIDNEQ(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldSubjectID, v)) +} + +// SubjectIDIn applies the In predicate on the "subject_id" field. +func SubjectIDIn(vs ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldSubjectID, vs...)) +} + +// SubjectIDNotIn applies the NotIn predicate on the "subject_id" field. +func SubjectIDNotIn(vs ...int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldSubjectID, vs...)) +} + +// SubjectIDGT applies the GT predicate on the "subject_id" field. +func SubjectIDGT(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldSubjectID, v)) +} + +// SubjectIDGTE applies the GTE predicate on the "subject_id" field. +func SubjectIDGTE(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldSubjectID, v)) +} + +// SubjectIDLT applies the LT predicate on the "subject_id" field. +func SubjectIDLT(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldSubjectID, v)) +} + +// SubjectIDLTE applies the LTE predicate on the "subject_id" field. +func SubjectIDLTE(v int) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLTE(FieldSubjectID, v)) +} + +// SubjectIDIsNil applies the IsNil predicate on the "subject_id" field. +func SubjectIDIsNil() predicate.AclEntry { + return predicate.AclEntry(sql.FieldIsNull(FieldSubjectID)) +} + +// SubjectIDNotNil applies the NotNil predicate on the "subject_id" field. +func SubjectIDNotNil() predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotNull(FieldSubjectID)) +} + +// PermissionsEQ applies the EQ predicate on the "permissions" field. +func PermissionsEQ(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldEQ(FieldPermissions, v)) +} + +// PermissionsNEQ applies the NEQ predicate on the "permissions" field. +func PermissionsNEQ(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNEQ(FieldPermissions, v)) +} + +// PermissionsIn applies the In predicate on the "permissions" field. +func PermissionsIn(vs ...*boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldIn(FieldPermissions, vs...)) +} + +// PermissionsNotIn applies the NotIn predicate on the "permissions" field. +func PermissionsNotIn(vs ...*boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldNotIn(FieldPermissions, vs...)) +} + +// PermissionsGT applies the GT predicate on the "permissions" field. +func PermissionsGT(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGT(FieldPermissions, v)) +} + +// PermissionsGTE applies the GTE predicate on the "permissions" field. +func PermissionsGTE(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldGTE(FieldPermissions, v)) +} + +// PermissionsLT applies the LT predicate on the "permissions" field. +func PermissionsLT(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLT(FieldPermissions, v)) +} + +// PermissionsLTE applies the LTE predicate on the "permissions" field. +func PermissionsLTE(v *boolset.BooleanSet) predicate.AclEntry { + return predicate.AclEntry(sql.FieldLTE(FieldPermissions, v)) +} + +// HasFile applies the HasEdge predicate on the "file" edge. +func HasFile() predicate.AclEntry { + return predicate.AclEntry(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasFileWith applies the HasEdge predicate on the "file" edge with a given conditions (other predicates). +func HasFileWith(preds ...predicate.File) predicate.AclEntry { + return predicate.AclEntry(func(s *sql.Selector) { + step := newFileStep() + 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.AclEntry) predicate.AclEntry { + return predicate.AclEntry(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AclEntry) predicate.AclEntry { + return predicate.AclEntry(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AclEntry) predicate.AclEntry { + return predicate.AclEntry(sql.NotPredicates(p)) +} diff --git a/ent/aclentry_create.go b/ent/aclentry_create.go new file mode 100644 index 00000000..720f708f --- /dev/null +++ b/ent/aclentry_create.go @@ -0,0 +1,894 @@ +// 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/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/file" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" +) + +// AclEntryCreate is the builder for creating a AclEntry entity. +type AclEntryCreate struct { + config + mutation *AclEntryMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetCreatedAt sets the "created_at" field. +func (aec *AclEntryCreate) SetCreatedAt(t time.Time) *AclEntryCreate { + aec.mutation.SetCreatedAt(t) + return aec +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (aec *AclEntryCreate) SetNillableCreatedAt(t *time.Time) *AclEntryCreate { + if t != nil { + aec.SetCreatedAt(*t) + } + return aec +} + +// SetUpdatedAt sets the "updated_at" field. +func (aec *AclEntryCreate) SetUpdatedAt(t time.Time) *AclEntryCreate { + aec.mutation.SetUpdatedAt(t) + return aec +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (aec *AclEntryCreate) SetNillableUpdatedAt(t *time.Time) *AclEntryCreate { + if t != nil { + aec.SetUpdatedAt(*t) + } + return aec +} + +// SetDeletedAt sets the "deleted_at" field. +func (aec *AclEntryCreate) SetDeletedAt(t time.Time) *AclEntryCreate { + aec.mutation.SetDeletedAt(t) + return aec +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (aec *AclEntryCreate) SetNillableDeletedAt(t *time.Time) *AclEntryCreate { + if t != nil { + aec.SetDeletedAt(*t) + } + return aec +} + +// SetFileID sets the "file_id" field. +func (aec *AclEntryCreate) SetFileID(i int) *AclEntryCreate { + aec.mutation.SetFileID(i) + return aec +} + +// SetSubjectType sets the "subject_type" field. +func (aec *AclEntryCreate) SetSubjectType(at aclentry.SubjectType) *AclEntryCreate { + aec.mutation.SetSubjectType(at) + return aec +} + +// SetSubjectID sets the "subject_id" field. +func (aec *AclEntryCreate) SetSubjectID(i int) *AclEntryCreate { + aec.mutation.SetSubjectID(i) + return aec +} + +// SetNillableSubjectID sets the "subject_id" field if the given value is not nil. +func (aec *AclEntryCreate) SetNillableSubjectID(i *int) *AclEntryCreate { + if i != nil { + aec.SetSubjectID(*i) + } + return aec +} + +// SetPermissions sets the "permissions" field. +func (aec *AclEntryCreate) SetPermissions(bs *boolset.BooleanSet) *AclEntryCreate { + aec.mutation.SetPermissions(bs) + return aec +} + +// SetFile sets the "file" edge to the File entity. +func (aec *AclEntryCreate) SetFile(f *File) *AclEntryCreate { + return aec.SetFileID(f.ID) +} + +// Mutation returns the AclEntryMutation object of the builder. +func (aec *AclEntryCreate) Mutation() *AclEntryMutation { + return aec.mutation +} + +// Save creates the AclEntry in the database. +func (aec *AclEntryCreate) Save(ctx context.Context) (*AclEntry, error) { + if err := aec.defaults(); err != nil { + return nil, err + } + return withHooks(ctx, aec.sqlSave, aec.mutation, aec.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (aec *AclEntryCreate) SaveX(ctx context.Context) *AclEntry { + v, err := aec.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (aec *AclEntryCreate) Exec(ctx context.Context) error { + _, err := aec.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aec *AclEntryCreate) ExecX(ctx context.Context) { + if err := aec.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (aec *AclEntryCreate) defaults() error { + if _, ok := aec.mutation.CreatedAt(); !ok { + if aclentry.DefaultCreatedAt == nil { + return fmt.Errorf("ent: uninitialized aclentry.DefaultCreatedAt (forgotten import ent/runtime?)") + } + v := aclentry.DefaultCreatedAt() + aec.mutation.SetCreatedAt(v) + } + if _, ok := aec.mutation.UpdatedAt(); !ok { + if aclentry.DefaultUpdatedAt == nil { + return fmt.Errorf("ent: uninitialized aclentry.DefaultUpdatedAt (forgotten import ent/runtime?)") + } + v := aclentry.DefaultUpdatedAt() + aec.mutation.SetUpdatedAt(v) + } + return nil +} + +// check runs all checks and user-defined validators on the builder. +func (aec *AclEntryCreate) check() error { + if _, ok := aec.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AclEntry.created_at"`)} + } + if _, ok := aec.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "AclEntry.updated_at"`)} + } + if _, ok := aec.mutation.FileID(); !ok { + return &ValidationError{Name: "file_id", err: errors.New(`ent: missing required field "AclEntry.file_id"`)} + } + if _, ok := aec.mutation.SubjectType(); !ok { + return &ValidationError{Name: "subject_type", err: errors.New(`ent: missing required field "AclEntry.subject_type"`)} + } + if v, ok := aec.mutation.SubjectType(); ok { + if err := aclentry.SubjectTypeValidator(v); err != nil { + return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)} + } + } + if _, ok := aec.mutation.Permissions(); !ok { + return &ValidationError{Name: "permissions", err: errors.New(`ent: missing required field "AclEntry.permissions"`)} + } + if _, ok := aec.mutation.FileID(); !ok { + return &ValidationError{Name: "file", err: errors.New(`ent: missing required edge "AclEntry.file"`)} + } + return nil +} + +func (aec *AclEntryCreate) sqlSave(ctx context.Context) (*AclEntry, error) { + if err := aec.check(); err != nil { + return nil, err + } + _node, _spec := aec.createSpec() + if err := sqlgraph.CreateNode(ctx, aec.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) + aec.mutation.id = &_node.ID + aec.mutation.done = true + return _node, nil +} + +func (aec *AclEntryCreate) createSpec() (*AclEntry, *sqlgraph.CreateSpec) { + var ( + _node = &AclEntry{config: aec.config} + _spec = sqlgraph.NewCreateSpec(aclentry.Table, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt)) + ) + + if id, ok := aec.mutation.ID(); ok { + _node.ID = id + id64 := int64(id) + _spec.ID.Value = id64 + } + + _spec.OnConflict = aec.conflict + if value, ok := aec.mutation.CreatedAt(); ok { + _spec.SetField(aclentry.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := aec.mutation.UpdatedAt(); ok { + _spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := aec.mutation.DeletedAt(); ok { + _spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value) + _node.DeletedAt = &value + } + if value, ok := aec.mutation.SubjectType(); ok { + _spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value) + _node.SubjectType = value + } + if value, ok := aec.mutation.SubjectID(); ok { + _spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value) + _node.SubjectID = value + } + if value, ok := aec.mutation.Permissions(); ok { + _spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value) + _node.Permissions = value + } + if nodes := aec.mutation.FileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: aclentry.FileTable, + Columns: []string{aclentry.FileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.FileID = 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.AclEntry.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.AclEntryUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (aec *AclEntryCreate) OnConflict(opts ...sql.ConflictOption) *AclEntryUpsertOne { + aec.conflict = opts + return &AclEntryUpsertOne{ + create: aec, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (aec *AclEntryCreate) OnConflictColumns(columns ...string) *AclEntryUpsertOne { + aec.conflict = append(aec.conflict, sql.ConflictColumns(columns...)) + return &AclEntryUpsertOne{ + create: aec, + } +} + +type ( + // AclEntryUpsertOne is the builder for "upsert"-ing + // one AclEntry node. + AclEntryUpsertOne struct { + create *AclEntryCreate + } + + // AclEntryUpsert is the "OnConflict" setter. + AclEntryUpsert struct { + *sql.UpdateSet + } +) + +// SetUpdatedAt sets the "updated_at" field. +func (u *AclEntryUpsert) SetUpdatedAt(v time.Time) *AclEntryUpsert { + u.Set(aclentry.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdateUpdatedAt() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldUpdatedAt) + return u +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *AclEntryUpsert) SetDeletedAt(v time.Time) *AclEntryUpsert { + u.Set(aclentry.FieldDeletedAt, v) + return u +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdateDeletedAt() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldDeletedAt) + return u +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *AclEntryUpsert) ClearDeletedAt() *AclEntryUpsert { + u.SetNull(aclentry.FieldDeletedAt) + return u +} + +// SetFileID sets the "file_id" field. +func (u *AclEntryUpsert) SetFileID(v int) *AclEntryUpsert { + u.Set(aclentry.FieldFileID, v) + return u +} + +// UpdateFileID sets the "file_id" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdateFileID() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldFileID) + return u +} + +// SetSubjectType sets the "subject_type" field. +func (u *AclEntryUpsert) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsert { + u.Set(aclentry.FieldSubjectType, v) + return u +} + +// UpdateSubjectType sets the "subject_type" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdateSubjectType() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldSubjectType) + return u +} + +// SetSubjectID sets the "subject_id" field. +func (u *AclEntryUpsert) SetSubjectID(v int) *AclEntryUpsert { + u.Set(aclentry.FieldSubjectID, v) + return u +} + +// UpdateSubjectID sets the "subject_id" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdateSubjectID() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldSubjectID) + return u +} + +// AddSubjectID adds v to the "subject_id" field. +func (u *AclEntryUpsert) AddSubjectID(v int) *AclEntryUpsert { + u.Add(aclentry.FieldSubjectID, v) + return u +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (u *AclEntryUpsert) ClearSubjectID() *AclEntryUpsert { + u.SetNull(aclentry.FieldSubjectID) + return u +} + +// SetPermissions sets the "permissions" field. +func (u *AclEntryUpsert) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsert { + u.Set(aclentry.FieldPermissions, v) + return u +} + +// UpdatePermissions sets the "permissions" field to the value that was provided on create. +func (u *AclEntryUpsert) UpdatePermissions() *AclEntryUpsert { + u.SetExcluded(aclentry.FieldPermissions) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create. +// Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *AclEntryUpsertOne) UpdateNewValues() *AclEntryUpsertOne { + 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(aclentry.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AclEntryUpsertOne) Ignore() *AclEntryUpsertOne { + 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 *AclEntryUpsertOne) DoNothing() *AclEntryUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AclEntryCreate.OnConflict +// documentation for more info. +func (u *AclEntryUpsertOne) Update(set func(*AclEntryUpsert)) *AclEntryUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AclEntryUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *AclEntryUpsertOne) SetUpdatedAt(v time.Time) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdateUpdatedAt() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *AclEntryUpsertOne) SetDeletedAt(v time.Time) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetDeletedAt(v) + }) +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdateDeletedAt() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateDeletedAt() + }) +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *AclEntryUpsertOne) ClearDeletedAt() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.ClearDeletedAt() + }) +} + +// SetFileID sets the "file_id" field. +func (u *AclEntryUpsertOne) SetFileID(v int) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetFileID(v) + }) +} + +// UpdateFileID sets the "file_id" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdateFileID() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateFileID() + }) +} + +// SetSubjectType sets the "subject_type" field. +func (u *AclEntryUpsertOne) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetSubjectType(v) + }) +} + +// UpdateSubjectType sets the "subject_type" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdateSubjectType() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateSubjectType() + }) +} + +// SetSubjectID sets the "subject_id" field. +func (u *AclEntryUpsertOne) SetSubjectID(v int) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetSubjectID(v) + }) +} + +// AddSubjectID adds v to the "subject_id" field. +func (u *AclEntryUpsertOne) AddSubjectID(v int) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.AddSubjectID(v) + }) +} + +// UpdateSubjectID sets the "subject_id" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdateSubjectID() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateSubjectID() + }) +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (u *AclEntryUpsertOne) ClearSubjectID() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.ClearSubjectID() + }) +} + +// SetPermissions sets the "permissions" field. +func (u *AclEntryUpsertOne) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.SetPermissions(v) + }) +} + +// UpdatePermissions sets the "permissions" field to the value that was provided on create. +func (u *AclEntryUpsertOne) UpdatePermissions() *AclEntryUpsertOne { + return u.Update(func(s *AclEntryUpsert) { + s.UpdatePermissions() + }) +} + +// Exec executes the query. +func (u *AclEntryUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AclEntryCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AclEntryUpsertOne) 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 *AclEntryUpsertOne) 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 *AclEntryUpsertOne) IDX(ctx context.Context) int { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +func (m *AclEntryCreate) SetRawID(t int) *AclEntryCreate { + m.mutation.SetRawID(t) + return m +} + +// AclEntryCreateBulk is the builder for creating many AclEntry entities in bulk. +type AclEntryCreateBulk struct { + config + err error + builders []*AclEntryCreate + conflict []sql.ConflictOption +} + +// Save creates the AclEntry entities in the database. +func (aecb *AclEntryCreateBulk) Save(ctx context.Context) ([]*AclEntry, error) { + if aecb.err != nil { + return nil, aecb.err + } + specs := make([]*sqlgraph.CreateSpec, len(aecb.builders)) + nodes := make([]*AclEntry, len(aecb.builders)) + mutators := make([]Mutator, len(aecb.builders)) + for i := range aecb.builders { + func(i int, root context.Context) { + builder := aecb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AclEntryMutation) + 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, aecb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = aecb.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, aecb.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, aecb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (aecb *AclEntryCreateBulk) SaveX(ctx context.Context) []*AclEntry { + v, err := aecb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (aecb *AclEntryCreateBulk) Exec(ctx context.Context) error { + _, err := aecb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aecb *AclEntryCreateBulk) ExecX(ctx context.Context) { + if err := aecb.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.AclEntry.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.AclEntryUpsert) { +// SetCreatedAt(v+v). +// }). +// Exec(ctx) +func (aecb *AclEntryCreateBulk) OnConflict(opts ...sql.ConflictOption) *AclEntryUpsertBulk { + aecb.conflict = opts + return &AclEntryUpsertBulk{ + create: aecb, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (aecb *AclEntryCreateBulk) OnConflictColumns(columns ...string) *AclEntryUpsertBulk { + aecb.conflict = append(aecb.conflict, sql.ConflictColumns(columns...)) + return &AclEntryUpsertBulk{ + create: aecb, + } +} + +// AclEntryUpsertBulk is the builder for "upsert"-ing +// a bulk of AclEntry nodes. +type AclEntryUpsertBulk struct { + create *AclEntryCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *AclEntryUpsertBulk) UpdateNewValues() *AclEntryUpsertBulk { + 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(aclentry.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.AclEntry.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *AclEntryUpsertBulk) Ignore() *AclEntryUpsertBulk { + 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 *AclEntryUpsertBulk) DoNothing() *AclEntryUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the AclEntryCreateBulk.OnConflict +// documentation for more info. +func (u *AclEntryUpsertBulk) Update(set func(*AclEntryUpsert)) *AclEntryUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&AclEntryUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *AclEntryUpsertBulk) SetUpdatedAt(v time.Time) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdateUpdatedAt() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *AclEntryUpsertBulk) SetDeletedAt(v time.Time) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetDeletedAt(v) + }) +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdateDeletedAt() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateDeletedAt() + }) +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *AclEntryUpsertBulk) ClearDeletedAt() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.ClearDeletedAt() + }) +} + +// SetFileID sets the "file_id" field. +func (u *AclEntryUpsertBulk) SetFileID(v int) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetFileID(v) + }) +} + +// UpdateFileID sets the "file_id" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdateFileID() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateFileID() + }) +} + +// SetSubjectType sets the "subject_type" field. +func (u *AclEntryUpsertBulk) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetSubjectType(v) + }) +} + +// UpdateSubjectType sets the "subject_type" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdateSubjectType() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateSubjectType() + }) +} + +// SetSubjectID sets the "subject_id" field. +func (u *AclEntryUpsertBulk) SetSubjectID(v int) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetSubjectID(v) + }) +} + +// AddSubjectID adds v to the "subject_id" field. +func (u *AclEntryUpsertBulk) AddSubjectID(v int) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.AddSubjectID(v) + }) +} + +// UpdateSubjectID sets the "subject_id" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdateSubjectID() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdateSubjectID() + }) +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (u *AclEntryUpsertBulk) ClearSubjectID() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.ClearSubjectID() + }) +} + +// SetPermissions sets the "permissions" field. +func (u *AclEntryUpsertBulk) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.SetPermissions(v) + }) +} + +// UpdatePermissions sets the "permissions" field to the value that was provided on create. +func (u *AclEntryUpsertBulk) UpdatePermissions() *AclEntryUpsertBulk { + return u.Update(func(s *AclEntryUpsert) { + s.UpdatePermissions() + }) +} + +// Exec executes the query. +func (u *AclEntryUpsertBulk) 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 AclEntryCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for AclEntryCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *AclEntryUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/aclentry_delete.go b/ent/aclentry_delete.go new file mode 100644 index 00000000..8670f2b4 --- /dev/null +++ b/ent/aclentry_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/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/predicate" +) + +// AclEntryDelete is the builder for deleting a AclEntry entity. +type AclEntryDelete struct { + config + hooks []Hook + mutation *AclEntryMutation +} + +// Where appends a list predicates to the AclEntryDelete builder. +func (aed *AclEntryDelete) Where(ps ...predicate.AclEntry) *AclEntryDelete { + aed.mutation.Where(ps...) + return aed +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (aed *AclEntryDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, aed.sqlExec, aed.mutation, aed.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (aed *AclEntryDelete) ExecX(ctx context.Context) int { + n, err := aed.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (aed *AclEntryDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(aclentry.Table, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt)) + if ps := aed.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, aed.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + aed.mutation.done = true + return affected, err +} + +// AclEntryDeleteOne is the builder for deleting a single AclEntry entity. +type AclEntryDeleteOne struct { + aed *AclEntryDelete +} + +// Where appends a list predicates to the AclEntryDelete builder. +func (aedo *AclEntryDeleteOne) Where(ps ...predicate.AclEntry) *AclEntryDeleteOne { + aedo.aed.mutation.Where(ps...) + return aedo +} + +// Exec executes the deletion query. +func (aedo *AclEntryDeleteOne) Exec(ctx context.Context) error { + n, err := aedo.aed.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{aclentry.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (aedo *AclEntryDeleteOne) ExecX(ctx context.Context) { + if err := aedo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/aclentry_query.go b/ent/aclentry_query.go new file mode 100644 index 00000000..d66e3c00 --- /dev/null +++ b/ent/aclentry_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/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/file" + "github.com/cloudreve/Cloudreve/v4/ent/predicate" +) + +// AclEntryQuery is the builder for querying AclEntry entities. +type AclEntryQuery struct { + config + ctx *QueryContext + order []aclentry.OrderOption + inters []Interceptor + predicates []predicate.AclEntry + withFile *FileQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AclEntryQuery builder. +func (aeq *AclEntryQuery) Where(ps ...predicate.AclEntry) *AclEntryQuery { + aeq.predicates = append(aeq.predicates, ps...) + return aeq +} + +// Limit the number of records to be returned by this query. +func (aeq *AclEntryQuery) Limit(limit int) *AclEntryQuery { + aeq.ctx.Limit = &limit + return aeq +} + +// Offset to start from. +func (aeq *AclEntryQuery) Offset(offset int) *AclEntryQuery { + aeq.ctx.Offset = &offset + return aeq +} + +// 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 (aeq *AclEntryQuery) Unique(unique bool) *AclEntryQuery { + aeq.ctx.Unique = &unique + return aeq +} + +// Order specifies how the records should be ordered. +func (aeq *AclEntryQuery) Order(o ...aclentry.OrderOption) *AclEntryQuery { + aeq.order = append(aeq.order, o...) + return aeq +} + +// QueryFile chains the current query on the "file" edge. +func (aeq *AclEntryQuery) QueryFile() *FileQuery { + query := (&FileClient{config: aeq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := aeq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := aeq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(aclentry.Table, aclentry.FieldID, selector), + sqlgraph.To(file.Table, file.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, aclentry.FileTable, aclentry.FileColumn), + ) + fromU = sqlgraph.SetNeighbors(aeq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first AclEntry entity from the query. +// Returns a *NotFoundError when no AclEntry was found. +func (aeq *AclEntryQuery) First(ctx context.Context) (*AclEntry, error) { + nodes, err := aeq.Limit(1).All(setContextOp(ctx, aeq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{aclentry.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (aeq *AclEntryQuery) FirstX(ctx context.Context) *AclEntry { + node, err := aeq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AclEntry ID from the query. +// Returns a *NotFoundError when no AclEntry ID was found. +func (aeq *AclEntryQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = aeq.Limit(1).IDs(setContextOp(ctx, aeq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{aclentry.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (aeq *AclEntryQuery) FirstIDX(ctx context.Context) int { + id, err := aeq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AclEntry entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AclEntry entity is found. +// Returns a *NotFoundError when no AclEntry entities are found. +func (aeq *AclEntryQuery) Only(ctx context.Context) (*AclEntry, error) { + nodes, err := aeq.Limit(2).All(setContextOp(ctx, aeq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{aclentry.Label} + default: + return nil, &NotSingularError{aclentry.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (aeq *AclEntryQuery) OnlyX(ctx context.Context) *AclEntry { + node, err := aeq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AclEntry ID in the query. +// Returns a *NotSingularError when more than one AclEntry ID is found. +// Returns a *NotFoundError when no entities are found. +func (aeq *AclEntryQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = aeq.Limit(2).IDs(setContextOp(ctx, aeq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{aclentry.Label} + default: + err = &NotSingularError{aclentry.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (aeq *AclEntryQuery) OnlyIDX(ctx context.Context) int { + id, err := aeq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AclEntries. +func (aeq *AclEntryQuery) All(ctx context.Context) ([]*AclEntry, error) { + ctx = setContextOp(ctx, aeq.ctx, "All") + if err := aeq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AclEntry, *AclEntryQuery]() + return withInterceptors[[]*AclEntry](ctx, aeq, qr, aeq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (aeq *AclEntryQuery) AllX(ctx context.Context) []*AclEntry { + nodes, err := aeq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AclEntry IDs. +func (aeq *AclEntryQuery) IDs(ctx context.Context) (ids []int, err error) { + if aeq.ctx.Unique == nil && aeq.path != nil { + aeq.Unique(true) + } + ctx = setContextOp(ctx, aeq.ctx, "IDs") + if err = aeq.Select(aclentry.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (aeq *AclEntryQuery) IDsX(ctx context.Context) []int { + ids, err := aeq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (aeq *AclEntryQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, aeq.ctx, "Count") + if err := aeq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, aeq, querierCount[*AclEntryQuery](), aeq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (aeq *AclEntryQuery) CountX(ctx context.Context) int { + count, err := aeq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (aeq *AclEntryQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, aeq.ctx, "Exist") + switch _, err := aeq.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 (aeq *AclEntryQuery) ExistX(ctx context.Context) bool { + exist, err := aeq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AclEntryQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (aeq *AclEntryQuery) Clone() *AclEntryQuery { + if aeq == nil { + return nil + } + return &AclEntryQuery{ + config: aeq.config, + ctx: aeq.ctx.Clone(), + order: append([]aclentry.OrderOption{}, aeq.order...), + inters: append([]Interceptor{}, aeq.inters...), + predicates: append([]predicate.AclEntry{}, aeq.predicates...), + withFile: aeq.withFile.Clone(), + // clone intermediate query. + sql: aeq.sql.Clone(), + path: aeq.path, + } +} + +// WithFile tells the query-builder to eager-load the nodes that are connected to +// the "file" edge. The optional arguments are used to configure the query builder of the edge. +func (aeq *AclEntryQuery) WithFile(opts ...func(*FileQuery)) *AclEntryQuery { + query := (&FileClient{config: aeq.config}).Query() + for _, opt := range opts { + opt(query) + } + aeq.withFile = query + return aeq +} + +// 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.AclEntry.Query(). +// GroupBy(aclentry.FieldCreatedAt). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (aeq *AclEntryQuery) GroupBy(field string, fields ...string) *AclEntryGroupBy { + aeq.ctx.Fields = append([]string{field}, fields...) + grbuild := &AclEntryGroupBy{build: aeq} + grbuild.flds = &aeq.ctx.Fields + grbuild.label = aclentry.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.AclEntry.Query(). +// Select(aclentry.FieldCreatedAt). +// Scan(ctx, &v) +func (aeq *AclEntryQuery) Select(fields ...string) *AclEntrySelect { + aeq.ctx.Fields = append(aeq.ctx.Fields, fields...) + sbuild := &AclEntrySelect{AclEntryQuery: aeq} + sbuild.label = aclentry.Label + sbuild.flds, sbuild.scan = &aeq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AclEntrySelect configured with the given aggregations. +func (aeq *AclEntryQuery) Aggregate(fns ...AggregateFunc) *AclEntrySelect { + return aeq.Select().Aggregate(fns...) +} + +func (aeq *AclEntryQuery) prepareQuery(ctx context.Context) error { + for _, inter := range aeq.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, aeq); err != nil { + return err + } + } + } + for _, f := range aeq.ctx.Fields { + if !aclentry.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if aeq.path != nil { + prev, err := aeq.path(ctx) + if err != nil { + return err + } + aeq.sql = prev + } + return nil +} + +func (aeq *AclEntryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AclEntry, error) { + var ( + nodes = []*AclEntry{} + _spec = aeq.querySpec() + loadedTypes = [1]bool{ + aeq.withFile != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AclEntry).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AclEntry{config: aeq.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, aeq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := aeq.withFile; query != nil { + if err := aeq.loadFile(ctx, query, nodes, nil, + func(n *AclEntry, e *File) { n.Edges.File = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (aeq *AclEntryQuery) loadFile(ctx context.Context, query *FileQuery, nodes []*AclEntry, init func(*AclEntry), assign func(*AclEntry, *File)) error { + ids := make([]int, 0, len(nodes)) + nodeids := make(map[int][]*AclEntry) + for i := range nodes { + fk := nodes[i].FileID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(file.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 "file_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (aeq *AclEntryQuery) sqlCount(ctx context.Context) (int, error) { + _spec := aeq.querySpec() + _spec.Node.Columns = aeq.ctx.Fields + if len(aeq.ctx.Fields) > 0 { + _spec.Unique = aeq.ctx.Unique != nil && *aeq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, aeq.driver, _spec) +} + +func (aeq *AclEntryQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt)) + _spec.From = aeq.sql + if unique := aeq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if aeq.path != nil { + _spec.Unique = true + } + if fields := aeq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, aclentry.FieldID) + for i := range fields { + if fields[i] != aclentry.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if aeq.withFile != nil { + _spec.Node.AddColumnOnce(aclentry.FieldFileID) + } + } + if ps := aeq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := aeq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := aeq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := aeq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (aeq *AclEntryQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(aeq.driver.Dialect()) + t1 := builder.Table(aclentry.Table) + columns := aeq.ctx.Fields + if len(columns) == 0 { + columns = aclentry.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if aeq.sql != nil { + selector = aeq.sql + selector.Select(selector.Columns(columns...)...) + } + if aeq.ctx.Unique != nil && *aeq.ctx.Unique { + selector.Distinct() + } + for _, p := range aeq.predicates { + p(selector) + } + for _, p := range aeq.order { + p(selector) + } + if offset := aeq.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 := aeq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AclEntryGroupBy is the group-by builder for AclEntry entities. +type AclEntryGroupBy struct { + selector + build *AclEntryQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (aegb *AclEntryGroupBy) Aggregate(fns ...AggregateFunc) *AclEntryGroupBy { + aegb.fns = append(aegb.fns, fns...) + return aegb +} + +// Scan applies the selector query and scans the result into the given value. +func (aegb *AclEntryGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, aegb.build.ctx, "GroupBy") + if err := aegb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AclEntryQuery, *AclEntryGroupBy](ctx, aegb.build, aegb, aegb.build.inters, v) +} + +func (aegb *AclEntryGroupBy) sqlScan(ctx context.Context, root *AclEntryQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(aegb.fns)) + for _, fn := range aegb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*aegb.flds)+len(aegb.fns)) + for _, f := range *aegb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*aegb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := aegb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AclEntrySelect is the builder for selecting fields of AclEntry entities. +type AclEntrySelect struct { + *AclEntryQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (aes *AclEntrySelect) Aggregate(fns ...AggregateFunc) *AclEntrySelect { + aes.fns = append(aes.fns, fns...) + return aes +} + +// Scan applies the selector query and scans the result into the given value. +func (aes *AclEntrySelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, aes.ctx, "Select") + if err := aes.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AclEntryQuery, *AclEntrySelect](ctx, aes.AclEntryQuery, aes, aes.inters, v) +} + +func (aes *AclEntrySelect) sqlScan(ctx context.Context, root *AclEntryQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(aes.fns)) + for _, fn := range aes.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*aes.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 := aes.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/ent/aclentry_update.go b/ent/aclentry_update.go new file mode 100644 index 00000000..e767ca80 --- /dev/null +++ b/ent/aclentry_update.go @@ -0,0 +1,542 @@ +// 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/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/file" + "github.com/cloudreve/Cloudreve/v4/ent/predicate" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" +) + +// AclEntryUpdate is the builder for updating AclEntry entities. +type AclEntryUpdate struct { + config + hooks []Hook + mutation *AclEntryMutation +} + +// Where appends a list predicates to the AclEntryUpdate builder. +func (aeu *AclEntryUpdate) Where(ps ...predicate.AclEntry) *AclEntryUpdate { + aeu.mutation.Where(ps...) + return aeu +} + +// SetUpdatedAt sets the "updated_at" field. +func (aeu *AclEntryUpdate) SetUpdatedAt(t time.Time) *AclEntryUpdate { + aeu.mutation.SetUpdatedAt(t) + return aeu +} + +// SetDeletedAt sets the "deleted_at" field. +func (aeu *AclEntryUpdate) SetDeletedAt(t time.Time) *AclEntryUpdate { + aeu.mutation.SetDeletedAt(t) + return aeu +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (aeu *AclEntryUpdate) SetNillableDeletedAt(t *time.Time) *AclEntryUpdate { + if t != nil { + aeu.SetDeletedAt(*t) + } + return aeu +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (aeu *AclEntryUpdate) ClearDeletedAt() *AclEntryUpdate { + aeu.mutation.ClearDeletedAt() + return aeu +} + +// SetFileID sets the "file_id" field. +func (aeu *AclEntryUpdate) SetFileID(i int) *AclEntryUpdate { + aeu.mutation.SetFileID(i) + return aeu +} + +// SetNillableFileID sets the "file_id" field if the given value is not nil. +func (aeu *AclEntryUpdate) SetNillableFileID(i *int) *AclEntryUpdate { + if i != nil { + aeu.SetFileID(*i) + } + return aeu +} + +// SetSubjectType sets the "subject_type" field. +func (aeu *AclEntryUpdate) SetSubjectType(at aclentry.SubjectType) *AclEntryUpdate { + aeu.mutation.SetSubjectType(at) + return aeu +} + +// SetNillableSubjectType sets the "subject_type" field if the given value is not nil. +func (aeu *AclEntryUpdate) SetNillableSubjectType(at *aclentry.SubjectType) *AclEntryUpdate { + if at != nil { + aeu.SetSubjectType(*at) + } + return aeu +} + +// SetSubjectID sets the "subject_id" field. +func (aeu *AclEntryUpdate) SetSubjectID(i int) *AclEntryUpdate { + aeu.mutation.ResetSubjectID() + aeu.mutation.SetSubjectID(i) + return aeu +} + +// SetNillableSubjectID sets the "subject_id" field if the given value is not nil. +func (aeu *AclEntryUpdate) SetNillableSubjectID(i *int) *AclEntryUpdate { + if i != nil { + aeu.SetSubjectID(*i) + } + return aeu +} + +// AddSubjectID adds i to the "subject_id" field. +func (aeu *AclEntryUpdate) AddSubjectID(i int) *AclEntryUpdate { + aeu.mutation.AddSubjectID(i) + return aeu +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (aeu *AclEntryUpdate) ClearSubjectID() *AclEntryUpdate { + aeu.mutation.ClearSubjectID() + return aeu +} + +// SetPermissions sets the "permissions" field. +func (aeu *AclEntryUpdate) SetPermissions(bs *boolset.BooleanSet) *AclEntryUpdate { + aeu.mutation.SetPermissions(bs) + return aeu +} + +// SetFile sets the "file" edge to the File entity. +func (aeu *AclEntryUpdate) SetFile(f *File) *AclEntryUpdate { + return aeu.SetFileID(f.ID) +} + +// Mutation returns the AclEntryMutation object of the builder. +func (aeu *AclEntryUpdate) Mutation() *AclEntryMutation { + return aeu.mutation +} + +// ClearFile clears the "file" edge to the File entity. +func (aeu *AclEntryUpdate) ClearFile() *AclEntryUpdate { + aeu.mutation.ClearFile() + return aeu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (aeu *AclEntryUpdate) Save(ctx context.Context) (int, error) { + if err := aeu.defaults(); err != nil { + return 0, err + } + return withHooks(ctx, aeu.sqlSave, aeu.mutation, aeu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (aeu *AclEntryUpdate) SaveX(ctx context.Context) int { + affected, err := aeu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (aeu *AclEntryUpdate) Exec(ctx context.Context) error { + _, err := aeu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aeu *AclEntryUpdate) ExecX(ctx context.Context) { + if err := aeu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (aeu *AclEntryUpdate) defaults() error { + if _, ok := aeu.mutation.UpdatedAt(); !ok { + if aclentry.UpdateDefaultUpdatedAt == nil { + return fmt.Errorf("ent: uninitialized aclentry.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)") + } + v := aclentry.UpdateDefaultUpdatedAt() + aeu.mutation.SetUpdatedAt(v) + } + return nil +} + +// check runs all checks and user-defined validators on the builder. +func (aeu *AclEntryUpdate) check() error { + if v, ok := aeu.mutation.SubjectType(); ok { + if err := aclentry.SubjectTypeValidator(v); err != nil { + return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)} + } + } + if _, ok := aeu.mutation.FileID(); aeu.mutation.FileCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "AclEntry.file"`) + } + return nil +} + +func (aeu *AclEntryUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := aeu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt)) + if ps := aeu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := aeu.mutation.UpdatedAt(); ok { + _spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := aeu.mutation.DeletedAt(); ok { + _spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value) + } + if aeu.mutation.DeletedAtCleared() { + _spec.ClearField(aclentry.FieldDeletedAt, field.TypeTime) + } + if value, ok := aeu.mutation.SubjectType(); ok { + _spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value) + } + if value, ok := aeu.mutation.SubjectID(); ok { + _spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value) + } + if value, ok := aeu.mutation.AddedSubjectID(); ok { + _spec.AddField(aclentry.FieldSubjectID, field.TypeInt, value) + } + if aeu.mutation.SubjectIDCleared() { + _spec.ClearField(aclentry.FieldSubjectID, field.TypeInt) + } + if value, ok := aeu.mutation.Permissions(); ok { + _spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value) + } + if aeu.mutation.FileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: aclentry.FileTable, + Columns: []string{aclentry.FileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := aeu.mutation.FileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: aclentry.FileTable, + Columns: []string{aclentry.FileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(file.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, aeu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{aclentry.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + aeu.mutation.done = true + return n, nil +} + +// AclEntryUpdateOne is the builder for updating a single AclEntry entity. +type AclEntryUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AclEntryMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (aeuo *AclEntryUpdateOne) SetUpdatedAt(t time.Time) *AclEntryUpdateOne { + aeuo.mutation.SetUpdatedAt(t) + return aeuo +} + +// SetDeletedAt sets the "deleted_at" field. +func (aeuo *AclEntryUpdateOne) SetDeletedAt(t time.Time) *AclEntryUpdateOne { + aeuo.mutation.SetDeletedAt(t) + return aeuo +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (aeuo *AclEntryUpdateOne) SetNillableDeletedAt(t *time.Time) *AclEntryUpdateOne { + if t != nil { + aeuo.SetDeletedAt(*t) + } + return aeuo +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (aeuo *AclEntryUpdateOne) ClearDeletedAt() *AclEntryUpdateOne { + aeuo.mutation.ClearDeletedAt() + return aeuo +} + +// SetFileID sets the "file_id" field. +func (aeuo *AclEntryUpdateOne) SetFileID(i int) *AclEntryUpdateOne { + aeuo.mutation.SetFileID(i) + return aeuo +} + +// SetNillableFileID sets the "file_id" field if the given value is not nil. +func (aeuo *AclEntryUpdateOne) SetNillableFileID(i *int) *AclEntryUpdateOne { + if i != nil { + aeuo.SetFileID(*i) + } + return aeuo +} + +// SetSubjectType sets the "subject_type" field. +func (aeuo *AclEntryUpdateOne) SetSubjectType(at aclentry.SubjectType) *AclEntryUpdateOne { + aeuo.mutation.SetSubjectType(at) + return aeuo +} + +// SetNillableSubjectType sets the "subject_type" field if the given value is not nil. +func (aeuo *AclEntryUpdateOne) SetNillableSubjectType(at *aclentry.SubjectType) *AclEntryUpdateOne { + if at != nil { + aeuo.SetSubjectType(*at) + } + return aeuo +} + +// SetSubjectID sets the "subject_id" field. +func (aeuo *AclEntryUpdateOne) SetSubjectID(i int) *AclEntryUpdateOne { + aeuo.mutation.ResetSubjectID() + aeuo.mutation.SetSubjectID(i) + return aeuo +} + +// SetNillableSubjectID sets the "subject_id" field if the given value is not nil. +func (aeuo *AclEntryUpdateOne) SetNillableSubjectID(i *int) *AclEntryUpdateOne { + if i != nil { + aeuo.SetSubjectID(*i) + } + return aeuo +} + +// AddSubjectID adds i to the "subject_id" field. +func (aeuo *AclEntryUpdateOne) AddSubjectID(i int) *AclEntryUpdateOne { + aeuo.mutation.AddSubjectID(i) + return aeuo +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (aeuo *AclEntryUpdateOne) ClearSubjectID() *AclEntryUpdateOne { + aeuo.mutation.ClearSubjectID() + return aeuo +} + +// SetPermissions sets the "permissions" field. +func (aeuo *AclEntryUpdateOne) SetPermissions(bs *boolset.BooleanSet) *AclEntryUpdateOne { + aeuo.mutation.SetPermissions(bs) + return aeuo +} + +// SetFile sets the "file" edge to the File entity. +func (aeuo *AclEntryUpdateOne) SetFile(f *File) *AclEntryUpdateOne { + return aeuo.SetFileID(f.ID) +} + +// Mutation returns the AclEntryMutation object of the builder. +func (aeuo *AclEntryUpdateOne) Mutation() *AclEntryMutation { + return aeuo.mutation +} + +// ClearFile clears the "file" edge to the File entity. +func (aeuo *AclEntryUpdateOne) ClearFile() *AclEntryUpdateOne { + aeuo.mutation.ClearFile() + return aeuo +} + +// Where appends a list predicates to the AclEntryUpdate builder. +func (aeuo *AclEntryUpdateOne) Where(ps ...predicate.AclEntry) *AclEntryUpdateOne { + aeuo.mutation.Where(ps...) + return aeuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (aeuo *AclEntryUpdateOne) Select(field string, fields ...string) *AclEntryUpdateOne { + aeuo.fields = append([]string{field}, fields...) + return aeuo +} + +// Save executes the query and returns the updated AclEntry entity. +func (aeuo *AclEntryUpdateOne) Save(ctx context.Context) (*AclEntry, error) { + if err := aeuo.defaults(); err != nil { + return nil, err + } + return withHooks(ctx, aeuo.sqlSave, aeuo.mutation, aeuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (aeuo *AclEntryUpdateOne) SaveX(ctx context.Context) *AclEntry { + node, err := aeuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (aeuo *AclEntryUpdateOne) Exec(ctx context.Context) error { + _, err := aeuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aeuo *AclEntryUpdateOne) ExecX(ctx context.Context) { + if err := aeuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (aeuo *AclEntryUpdateOne) defaults() error { + if _, ok := aeuo.mutation.UpdatedAt(); !ok { + if aclentry.UpdateDefaultUpdatedAt == nil { + return fmt.Errorf("ent: uninitialized aclentry.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)") + } + v := aclentry.UpdateDefaultUpdatedAt() + aeuo.mutation.SetUpdatedAt(v) + } + return nil +} + +// check runs all checks and user-defined validators on the builder. +func (aeuo *AclEntryUpdateOne) check() error { + if v, ok := aeuo.mutation.SubjectType(); ok { + if err := aclentry.SubjectTypeValidator(v); err != nil { + return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)} + } + } + if _, ok := aeuo.mutation.FileID(); aeuo.mutation.FileCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "AclEntry.file"`) + } + return nil +} + +func (aeuo *AclEntryUpdateOne) sqlSave(ctx context.Context) (_node *AclEntry, err error) { + if err := aeuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt)) + id, ok := aeuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AclEntry.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := aeuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, aclentry.FieldID) + for _, f := range fields { + if !aclentry.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != aclentry.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := aeuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := aeuo.mutation.UpdatedAt(); ok { + _spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := aeuo.mutation.DeletedAt(); ok { + _spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value) + } + if aeuo.mutation.DeletedAtCleared() { + _spec.ClearField(aclentry.FieldDeletedAt, field.TypeTime) + } + if value, ok := aeuo.mutation.SubjectType(); ok { + _spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value) + } + if value, ok := aeuo.mutation.SubjectID(); ok { + _spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value) + } + if value, ok := aeuo.mutation.AddedSubjectID(); ok { + _spec.AddField(aclentry.FieldSubjectID, field.TypeInt, value) + } + if aeuo.mutation.SubjectIDCleared() { + _spec.ClearField(aclentry.FieldSubjectID, field.TypeInt) + } + if value, ok := aeuo.mutation.Permissions(); ok { + _spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value) + } + if aeuo.mutation.FileCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: aclentry.FileTable, + Columns: []string{aclentry.FileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := aeuo.mutation.FileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: aclentry.FileTable, + Columns: []string{aclentry.FileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &AclEntry{config: aeuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, aeuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{aclentry.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + aeuo.mutation.done = true + return _node, nil +} diff --git a/ent/client.go b/ent/client.go index 58543f91..1cf7f5b1 100644 --- a/ent/client.go +++ b/ent/client.go @@ -15,6 +15,7 @@ import ( "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" @@ -41,6 +42,8 @@ type Client struct { config // Schema is the client for creating, migrating and dropping schema. Schema *migrate.Schema + // AclEntry is the client for interacting with the AclEntry builders. + AclEntry *AclEntryClient // DavAccount is the client for interacting with the DavAccount builders. DavAccount *DavAccountClient // DirectLink is the client for interacting with the DirectLink builders. @@ -86,6 +89,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) + c.AclEntry = NewAclEntryClient(c.config) c.DavAccount = NewDavAccountClient(c.config) c.DirectLink = NewDirectLinkClient(c.config) c.Entity = NewEntityClient(c.config) @@ -195,6 +199,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { return &Tx{ ctx: ctx, config: cfg, + AclEntry: NewAclEntryClient(cfg), DavAccount: NewDavAccountClient(cfg), DirectLink: NewDirectLinkClient(cfg), Entity: NewEntityClient(cfg), @@ -231,6 +236,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) return &Tx{ ctx: ctx, config: cfg, + AclEntry: NewAclEntryClient(cfg), DavAccount: NewDavAccountClient(cfg), DirectLink: NewDirectLinkClient(cfg), Entity: NewEntityClient(cfg), @@ -254,7 +260,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) // Debug returns a new debug-client. It's used to get verbose logging on specific operations. // // client.Debug(). -// DavAccount. +// AclEntry. // Query(). // Count(ctx) func (c *Client) Debug() *Client { @@ -277,7 +283,7 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ - c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, + c.AclEntry, c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.Setting, c.Share, c.StoragePolicy, c.Task, c.User, } { @@ -289,7 +295,7 @@ func (c *Client) Use(hooks ...Hook) { // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ - c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, + c.AclEntry, c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.Setting, c.Share, c.StoragePolicy, c.Task, c.User, } { @@ -300,6 +306,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) { // Mutate implements the ent.Mutator interface. func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { + case *AclEntryMutation: + return c.AclEntry.mutate(ctx, m) case *DavAccountMutation: return c.DavAccount.mutate(ctx, m) case *DirectLinkMutation: @@ -339,6 +347,157 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { } } +// AclEntryClient is a client for the AclEntry schema. +type AclEntryClient struct { + config +} + +// NewAclEntryClient returns a client for the AclEntry from the given config. +func NewAclEntryClient(c config) *AclEntryClient { + return &AclEntryClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `aclentry.Hooks(f(g(h())))`. +func (c *AclEntryClient) Use(hooks ...Hook) { + c.hooks.AclEntry = append(c.hooks.AclEntry, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `aclentry.Intercept(f(g(h())))`. +func (c *AclEntryClient) Intercept(interceptors ...Interceptor) { + c.inters.AclEntry = append(c.inters.AclEntry, interceptors...) +} + +// Create returns a builder for creating a AclEntry entity. +func (c *AclEntryClient) Create() *AclEntryCreate { + mutation := newAclEntryMutation(c.config, OpCreate) + return &AclEntryCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AclEntry entities. +func (c *AclEntryClient) CreateBulk(builders ...*AclEntryCreate) *AclEntryCreateBulk { + return &AclEntryCreateBulk{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 *AclEntryClient) MapCreateBulk(slice any, setFunc func(*AclEntryCreate, int)) *AclEntryCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AclEntryCreateBulk{err: fmt.Errorf("calling to AclEntryClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AclEntryCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AclEntryCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AclEntry. +func (c *AclEntryClient) Update() *AclEntryUpdate { + mutation := newAclEntryMutation(c.config, OpUpdate) + return &AclEntryUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AclEntryClient) UpdateOne(ae *AclEntry) *AclEntryUpdateOne { + mutation := newAclEntryMutation(c.config, OpUpdateOne, withAclEntry(ae)) + return &AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AclEntryClient) UpdateOneID(id int) *AclEntryUpdateOne { + mutation := newAclEntryMutation(c.config, OpUpdateOne, withAclEntryID(id)) + return &AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AclEntry. +func (c *AclEntryClient) Delete() *AclEntryDelete { + mutation := newAclEntryMutation(c.config, OpDelete) + return &AclEntryDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AclEntryClient) DeleteOne(ae *AclEntry) *AclEntryDeleteOne { + return c.DeleteOneID(ae.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AclEntryClient) DeleteOneID(id int) *AclEntryDeleteOne { + builder := c.Delete().Where(aclentry.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AclEntryDeleteOne{builder} +} + +// Query returns a query builder for AclEntry. +func (c *AclEntryClient) Query() *AclEntryQuery { + return &AclEntryQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAclEntry}, + inters: c.Interceptors(), + } +} + +// Get returns a AclEntry entity by its id. +func (c *AclEntryClient) Get(ctx context.Context, id int) (*AclEntry, error) { + return c.Query().Where(aclentry.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AclEntryClient) GetX(ctx context.Context, id int) *AclEntry { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryFile queries the file edge of a AclEntry. +func (c *AclEntryClient) QueryFile(ae *AclEntry) *FileQuery { + query := (&FileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ae.ID + step := sqlgraph.NewStep( + sqlgraph.From(aclentry.Table, aclentry.FieldID, id), + sqlgraph.To(file.Table, file.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, aclentry.FileTable, aclentry.FileColumn), + ) + fromV = sqlgraph.Neighbors(ae.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AclEntryClient) Hooks() []Hook { + hooks := c.hooks.AclEntry + return append(hooks[:len(hooks):len(hooks)], aclentry.Hooks[:]...) +} + +// Interceptors returns the client interceptors. +func (c *AclEntryClient) Interceptors() []Interceptor { + inters := c.inters.AclEntry + return append(inters[:len(inters):len(inters)], aclentry.Interceptors[:]...) +} + +func (c *AclEntryClient) mutate(ctx context.Context, m *AclEntryMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AclEntryCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AclEntryUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AclEntryDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown AclEntry mutation op: %q", m.Op()) + } +} + // DavAccountClient is a client for the DavAccount schema. type DavAccountClient struct { config @@ -1044,6 +1203,22 @@ func (c *FileClient) QueryShares(f *File) *ShareQuery { return query } +// QueryACLEntries queries the acl_entries edge of a File. +func (c *FileClient) QueryACLEntries(f *File) *AclEntryQuery { + query := (&AclEntryClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := f.ID + step := sqlgraph.NewStep( + sqlgraph.From(file.Table, file.FieldID, id), + sqlgraph.To(aclentry.Table, aclentry.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, file.ACLEntriesTable, file.ACLEntriesColumn), + ) + fromV = sqlgraph.Neighbors(f.driver.Dialect(), step) + return fromV, nil + } + return query +} + // QueryDirectLinks queries the direct_links edge of a File. func (c *FileClient) QueryDirectLinks(f *File) *DirectLinkQuery { query := (&DirectLinkClient{config: c.config}).Query() @@ -3244,14 +3419,14 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) // hooks and interceptors per client, for fast access. type ( hooks struct { - DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, Metadata, - Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, StoragePolicy, Task, - User []ent.Hook + AclEntry, DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, + Metadata, Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, + StoragePolicy, Task, User []ent.Hook } inters struct { - DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, Metadata, - Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, StoragePolicy, Task, - User []ent.Interceptor + AclEntry, DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, + Metadata, Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, + StoragePolicy, Task, User []ent.Interceptor } ) diff --git a/ent/ent.go b/ent/ent.go index 80840097..2561784a 100644 --- a/ent/ent.go +++ b/ent/ent.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" @@ -89,6 +90,7 @@ var ( func checkColumn(table, column string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + aclentry.Table: aclentry.ValidColumn, davaccount.Table: davaccount.ValidColumn, directlink.Table: directlink.ValidColumn, entity.Table: entity.ValidColumn, diff --git a/ent/file.go b/ent/file.go index d65450b0..0393867c 100644 --- a/ent/file.go +++ b/ent/file.go @@ -65,11 +65,13 @@ type FileEdges struct { Entities []*Entity `json:"entities,omitempty"` // Shares holds the value of the shares edge. Shares []*Share `json:"shares,omitempty"` + // ACLEntries holds the value of the acl_entries edge. + ACLEntries []*AclEntry `json:"acl_entries,omitempty"` // DirectLinks holds the value of the direct_links edge. DirectLinks []*DirectLink `json:"direct_links,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [8]bool + loadedTypes [9]bool } // OwnerOrErr returns the Owner value or an error if the edge @@ -147,10 +149,19 @@ func (e FileEdges) SharesOrErr() ([]*Share, error) { return nil, &NotLoadedError{edge: "shares"} } +// ACLEntriesOrErr returns the ACLEntries value or an error if the edge +// was not loaded in eager-loading. +func (e FileEdges) ACLEntriesOrErr() ([]*AclEntry, error) { + if e.loadedTypes[7] { + return e.ACLEntries, nil + } + return nil, &NotLoadedError{edge: "acl_entries"} +} + // DirectLinksOrErr returns the DirectLinks value or an error if the edge // was not loaded in eager-loading. func (e FileEdges) DirectLinksOrErr() ([]*DirectLink, error) { - if e.loadedTypes[7] { + if e.loadedTypes[8] { return e.DirectLinks, nil } return nil, &NotLoadedError{edge: "direct_links"} @@ -308,6 +319,11 @@ func (f *File) QueryShares() *ShareQuery { return NewFileClient(f.config).QueryShares(f) } +// QueryACLEntries queries the "acl_entries" edge of the File entity. +func (f *File) QueryACLEntries() *AclEntryQuery { + return NewFileClient(f.config).QueryACLEntries(f) +} + // QueryDirectLinks queries the "direct_links" edge of the File entity. func (f *File) QueryDirectLinks() *DirectLinkQuery { return NewFileClient(f.config).QueryDirectLinks(f) @@ -414,10 +430,16 @@ func (e *File) SetShares(v []*Share) { e.Edges.loadedTypes[6] = true } +// SetACLEntries manually set the edge as loaded state. +func (e *File) SetACLEntries(v []*AclEntry) { + e.Edges.ACLEntries = v + e.Edges.loadedTypes[7] = true +} + // SetDirectLinks manually set the edge as loaded state. func (e *File) SetDirectLinks(v []*DirectLink) { e.Edges.DirectLinks = v - e.Edges.loadedTypes[7] = true + e.Edges.loadedTypes[8] = true } // Files is a parsable slice of File. diff --git a/ent/file/file.go b/ent/file/file.go index 61315d4c..51c319d8 100644 --- a/ent/file/file.go +++ b/ent/file/file.go @@ -51,6 +51,8 @@ const ( EdgeEntities = "entities" // EdgeShares holds the string denoting the shares edge name in mutations. EdgeShares = "shares" + // EdgeACLEntries holds the string denoting the acl_entries edge name in mutations. + EdgeACLEntries = "acl_entries" // EdgeDirectLinks holds the string denoting the direct_links edge name in mutations. EdgeDirectLinks = "direct_links" // Table holds the table name of the file in the database. @@ -96,6 +98,13 @@ const ( SharesInverseTable = "shares" // SharesColumn is the table column denoting the shares relation/edge. SharesColumn = "file_shares" + // ACLEntriesTable is the table that holds the acl_entries relation/edge. + ACLEntriesTable = "acl_entries" + // ACLEntriesInverseTable is the table name for the AclEntry entity. + // It exists in this package in order to avoid circular dependency with the "aclentry" package. + ACLEntriesInverseTable = "acl_entries" + // ACLEntriesColumn is the table column denoting the acl_entries relation/edge. + ACLEntriesColumn = "file_id" // DirectLinksTable is the table that holds the direct_links relation/edge. DirectLinksTable = "direct_links" // DirectLinksInverseTable is the table name for the DirectLink entity. @@ -289,6 +298,20 @@ func ByShares(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { } } +// ByACLEntriesCount orders the results by acl_entries count. +func ByACLEntriesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newACLEntriesStep(), opts...) + } +} + +// ByACLEntries orders the results by acl_entries terms. +func ByACLEntries(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newACLEntriesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + // ByDirectLinksCount orders the results by direct_links count. func ByDirectLinksCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { @@ -351,6 +374,13 @@ func newSharesStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, SharesTable, SharesColumn), ) } +func newACLEntriesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ACLEntriesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ACLEntriesTable, ACLEntriesColumn), + ) +} func newDirectLinksStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), diff --git a/ent/file/where.go b/ent/file/where.go index 1b90793a..9d3ee7f6 100644 --- a/ent/file/where.go +++ b/ent/file/where.go @@ -641,6 +641,29 @@ func HasSharesWith(preds ...predicate.Share) predicate.File { }) } +// HasACLEntries applies the HasEdge predicate on the "acl_entries" edge. +func HasACLEntries() predicate.File { + return predicate.File(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ACLEntriesTable, ACLEntriesColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasACLEntriesWith applies the HasEdge predicate on the "acl_entries" edge with a given conditions (other predicates). +func HasACLEntriesWith(preds ...predicate.AclEntry) predicate.File { + return predicate.File(func(s *sql.Selector) { + step := newACLEntriesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // HasDirectLinks applies the HasEdge predicate on the "direct_links" edge. func HasDirectLinks() predicate.File { return predicate.File(func(s *sql.Selector) { diff --git a/ent/file_create.go b/ent/file_create.go index 1e3470fd..3bb1dc09 100644 --- a/ent/file_create.go +++ b/ent/file_create.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/file" @@ -254,6 +255,21 @@ func (fc *FileCreate) AddShares(s ...*Share) *FileCreate { return fc.AddShareIDs(ids...) } +// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs. +func (fc *FileCreate) AddACLEntryIDs(ids ...int) *FileCreate { + fc.mutation.AddACLEntryIDs(ids...) + return fc +} + +// AddACLEntries adds the "acl_entries" edges to the AclEntry entity. +func (fc *FileCreate) AddACLEntries(a ...*AclEntry) *FileCreate { + ids := make([]int, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return fc.AddACLEntryIDs(ids...) +} + // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. func (fc *FileCreate) AddDirectLinkIDs(ids ...int) *FileCreate { fc.mutation.AddDirectLinkIDs(ids...) @@ -538,6 +554,22 @@ func (fc *FileCreate) createSpec() (*File, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := fc.mutation.ACLEntriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } if nodes := fc.mutation.DirectLinksIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/ent/file_query.go b/ent/file_query.go index a978798f..b2c84da7 100644 --- a/ent/file_query.go +++ b/ent/file_query.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/file" @@ -35,6 +36,7 @@ type FileQuery struct { withMetadata *MetadataQuery withEntities *EntityQuery withShares *ShareQuery + withACLEntries *AclEntryQuery withDirectLinks *DirectLinkQuery // intermediate query (i.e. traversal path). sql *sql.Selector @@ -226,6 +228,28 @@ func (fq *FileQuery) QueryShares() *ShareQuery { return query } +// QueryACLEntries chains the current query on the "acl_entries" edge. +func (fq *FileQuery) QueryACLEntries() *AclEntryQuery { + query := (&AclEntryClient{config: fq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := fq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := fq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(file.Table, file.FieldID, selector), + sqlgraph.To(aclentry.Table, aclentry.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, file.ACLEntriesTable, file.ACLEntriesColumn), + ) + fromU = sqlgraph.SetNeighbors(fq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // QueryDirectLinks chains the current query on the "direct_links" edge. func (fq *FileQuery) QueryDirectLinks() *DirectLinkQuery { query := (&DirectLinkClient{config: fq.config}).Query() @@ -447,6 +471,7 @@ func (fq *FileQuery) Clone() *FileQuery { withMetadata: fq.withMetadata.Clone(), withEntities: fq.withEntities.Clone(), withShares: fq.withShares.Clone(), + withACLEntries: fq.withACLEntries.Clone(), withDirectLinks: fq.withDirectLinks.Clone(), // clone intermediate query. sql: fq.sql.Clone(), @@ -531,6 +556,17 @@ func (fq *FileQuery) WithShares(opts ...func(*ShareQuery)) *FileQuery { return fq } +// WithACLEntries tells the query-builder to eager-load the nodes that are connected to +// the "acl_entries" edge. The optional arguments are used to configure the query builder of the edge. +func (fq *FileQuery) WithACLEntries(opts ...func(*AclEntryQuery)) *FileQuery { + query := (&AclEntryClient{config: fq.config}).Query() + for _, opt := range opts { + opt(query) + } + fq.withACLEntries = query + return fq +} + // WithDirectLinks tells the query-builder to eager-load the nodes that are connected to // the "direct_links" edge. The optional arguments are used to configure the query builder of the edge. func (fq *FileQuery) WithDirectLinks(opts ...func(*DirectLinkQuery)) *FileQuery { @@ -620,7 +656,7 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e var ( nodes = []*File{} _spec = fq.querySpec() - loadedTypes = [8]bool{ + loadedTypes = [9]bool{ fq.withOwner != nil, fq.withStoragePolicies != nil, fq.withParent != nil, @@ -628,6 +664,7 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e fq.withMetadata != nil, fq.withEntities != nil, fq.withShares != nil, + fq.withACLEntries != nil, fq.withDirectLinks != nil, } ) @@ -695,6 +732,13 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e return nil, err } } + if query := fq.withACLEntries; query != nil { + if err := fq.loadACLEntries(ctx, query, nodes, + func(n *File) { n.Edges.ACLEntries = []*AclEntry{} }, + func(n *File, e *AclEntry) { n.Edges.ACLEntries = append(n.Edges.ACLEntries, e) }); err != nil { + return nil, err + } + } if query := fq.withDirectLinks; query != nil { if err := fq.loadDirectLinks(ctx, query, nodes, func(n *File) { n.Edges.DirectLinks = []*DirectLink{} }, @@ -944,6 +988,36 @@ func (fq *FileQuery) loadShares(ctx context.Context, query *ShareQuery, nodes [] } return nil } +func (fq *FileQuery) loadACLEntries(ctx context.Context, query *AclEntryQuery, nodes []*File, init func(*File), assign func(*File, *AclEntry)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int]*File) + 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(aclentry.FieldFileID) + } + query.Where(predicate.AclEntry(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(file.ACLEntriesColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.FileID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "file_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (fq *FileQuery) loadDirectLinks(ctx context.Context, query *DirectLinkQuery, nodes []*File, init func(*File), assign func(*File, *DirectLink)) error { fks := make([]driver.Value, 0, len(nodes)) nodeids := make(map[int]*File) diff --git a/ent/file_update.go b/ent/file_update.go index 24ee76d7..5cc01014 100644 --- a/ent/file_update.go +++ b/ent/file_update.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/file" @@ -315,6 +316,21 @@ func (fu *FileUpdate) AddShares(s ...*Share) *FileUpdate { return fu.AddShareIDs(ids...) } +// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs. +func (fu *FileUpdate) AddACLEntryIDs(ids ...int) *FileUpdate { + fu.mutation.AddACLEntryIDs(ids...) + return fu +} + +// AddACLEntries adds the "acl_entries" edges to the AclEntry entity. +func (fu *FileUpdate) AddACLEntries(a ...*AclEntry) *FileUpdate { + ids := make([]int, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return fu.AddACLEntryIDs(ids...) +} + // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. func (fu *FileUpdate) AddDirectLinkIDs(ids ...int) *FileUpdate { fu.mutation.AddDirectLinkIDs(ids...) @@ -437,6 +453,27 @@ func (fu *FileUpdate) RemoveShares(s ...*Share) *FileUpdate { return fu.RemoveShareIDs(ids...) } +// ClearACLEntries clears all "acl_entries" edges to the AclEntry entity. +func (fu *FileUpdate) ClearACLEntries() *FileUpdate { + fu.mutation.ClearACLEntries() + return fu +} + +// RemoveACLEntryIDs removes the "acl_entries" edge to AclEntry entities by IDs. +func (fu *FileUpdate) RemoveACLEntryIDs(ids ...int) *FileUpdate { + fu.mutation.RemoveACLEntryIDs(ids...) + return fu +} + +// RemoveACLEntries removes "acl_entries" edges to AclEntry entities. +func (fu *FileUpdate) RemoveACLEntries(a ...*AclEntry) *FileUpdate { + ids := make([]int, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return fu.RemoveACLEntryIDs(ids...) +} + // ClearDirectLinks clears all "direct_links" edges to the DirectLink entity. func (fu *FileUpdate) ClearDirectLinks() *FileUpdate { fu.mutation.ClearDirectLinks() @@ -808,6 +845,51 @@ func (fu *FileUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if fu.mutation.ACLEntriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := fu.mutation.RemovedACLEntriesIDs(); len(nodes) > 0 && !fu.mutation.ACLEntriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.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 := fu.mutation.ACLEntriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if fu.mutation.DirectLinksCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -1153,6 +1235,21 @@ func (fuo *FileUpdateOne) AddShares(s ...*Share) *FileUpdateOne { return fuo.AddShareIDs(ids...) } +// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs. +func (fuo *FileUpdateOne) AddACLEntryIDs(ids ...int) *FileUpdateOne { + fuo.mutation.AddACLEntryIDs(ids...) + return fuo +} + +// AddACLEntries adds the "acl_entries" edges to the AclEntry entity. +func (fuo *FileUpdateOne) AddACLEntries(a ...*AclEntry) *FileUpdateOne { + ids := make([]int, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return fuo.AddACLEntryIDs(ids...) +} + // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. func (fuo *FileUpdateOne) AddDirectLinkIDs(ids ...int) *FileUpdateOne { fuo.mutation.AddDirectLinkIDs(ids...) @@ -1275,6 +1372,27 @@ func (fuo *FileUpdateOne) RemoveShares(s ...*Share) *FileUpdateOne { return fuo.RemoveShareIDs(ids...) } +// ClearACLEntries clears all "acl_entries" edges to the AclEntry entity. +func (fuo *FileUpdateOne) ClearACLEntries() *FileUpdateOne { + fuo.mutation.ClearACLEntries() + return fuo +} + +// RemoveACLEntryIDs removes the "acl_entries" edge to AclEntry entities by IDs. +func (fuo *FileUpdateOne) RemoveACLEntryIDs(ids ...int) *FileUpdateOne { + fuo.mutation.RemoveACLEntryIDs(ids...) + return fuo +} + +// RemoveACLEntries removes "acl_entries" edges to AclEntry entities. +func (fuo *FileUpdateOne) RemoveACLEntries(a ...*AclEntry) *FileUpdateOne { + ids := make([]int, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return fuo.RemoveACLEntryIDs(ids...) +} + // ClearDirectLinks clears all "direct_links" edges to the DirectLink entity. func (fuo *FileUpdateOne) ClearDirectLinks() *FileUpdateOne { fuo.mutation.ClearDirectLinks() @@ -1676,6 +1794,51 @@ func (fuo *FileUpdateOne) sqlSave(ctx context.Context) (_node *File, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if fuo.mutation.ACLEntriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := fuo.mutation.RemovedACLEntriesIDs(); len(nodes) > 0 && !fuo.mutation.ACLEntriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.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 := fuo.mutation.ACLEntriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: file.ACLEntriesTable, + Columns: []string{file.ACLEntriesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if fuo.mutation.DirectLinksCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/ent/hook/hook.go b/ent/hook/hook.go index 8e60de03..5864e546 100644 --- a/ent/hook/hook.go +++ b/ent/hook/hook.go @@ -9,6 +9,18 @@ import ( "github.com/cloudreve/Cloudreve/v4/ent" ) +// The AclEntryFunc type is an adapter to allow the use of ordinary +// function as AclEntry mutator. +type AclEntryFunc func(context.Context, *ent.AclEntryMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f AclEntryFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.AclEntryMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AclEntryMutation", m) +} + // The DavAccountFunc type is an adapter to allow the use of ordinary // function as DavAccount mutator. type DavAccountFunc func(context.Context, *ent.DavAccountMutation) (ent.Value, error) diff --git a/ent/intercept/intercept.go b/ent/intercept/intercept.go index 6c1d9c86..944aba9e 100644 --- a/ent/intercept/intercept.go +++ b/ent/intercept/intercept.go @@ -8,6 +8,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" @@ -84,6 +85,33 @@ func (f TraverseFunc) Traverse(ctx context.Context, q ent.Query) error { return f(ctx, query) } +// The AclEntryFunc type is an adapter to allow the use of ordinary function as a Querier. +type AclEntryFunc func(context.Context, *ent.AclEntryQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f AclEntryFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.AclEntryQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.AclEntryQuery", q) +} + +// The TraverseAclEntry type is an adapter to allow the use of ordinary function as Traverser. +type TraverseAclEntry func(context.Context, *ent.AclEntryQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraverseAclEntry) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraverseAclEntry) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.AclEntryQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.AclEntryQuery", q) +} + // The DavAccountFunc type is an adapter to allow the use of ordinary function as a Querier. type DavAccountFunc func(context.Context, *ent.DavAccountQuery) (ent.Value, error) @@ -546,6 +574,8 @@ func (f TraverseUser) Traverse(ctx context.Context, q ent.Query) error { // NewQuery returns the generic Query interface for the given typed query. func NewQuery(q ent.Query) (Query, error) { switch q := q.(type) { + case *ent.AclEntryQuery: + return &query[*ent.AclEntryQuery, predicate.AclEntry, aclentry.OrderOption]{typ: ent.TypeAclEntry, tq: q}, nil case *ent.DavAccountQuery: return &query[*ent.DavAccountQuery, predicate.DavAccount, davaccount.OrderOption]{typ: ent.TypeDavAccount, tq: q}, nil case *ent.DirectLinkQuery: diff --git a/ent/internal/schema.go b/ent/internal/schema.go index e4a28cef..9971a43b 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\":\"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\":\"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\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\"},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_id\",\"ref_name\":\"groups\",\"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\":\"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}],\"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\":\"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\":5,\"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\":\"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\"}],\"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\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"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\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}}],\"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\":\"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\":\"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\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\"},{\"name\":\"storage_policies\",\"type\":\"StoragePolicy\",\"field\":\"storage_policy_id\",\"ref_name\":\"groups\",\"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\":\"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}],\"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\":\"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\":5,\"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\":\"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\"}],\"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\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":9,\"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\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}}],\"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 cb5031ba..a5b568e2 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -8,6 +8,38 @@ import ( ) var ( + // ACLEntriesColumns holds the columns for the "acl_entries" table. + ACLEntriesColumns = []*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: "subject_type", Type: field.TypeEnum, Enums: []string{"user", "group", "anonymous", "everyone"}}, + {Name: "subject_id", Type: field.TypeInt, Nullable: true}, + {Name: "permissions", Type: field.TypeBytes}, + {Name: "file_id", Type: field.TypeInt}, + } + // ACLEntriesTable holds the schema information for the "acl_entries" table. + ACLEntriesTable = &schema.Table{ + Name: "acl_entries", + Columns: ACLEntriesColumns, + PrimaryKey: []*schema.Column{ACLEntriesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "acl_entries_files_acl_entries", + Columns: []*schema.Column{ACLEntriesColumns[7]}, + RefColumns: []*schema.Column{FilesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "aclentry_file_id_subject_type_subject_id", + Unique: true, + Columns: []*schema.Column{ACLEntriesColumns[7], ACLEntriesColumns[4], ACLEntriesColumns[5]}, + }, + }, + } // DavAccountsColumns holds the columns for the "dav_accounts" table. DavAccountsColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, @@ -552,6 +584,7 @@ var ( } // Tables holds all the tables in the schema. Tables = []*schema.Table{ + ACLEntriesTable, DavAccountsTable, DirectLinksTable, EntitiesTable, @@ -574,6 +607,7 @@ var ( ) func init() { + ACLEntriesTable.ForeignKeys[0].RefTable = FilesTable DavAccountsTable.ForeignKeys[0].RefTable = UsersTable DirectLinksTable.ForeignKeys[0].RefTable = FilesTable EntitiesTable.ForeignKeys[0].RefTable = StoragePoliciesTable diff --git a/ent/mutation.go b/ent/mutation.go index 46ef5404..b1c7953e 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" @@ -44,6 +45,7 @@ const ( OpUpdateOne = ent.OpUpdateOne // Node types. + TypeAclEntry = "AclEntry" TypeDavAccount = "DavAccount" TypeDirectLink = "DirectLink" TypeEntity = "Entity" @@ -63,6 +65,788 @@ const ( TypeUser = "User" ) +// AclEntryMutation represents an operation that mutates the AclEntry nodes in the graph. +type AclEntryMutation struct { + config + op Op + typ string + id *int + created_at *time.Time + updated_at *time.Time + deleted_at *time.Time + subject_type *aclentry.SubjectType + subject_id *int + addsubject_id *int + permissions **boolset.BooleanSet + clearedFields map[string]struct{} + file *int + clearedfile bool + done bool + oldValue func(context.Context) (*AclEntry, error) + predicates []predicate.AclEntry +} + +var _ ent.Mutation = (*AclEntryMutation)(nil) + +// aclentryOption allows management of the mutation configuration using functional options. +type aclentryOption func(*AclEntryMutation) + +// newAclEntryMutation creates new mutation for the AclEntry entity. +func newAclEntryMutation(c config, op Op, opts ...aclentryOption) *AclEntryMutation { + m := &AclEntryMutation{ + config: c, + op: op, + typ: TypeAclEntry, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAclEntryID sets the ID field of the mutation. +func withAclEntryID(id int) aclentryOption { + return func(m *AclEntryMutation) { + var ( + err error + once sync.Once + value *AclEntry + ) + m.oldValue = func(ctx context.Context) (*AclEntry, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AclEntry.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAclEntry sets the old AclEntry of the mutation. +func withAclEntry(node *AclEntry) aclentryOption { + return func(m *AclEntryMutation) { + m.oldValue = func(context.Context) (*AclEntry, 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 AclEntryMutation) 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 AclEntryMutation) 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 *AclEntryMutation) 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 *AclEntryMutation) 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().AclEntry.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 *AclEntryMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AclEntryMutation) 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 AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) 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 *AclEntryMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AclEntryMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AclEntryMutation) 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 AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) 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 *AclEntryMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetDeletedAt sets the "deleted_at" field. +func (m *AclEntryMutation) SetDeletedAt(t time.Time) { + m.deleted_at = &t +} + +// DeletedAt returns the value of the "deleted_at" field in the mutation. +func (m *AclEntryMutation) 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 AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) 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 *AclEntryMutation) ClearDeletedAt() { + m.deleted_at = nil + m.clearedFields[aclentry.FieldDeletedAt] = struct{}{} +} + +// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation. +func (m *AclEntryMutation) DeletedAtCleared() bool { + _, ok := m.clearedFields[aclentry.FieldDeletedAt] + return ok +} + +// ResetDeletedAt resets all changes to the "deleted_at" field. +func (m *AclEntryMutation) ResetDeletedAt() { + m.deleted_at = nil + delete(m.clearedFields, aclentry.FieldDeletedAt) +} + +// SetFileID sets the "file_id" field. +func (m *AclEntryMutation) SetFileID(i int) { + m.file = &i +} + +// FileID returns the value of the "file_id" field in the mutation. +func (m *AclEntryMutation) FileID() (r int, exists bool) { + v := m.file + if v == nil { + return + } + return *v, true +} + +// OldFileID returns the old "file_id" field's value of the AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) OldFileID(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFileID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFileID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFileID: %w", err) + } + return oldValue.FileID, nil +} + +// ResetFileID resets all changes to the "file_id" field. +func (m *AclEntryMutation) ResetFileID() { + m.file = nil +} + +// SetSubjectType sets the "subject_type" field. +func (m *AclEntryMutation) SetSubjectType(at aclentry.SubjectType) { + m.subject_type = &at +} + +// SubjectType returns the value of the "subject_type" field in the mutation. +func (m *AclEntryMutation) SubjectType() (r aclentry.SubjectType, exists bool) { + v := m.subject_type + if v == nil { + return + } + return *v, true +} + +// OldSubjectType returns the old "subject_type" field's value of the AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) OldSubjectType(ctx context.Context) (v aclentry.SubjectType, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSubjectType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSubjectType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSubjectType: %w", err) + } + return oldValue.SubjectType, nil +} + +// ResetSubjectType resets all changes to the "subject_type" field. +func (m *AclEntryMutation) ResetSubjectType() { + m.subject_type = nil +} + +// SetSubjectID sets the "subject_id" field. +func (m *AclEntryMutation) SetSubjectID(i int) { + m.subject_id = &i + m.addsubject_id = nil +} + +// SubjectID returns the value of the "subject_id" field in the mutation. +func (m *AclEntryMutation) SubjectID() (r int, exists bool) { + v := m.subject_id + if v == nil { + return + } + return *v, true +} + +// OldSubjectID returns the old "subject_id" field's value of the AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) OldSubjectID(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSubjectID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSubjectID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSubjectID: %w", err) + } + return oldValue.SubjectID, nil +} + +// AddSubjectID adds i to the "subject_id" field. +func (m *AclEntryMutation) AddSubjectID(i int) { + if m.addsubject_id != nil { + *m.addsubject_id += i + } else { + m.addsubject_id = &i + } +} + +// AddedSubjectID returns the value that was added to the "subject_id" field in this mutation. +func (m *AclEntryMutation) AddedSubjectID() (r int, exists bool) { + v := m.addsubject_id + if v == nil { + return + } + return *v, true +} + +// ClearSubjectID clears the value of the "subject_id" field. +func (m *AclEntryMutation) ClearSubjectID() { + m.subject_id = nil + m.addsubject_id = nil + m.clearedFields[aclentry.FieldSubjectID] = struct{}{} +} + +// SubjectIDCleared returns if the "subject_id" field was cleared in this mutation. +func (m *AclEntryMutation) SubjectIDCleared() bool { + _, ok := m.clearedFields[aclentry.FieldSubjectID] + return ok +} + +// ResetSubjectID resets all changes to the "subject_id" field. +func (m *AclEntryMutation) ResetSubjectID() { + m.subject_id = nil + m.addsubject_id = nil + delete(m.clearedFields, aclentry.FieldSubjectID) +} + +// SetPermissions sets the "permissions" field. +func (m *AclEntryMutation) SetPermissions(bs *boolset.BooleanSet) { + m.permissions = &bs +} + +// Permissions returns the value of the "permissions" field in the mutation. +func (m *AclEntryMutation) Permissions() (r *boolset.BooleanSet, exists bool) { + v := m.permissions + if v == nil { + return + } + return *v, true +} + +// OldPermissions returns the old "permissions" field's value of the AclEntry entity. +// If the AclEntry 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 *AclEntryMutation) OldPermissions(ctx context.Context) (v *boolset.BooleanSet, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPermissions is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPermissions requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPermissions: %w", err) + } + return oldValue.Permissions, nil +} + +// ResetPermissions resets all changes to the "permissions" field. +func (m *AclEntryMutation) ResetPermissions() { + m.permissions = nil +} + +// ClearFile clears the "file" edge to the File entity. +func (m *AclEntryMutation) ClearFile() { + m.clearedfile = true + m.clearedFields[aclentry.FieldFileID] = struct{}{} +} + +// FileCleared reports if the "file" edge to the File entity was cleared. +func (m *AclEntryMutation) FileCleared() bool { + return m.clearedfile +} + +// FileIDs returns the "file" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// FileID instead. It exists only for internal usage by the builders. +func (m *AclEntryMutation) FileIDs() (ids []int) { + if id := m.file; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetFile resets all changes to the "file" edge. +func (m *AclEntryMutation) ResetFile() { + m.file = nil + m.clearedfile = false +} + +// Where appends a list predicates to the AclEntryMutation builder. +func (m *AclEntryMutation) Where(ps ...predicate.AclEntry) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AclEntryMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AclEntryMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AclEntry, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AclEntryMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AclEntryMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (AclEntry). +func (m *AclEntryMutation) 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 *AclEntryMutation) Fields() []string { + fields := make([]string, 0, 7) + if m.created_at != nil { + fields = append(fields, aclentry.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, aclentry.FieldUpdatedAt) + } + if m.deleted_at != nil { + fields = append(fields, aclentry.FieldDeletedAt) + } + if m.file != nil { + fields = append(fields, aclentry.FieldFileID) + } + if m.subject_type != nil { + fields = append(fields, aclentry.FieldSubjectType) + } + if m.subject_id != nil { + fields = append(fields, aclentry.FieldSubjectID) + } + if m.permissions != nil { + fields = append(fields, aclentry.FieldPermissions) + } + 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 *AclEntryMutation) Field(name string) (ent.Value, bool) { + switch name { + case aclentry.FieldCreatedAt: + return m.CreatedAt() + case aclentry.FieldUpdatedAt: + return m.UpdatedAt() + case aclentry.FieldDeletedAt: + return m.DeletedAt() + case aclentry.FieldFileID: + return m.FileID() + case aclentry.FieldSubjectType: + return m.SubjectType() + case aclentry.FieldSubjectID: + return m.SubjectID() + case aclentry.FieldPermissions: + return m.Permissions() + } + 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 *AclEntryMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case aclentry.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case aclentry.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case aclentry.FieldDeletedAt: + return m.OldDeletedAt(ctx) + case aclentry.FieldFileID: + return m.OldFileID(ctx) + case aclentry.FieldSubjectType: + return m.OldSubjectType(ctx) + case aclentry.FieldSubjectID: + return m.OldSubjectID(ctx) + case aclentry.FieldPermissions: + return m.OldPermissions(ctx) + } + return nil, fmt.Errorf("unknown AclEntry 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 *AclEntryMutation) SetField(name string, value ent.Value) error { + switch name { + case aclentry.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 aclentry.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 aclentry.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 aclentry.FieldFileID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFileID(v) + return nil + case aclentry.FieldSubjectType: + v, ok := value.(aclentry.SubjectType) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubjectType(v) + return nil + case aclentry.FieldSubjectID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubjectID(v) + return nil + case aclentry.FieldPermissions: + v, ok := value.(*boolset.BooleanSet) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPermissions(v) + return nil + } + return fmt.Errorf("unknown AclEntry field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AclEntryMutation) AddedFields() []string { + var fields []string + if m.addsubject_id != nil { + fields = append(fields, aclentry.FieldSubjectID) + } + 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 *AclEntryMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case aclentry.FieldSubjectID: + return m.AddedSubjectID() + } + 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 *AclEntryMutation) AddField(name string, value ent.Value) error { + switch name { + case aclentry.FieldSubjectID: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSubjectID(v) + return nil + } + return fmt.Errorf("unknown AclEntry numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AclEntryMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(aclentry.FieldDeletedAt) { + fields = append(fields, aclentry.FieldDeletedAt) + } + if m.FieldCleared(aclentry.FieldSubjectID) { + fields = append(fields, aclentry.FieldSubjectID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AclEntryMutation) 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 *AclEntryMutation) ClearField(name string) error { + switch name { + case aclentry.FieldDeletedAt: + m.ClearDeletedAt() + return nil + case aclentry.FieldSubjectID: + m.ClearSubjectID() + return nil + } + return fmt.Errorf("unknown AclEntry 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 *AclEntryMutation) ResetField(name string) error { + switch name { + case aclentry.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case aclentry.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case aclentry.FieldDeletedAt: + m.ResetDeletedAt() + return nil + case aclentry.FieldFileID: + m.ResetFileID() + return nil + case aclentry.FieldSubjectType: + m.ResetSubjectType() + return nil + case aclentry.FieldSubjectID: + m.ResetSubjectID() + return nil + case aclentry.FieldPermissions: + m.ResetPermissions() + return nil + } + return fmt.Errorf("unknown AclEntry field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AclEntryMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.file != nil { + edges = append(edges, aclentry.EdgeFile) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AclEntryMutation) AddedIDs(name string) []ent.Value { + switch name { + case aclentry.EdgeFile: + if id := m.file; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AclEntryMutation) 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 *AclEntryMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AclEntryMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedfile { + edges = append(edges, aclentry.EdgeFile) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AclEntryMutation) EdgeCleared(name string) bool { + switch name { + case aclentry.EdgeFile: + return m.clearedfile + } + 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 *AclEntryMutation) ClearEdge(name string) error { + switch name { + case aclentry.EdgeFile: + m.ClearFile() + return nil + } + return fmt.Errorf("unknown AclEntry 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 *AclEntryMutation) ResetEdge(name string) error { + switch name { + case aclentry.EdgeFile: + m.ResetFile() + return nil + } + return fmt.Errorf("unknown AclEntry edge %s", name) +} + // DavAccountMutation represents an operation that mutates the DavAccount nodes in the graph. type DavAccountMutation struct { config @@ -3008,6 +3792,9 @@ type FileMutation struct { shares map[int]struct{} removedshares map[int]struct{} clearedshares bool + acl_entries map[int]struct{} + removedacl_entries map[int]struct{} + clearedacl_entries bool direct_links map[int]struct{} removeddirect_links map[int]struct{} cleareddirect_links bool @@ -3946,6 +4733,60 @@ func (m *FileMutation) ResetShares() { m.removedshares = nil } +// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by ids. +func (m *FileMutation) AddACLEntryIDs(ids ...int) { + if m.acl_entries == nil { + m.acl_entries = make(map[int]struct{}) + } + for i := range ids { + m.acl_entries[ids[i]] = struct{}{} + } +} + +// ClearACLEntries clears the "acl_entries" edge to the AclEntry entity. +func (m *FileMutation) ClearACLEntries() { + m.clearedacl_entries = true +} + +// ACLEntriesCleared reports if the "acl_entries" edge to the AclEntry entity was cleared. +func (m *FileMutation) ACLEntriesCleared() bool { + return m.clearedacl_entries +} + +// RemoveACLEntryIDs removes the "acl_entries" edge to the AclEntry entity by IDs. +func (m *FileMutation) RemoveACLEntryIDs(ids ...int) { + if m.removedacl_entries == nil { + m.removedacl_entries = make(map[int]struct{}) + } + for i := range ids { + delete(m.acl_entries, ids[i]) + m.removedacl_entries[ids[i]] = struct{}{} + } +} + +// RemovedACLEntries returns the removed IDs of the "acl_entries" edge to the AclEntry entity. +func (m *FileMutation) RemovedACLEntriesIDs() (ids []int) { + for id := range m.removedacl_entries { + ids = append(ids, id) + } + return +} + +// ACLEntriesIDs returns the "acl_entries" edge IDs in the mutation. +func (m *FileMutation) ACLEntriesIDs() (ids []int) { + for id := range m.acl_entries { + ids = append(ids, id) + } + return +} + +// ResetACLEntries resets all changes to the "acl_entries" edge. +func (m *FileMutation) ResetACLEntries() { + m.acl_entries = nil + m.clearedacl_entries = false + m.removedacl_entries = nil +} + // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by ids. func (m *FileMutation) AddDirectLinkIDs(ids ...int) { if m.direct_links == nil { @@ -4369,7 +5210,7 @@ func (m *FileMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *FileMutation) AddedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.owner != nil { edges = append(edges, file.EdgeOwner) } @@ -4391,6 +5232,9 @@ func (m *FileMutation) AddedEdges() []string { if m.shares != nil { edges = append(edges, file.EdgeShares) } + if m.acl_entries != nil { + edges = append(edges, file.EdgeACLEntries) + } if m.direct_links != nil { edges = append(edges, file.EdgeDirectLinks) } @@ -4437,6 +5281,12 @@ func (m *FileMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case file.EdgeACLEntries: + ids := make([]ent.Value, 0, len(m.acl_entries)) + for id := range m.acl_entries { + ids = append(ids, id) + } + return ids case file.EdgeDirectLinks: ids := make([]ent.Value, 0, len(m.direct_links)) for id := range m.direct_links { @@ -4449,7 +5299,7 @@ func (m *FileMutation) AddedIDs(name string) []ent.Value { // RemovedEdges returns all edge names that were removed in this mutation. func (m *FileMutation) RemovedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.removedchildren != nil { edges = append(edges, file.EdgeChildren) } @@ -4462,6 +5312,9 @@ func (m *FileMutation) RemovedEdges() []string { if m.removedshares != nil { edges = append(edges, file.EdgeShares) } + if m.removedacl_entries != nil { + edges = append(edges, file.EdgeACLEntries) + } if m.removeddirect_links != nil { edges = append(edges, file.EdgeDirectLinks) } @@ -4496,6 +5349,12 @@ func (m *FileMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case file.EdgeACLEntries: + ids := make([]ent.Value, 0, len(m.removedacl_entries)) + for id := range m.removedacl_entries { + ids = append(ids, id) + } + return ids case file.EdgeDirectLinks: ids := make([]ent.Value, 0, len(m.removeddirect_links)) for id := range m.removeddirect_links { @@ -4508,7 +5367,7 @@ func (m *FileMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *FileMutation) ClearedEdges() []string { - edges := make([]string, 0, 8) + edges := make([]string, 0, 9) if m.clearedowner { edges = append(edges, file.EdgeOwner) } @@ -4530,6 +5389,9 @@ func (m *FileMutation) ClearedEdges() []string { if m.clearedshares { edges = append(edges, file.EdgeShares) } + if m.clearedacl_entries { + edges = append(edges, file.EdgeACLEntries) + } if m.cleareddirect_links { edges = append(edges, file.EdgeDirectLinks) } @@ -4554,6 +5416,8 @@ func (m *FileMutation) EdgeCleared(name string) bool { return m.clearedentities case file.EdgeShares: return m.clearedshares + case file.EdgeACLEntries: + return m.clearedacl_entries case file.EdgeDirectLinks: return m.cleareddirect_links } @@ -4602,6 +5466,9 @@ func (m *FileMutation) ResetEdge(name string) error { case file.EdgeShares: m.ResetShares() return nil + case file.EdgeACLEntries: + m.ResetACLEntries() + return nil case file.EdgeDirectLinks: m.ResetDirectLinks() return nil diff --git a/ent/mutationhelper.go b/ent/mutationhelper.go index 08964da8..da2f1384 100644 --- a/ent/mutationhelper.go +++ b/ent/mutationhelper.go @@ -4,6 +4,12 @@ package ent // SetUpdatedAt sets the "updated_at" field. +func (m *AclEntryMutation) SetRawID(t int) { + m.id = &t +} + +// SetUpdatedAt sets the "updated_at" field. + func (m *DavAccountMutation) SetRawID(t int) { m.id = &t } diff --git a/ent/predicate/predicate.go b/ent/predicate/predicate.go index dc8b3716..32d1b58f 100644 --- a/ent/predicate/predicate.go +++ b/ent/predicate/predicate.go @@ -6,6 +6,9 @@ import ( "entgo.io/ent/dialect/sql" ) +// AclEntry is the predicate function for aclentry builders. +type AclEntry func(*sql.Selector) + // DavAccount is the predicate function for davaccount builders. type DavAccount func(*sql.Selector) diff --git a/ent/runtime/runtime.go b/ent/runtime/runtime.go index 34e079c7..78e22514 100644 --- a/ent/runtime/runtime.go +++ b/ent/runtime/runtime.go @@ -5,6 +5,7 @@ package runtime import ( "time" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" @@ -30,6 +31,25 @@ import ( // (default values, validators, hooks and policies) and stitches it // to their package variables. func init() { + aclentryMixin := schema.AclEntry{}.Mixin() + aclentryMixinHooks0 := aclentryMixin[0].Hooks() + aclentry.Hooks[0] = aclentryMixinHooks0[0] + aclentryMixinInters0 := aclentryMixin[0].Interceptors() + aclentry.Interceptors[0] = aclentryMixinInters0[0] + aclentryMixinFields0 := aclentryMixin[0].Fields() + _ = aclentryMixinFields0 + aclentryFields := schema.AclEntry{}.Fields() + _ = aclentryFields + // aclentryDescCreatedAt is the schema descriptor for created_at field. + aclentryDescCreatedAt := aclentryMixinFields0[0].Descriptor() + // aclentry.DefaultCreatedAt holds the default value on creation for the created_at field. + aclentry.DefaultCreatedAt = aclentryDescCreatedAt.Default.(func() time.Time) + // aclentryDescUpdatedAt is the schema descriptor for updated_at field. + aclentryDescUpdatedAt := aclentryMixinFields0[1].Descriptor() + // aclentry.DefaultUpdatedAt holds the default value on creation for the updated_at field. + aclentry.DefaultUpdatedAt = aclentryDescUpdatedAt.Default.(func() time.Time) + // aclentry.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + aclentry.UpdateDefaultUpdatedAt = aclentryDescUpdatedAt.UpdateDefault.(func() time.Time) davaccountMixin := schema.DavAccount{}.Mixin() davaccountMixinHooks0 := davaccountMixin[0].Hooks() davaccount.Hooks[0] = davaccountMixinHooks0[0] diff --git a/ent/schema/aclentry.go b/ent/schema/aclentry.go new file mode 100644 index 00000000..5aff8492 --- /dev/null +++ b/ent/schema/aclentry.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" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" +) + +// AclEntry holds the schema definition for per-file access control entries. +// Each row grants a capability bitmask (read/create/update/delete) on a file +// or directory to a subject: a specific user, a user group, anonymous +// visitors, or every other authenticated user. +type AclEntry struct { + ent.Schema +} + +// Fields of the AclEntry. +func (AclEntry) Fields() []ent.Field { + return []ent.Field{ + field.Int("file_id"), + field.Enum("subject_type"). + Values("user", "group", "anonymous", "everyone"), + field.Int("subject_id"). + Optional(), + field.Bytes("permissions").GoType(&boolset.BooleanSet{}), + } +} + +// Edges of the AclEntry. +func (AclEntry) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("file", File.Type). + Ref("acl_entries"). + Field("file_id"). + Unique(). + Required(), + } +} + +// Indexes of the AclEntry. +func (AclEntry) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("file_id", "subject_type", "subject_id"). + Unique(), + } +} + +func (AclEntry) Mixin() []ent.Mixin { + return []ent.Mixin{ + CommonMixin{}, + } +} diff --git a/ent/schema/file.go b/ent/schema/file.go index 2fe50441..7728ffd3 100644 --- a/ent/schema/file.go +++ b/ent/schema/file.go @@ -68,6 +68,7 @@ func (File) Edges() []ent.Edge { edge.To("metadata", Metadata.Type), edge.To("entities", Entity.Type), edge.To("shares", Share.Type), + edge.To("acl_entries", AclEntry.Type), edge.To("direct_links", DirectLink.Type), } } diff --git a/ent/tx.go b/ent/tx.go index 8d6aa9da..78cf74f5 100644 --- a/ent/tx.go +++ b/ent/tx.go @@ -14,6 +14,8 @@ import ( // Tx is a transactional client that is created by calling Client.Tx(). type Tx struct { config + // AclEntry is the client for interacting with the AclEntry builders. + AclEntry *AclEntryClient // DavAccount is the client for interacting with the DavAccount builders. DavAccount *DavAccountClient // DirectLink is the client for interacting with the DirectLink builders. @@ -179,6 +181,7 @@ func (tx *Tx) Client() *Client { } func (tx *Tx) init() { + tx.AclEntry = NewAclEntryClient(tx.config) tx.DavAccount = NewDavAccountClient(tx.config) tx.DirectLink = NewDirectLinkClient(tx.config) tx.Entity = NewEntityClient(tx.config) @@ -205,7 +208,7 @@ func (tx *Tx) init() { // of them in order to commit or rollback the transaction. // // If a closed transaction is embedded in one of the generated entities, and the entity -// applies a query, for example: DavAccount.QueryXXX(), the query will be executed +// applies a query, for example: AclEntry.QueryXXX(), the query will be executed // through the driver which created this transaction. // // Note that txDriver is not goroutine safe. diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json index 7d5b97f8..b6ecd211 100644 --- a/frontend/public/locales/en-US/application.json +++ b/frontend/public/locales/en-US/application.json @@ -179,6 +179,15 @@ "typeToSearch": "Enter name or email...", "searchProperty": "Search files with the same property", "permissions": "Permissions", + "aclDes": "Granular permissions for \"{{name}}\". Listed entries override the share link's default permissions for matching visitors.", + "aclSubject": "Subject", + "aclAnonymous": "Anonymous visitors", + "aclEveryone": "Everyone else", + "aclGroup": "group", + "aclPerm_read": "Read", + "aclPerm_create": "Create", + "aclPerm_update": "Update", + "aclPerm_delete": "Delete", "quality": "Quality", "audioTrack": "Audio", "auto": "Auto", diff --git a/frontend/public/locales/en-US/dashboard.json b/frontend/public/locales/en-US/dashboard.json index 0eb9e93a..7847aeb0 100644 --- a/frontend/public/locales/en-US/dashboard.json +++ b/frontend/public/locales/en-US/dashboard.json @@ -1338,6 +1338,8 @@ "allNodes": "All nodes", "esclateAnonymity": "Escalate anonymity", "esclateAnonymityDes": "When enabled, users can assign higher permissions for anonymous users (write/delete/create). When disabled, users can only assign read-only permission for anonymous users. Changing this setting will not affect existing sharing links or files.", + "setExplicitUser": "Set file permissions", + "setExplicitUserDes": "Members can set granular read/create/update/delete permissions on files and folders they own, for specific users, groups, anonymous visitors, or everyone else.", "defaultPinned": "Default pinned shares", "defaultPinnedDes": "Users of this group will always see these shared links pinned at the top of their sidebar. Users cannot unpin them. Search shared links by their numeric ID, which can be found in the leftmost column of the <0>share list.", "pinnedShare": "Share", diff --git a/frontend/public/locales/zh-CN/application.json b/frontend/public/locales/zh-CN/application.json index dae65b0b..6ab60abc 100644 --- a/frontend/public/locales/zh-CN/application.json +++ b/frontend/public/locales/zh-CN/application.json @@ -179,6 +179,15 @@ "typeToSearch": "输入昵称或邮箱...", "searchProperty": "搜索相同属性的文件", "permissions": "权限", + "aclDes": "\"{{name}}\" 的细粒度权限。列出的条目将覆盖分享链接默认权限,适用于匹配的访客。", + "aclSubject": "对象", + "aclAnonymous": "匿名访客", + "aclEveryone": "其他所有人", + "aclGroup": "用户组", + "aclPerm_read": "读取", + "aclPerm_create": "创建", + "aclPerm_update": "更新", + "aclPerm_delete": "删除", "quality": "清晰度", "audioTrack": "音轨", "auto": "自动", diff --git a/frontend/public/locales/zh-CN/dashboard.json b/frontend/public/locales/zh-CN/dashboard.json index f76b053e..735d6670 100644 --- a/frontend/public/locales/zh-CN/dashboard.json +++ b/frontend/public/locales/zh-CN/dashboard.json @@ -1338,6 +1338,8 @@ "allNodes": "所有节点", "esclateAnonymity": "提升匿名用户权限", "esclateAnonymityDes": "开启后,用户可以为匿名用户设置更高权限 (修改/创建/删除);关闭后,用户最高只能赋予匿名用户只读权限。更改此设置不会影响已设置的分享链接或文件。", + "setExplicitUser": "设置文件权限", + "setExplicitUserDes": "开启后,用户组成员可以为其拥有的文件和文件夹设置细粒度权限(读取/创建/更新/删除),对象可为指定用户、用户组、匿名访客或所有其他人。", "defaultPinned": "默认固定分享", "defaultPinnedDes": "此用户组的用户登录后,侧边栏会始终在最顶部展示这些分享快捷方式,用户无法取消固定。请通过数字 ID 搜索分享链接,你可在 <0>分享列表 最左侧看到数字 ID。", "pinnedShare": "分享", diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index 0affced9..a42dc246 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -44,6 +44,9 @@ import { User as UserEnt, } from "./dashboard.ts"; import { + AclEntry, + AclSubject, + AclUpsertService, ArchiveListFilesResponse, ArchiveListFilesService, CreateFileService, @@ -677,6 +680,75 @@ export function getFileEntityUrl(req: FileURLService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/acl", + { + method: "GET", + params: { uri }, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertAclEntry(req: AclUpsertService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/acl", + { + method: "PUT", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function deleteAclEntry(uri: string, id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/acl", + { + method: "DELETE", + params: { uri, id }, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function searchAclSubjects(keyword: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/acl/subjects", + { + method: "GET", + params: { keyword }, + }, + { + ...defaultOpts, + bypassSnackbar: () => true, + }, + ), + ); + }; +} + export function getFileInfo(req: GetFileInfoService, skipError = false): ThunkResponse { return async (dispatch, _getState) => { return await dispatch( diff --git a/frontend/src/api/explorer.ts b/frontend/src/api/explorer.ts index bf75c67f..b85f4d35 100644 --- a/frontend/src/api/explorer.ts +++ b/frontend/src/api/explorer.ts @@ -286,6 +286,31 @@ export const NavigatorCapability = { enter_folder: 23, }; +export type AclPermissionKey = "read" | "create" | "update" | "delete"; + +export type AclSubjectType = "user" | "group" | "anonymous" | "everyone"; + +export interface AclEntry { + id: number; + subject_type: AclSubjectType; + subject_id: number; + subject_name?: string; + permissions: AclPermissionKey[]; +} + +export interface AclSubject { + type: "user" | "group"; + id: number; + name: string; +} + +export interface AclUpsertService { + uri: string; + subject_type: AclSubjectType; + subject_id?: number; + permissions: AclPermissionKey[]; +} + export interface PinFileService { uri: string; name?: string; diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts index c7caa780..7b5c7a96 100644 --- a/frontend/src/api/user.ts +++ b/frontend/src/api/user.ts @@ -96,6 +96,7 @@ export const GroupPermission = { remote_download: 9, redirected_source: 11, advance_delete: 12, + set_explicit_user: 15, unique_direct_link: 17, webdav_read_only: 18, admin_users: 19, diff --git a/frontend/src/component/Admin/Group/EditGroup/ShareSection.tsx b/frontend/src/component/Admin/Group/EditGroup/ShareSection.tsx index 047692e2..60a42a24 100644 --- a/frontend/src/component/Admin/Group/EditGroup/ShareSection.tsx +++ b/frontend/src/component/Admin/Group/EditGroup/ShareSection.tsx @@ -37,6 +37,18 @@ const ShareSection = () => { [setGroup], ); + const onSetExplicitUserChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions) + .set(GroupPermission.set_explicit_user, e.target.checked) + .toString(), + })); + }, + [setGroup], + ); + return ( @@ -84,14 +96,19 @@ const ShareSection = () => { } + control={ + + } label={ - {t("group.esclateAnonymity")} + {t("group.setExplicitUser")} } /> - {t("group.esclateAnonymityDes")} + {t("group.setExplicitUserDes")} )} diff --git a/frontend/src/component/FileManager/ContextMenu/MoreMenuItems.tsx b/frontend/src/component/FileManager/ContextMenu/MoreMenuItems.tsx index 75b49093..848e94c2 100644 --- a/frontend/src/component/FileManager/ContextMenu/MoreMenuItems.tsx +++ b/frontend/src/component/FileManager/ContextMenu/MoreMenuItems.tsx @@ -3,6 +3,7 @@ import { useCallback, useContext } from "react"; import { useTranslation } from "react-i18next"; import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; import { + setAclDialog, setCreateArchiveDialog, setDirectLinkManagementDialog, setManageShareDialog, @@ -15,6 +16,7 @@ import BranchForkLink from "../../Icons/BranchForkLink.tsx"; import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; import ImageArrowCounterclockwise from "../../Icons/ImageAarowCounterclockwise.tsx"; import LinkSetting from "../../Icons/LinkSetting.tsx"; +import PersonLock from "../../Icons/PersonLock.tsx"; import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; @@ -73,6 +75,23 @@ const MoreMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => { {t("application:fileManager.manageShares")} )} + {displayOpt.showAcl && ( + + dispatch( + setAclDialog({ + open: true, + file: targets[0], + }), + ), + )} + > + + + + {t("application:fileManager.permissions")} + + )} {displayOpt.showDirectLinkManagement && ( diff --git a/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts b/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts index 4de3e843..0fde3ef8 100644 --- a/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts +++ b/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts @@ -100,6 +100,7 @@ export interface DisplayOption { showVersionControl?: boolean; showDirectLinkManagement?: boolean; showManageShares?: boolean; + showAcl?: boolean; showCreateArchive?: boolean; showResetThumb?: boolean; @@ -340,9 +341,16 @@ export const getActionOpt = ( display.orCapability && display.orCapability.enabled(NavigatorCapability.update_metadata); + display.showAcl = + targets.length == 1 && + !!targets[0].owned && + !!currentUser && + groupBs.enabled(GroupPermission.set_explicit_user); + display.showMore = display.showVersionControl || display.showManageShares || + display.showAcl || display.showCreateArchive || display.showDirectLinkManagement || display.showResetThumb; diff --git a/frontend/src/component/FileManager/Dialogs/Acl/AclDialog.tsx b/frontend/src/component/FileManager/Dialogs/Acl/AclDialog.tsx new file mode 100644 index 00000000..bc72fa9f --- /dev/null +++ b/frontend/src/component/FileManager/Dialogs/Acl/AclDialog.tsx @@ -0,0 +1,300 @@ +import { + Box, + Checkbox, + DialogContent, + IconButton, + Skeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { deleteAclEntry, getAclEntries, searchAclSubjects, upsertAclEntry } from "../../../../api/api.ts"; +import { AclEntry, AclPermissionKey, AclSubject, AclSubjectType } from "../../../../api/explorer.ts"; +import { closeAclDialog } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import { + DenseAutocomplete, + DenseFilledTextField, + NoWrapTableCell, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import Globe from "../../../Icons/Globe.tsx"; +import PeopleTeam from "../../../Icons/PeopleTeam.tsx"; +import PersonOutlined from "../../../Icons/PersonOutlined.tsx"; +import PersonStar from "../../../Icons/PersonStar.tsx"; + +const permissionOrder: AclPermissionKey[] = ["read", "create", "update", "delete"]; + +interface PendingSubject { + type: AclSubjectType; + id: number; + label: string; +} + +const AclDialog = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [entries, setEntries] = useState(undefined); + const [pending, setPending] = useState([]); + const [subjectOptions, setSubjectOptions] = useState([]); + const [keyword, setKeyword] = useState(""); + + const open = useAppSelector((state) => state.globalState.aclDialogOpen); + const target = useAppSelector((state) => state.globalState.aclDialogFile); + + const uri = target?.path; + + const generalSubject = useCallback( + (type: "anonymous" | "everyone"): PendingSubject => ({ + type, + id: 0, + label: t(`fileManager.acl${type === "anonymous" ? "Anonymous" : "Everyone"}`), + }), + [t], + ); + + const subjectLabel = useCallback( + (e: AclEntry) => { + if (e.subject_type === "anonymous" || e.subject_type === "everyone") { + return t(`fileManager.acl${e.subject_type === "anonymous" ? "Anonymous" : "Everyone"}`); + } + return e.subject_name || `#${e.subject_id}`; + }, + [t], + ); + + useEffect(() => { + if (open && uri) { + setEntries(undefined); + dispatch(getAclEntries(uri)).then((res) => setEntries(res)); + } + }, [open, uri, dispatch]); + + // Search selectable subjects; anonymous/everyone are always offered when not + // already present on the file. + useEffect(() => { + if (!open) { + return; + } + const handle = setTimeout(() => { + const fixed: PendingSubject[] = ["anonymous", "everyone"] + .filter((ty) => !entries?.some((e) => e.subject_type === ty)) + .map((ty) => generalSubject(ty as "anonymous" | "everyone")) + .filter((s) => keyword == "" || s.label.toLowerCase().includes(keyword.toLowerCase())); + dispatch(searchAclSubjects(keyword)) + .then((res: AclSubject[]) => { + const dynamic = (res || []) + .filter((s) => !entries?.some((e) => e.subject_type === s.type && e.subject_id === s.id)) + .map((s) => ({ + type: s.type as AclSubjectType, + id: s.id, + label: s.type === "user" ? s.name : `${s.name} (${t("fileManager.aclGroup")})`, + })); + setSubjectOptions([...dynamic, ...fixed]); + }) + .catch(() => setSubjectOptions(fixed)); + }, 300); + return () => clearTimeout(handle); + }, [keyword, open, entries, dispatch, generalSubject, t]); + + const togglePermission = useCallback( + (entry: AclEntry, perm: AclPermissionKey) => { + if (!uri) { + return; + } + const next = entry.permissions.includes(perm) + ? entry.permissions.filter((p) => p !== perm) + : [...entry.permissions, perm]; + setPending((p) => [...p, entry.id]); + dispatch( + upsertAclEntry({ + uri, + subject_type: entry.subject_type, + subject_id: entry.subject_id, + permissions: next, + }), + ) + .then((res) => { + setEntries((prev) => + prev?.map((e) => (e.id === entry.id ? { ...e, permissions: res.permissions } : e)), + ); + }) + .finally(() => setPending((p) => p.filter((id) => id !== entry.id))); + }, + [uri, dispatch], + ); + + const removeEntry = useCallback( + (entry: AclEntry) => { + if (!uri) { + return; + } + setPending((p) => [...p, entry.id]); + dispatch(deleteAclEntry(uri, entry.id)) + .then(() => setEntries((prev) => prev?.filter((e) => e.id !== entry.id))) + .finally(() => setPending((p) => p.filter((id) => id !== entry.id))); + }, + [uri, dispatch], + ); + + const addSubject = useCallback( + (subject: PendingSubject | null) => { + if (!subject || !uri) { + return; + } + setPending((p) => [...p, -1]); + dispatch( + upsertAclEntry({ + uri, + subject_type: subject.type, + subject_id: subject.id, + permissions: ["read"], + }), + ) + .then((res) => { + setEntries((prev) => [...(prev ?? []), { ...res, subject_name: subject.label }]); + setKeyword(""); + }) + .finally(() => setPending((p) => p.filter((id) => id !== -1))); + }, + [uri, dispatch], + ); + + const subjectIcon = useMemo( + () => ({ + user: , + group: , + anonymous: , + everyone: , + }), + [], + ); + + return ( + dispatch(closeAclDialog()), + fullWidth: true, + maxWidth: "md", + }} + > + + + + {t("fileManager.aclDes", { name: target?.name })} + + setKeyword(v)} + getOptionLabel={(o) => (o as PendingSubject).label} + onChange={(_, v) => addSubject(v as PendingSubject | null)} + value={null} + blurOnSelect + sx={{ mb: 2 }} + renderOption={(props, option) => { + const o = option as PendingSubject; + return ( +
  • + + {subjectIcon[o.type]} + {o.label} + +
  • + ); + }} + renderInput={(params) => ( + + )} + /> + + + + + {t("fileManager.aclSubject")} + {permissionOrder.map((p) => ( + + {t(`fileManager.aclPerm_${p}`)} + + ))} + + + + + {entries === undefined && ( + + + + + {permissionOrder.map((p) => ( + + + + ))} + + + )} + {entries?.map((e) => ( + + + + {subjectIcon[e.subject_type]} + {subjectLabel(e)} + + + {permissionOrder.map((p) => ( + + togglePermission(e, p)} + /> + + ))} + + removeEntry(e)} + > + + + + + ))} + +
    + {entries && entries.length === 0 && ( + + + {t("application:setting.listEmpty")} + + + )} +
    +
    +
    +
    + ); +}; + +export default AclDialog; diff --git a/frontend/src/component/FileManager/Dialogs/Dialogs.tsx b/frontend/src/component/FileManager/Dialogs/Dialogs.tsx index a194b8f3..79a2e6b7 100644 --- a/frontend/src/component/FileManager/Dialogs/Dialogs.tsx +++ b/frontend/src/component/FileManager/Dialogs/Dialogs.tsx @@ -9,6 +9,7 @@ import ChangeIcon from "./ChangeIcon.tsx"; import ShareDialog from "./Share/ShareDialog.tsx"; import VersionControl from "./VersionControl.tsx"; import ManageShares from "./Share/ManageShares.tsx"; +import AclDialog from "./Acl/AclDialog.tsx"; import StaleVersionConfirm from "./StaleVersionConfirm.tsx"; import SaveAs from "./SaveAs.tsx"; import Photopea from "../../Viewers/Photopea/Photopea.tsx"; @@ -60,6 +61,7 @@ const Dialogs = () => { + diff --git a/frontend/src/redux/globalStateSlice.ts b/frontend/src/redux/globalStateSlice.ts index eb16818f..92a15c5a 100644 --- a/frontend/src/redux/globalStateSlice.ts +++ b/frontend/src/redux/globalStateSlice.ts @@ -156,6 +156,10 @@ export interface GlobalStateSlice { manageShareDialogOpen?: boolean; manageShareDialogFile?: FileResponse; + // File permission (ACL) dialog + aclDialogOpen?: boolean; + aclDialogFile?: FileResponse; + // Stale version action dialog staleVersionDialogOpen?: boolean; staleVersionUri?: string; @@ -407,6 +411,7 @@ export const globalStateSlice = createSlice({ state.shareLinkDialogOpen = state.shareLinkDialogOpen ? false : undefined; state.versionControlDialogOpen = state.versionControlDialogOpen ? false : undefined; state.manageShareDialogOpen = state.manageShareDialogOpen ? false : undefined; + state.aclDialogOpen = state.aclDialogOpen ? false : undefined; state.createNewDialogOpen = state.createNewDialogOpen ? false : undefined; state.selectOptionDialogOpen = state.selectOptionDialogOpen ? false : undefined; state.batchDownloadLogDialogOpen = state.batchDownloadLogDialogOpen ? false : undefined; @@ -782,6 +787,13 @@ export const globalStateSlice = createSlice({ closeManageShareDialog: (state) => { state.manageShareDialogOpen = false; }, + setAclDialog: (state, action: PayloadAction<{ open: boolean; file: FileResponse }>) => { + state.aclDialogOpen = action.payload.open; + state.aclDialogFile = action.payload.file; + }, + closeAclDialog: (state) => { + state.aclDialogOpen = false; + }, setImageViewer: (state, action: PayloadAction) => { state.imageViewer = action.payload; }, @@ -911,6 +923,8 @@ export const { closeImageViewer, setManageShareDialog, closeManageShareDialog, + setAclDialog, + closeAclDialog, setVersionControlDialog, closeVersionControlDialog, closeSidebar, diff --git a/inventory/acl.go b/inventory/acl.go new file mode 100644 index 00000000..12607e6e --- /dev/null +++ b/inventory/acl.go @@ -0,0 +1,156 @@ +package inventory + +import ( + "context" + "fmt" + + "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" + "github.com/cloudreve/Cloudreve/v4/pkg/conf" +) + +type ( + AclClient interface { + TxOperator + // List returns all ACL entries for a file, ordered by subject type. + List(ctx context.Context, fileID int) ([]*ent.AclEntry, error) + // Upsert creates or updates the entry for (file, subject). + Upsert(ctx context.Context, params *UpsertAclEntryParams) (*ent.AclEntry, error) + // Delete removes an ACL entry scoped to the given file. + Delete(ctx context.Context, fileID, id int) error + // EffectivePermissions unions the permission bits of all entries on + // fileID matching the acting user. Anonymous users (id 0) only match + // the anonymous tier; authenticated users match their explicit user + // and group rows plus the everyone tier. Returns nil when no entry + // matches — callers then fall back to share-level defaults. + EffectivePermissions(ctx context.Context, fileID int, user *ent.User) (*boolset.BooleanSet, error) + } + + UpsertAclEntryParams struct { + FileID int + SubjectType aclentry.SubjectType + SubjectID int + Permissions *boolset.BooleanSet + } +) + +func NewAclClient(client *ent.Client, dbType conf.DBType) AclClient { + return &aclClient{ + client: client, + maxSQlParam: sqlParamLimit(dbType), + } +} + +type aclClient struct { + maxSQlParam int + client *ent.Client +} + +func (c *aclClient) SetClient(newClient *ent.Client) TxOperator { + return &aclClient{client: newClient, maxSQlParam: c.maxSQlParam} +} + +func (c *aclClient) GetClient() *ent.Client { + return c.client +} + +func (c *aclClient) List(ctx context.Context, fileID int) ([]*ent.AclEntry, error) { + return c.client.AclEntry.Query(). + Where(aclentry.FileID(fileID)). + Order(aclentry.BySubjectType(), aclentry.BySubjectID()). + All(ctx) +} + +func (c *aclClient) Upsert(ctx context.Context, params *UpsertAclEntryParams) (*ent.AclEntry, error) { + if params.Permissions == nil { + params.Permissions = &boolset.BooleanSet{} + } + + id, err := c.client.AclEntry.Create(). + SetFileID(params.FileID). + SetSubjectType(params.SubjectType). + SetSubjectID(params.SubjectID). + SetPermissions(params.Permissions). + OnConflictColumns(aclentry.FieldFileID, aclentry.FieldSubjectType, aclentry.FieldSubjectID). + UpdateNewValues(). + ID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to upsert ACL entry: %w", err) + } + + return c.client.AclEntry.Get(ctx, id) +} + +func (c *aclClient) Delete(ctx context.Context, fileID, id int) error { + affected, err := c.client.AclEntry.Delete(). + Where(aclentry.ID(id), aclentry.FileID(fileID)). + Exec(ctx) + if err != nil { + return fmt.Errorf("failed to delete ACL entry: %w", err) + } + if affected == 0 { + return &ent.NotFoundError{} + } + return nil +} + +func (c *aclClient) EffectivePermissions(ctx context.Context, fileID int, user *ent.User) (*boolset.BooleanSet, error) { + entries, err := c.client.AclEntry.Query(). + Where(aclentry.FileID(fileID)). + All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query ACL entries: %w", err) + } + if len(entries) == 0 { + return nil, nil + } + + if IsAnonymousUser(user) { + for _, e := range entries { + if e.SubjectType == aclentry.SubjectTypeAnonymous { + if e.Permissions == nil { + return &boolset.BooleanSet{}, nil + } + return e.Permissions, nil + } + } + return nil, nil + } + + groupID := 0 + if group := user.Edges.Group; group != nil { + groupID = group.ID + } + + res := &boolset.BooleanSet{} + matched := false + for _, e := range entries { + hit := false + switch e.SubjectType { + case aclentry.SubjectTypeEveryone: + hit = true + case aclentry.SubjectTypeUser: + hit = e.SubjectID == user.ID + case aclentry.SubjectTypeGroup: + hit = groupID > 0 && e.SubjectID == groupID + } + if !hit { + continue + } + matched = true + if e.Permissions == nil { + continue + } + for i := 0; i <= int(types.AclPermDelete); i++ { + if e.Permissions.Enabled(i) { + boolset.Set(i, true, res) + } + } + } + if !matched { + return nil, nil + } + return res, nil +} diff --git a/inventory/acl_test.go b/inventory/acl_test.go new file mode 100644 index 00000000..51f15331 --- /dev/null +++ b/inventory/acl_test.go @@ -0,0 +1,154 @@ +package inventory + +import ( + "context" + "testing" + + "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" + "github.com/cloudreve/Cloudreve/v4/ent/enttest" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" + "github.com/cloudreve/Cloudreve/v4/pkg/conf" + "github.com/stretchr/testify/require" +) + +func aclFixture(t *testing.T, client *ent.Client) (*ent.User, *ent.Group, *ent.File) { + t.Helper() + ctx := context.Background() + group := client.Group.Create().SetName("g").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx) + user := client.User.Create().SetEmail("u@example.com").SetNick("u").SetGroup(group).SaveX(ctx) + file := client.File.Create().SetName("dir").SetType(int(types.FileTypeFolder)).SetOwner(user).SaveX(ctx) + user.SetGroup(group) + return user, group, file +} + +func aclEntry(t *testing.T, client *ent.Client, fileID int, st aclentry.SubjectType, sid int, perms ...types.AclPermission) *ent.AclEntry { + t.Helper() + bs := &boolset.BooleanSet{} + for _, p := range perms { + boolset.Set(int(p), true, bs) + } + return client.AclEntry.Create(). + SetFileID(fileID). + SetSubjectType(st). + SetSubjectID(sid). + SetPermissions(bs). + SaveX(context.Background()) +} + +func TestAclUpsertAndList(t *testing.T) { + client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared") + t.Cleanup(func() { require.NoError(t, client.Close()) }) + ctx := context.Background() + _, _, file := aclFixture(t, client) + c := NewAclClient(client, conf.SQLiteDB) + + bs := &boolset.BooleanSet{} + boolset.Set(int(types.AclPermRead), true, bs) + e, err := c.Upsert(ctx, &UpsertAclEntryParams{ + FileID: file.ID, SubjectType: aclentry.SubjectTypeUser, SubjectID: 7, Permissions: bs, + }) + require.NoError(t, err) + require.NotZero(t, e.ID) + + // Second upsert on same (file, subject) updates rather than duplicating. + boolset.Set(int(types.AclPermDelete), true, bs) + e2, err := c.Upsert(ctx, &UpsertAclEntryParams{ + FileID: file.ID, SubjectType: aclentry.SubjectTypeUser, SubjectID: 7, Permissions: bs, + }) + require.NoError(t, err) + require.Equal(t, e.ID, e2.ID) + + entries, err := c.List(ctx, file.ID) + require.NoError(t, err) + require.Len(t, entries, 1) + require.True(t, entries[0].Permissions.Enabled(int(types.AclPermDelete))) + + require.NoError(t, c.Delete(ctx, file.ID, e.ID)) + entries, err = c.List(ctx, file.ID) + require.NoError(t, err) + require.Empty(t, entries) + + // Delete of a missing row reports not found. + require.Error(t, c.Delete(ctx, file.ID, e.ID)) +} + +func TestAclEffectivePermissionsAnonymous(t *testing.T) { + client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared") + t.Cleanup(func() { require.NoError(t, client.Close()) }) + ctx := context.Background() + _, _, file := aclFixture(t, client) + c := NewAclClient(client, conf.SQLiteDB) + anon := &ent.User{ID: 0} + + // No rows at all -> nil (fall back to share defaults). + res, err := c.EffectivePermissions(ctx, file.ID, anon) + require.NoError(t, err) + require.Nil(t, res) + + // An anonymous row applies to anonymous visitors; other tiers do not. + aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0, types.AclPermRead) + res, err = c.EffectivePermissions(ctx, file.ID, anon) + require.NoError(t, err) + require.Nil(t, res) + + aclEntry(t, client, file.ID, aclentry.SubjectTypeAnonymous, 0, types.AclPermCreate) + res, err = c.EffectivePermissions(ctx, file.ID, anon) + require.NoError(t, err) + require.NotNil(t, res) + require.True(t, res.Enabled(int(types.AclPermCreate))) + require.False(t, res.Enabled(int(types.AclPermRead))) +} + +func TestAclEffectivePermissionsUnion(t *testing.T) { + client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared") + t.Cleanup(func() { require.NoError(t, client.Close()) }) + ctx := context.Background() + user, group, file := aclFixture(t, client) + c := NewAclClient(client, conf.SQLiteDB) + + aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0, types.AclPermRead) + aclEntry(t, client, file.ID, aclentry.SubjectTypeGroup, group.ID, types.AclPermCreate) + aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID, types.AclPermDelete) + // Unrelated rows must not leak in. + aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID+99, types.AclPermUpdate) + aclEntry(t, client, file.ID, aclentry.SubjectTypeGroup, group.ID+99, types.AclPermUpdate) + + res, err := c.EffectivePermissions(ctx, file.ID, user) + require.NoError(t, err) + require.NotNil(t, res) + require.True(t, res.Enabled(int(types.AclPermRead))) + require.True(t, res.Enabled(int(types.AclPermCreate))) + require.True(t, res.Enabled(int(types.AclPermDelete))) + require.False(t, res.Enabled(int(types.AclPermUpdate))) +} + +func TestAclEffectivePermissionsNoMatchIsNil(t *testing.T) { + client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared") + t.Cleanup(func() { require.NoError(t, client.Close()) }) + ctx := context.Background() + user, _, file := aclFixture(t, client) + c := NewAclClient(client, conf.SQLiteDB) + + aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID+99, types.AclPermRead) + res, err := c.EffectivePermissions(ctx, file.ID, user) + require.NoError(t, err) + require.Nil(t, res) +} + +func TestAclEffectivePermissionsExplicitEmptyRevokes(t *testing.T) { + client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared") + t.Cleanup(func() { require.NoError(t, client.Close()) }) + ctx := context.Background() + user, _, file := aclFixture(t, client) + c := NewAclClient(client, conf.SQLiteDB) + + // An everyone row with zero bits = explicit revocation for all + // authenticated visitors; distinct from nil (no match). + aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0) + res, err := c.EffectivePermissions(ctx, file.ID, user) + require.NoError(t, err) + require.NotNil(t, res) + require.False(t, res.Enabled(int(types.AclPermRead))) +} diff --git a/inventory/file.go b/inventory/file.go index 98d45b77..3c96ce02 100644 --- a/inventory/file.go +++ b/inventory/file.go @@ -9,6 +9,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" "github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/file" @@ -637,6 +638,10 @@ func (f *fileClient) Delete(ctx context.Context, files []*ent.File, options *typ return nil, nil, fmt.Errorf("failed to delete direct links of files %v: %w", group, err) } + if _, err := f.client.AclEntry.Delete().Where(aclentry.FileIDIn(chunks[i]...)).Exec(hardDeleteCtx); err != nil { + return nil, nil, fmt.Errorf("failed to delete ACL entries of files %v: %w", group, err) + } + // 5. Delete files. if _, err := f.client.File.Delete().Where(group).Exec(hardDeleteCtx); err != nil { return nil, nil, fmt.Errorf("failed to delete files %v: %w", group, err) diff --git a/inventory/types/types.go b/inventory/types/types.go index 1128b051..1c53e4d3 100644 --- a/inventory/types/types.go +++ b/inventory/types/types.go @@ -135,6 +135,7 @@ type ( FilePermission int DavAccountOption int NodeCapability int + AclPermission int NodeSetting struct { Provider DownloaderProvider `json:"provider,omitempty"` @@ -304,7 +305,9 @@ const ( GroupPermissionAdvanceDelete GroupPermission_CommunityPlaceholder3 GroupPermission_CommunityPlaceholder4 - GroupPermissionSetExplicitUser_placeholder + // GroupPermissionSetExplicitUser allows members to manage per-file ACL + // entries (Permissions dialog) on files they own. + GroupPermissionSetExplicitUser GroupPermissionIgnoreFileOwnership // not used GroupPermissionUniqueRedirectDirectLink // GroupPermissionWebDAVReadOnly restricts the group's WebDAV access to @@ -327,6 +330,24 @@ const ( GroupPermissionAdminReports ) +// AclPermission is a bit position in an ACL entry's permission bitmask. +// Read grants listing/download, Create grants uploads and new entries, +// Update grants rename/metadata/content changes, Delete grants removal. +const ( + AclPermRead AclPermission = iota + AclPermCreate + AclPermUpdate + AclPermDelete +) + +// AclPermissionList maps each ACL bit to its API-facing key. +var AclPermissionList = map[AclPermission]string{ + AclPermRead: "read", + AclPermCreate: "create", + AclPermUpdate: "update", + AclPermDelete: "delete", +} + // DelegatedAdminPermissions lists every per-section admin permission bit. // GroupPermissionIsAdmin implies all of them. func DelegatedAdminPermissions() []GroupPermission { diff --git a/pkg/filemanager/fs/dbfs/dbfs.go b/pkg/filemanager/fs/dbfs/dbfs.go index 81e9d81e..e8b0f62f 100644 --- a/pkg/filemanager/fs/dbfs/dbfs.go +++ b/pkg/filemanager/fs/dbfs/dbfs.go @@ -97,6 +97,7 @@ func (f *DBFS) writePermitted(file *File, capability NavigatorCapability) bool { type DBFSDependencies struct { FileClient inventory.FileClient ShareClient inventory.ShareClient + AclClient inventory.AclClient UserClient inventory.UserClient StoragePolicyClient inventory.StoragePolicyClient DirectLinkClient inventory.DirectLinkClient @@ -116,6 +117,7 @@ func NewDatabaseFS(u *ent.User, deps DBFSDependencies) fs.FileSystem { navigators: make(map[string]Navigator), fileClient: deps.FileClient, shareClient: deps.ShareClient, + aclClient: deps.AclClient, l: deps.Logger, ls: deps.LockSystem, settingClient: deps.SettingProvider, @@ -137,6 +139,7 @@ type DBFS struct { userClient inventory.UserClient storagePolicyClient inventory.StoragePolicyClient shareClient inventory.ShareClient + aclClient inventory.AclClient directLinkClient inventory.DirectLinkClient l logging.Logger ls lock.LockSystem @@ -797,7 +800,7 @@ func (f *DBFS) getNavigator(ctx context.Context, path *fs.URI, requiredCapabilit case constants.FileSystemMy: n = NewMyNavigator(f.user, f.fileClient, f.userClient, f.l, config, f.hasher) case constants.FileSystemShare: - n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.l, config, f.hasher) + n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.aclClient, f.l, config, f.hasher) case constants.FileSystemTrash: n = NewTrashNavigator(f.user, f.fileClient, f.l, config, f.hasher) case constants.FileSystemSharedWithMe: diff --git a/pkg/filemanager/fs/dbfs/share_collaboration_test.go b/pkg/filemanager/fs/dbfs/share_collaboration_test.go index 944697a1..f6678341 100644 --- a/pkg/filemanager/fs/dbfs/share_collaboration_test.go +++ b/pkg/filemanager/fs/dbfs/share_collaboration_test.go @@ -192,3 +192,77 @@ func TestShareNavigatorExecuteHookPreviewOnly(t *testing.T) { require.Equal(t, 1, sc.calls) }) } + +func TestShareCapabilitiesAcl(t *testing.T) { + owner := &ent.User{ID: 1} + visitor := &ent.User{ID: 2} + + enabled := func(bs *boolset.BooleanSet, c NavigatorCapability) bool { + return bs.Enabled(int(c)) + } + aclBs := func(perms ...types.AclPermission) *boolset.BooleanSet { + bs := &boolset.BooleanSet{} + for _, p := range perms { + boolset.Set(int(p), true, bs) + } + return bs + } + + t.Run("matched acl overrides share props", func(t *testing.T) { + n := &shareNavigator{ + user: visitor, + owner: owner, + share: shareWithProps(&types.ShareProps{AllowEdit: true}), + aclCaps: aclBs(types.AclPermRead), + } + caps := n.shareCapabilities() + require.True(t, enabled(caps, NavigatorCapabilityDownloadFile)) + require.False(t, enabled(caps, NavigatorCapabilityRenameFile)) + require.False(t, enabled(caps, NavigatorCapabilityDeleteFile)) + }) + + t.Run("explicit empty acl revokes fallback", func(t *testing.T) { + n := &shareNavigator{ + user: visitor, + owner: owner, + share: shareWithProps(nil), + aclCaps: aclBs(), + } + caps := n.shareCapabilities() + require.False(t, enabled(caps, NavigatorCapabilityDownloadFile)) + require.False(t, enabled(caps, NavigatorCapabilityListChildren)) + }) + + t.Run("no match falls back to share props", func(t *testing.T) { + n := &shareNavigator{ + user: visitor, + owner: owner, + share: shareWithProps(&types.ShareProps{AllowUpload: true}), + } + caps := n.shareCapabilities() + require.True(t, enabled(caps, NavigatorCapabilityUploadFile)) + require.True(t, enabled(caps, NavigatorCapabilityDownloadFile)) + }) + + t.Run("owner bypasses acl", func(t *testing.T) { + n := &shareNavigator{ + user: owner, + owner: owner, + share: shareWithProps(nil), + aclCaps: aclBs(), + } + caps := n.shareCapabilities() + require.True(t, enabled(caps, NavigatorCapabilityDownloadFile)) + }) + + t.Run("create update delete bits map", func(t *testing.T) { + caps := aclPermsToCapabilities(aclBs(types.AclPermCreate, types.AclPermUpdate, types.AclPermDelete)) + require.True(t, enabled(caps, NavigatorCapabilityUploadFile)) + require.True(t, enabled(caps, NavigatorCapabilityCreateFile)) + require.True(t, enabled(caps, NavigatorCapabilityRenameFile)) + require.True(t, enabled(caps, NavigatorCapabilityUpdateMetadata)) + require.True(t, enabled(caps, NavigatorCapabilityDeleteFile)) + require.True(t, enabled(caps, NavigatorCapabilitySoftDelete)) + require.False(t, enabled(caps, NavigatorCapabilityDownloadFile)) + }) +} diff --git a/pkg/filemanager/fs/dbfs/share_navigator.go b/pkg/filemanager/fs/dbfs/share_navigator.go index 38c0fb27..586f642d 100644 --- a/pkg/filemanager/fs/dbfs/share_navigator.go +++ b/pkg/filemanager/fs/dbfs/share_navigator.go @@ -38,12 +38,13 @@ var shareNavigatorCapability = &boolset.BooleanSet{} // NewShareNavigator creates a navigator for user's "shared" file system. func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient, - l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator { + aclClient inventory.AclClient, l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator { n := &shareNavigator{ user: u, l: l, fileClient: fileClient, shareClient: shareClient, + aclClient: aclClient, config: config, } n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config) @@ -56,6 +57,7 @@ type ( user *ent.User fileClient inventory.FileClient shareClient inventory.ShareClient + aclClient inventory.AclClient config *setting.DBFS *baseNavigator @@ -64,8 +66,12 @@ type ( ownerRoot *File share *ent.Share owner *ent.User - disableRecycle bool - persist func() + // aclCaps carries the unioned permission bits of ACL entries on the + // shared file matching the acting user. nil means no entry matched — + // capabilities then fall back to share props. + aclCaps *boolset.BooleanSet + disableRecycle bool + persist func() } shareNavigatorState struct { @@ -74,6 +80,7 @@ type ( SingleFileShare bool Share *ent.Share Owner *ent.User + AclCaps *boolset.BooleanSet } ) @@ -86,6 +93,7 @@ func (n *shareNavigator) PersistState(kv cache.Driver, key string) { SingleFileShare: n.singleFileShare, Share: n.share, Owner: n.owner, + AclCaps: n.aclCaps, }, ContextHintTTL) } } @@ -97,6 +105,7 @@ func (n *shareNavigator) RestoreState(s State) error { n.ownerRoot = state.OwnerRoot n.singleFileShare = state.SingleFileShare n.share = state.Share + n.aclCaps = state.AclCaps n.owner = state.Owner return nil } @@ -142,6 +151,14 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error) // Share must be assigned before capabilities are derived from its props. n.share = share + // Resolve per-file ACL entries for non-owner visitors; matched rows + // replace the share-props capability set for this user. + if n.aclClient != nil && n.user.ID != n.owner.ID { + if caps, err := n.aclClient.EffectivePermissions(ctx, share.Edges.File.ID, n.user); err == nil { + n.aclCaps = caps + } + } + // Share permission setting should overwrite root folder's permission n.shareRoot = newFile(nil, share.Edges.File) @@ -296,6 +313,12 @@ func (n *shareNavigator) Capabilities(isSearching bool) *fs.NavigatorProps { // shareCapabilities derives the effective capability set from share props. func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet { + // Matched ACL entries fully define a non-owner visitor's capabilities; + // when no entry matched (nil) share props apply as the link default. + if n.aclCaps != nil && n.owner != nil && n.user.ID != n.owner.ID { + return aclPermsToCapabilities(n.aclCaps) + } + bs := &boolset.BooleanSet{} boolset.Sets(map[NavigatorCapability]bool{ NavigatorCapabilityListChildren: true, @@ -345,6 +368,45 @@ func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet { return bs } +// aclPermsToCapabilities maps ACL permission bits (read/create/update/delete) +// to the navigator capability set granted through a share link. +func aclPermsToCapabilities(perms *boolset.BooleanSet) *boolset.BooleanSet { + bs := &boolset.BooleanSet{} + if perms.Enabled(int(types.AclPermRead)) { + boolset.Sets(map[NavigatorCapability]bool{ + NavigatorCapabilityListChildren: true, + NavigatorCapabilityDownloadFile: true, + NavigatorCapabilityEnterFolder: true, + NavigatorCapabilityInfo: true, + NavigatorCapabilityGenerateThumb: true, + }, bs) + } + if perms.Enabled(int(types.AclPermCreate)) { + boolset.Sets(map[NavigatorCapability]bool{ + NavigatorCapabilityUploadFile: true, + NavigatorCapabilityCreateFile: true, + NavigatorCapabilityLockFile: true, + NavigatorCapabilityEnterFolder: true, + }, bs) + } + if perms.Enabled(int(types.AclPermUpdate)) { + boolset.Sets(map[NavigatorCapability]bool{ + NavigatorCapabilityRenameFile: true, + NavigatorCapabilityUpdateMetadata: true, + NavigatorCapabilityUploadFile: true, + NavigatorCapabilityCreateFile: true, + NavigatorCapabilityLockFile: true, + }, bs) + } + if perms.Enabled(int(types.AclPermDelete)) { + boolset.Sets(map[NavigatorCapability]bool{ + NavigatorCapabilityDeleteFile: true, + NavigatorCapabilitySoftDelete: true, + }, bs) + } + return bs +} + func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) { oldBase := n.baseNavigator.fileClient revertFile, err := followTxClients(ctx, &n.fileClient) diff --git a/pkg/filemanager/manager/manager.go b/pkg/filemanager/manager/manager.go index 39a39085..59aa478c 100644 --- a/pkg/filemanager/manager/manager.go +++ b/pkg/filemanager/manager/manager.go @@ -168,6 +168,7 @@ func NewFileManager(dep dependency.Dep, u *ent.User) FileManager { fs: dbfs.NewDatabaseFS(u, dbfs.DBFSDependencies{ FileClient: dep.FileClient(), ShareClient: dep.ShareClient(), + AclClient: dep.AclClient(), UserClient: dep.UserClient(), StoragePolicyClient: dep.StoragePolicyClient(), DirectLinkClient: dep.DirectLinkClient(), diff --git a/routers/controllers/file.go b/routers/controllers/file.go index d1d2c7f4..a164efac 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -353,6 +353,57 @@ func PatchMetadata(c *gin.Context) { c.JSON(200, serializer.Response{}) } +// ListAcl lists ACL entries of a file. +func ListAcl(c *gin.Context) { + service := ParametersFromContext[*explorer.AclListService](c, explorer.AclListParamCtx{}) + resp, err := service.Get(c) + if respondErr(c, err) { + return + } + + c.JSON(200, serializer.Response{ + Data: resp, + }) +} + +// UpsertAcl creates or updates an ACL entry. +func UpsertAcl(c *gin.Context) { + service := ParametersFromContext[*explorer.AclUpsertService](c, explorer.AclUpsertParamCtx{}) + resp, err := service.Update(c) + if respondErr(c, err) { + return + } + + c.JSON(200, serializer.Response{ + Data: resp, + }) +} + +// DeleteAcl removes an ACL entry. +func DeleteAcl(c *gin.Context) { + service := ParametersFromContext[*explorer.AclDeleteService](c, explorer.AclDeleteParamCtx{}) + err := service.Delete(c) + if respondErr(c, err) { + return + } + + c.JSON(200, serializer.Response{}) +} + +// SearchAclSubjects resolves selectable ACL subjects (users by exact email, +// groups by name). +func SearchAclSubjects(c *gin.Context) { + service := ParametersFromContext[*explorer.AclSubjectSearchService](c, explorer.AclSubjectSearchParamCtx{}) + resp, err := service.Get(c) + if respondErr(c, err) { + return + } + + c.JSON(200, serializer.Response{ + Data: resp, + }) +} + // GetFileInfo gets file info func GetFileInfo(c *gin.Context) { service := ParametersFromContext[*explorer.GetFileInfoService](c, explorer.GetFileInfoParameterCtx{}) diff --git a/routers/router.go b/routers/router.go index df1c2f47..ddd2216a 100644 --- a/routers/router.go +++ b/routers/router.go @@ -787,6 +787,28 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine { controllers.FromQuery[explorer.GetFileInfoService](explorer.GetFileInfoParameterCtx{}), controllers.GetFileInfo, ) + // Per-file ACL entries (Permissions dialog) + acl := file.Group("acl") + { + acl.GET("", + controllers.FromQuery[explorer.AclListService](explorer.AclListParamCtx{}), + controllers.ListAcl, + ) + acl.GET("subjects", + controllers.FromQuery[explorer.AclSubjectSearchService](explorer.AclSubjectSearchParamCtx{}), + controllers.SearchAclSubjects, + ) + acl.PUT("", + middleware.RequiredScopes(types.ScopeFilesWrite), + controllers.FromJSON[explorer.AclUpsertService](explorer.AclUpsertParamCtx{}), + controllers.UpsertAcl, + ) + acl.DELETE("", + middleware.RequiredScopes(types.ScopeFilesWrite), + controllers.FromQuery[explorer.AclDeleteService](explorer.AclDeleteParamCtx{}), + controllers.DeleteAcl, + ) + } // Version management version := file.Group("version", middleware.RequiredScopes(types.ScopeFilesWrite)) { diff --git a/service/explorer/acl.go b/service/explorer/acl.go new file mode 100644 index 00000000..98f53b09 --- /dev/null +++ b/service/explorer/acl.go @@ -0,0 +1,240 @@ +package explorer + +import ( + "fmt" + "strings" + + "github.com/cloudreve/Cloudreve/v4/application/dependency" + "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/aclentry" + "github.com/cloudreve/Cloudreve/v4/inventory" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" + "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" + "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager" + "github.com/cloudreve/Cloudreve/v4/pkg/serializer" + "github.com/gin-gonic/gin" + "github.com/samber/lo" +) + +// AclEntryResponse is one row in the Permissions dialog. +type AclEntryResponse struct { + ID int `json:"id"` + SubjectType string `json:"subject_type"` + SubjectID int `json:"subject_id"` + SubjectName string `json:"subject_name"` + Permissions []string `json:"permissions"` +} + +// AclSubjectResponse is a selectable subject in the Permissions dialog. +type AclSubjectResponse struct { + Type string `json:"type"` + ID int `json:"id"` + Name string `json:"name"` +} + +type ( + // AclListService lists ACL entries of a file. + AclListService struct { + Uri string `form:"uri" binding:"required"` + } + AclListParamCtx struct{} + + // AclUpsertService creates or updates an ACL entry. + AclUpsertService struct { + Uri string `json:"uri" binding:"required"` + SubjectType string `json:"subject_type" binding:"required,oneof=user group anonymous everyone"` + SubjectID int `json:"subject_id"` + Permissions []string `json:"permissions"` + } + AclUpsertParamCtx struct{} + + // AclDeleteService removes an ACL entry. + AclDeleteService struct { + Uri string `form:"uri" binding:"required"` + ID int `form:"id" binding:"required,min=1"` + } + AclDeleteParamCtx struct{} + + // AclSubjectSearchService resolves selectable subjects for the dialog. + AclSubjectSearchService struct { + Keyword string `form:"keyword"` + } + AclSubjectSearchParamCtx struct{} +) + +func aclPermissionsToSet(perms []string) (*boolset.BooleanSet, error) { + bs := &boolset.BooleanSet{} + nameToBit := lo.Invert(types.AclPermissionList) + for _, p := range perms { + bit, ok := nameToBit[p] + if !ok { + return nil, fmt.Errorf("unknown permission %q", p) + } + boolset.Set(int(bit), true, bs) + } + return bs, nil +} + +func aclSetToPermissions(bs *boolset.BooleanSet) []string { + res := []string{} + for bit, name := range types.AclPermissionList { + if bs != nil && bs.Enabled(int(bit)) { + res = append(res, name) + } + } + return res +} + +// ownedAclFile resolves the target file and enforces that the acting user +// owns it and their group may manage explicit permissions. +func ownedAclFile(c *gin.Context, uriRaw string) (int, error) { + user := inventory.UserFromContext(c) + uri, err := fs.NewUriFromString(uriRaw) + if err != nil { + return 0, serializer.NewError(serializer.CodeParamErr, "unknown uri", err) + } + + m := manager.NewFileManager(dependency.FromContext(c), user) + defer m.Recycle() + + file, err := m.Get(c, uri) + if err != nil { + return 0, fmt.Errorf("failed to get file: %w", err) + } + if file.OwnerID() != user.ID { + return 0, serializer.NewError(serializer.CodeNoPermissionErr, "Only the owner can manage permissions", nil) + } + + if user.Edges.Group == nil || user.Edges.Group.Permissions == nil || + !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionSetExplicitUser)) { + return 0, serializer.NewError(serializer.CodeNoPermissionErr, "Permission management is not enabled for your group", nil) + } + + return file.ID(), nil +} + +func (s *AclListService) Get(c *gin.Context) ([]*AclEntryResponse, error) { + dep := dependency.FromContext(c) + fileID, err := ownedAclFile(c, s.Uri) + if err != nil { + return nil, err + } + + entries, err := dep.AclClient().List(c, fileID) + if err != nil { + return nil, serializer.NewError(serializer.CodeDBError, "Failed to list permissions", err) + } + + // Resolve subject display names. + names := map[string]string{} + for _, e := range entries { + key := fmt.Sprintf("%s:%d", e.SubjectType, e.SubjectID) + switch e.SubjectType { + case aclentry.SubjectTypeUser: + if u, err := dep.UserClient().GetByID(c, e.SubjectID); err == nil { + names[key] = u.Email + } + case aclentry.SubjectTypeGroup: + if g, err := dep.GroupClient().GetByID(c, e.SubjectID); err == nil { + names[key] = g.Name + } + } + } + + return lo.Map(entries, func(e *ent.AclEntry, _ int) *AclEntryResponse { + name := names[fmt.Sprintf("%s:%d", e.SubjectType, e.SubjectID)] + if e.SubjectType == aclentry.SubjectTypeAnonymous { + name = "anonymous" + } else if e.SubjectType == aclentry.SubjectTypeEveryone { + name = "everyone" + } + return &AclEntryResponse{ + ID: e.ID, + SubjectType: string(e.SubjectType), + SubjectID: e.SubjectID, + SubjectName: name, + Permissions: aclSetToPermissions(e.Permissions), + } + }), nil +} + +func (s *AclUpsertService) Update(c *gin.Context) (*AclEntryResponse, error) { + dep := dependency.FromContext(c) + fileID, err := ownedAclFile(c, s.Uri) + if err != nil { + return nil, err + } + + perms, err := aclPermissionsToSet(s.Permissions) + if err != nil { + return nil, serializer.NewError(serializer.CodeParamErr, "Invalid permissions", err) + } + + subjectType := aclentry.SubjectType(s.SubjectType) + if (subjectType == aclentry.SubjectTypeAnonymous || subjectType == aclentry.SubjectTypeEveryone) && s.SubjectID != 0 { + return nil, serializer.NewError(serializer.CodeParamErr, "General access entries must not carry a subject id", nil) + } + if (subjectType == aclentry.SubjectTypeUser || subjectType == aclentry.SubjectTypeGroup) && s.SubjectID <= 0 { + return nil, serializer.NewError(serializer.CodeParamErr, "Subject id is required", nil) + } + + e, err := dep.AclClient().Upsert(c, &inventory.UpsertAclEntryParams{ + FileID: fileID, + SubjectType: subjectType, + SubjectID: s.SubjectID, + Permissions: perms, + }) + if err != nil { + return nil, serializer.NewError(serializer.CodeDBError, "Failed to save permission", err) + } + + return &AclEntryResponse{ + ID: e.ID, + SubjectType: string(e.SubjectType), + SubjectID: e.SubjectID, + Permissions: aclSetToPermissions(e.Permissions), + }, nil +} + +func (s *AclDeleteService) Delete(c *gin.Context) error { + dep := dependency.FromContext(c) + fileID, err := ownedAclFile(c, s.Uri) + if err != nil { + return err + } + + if err := dep.AclClient().Delete(c, fileID, s.ID); err != nil { + return serializer.NewError(serializer.CodeDBError, "Failed to delete permission", err) + } + return nil +} + +func (s *AclSubjectSearchService) Get(c *gin.Context) ([]*AclSubjectResponse, error) { + user := inventory.UserFromContext(c) + if user.Edges.Group == nil || user.Edges.Group.Permissions == nil || + !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionSetExplicitUser)) { + return nil, serializer.NewError(serializer.CodeNoPermissionErr, "Permission management is not enabled for your group", nil) + } + + dep := dependency.FromContext(c) + res := []*AclSubjectResponse{} + + // Exact email match only — user enumeration by prefix is not exposed. + if s.Keyword != "" { + if u, err := dep.UserClient().GetByEmail(c, s.Keyword); err == nil { + res = append(res, &AclSubjectResponse{Type: "user", ID: u.ID, Name: u.Email}) + } + } + + groups, err := dep.GroupClient().ListAll(c) + if err == nil { + for _, g := range groups { + if s.Keyword == "" || strings.Contains(strings.ToLower(g.Name), strings.ToLower(s.Keyword)) { + res = append(res, &AclSubjectResponse{Type: "group", ID: g.ID, Name: g.Name}) + } + } + } + + return res, nil +} From b5ae7f20a10dbda14122faf51997e75b3d8b8c92 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 19:49:36 +0200 Subject: [PATCH 2/2] docs(roadmap): mark file/dir ACL done (#181) Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 3055ff9d..9c4befcf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -171,7 +171,7 @@ Order = user-visible value first; each ships with backend + UI + tests. 1. **Share collaboration** — write/upload/delete via share link, anonymous upload, share ACL (users/groups), preview-only mode (fixes #3555, #3390, #3340, #3517, #3578; uses `NavigatorCapability` placeholder slots + `ShareProps` extension + `share` entity fields). See §1.3a for the extracted UI spec. - [x] PR #140 — `allow_upload`/`allow_edit`/`preview_only`/`upload_only` props, props-derived capability sets enforced server-side (`writePermitted`), same-share move/copy, anonymous upload, drop-box listing suppression, download denial via `IsDownloadCtxKey` hooks (fixes #3555 preview-only, #3340 drop-box) - - [ ] File/dir ACL entity — `(subject_type ∈ user|group, subject_id)` → R/C/U/D bitmask + anonymous/everyone tiers; Permissions dialog under More actions; enforced in navigator capability checks (#3517) + - [x] File/dir ACL entity — `(subject_type ∈ user|group|anonymous|everyone, subject_id)` → R/C/U/D bitmask; Permissions dialog under More actions; enforced in share-navigator capability checks; group bit 15 gate (#181, fixes #3517) - [ ] Default shares — `setting.default_shares` chip-input of share IDs; materialize as share-shortcut entries on user create (#3390) - [ ] Paid shares — `share.price_points` + gate page + purchase/resume-ticket flow; needs B.4 credits first 2. **Storage policy advanced** — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a.