From 522fcca6af3f24c8817c4504374f083353a6d78d Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 5 Jun 2025 10:00:37 +0800 Subject: [PATCH] feat(explorer): save user's view setting to server / optionally share view setting via share link (#2232) --- assets | 2 +- ent/internal/schema.go | 2 +- ent/migrate/schema.go | 5 +- ent/mutation.go | 75 +++++++++- ent/schema/share.go | 2 + ent/share.go | 17 +++ ent/share/share.go | 3 + ent/share/where.go | 10 ++ ent/share_create.go | 71 +++++++++ ent/share_update.go | 37 +++++ inventory/file.go | 17 +++ inventory/share.go | 9 ++ inventory/types/types.go | 42 +++++- pkg/filemanager/fs/dbfs/dbfs.go | 133 ++++++----------- pkg/filemanager/fs/dbfs/file.go | 49 ++++++- pkg/filemanager/fs/dbfs/my_navigator.go | 5 + pkg/filemanager/fs/dbfs/navigator.go | 5 + pkg/filemanager/fs/dbfs/props.go | 138 ++++++++++++++++++ pkg/filemanager/fs/dbfs/share_navigator.go | 5 + .../fs/dbfs/sharewithme_navigator.go | 10 ++ pkg/filemanager/fs/dbfs/trash_navigator.go | 31 +++- pkg/filemanager/fs/fs.go | 5 + pkg/filemanager/manager/manager.go | 3 + pkg/filemanager/manager/operation.go | 39 +++++ routers/controllers/file.go | 12 ++ routers/router.go | 5 + service/explorer/file.go | 34 ++++- service/explorer/response.go | 25 ++-- service/share/manage.go | 2 + service/user/response.go | 48 +++--- service/user/setting.go | 19 ++- 31 files changed, 703 insertions(+), 157 deletions(-) create mode 100644 pkg/filemanager/fs/dbfs/props.go diff --git a/assets b/assets index d674a23b..9f91f8c9 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d674a23b21bdeb0a415985d4d5dc2b2051bc80d1 +Subproject commit 9f91f8c98a9e645ce49c9180af1fce75b1eb46d5 diff --git a/ent/internal/schema.go b/ent/internal/schema.go index 6082b15d..160f7aff 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\":\"recycle_options\",\"type\":{\"Type\":3,\"Ident\":\"*types.EntityRecycleOption\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"EntityRecycleOption\",\"Ident\":\"types.EntityRecycleOption\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"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\":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\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"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\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"primary_entity\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_children\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"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\":6,\"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\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_files\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_children\",\"name\"]},{\"fields\":[\"file_children\",\"type\",\"updated_at\"]},{\"fields\":[\"file_children\",\"type\",\"size\"]}],\"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\":\"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\":\"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}}],\"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\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"bucket_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_private\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"access_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"dir_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_name_rule\",\"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.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\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"node_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":11,\"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}}],\"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\":\"entities\",\"type\":\"Entity\"}],\"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\":\"storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"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\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":8,\"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\":\"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\":\"recycle_options\",\"type\":{\"Type\":3,\"Ident\":\"*types.EntityRecycleOption\",\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"PkgName\":\"types\",\"Nillable\":true,\"RType\":{\"Name\":\"EntityRecycleOption\",\"Ident\":\"types.EntityRecycleOption\",\"Kind\":22,\"PkgPath\":\"github.com/cloudreve/Cloudreve/v4/inventory/types\",\"Methods\":{}}},\"optional\":true,\"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\":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\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"owner_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"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\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"primary_entity\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_children\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"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\":6,\"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\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"storage_policy_files\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"file_children\",\"name\"]},{\"fields\":[\"file_children\",\"type\",\"updated_at\"]},{\"fields\":[\"file_children\",\"type\",\"size\"]}],\"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\":\"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\":\"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\":\"server\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"bucket_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_private\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"access_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"secret_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"max_size\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"dir_name_rule\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"file_name_rule\",\"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.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\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"node_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":11,\"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}}],\"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\":\"entities\",\"type\":\"Entity\"}],\"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\":\"storage\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"two_factor_secret\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"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\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_users\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":8,\"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 00a42551..84465d16 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -300,6 +300,7 @@ var ( {Name: "downloads", Type: field.TypeInt, Default: 0}, {Name: "expires", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}}, {Name: "remain_downloads", Type: field.TypeInt, Nullable: true}, + {Name: "props", Type: field.TypeJSON, Nullable: true}, {Name: "file_shares", Type: field.TypeInt, Nullable: true}, {Name: "user_shares", Type: field.TypeInt, Nullable: true}, } @@ -311,13 +312,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "shares_files_shares", - Columns: []*schema.Column{SharesColumns[9]}, + Columns: []*schema.Column{SharesColumns[10]}, RefColumns: []*schema.Column{FilesColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "shares_users_shares", - Columns: []*schema.Column{SharesColumns[10]}, + Columns: []*schema.Column{SharesColumns[11]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/ent/mutation.go b/ent/mutation.go index 28ebdd4f..2620a599 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -8958,6 +8958,7 @@ type ShareMutation struct { expires *time.Time remain_downloads *int addremain_downloads *int + props **types.ShareProps clearedFields map[string]struct{} user *int cleareduser bool @@ -9467,6 +9468,55 @@ func (m *ShareMutation) ResetRemainDownloads() { delete(m.clearedFields, share.FieldRemainDownloads) } +// SetProps sets the "props" field. +func (m *ShareMutation) SetProps(tp *types.ShareProps) { + m.props = &tp +} + +// Props returns the value of the "props" field in the mutation. +func (m *ShareMutation) Props() (r *types.ShareProps, exists bool) { + v := m.props + if v == nil { + return + } + return *v, true +} + +// OldProps returns the old "props" field's value of the Share entity. +// If the Share 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 *ShareMutation) OldProps(ctx context.Context) (v *types.ShareProps, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProps is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProps requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProps: %w", err) + } + return oldValue.Props, nil +} + +// ClearProps clears the value of the "props" field. +func (m *ShareMutation) ClearProps() { + m.props = nil + m.clearedFields[share.FieldProps] = struct{}{} +} + +// PropsCleared returns if the "props" field was cleared in this mutation. +func (m *ShareMutation) PropsCleared() bool { + _, ok := m.clearedFields[share.FieldProps] + return ok +} + +// ResetProps resets all changes to the "props" field. +func (m *ShareMutation) ResetProps() { + m.props = nil + delete(m.clearedFields, share.FieldProps) +} + // SetUserID sets the "user" edge to the User entity by id. func (m *ShareMutation) SetUserID(id int) { m.user = &id @@ -9579,7 +9629,7 @@ func (m *ShareMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ShareMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 9) if m.created_at != nil { fields = append(fields, share.FieldCreatedAt) } @@ -9604,6 +9654,9 @@ func (m *ShareMutation) Fields() []string { if m.remain_downloads != nil { fields = append(fields, share.FieldRemainDownloads) } + if m.props != nil { + fields = append(fields, share.FieldProps) + } return fields } @@ -9628,6 +9681,8 @@ func (m *ShareMutation) Field(name string) (ent.Value, bool) { return m.Expires() case share.FieldRemainDownloads: return m.RemainDownloads() + case share.FieldProps: + return m.Props() } return nil, false } @@ -9653,6 +9708,8 @@ func (m *ShareMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldExpires(ctx) case share.FieldRemainDownloads: return m.OldRemainDownloads(ctx) + case share.FieldProps: + return m.OldProps(ctx) } return nil, fmt.Errorf("unknown Share field %s", name) } @@ -9718,6 +9775,13 @@ func (m *ShareMutation) SetField(name string, value ent.Value) error { } m.SetRemainDownloads(v) return nil + case share.FieldProps: + v, ok := value.(*types.ShareProps) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProps(v) + return nil } return fmt.Errorf("unknown Share field %s", name) } @@ -9799,6 +9863,9 @@ func (m *ShareMutation) ClearedFields() []string { if m.FieldCleared(share.FieldRemainDownloads) { fields = append(fields, share.FieldRemainDownloads) } + if m.FieldCleared(share.FieldProps) { + fields = append(fields, share.FieldProps) + } return fields } @@ -9825,6 +9892,9 @@ func (m *ShareMutation) ClearField(name string) error { case share.FieldRemainDownloads: m.ClearRemainDownloads() return nil + case share.FieldProps: + m.ClearProps() + return nil } return fmt.Errorf("unknown Share nullable field %s", name) } @@ -9857,6 +9927,9 @@ func (m *ShareMutation) ResetField(name string) error { case share.FieldRemainDownloads: m.ResetRemainDownloads() return nil + case share.FieldProps: + m.ResetProps() + return nil } return fmt.Errorf("unknown Share field %s", name) } diff --git a/ent/schema/share.go b/ent/schema/share.go index b7d2ecee..d5fdf989 100644 --- a/ent/schema/share.go +++ b/ent/schema/share.go @@ -5,6 +5,7 @@ import ( "entgo.io/ent/dialect" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" + "github.com/cloudreve/Cloudreve/v4/inventory/types" ) // Share holds the schema definition for the Share entity. @@ -30,6 +31,7 @@ func (Share) Fields() []ent.Field { field.Int("remain_downloads"). Nillable(). Optional(), + field.JSON("props", &types.ShareProps{}).Optional(), } } diff --git a/ent/share.go b/ent/share.go index b998e904..ee650476 100644 --- a/ent/share.go +++ b/ent/share.go @@ -3,6 +3,7 @@ package ent import ( + "encoding/json" "fmt" "strings" "time" @@ -12,6 +13,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/user" + "github.com/cloudreve/Cloudreve/v4/inventory/types" ) // Share is the model entity for the Share schema. @@ -35,6 +37,8 @@ type Share struct { Expires *time.Time `json:"expires,omitempty"` // RemainDownloads holds the value of the "remain_downloads" field. RemainDownloads *int `json:"remain_downloads,omitempty"` + // Props holds the value of the "props" field. + Props *types.ShareProps `json:"props,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the ShareQuery when eager-loading is set. Edges ShareEdges `json:"edges"` @@ -85,6 +89,8 @@ func (*Share) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case share.FieldProps: + values[i] = new([]byte) case share.FieldID, share.FieldViews, share.FieldDownloads, share.FieldRemainDownloads: values[i] = new(sql.NullInt64) case share.FieldPassword: @@ -167,6 +173,14 @@ func (s *Share) assignValues(columns []string, values []any) error { s.RemainDownloads = new(int) *s.RemainDownloads = int(value.Int64) } + case share.FieldProps: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field props", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &s.Props); err != nil { + return fmt.Errorf("unmarshal field props: %w", err) + } + } case share.ForeignKeys[0]: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for edge-field file_shares", value) @@ -256,6 +270,9 @@ func (s *Share) String() string { builder.WriteString("remain_downloads=") builder.WriteString(fmt.Sprintf("%v", *v)) } + builder.WriteString(", ") + builder.WriteString("props=") + builder.WriteString(fmt.Sprintf("%v", s.Props)) builder.WriteByte(')') return builder.String() } diff --git a/ent/share/share.go b/ent/share/share.go index 8327fe6e..631f7c89 100644 --- a/ent/share/share.go +++ b/ent/share/share.go @@ -31,6 +31,8 @@ const ( FieldExpires = "expires" // FieldRemainDownloads holds the string denoting the remain_downloads field in the database. FieldRemainDownloads = "remain_downloads" + // FieldProps holds the string denoting the props field in the database. + FieldProps = "props" // EdgeUser holds the string denoting the user edge name in mutations. EdgeUser = "user" // EdgeFile holds the string denoting the file edge name in mutations. @@ -64,6 +66,7 @@ var Columns = []string{ FieldDownloads, FieldExpires, FieldRemainDownloads, + FieldProps, } // ForeignKeys holds the SQL foreign-keys that are owned by the "shares" diff --git a/ent/share/where.go b/ent/share/where.go index f9361324..b2bbde9c 100644 --- a/ent/share/where.go +++ b/ent/share/where.go @@ -480,6 +480,16 @@ func RemainDownloadsNotNil() predicate.Share { return predicate.Share(sql.FieldNotNull(FieldRemainDownloads)) } +// PropsIsNil applies the IsNil predicate on the "props" field. +func PropsIsNil() predicate.Share { + return predicate.Share(sql.FieldIsNull(FieldProps)) +} + +// PropsNotNil applies the NotNil predicate on the "props" field. +func PropsNotNil() predicate.Share { + return predicate.Share(sql.FieldNotNull(FieldProps)) +} + // HasUser applies the HasEdge predicate on the "user" edge. func HasUser() predicate.Share { return predicate.Share(func(s *sql.Selector) { diff --git a/ent/share_create.go b/ent/share_create.go index 31a3525e..9380d2ba 100644 --- a/ent/share_create.go +++ b/ent/share_create.go @@ -14,6 +14,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/user" + "github.com/cloudreve/Cloudreve/v4/inventory/types" ) // ShareCreate is the builder for creating a Share entity. @@ -136,6 +137,12 @@ func (sc *ShareCreate) SetNillableRemainDownloads(i *int) *ShareCreate { return sc } +// SetProps sets the "props" field. +func (sc *ShareCreate) SetProps(tp *types.ShareProps) *ShareCreate { + sc.mutation.SetProps(tp) + return sc +} + // SetUserID sets the "user" edge to the User entity by ID. func (sc *ShareCreate) SetUserID(id int) *ShareCreate { sc.mutation.SetUserID(id) @@ -316,6 +323,10 @@ func (sc *ShareCreate) createSpec() (*Share, *sqlgraph.CreateSpec) { _spec.SetField(share.FieldRemainDownloads, field.TypeInt, value) _node.RemainDownloads = &value } + if value, ok := sc.mutation.Props(); ok { + _spec.SetField(share.FieldProps, field.TypeJSON, value) + _node.Props = value + } if nodes := sc.mutation.UserIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -528,6 +539,24 @@ func (u *ShareUpsert) ClearRemainDownloads() *ShareUpsert { return u } +// SetProps sets the "props" field. +func (u *ShareUpsert) SetProps(v *types.ShareProps) *ShareUpsert { + u.Set(share.FieldProps, v) + return u +} + +// UpdateProps sets the "props" field to the value that was provided on create. +func (u *ShareUpsert) UpdateProps() *ShareUpsert { + u.SetExcluded(share.FieldProps) + return u +} + +// ClearProps clears the value of the "props" field. +func (u *ShareUpsert) ClearProps() *ShareUpsert { + u.SetNull(share.FieldProps) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create. // Using this option is equivalent to using: // @@ -720,6 +749,27 @@ func (u *ShareUpsertOne) ClearRemainDownloads() *ShareUpsertOne { }) } +// SetProps sets the "props" field. +func (u *ShareUpsertOne) SetProps(v *types.ShareProps) *ShareUpsertOne { + return u.Update(func(s *ShareUpsert) { + s.SetProps(v) + }) +} + +// UpdateProps sets the "props" field to the value that was provided on create. +func (u *ShareUpsertOne) UpdateProps() *ShareUpsertOne { + return u.Update(func(s *ShareUpsert) { + s.UpdateProps() + }) +} + +// ClearProps clears the value of the "props" field. +func (u *ShareUpsertOne) ClearProps() *ShareUpsertOne { + return u.Update(func(s *ShareUpsert) { + s.ClearProps() + }) +} + // Exec executes the query. func (u *ShareUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -1083,6 +1133,27 @@ func (u *ShareUpsertBulk) ClearRemainDownloads() *ShareUpsertBulk { }) } +// SetProps sets the "props" field. +func (u *ShareUpsertBulk) SetProps(v *types.ShareProps) *ShareUpsertBulk { + return u.Update(func(s *ShareUpsert) { + s.SetProps(v) + }) +} + +// UpdateProps sets the "props" field to the value that was provided on create. +func (u *ShareUpsertBulk) UpdateProps() *ShareUpsertBulk { + return u.Update(func(s *ShareUpsert) { + s.UpdateProps() + }) +} + +// ClearProps clears the value of the "props" field. +func (u *ShareUpsertBulk) ClearProps() *ShareUpsertBulk { + return u.Update(func(s *ShareUpsert) { + s.ClearProps() + }) +} + // Exec executes the query. func (u *ShareUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/ent/share_update.go b/ent/share_update.go index b02e89b6..7cdbd1b9 100644 --- a/ent/share_update.go +++ b/ent/share_update.go @@ -15,6 +15,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/ent/predicate" "github.com/cloudreve/Cloudreve/v4/ent/share" "github.com/cloudreve/Cloudreve/v4/ent/user" + "github.com/cloudreve/Cloudreve/v4/inventory/types" ) // ShareUpdate is the builder for updating Share entities. @@ -165,6 +166,18 @@ func (su *ShareUpdate) ClearRemainDownloads() *ShareUpdate { return su } +// SetProps sets the "props" field. +func (su *ShareUpdate) SetProps(tp *types.ShareProps) *ShareUpdate { + su.mutation.SetProps(tp) + return su +} + +// ClearProps clears the value of the "props" field. +func (su *ShareUpdate) ClearProps() *ShareUpdate { + su.mutation.ClearProps() + return su +} + // SetUserID sets the "user" edge to the User entity by ID. func (su *ShareUpdate) SetUserID(id int) *ShareUpdate { su.mutation.SetUserID(id) @@ -313,6 +326,12 @@ func (su *ShareUpdate) sqlSave(ctx context.Context) (n int, err error) { if su.mutation.RemainDownloadsCleared() { _spec.ClearField(share.FieldRemainDownloads, field.TypeInt) } + if value, ok := su.mutation.Props(); ok { + _spec.SetField(share.FieldProps, field.TypeJSON, value) + } + if su.mutation.PropsCleared() { + _spec.ClearField(share.FieldProps, field.TypeJSON) + } if su.mutation.UserCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -526,6 +545,18 @@ func (suo *ShareUpdateOne) ClearRemainDownloads() *ShareUpdateOne { return suo } +// SetProps sets the "props" field. +func (suo *ShareUpdateOne) SetProps(tp *types.ShareProps) *ShareUpdateOne { + suo.mutation.SetProps(tp) + return suo +} + +// ClearProps clears the value of the "props" field. +func (suo *ShareUpdateOne) ClearProps() *ShareUpdateOne { + suo.mutation.ClearProps() + return suo +} + // SetUserID sets the "user" edge to the User entity by ID. func (suo *ShareUpdateOne) SetUserID(id int) *ShareUpdateOne { suo.mutation.SetUserID(id) @@ -704,6 +735,12 @@ func (suo *ShareUpdateOne) sqlSave(ctx context.Context) (_node *Share, err error if suo.mutation.RemainDownloadsCleared() { _spec.ClearField(share.FieldRemainDownloads, field.TypeInt) } + if value, ok := suo.mutation.Props(); ok { + _spec.SetField(share.FieldProps, field.TypeJSON, value) + } + if suo.mutation.PropsCleared() { + _spec.ClearField(share.FieldProps, field.TypeJSON) + } if suo.mutation.UserCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/inventory/file.go b/inventory/file.go index 70e48620..a99d40f9 100644 --- a/inventory/file.go +++ b/inventory/file.go @@ -209,6 +209,8 @@ type FileClient interface { Update(ctx context.Context, file *ent.File) (*ent.File, error) // ListEntities lists entities ListEntities(ctx context.Context, args *ListEntityParameters) (*ListEntityResult, error) + // UpdateProps updates props of a file + UpdateProps(ctx context.Context, file *ent.File, props *types.FileProps) (*ent.File, error) } func NewFileClient(client *ent.Client, dbType conf.DBType, hasher hashid.Encoder) FileClient { @@ -275,6 +277,17 @@ func (f *fileClient) Update(ctx context.Context, file *ent.File) (*ent.File, err return q.Save(ctx) } +func (f *fileClient) UpdateProps(ctx context.Context, file *ent.File, props *types.FileProps) (*ent.File, error) { + file, err := f.client.File.UpdateOne(file). + SetProps(props). + Save(ctx) + if err != nil { + return nil, err + } + + return file, nil +} + func (f *fileClient) CountByTimeRange(ctx context.Context, start, end *time.Time) (int, error) { if start == nil || end == nil { return f.client.File.Query().Count(ctx) @@ -554,6 +567,10 @@ func (f *fileClient) Copy(ctx context.Context, files []*ent.File, dstMap map[int stm.SetPrimaryEntity(file.PrimaryEntity) } + if file.Props != nil && dstMap[file.FileChildren][0].OwnerID == file.OwnerID { + stm.SetProps(file.Props) + } + return stm }) diff --git a/inventory/share.go b/inventory/share.go index a2e0f965..3f18b40c 100644 --- a/inventory/share.go +++ b/inventory/share.go @@ -3,6 +3,7 @@ package inventory import ( "context" "fmt" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "time" "entgo.io/ent/dialect/sql" @@ -62,6 +63,7 @@ type ( Expires *time.Time OwnerID int FileID int + Props *types.ShareProps } ListShareArgs struct { @@ -122,6 +124,10 @@ func (c *shareClient) Upsert(ctx context.Context, params *CreateShareParams) (*e createQuery.ClearExpires() } + if params.Props != nil { + createQuery.SetProps(params.Props) + } + return createQuery.Save(ctx) } @@ -138,6 +144,9 @@ func (c *shareClient) Upsert(ctx context.Context, params *CreateShareParams) (*e if params.Expires != nil { query.SetNillableExpires(params.Expires) } + if params.Props != nil { + query.SetProps(params.Props) + } return query.Save(ctx) } diff --git a/inventory/types/types.go b/inventory/types/types.go index 9d77d581..3cadc33b 100644 --- a/inventory/types/types.go +++ b/inventory/types/types.go @@ -7,13 +7,15 @@ import ( // UserSetting 用户其他配置 type ( UserSetting struct { - ProfileOff bool `json:"profile_off,omitempty"` - PreferredTheme string `json:"preferred_theme,omitempty"` - VersionRetention bool `json:"version_retention,omitempty"` - VersionRetentionExt []string `json:"version_retention_ext,omitempty"` - VersionRetentionMax int `json:"version_retention_max,omitempty"` - Pined []PinedFile `json:"pined,omitempty"` - Language string `json:"email_language,omitempty"` + ProfileOff bool `json:"profile_off,omitempty"` + PreferredTheme string `json:"preferred_theme,omitempty"` + VersionRetention bool `json:"version_retention,omitempty"` + VersionRetentionExt []string `json:"version_retention_ext,omitempty"` + VersionRetentionMax int `json:"version_retention_max,omitempty"` + Pined []PinedFile `json:"pined,omitempty"` + Language string `json:"email_language,omitempty"` + DisableViewSync bool `json:"disable_view_sync,omitempty"` + FsViewMap map[string]ExplorerView `json:"fs_view_map,omitempty"` } PinedFile struct { @@ -149,6 +151,32 @@ type ( PolicyType string FileProps struct { + View *ExplorerView `json:"view,omitempty"` + } + + ExplorerView struct { + PageSize int `json:"page_size" binding:"min=50"` + Order string `json:"order,omitempty" binding:"max=255"` + OrderDirection string `json:"order_direction,omitempty" binding:"eq=asc|eq=desc"` + View string `json:"view,omitempty" binding:"eq=list|eq=grid|eq=gallery"` + Thumbnail bool `json:"thumbnail,omitempty"` + GalleryWidth int `json:"gallery_width,omitempty" binding:"min=50,max=500"` + Columns []ListViewColumn `json:"columns,omitempty" binding:"max=1000"` + } + + ListViewColumn struct { + Type int `json:"type" binding:"min=0"` + Width *int `json:"width,omitempty"` + Props *ColumTypeProps `json:"props,omitempty"` + } + + ColumTypeProps struct { + MetadataKey string `json:"metadata_key,omitempty" binding:"max=255"` + } + + ShareProps struct { + // Whether to share view setting from owner + ShareView bool `json:"share_view,omitempty"` } ) diff --git a/pkg/filemanager/fs/dbfs/dbfs.go b/pkg/filemanager/fs/dbfs/dbfs.go index eb9cdfb1..cb7d3dc7 100644 --- a/pkg/filemanager/fs/dbfs/dbfs.go +++ b/pkg/filemanager/fs/dbfs/dbfs.go @@ -35,6 +35,7 @@ const ( ContextHintTTL = 5 * 60 // 5 minutes folderSummaryCachePrefix = "folder_summary_" + defaultPageSize = 100 ) type ( @@ -119,15 +120,44 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi searchParams := path.SearchParameters() isSearching := searchParams != nil + parent, err := f.getFileByPath(ctx, navigator, path) + if err != nil { + return nil, nil, fmt.Errorf("Parent not exist: %w", err) + } + + pageSize := 0 + orderDirection := "" + orderBy := "" + + view := navigator.GetView(ctx, parent) + if view != nil { + pageSize = view.PageSize + orderDirection = view.OrderDirection + orderBy = view.Order + } + + if o.PageSize > 0 { + pageSize = o.PageSize + } + if o.OrderDirection != "" { + orderDirection = o.OrderDirection + } + if o.OrderBy != "" { + orderBy = o.OrderBy + } + // Validate pagination args props := navigator.Capabilities(isSearching) - if o.PageSize > props.MaxPageSize { - o.PageSize = props.MaxPageSize + if pageSize > props.MaxPageSize { + pageSize = props.MaxPageSize + } else if pageSize == 0 { + pageSize = defaultPageSize } - parent, err := f.getFileByPath(ctx, navigator, path) - if err != nil { - return nil, nil, fmt.Errorf("Parent not exist: %w", err) + if view != nil { + view.PageSize = pageSize + view.OrderDirection = orderDirection + view.Order = orderBy } var hintId *uuid.UUID @@ -155,9 +185,9 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi children, err := navigator.Children(ctx, parent, &ListArgs{ Page: &inventory.PaginationArgs{ Page: o.FsOption.Page, - PageSize: o.PageSize, - OrderBy: o.OrderBy, - Order: inventory.OrderDirection(o.OrderDirection), + PageSize: pageSize, + OrderBy: orderBy, + Order: inventory.OrderDirection(orderDirection), UseCursorPagination: o.useCursorPagination, PageToken: o.pageToken, }, @@ -188,6 +218,7 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi SingleFileView: children.SingleFileView, Parent: parent, StoragePolicy: storagePolicy, + View: view, }, nil } @@ -270,89 +301,6 @@ func (f *DBFS) CreateEntity(ctx context.Context, file fs.File, policy *ent.Stora return fs.NewEntity(entity), nil } -func (f *DBFS) PatchMetadata(ctx context.Context, path []*fs.URI, metas ...fs.MetadataPatch) error { - ae := serializer.NewAggregateError() - targets := make([]*File, 0, len(path)) - for _, p := range path { - navigator, err := f.getNavigator(ctx, p, NavigatorCapabilityUpdateMetadata, NavigatorCapabilityLockFile) - if err != nil { - ae.Add(p.String(), err) - continue - } - - target, err := f.getFileByPath(ctx, navigator, p) - if err != nil { - ae.Add(p.String(), fmt.Errorf("failed to get target file: %w", err)) - continue - } - - // Require Update permission - if _, ok := ctx.Value(ByPassOwnerCheckCtxKey{}).(bool); !ok && target.OwnerID() != f.user.ID { - return fs.ErrOwnerOnly.WithError(fmt.Errorf("permission denied")) - } - - if target.IsRootFolder() { - ae.Add(p.String(), fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot move root folder"))) - continue - } - - targets = append(targets, target) - } - - if len(targets) == 0 { - return ae.Aggregate() - } - - // Lock all targets - lockTargets := lo.Map(targets, func(value *File, key int) *LockByPath { - return &LockByPath{value.Uri(true), value, value.Type(), ""} - }) - ls, err := f.acquireByPath(ctx, -1, f.user, true, fs.LockApp(fs.ApplicationUpdateMetadata), lockTargets...) - defer func() { _ = f.Release(ctx, ls) }() - if err != nil { - return err - } - - metadataMap := make(map[string]string) - privateMap := make(map[string]bool) - deleted := make([]string, 0) - for _, meta := range metas { - if meta.Remove { - deleted = append(deleted, meta.Key) - continue - } - metadataMap[meta.Key] = meta.Value - if meta.Private { - privateMap[meta.Key] = meta.Private - } - } - - fc, tx, ctx, err := inventory.WithTx(ctx, f.fileClient) - if err != nil { - return serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err) - } - - for _, target := range targets { - if err := fc.UpsertMetadata(ctx, target.Model, metadataMap, privateMap); err != nil { - _ = inventory.Rollback(tx) - return fmt.Errorf("failed to upsert metadata: %w", err) - } - - if len(deleted) > 0 { - if err := fc.RemoveMetadata(ctx, target.Model, deleted...); err != nil { - _ = inventory.Rollback(tx) - return fmt.Errorf("failed to remove metadata: %w", err) - } - } - } - - if err := inventory.Commit(tx); err != nil { - return serializer.NewError(serializer.CodeDBError, "Failed to commit metadata change", err) - } - - return ae.Aggregate() -} - func (f *DBFS) SharedAddressTranslation(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.File, *fs.URI, error) { o := newDbfsOption() for _, opt := range opts { @@ -470,6 +418,9 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil target.FileExtendedInfo = extendedInfo if target.OwnerID() == f.user.ID || f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { target.FileExtendedInfo.Shares = target.Model.Edges.Shares + if target.Model.Props != nil { + target.FileExtendedInfo.View = target.Model.Props.View + } } entities := target.Entities() diff --git a/pkg/filemanager/fs/dbfs/file.go b/pkg/filemanager/fs/dbfs/file.go index 0a1950d7..804598e0 100644 --- a/pkg/filemanager/fs/dbfs/file.go +++ b/pkg/filemanager/fs/dbfs/file.go @@ -22,13 +22,20 @@ func init() { gob.Register(map[int]*File{}) } -var filePool = &sync.Pool{ - New: func() any { - return &File{ - Children: make(map[string]*File), - } - }, -} +var ( + filePool = &sync.Pool{ + New: func() any { + return &File{ + Children: make(map[string]*File), + } + }, + } + defaultView = &types.ExplorerView{ + PageSize: defaultPageSize, + View: "grid", + Thumbnail: true, + } +) type ( File struct { @@ -42,7 +49,8 @@ type ( FileExtendedInfo *fs.FileExtendedInfo FileFolderSummary *fs.FolderSummary - mu *sync.Mutex + disableView bool + mu *sync.Mutex } ) @@ -181,6 +189,31 @@ func (f *File) Uri(isRoot bool) *fs.URI { return parent.Path[index].Join(elements...) } +// View returns the view setting of the file, can be inherited from parent. +func (f *File) View() *types.ExplorerView { + // If owner has disabled view sync, return nil + owner := f.Owner() + if owner != nil && owner.Settings != nil && owner.Settings.DisableViewSync { + return nil + } + + // If navigator has disabled view sync, return nil + userRoot := f.UserRoot() + if userRoot == nil || userRoot.disableView { + return nil + } + + current := f + for current != nil { + if current.Model.Props != nil && current.Model.Props.View != nil { + return current.Model.Props.View + } + current = current.Parent + } + + return defaultView +} + // UserRoot return the root file from user's view. func (f *File) UserRoot() *File { root := f diff --git a/pkg/filemanager/fs/dbfs/my_navigator.go b/pkg/filemanager/fs/dbfs/my_navigator.go index 58053306..b607ba21 100644 --- a/pkg/filemanager/fs/dbfs/my_navigator.go +++ b/pkg/filemanager/fs/dbfs/my_navigator.go @@ -106,6 +106,7 @@ func (n *myNavigator) To(ctx context.Context, path *fs.URI) (*File, error) { rootPath := path.Root() n.root.Path[pathIndexRoot], n.root.Path[pathIndexUser] = rootPath, rootPath n.root.OwnerModel = targetUser + n.root.disableView = fsUid != n.user.ID n.root.IsUserRoot = true n.root.CapabilitiesBs = n.Capabilities(false).Capability } @@ -178,3 +179,7 @@ func (n *myNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *myNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { return nil } + +func (n *myNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView { + return file.View() +} diff --git a/pkg/filemanager/fs/dbfs/navigator.go b/pkg/filemanager/fs/dbfs/navigator.go index 57f9fba9..db642fb2 100644 --- a/pkg/filemanager/fs/dbfs/navigator.go +++ b/pkg/filemanager/fs/dbfs/navigator.go @@ -53,6 +53,8 @@ type ( FollowTx(ctx context.Context) (func(), error) // ExecuteHook performs custom operations before or after certain actions. ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error + // GetView returns the view setting of the given file. + GetView(ctx context.Context, file *File) *types.ExplorerView } State interface{} @@ -100,6 +102,7 @@ const ( NavigatorCapability_CommunityPlacehodler8 NavigatorCapability_CommunityPlacehodler9 NavigatorCapabilityEnterFolder + NavigatorCapabilityModifyProps searchTokenSeparator = "|" ) @@ -120,6 +123,7 @@ func init() { NavigatorCapabilityInfo: true, NavigatorCapabilityVersionControl: true, NavigatorCapabilityEnterFolder: true, + NavigatorCapabilityModifyProps: true, }, myNavigatorCapability) boolset.Sets(map[NavigatorCapability]bool{ NavigatorCapabilityDownloadFile: true, @@ -129,6 +133,7 @@ func init() { NavigatorCapabilityInfo: true, NavigatorCapabilityVersionControl: true, NavigatorCapabilityEnterFolder: true, + NavigatorCapabilityModifyProps: true, }, shareNavigatorCapability) boolset.Sets(map[NavigatorCapability]bool{ NavigatorCapabilityListChildren: true, diff --git a/pkg/filemanager/fs/dbfs/props.go b/pkg/filemanager/fs/dbfs/props.go new file mode 100644 index 00000000..7b7e409d --- /dev/null +++ b/pkg/filemanager/fs/dbfs/props.go @@ -0,0 +1,138 @@ +package dbfs + +import ( + "context" + "fmt" + + "github.com/cloudreve/Cloudreve/v4/inventory" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" + "github.com/cloudreve/Cloudreve/v4/pkg/serializer" + "github.com/samber/lo" +) + +func (f *DBFS) PatchProps(ctx context.Context, uri *fs.URI, props *types.FileProps, delete bool) error { + navigator, err := f.getNavigator(ctx, uri, NavigatorCapabilityModifyProps, NavigatorCapabilityLockFile) + if err != nil { + return err + } + + target, err := f.getFileByPath(ctx, navigator, uri) + if err != nil { + return fmt.Errorf("failed to get target file: %w", err) + } + + if target.OwnerID() != f.user.ID && !f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + return fs.ErrOwnerOnly.WithError(fmt.Errorf("only file owner can modify file props")) + } + + // Lock target + lr := &LockByPath{target.Uri(true), target, target.Type(), ""} + ls, err := f.acquireByPath(ctx, -1, f.user, false, fs.LockApp(fs.ApplicationUpdateMetadata), lr) + defer func() { _ = f.Release(ctx, ls) }() + if err != nil { + return err + } + + currentProps := target.Model.Props + if currentProps == nil { + currentProps = &types.FileProps{} + } + + if props.View != nil { + if delete { + currentProps.View = nil + } else { + currentProps.View = props.View + } + } + + if _, err := f.fileClient.UpdateProps(ctx, target.Model, currentProps); err != nil { + return serializer.NewError(serializer.CodeDBError, "failed to update file props", err) + } + + return nil +} + +func (f *DBFS) PatchMetadata(ctx context.Context, path []*fs.URI, metas ...fs.MetadataPatch) error { + ae := serializer.NewAggregateError() + targets := make([]*File, 0, len(path)) + for _, p := range path { + navigator, err := f.getNavigator(ctx, p, NavigatorCapabilityUpdateMetadata, NavigatorCapabilityLockFile) + if err != nil { + ae.Add(p.String(), err) + continue + } + + target, err := f.getFileByPath(ctx, navigator, p) + if err != nil { + ae.Add(p.String(), fmt.Errorf("failed to get target file: %w", err)) + continue + } + + // Require Update permission + if _, ok := ctx.Value(ByPassOwnerCheckCtxKey{}).(bool); !ok && target.OwnerID() != f.user.ID { + return fs.ErrOwnerOnly.WithError(fmt.Errorf("permission denied")) + } + + if target.IsRootFolder() { + ae.Add(p.String(), fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot move root folder"))) + continue + } + + targets = append(targets, target) + } + + if len(targets) == 0 { + return ae.Aggregate() + } + + // Lock all targets + lockTargets := lo.Map(targets, func(value *File, key int) *LockByPath { + return &LockByPath{value.Uri(true), value, value.Type(), ""} + }) + ls, err := f.acquireByPath(ctx, -1, f.user, true, fs.LockApp(fs.ApplicationUpdateMetadata), lockTargets...) + defer func() { _ = f.Release(ctx, ls) }() + if err != nil { + return err + } + + metadataMap := make(map[string]string) + privateMap := make(map[string]bool) + deleted := make([]string, 0) + for _, meta := range metas { + if meta.Remove { + deleted = append(deleted, meta.Key) + continue + } + metadataMap[meta.Key] = meta.Value + if meta.Private { + privateMap[meta.Key] = meta.Private + } + } + + fc, tx, ctx, err := inventory.WithTx(ctx, f.fileClient) + if err != nil { + return serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err) + } + + for _, target := range targets { + if err := fc.UpsertMetadata(ctx, target.Model, metadataMap, privateMap); err != nil { + _ = inventory.Rollback(tx) + return fmt.Errorf("failed to upsert metadata: %w", err) + } + + if len(deleted) > 0 { + if err := fc.RemoveMetadata(ctx, target.Model, deleted...); err != nil { + _ = inventory.Rollback(tx) + return fmt.Errorf("failed to remove metadata: %w", err) + } + } + } + + if err := inventory.Commit(tx); err != nil { + return serializer.NewError(serializer.CodeDBError, "Failed to commit metadata change", err) + } + + return ae.Aggregate() +} diff --git a/pkg/filemanager/fs/dbfs/share_navigator.go b/pkg/filemanager/fs/dbfs/share_navigator.go index 470b48fa..374994f3 100644 --- a/pkg/filemanager/fs/dbfs/share_navigator.go +++ b/pkg/filemanager/fs/dbfs/share_navigator.go @@ -148,6 +148,7 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error) n.shareRoot.Path[pathIndexUser] = path.Root() n.shareRoot.OwnerModel = n.owner n.shareRoot.IsUserRoot = true + n.shareRoot.disableView = (share.Props == nil || !share.Props.ShareView) && n.user.ID != n.owner.ID n.shareRoot.CapabilitiesBs = n.Capabilities(false).Capability // Check if any ancestors is deleted @@ -303,3 +304,7 @@ func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error { return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f) } + +func (n *shareNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView { + return file.View() +} diff --git a/pkg/filemanager/fs/dbfs/sharewithme_navigator.go b/pkg/filemanager/fs/dbfs/sharewithme_navigator.go index 4d896b77..1591b721 100644 --- a/pkg/filemanager/fs/dbfs/sharewithme_navigator.go +++ b/pkg/filemanager/fs/dbfs/sharewithme_navigator.go @@ -4,8 +4,11 @@ import ( "context" "errors" "fmt" + + "github.com/cloudreve/Cloudreve/v4/application/constants" "github.com/cloudreve/Cloudreve/v4/ent" "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/cache" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" @@ -139,3 +142,10 @@ func (n *sharedWithMeNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *sharedWithMeNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { return nil } + +func (n *sharedWithMeNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView { + if view, ok := n.user.Settings.FsViewMap[string(constants.FileSystemSharedWithMe)]; ok { + return &view + } + return defaultView +} diff --git a/pkg/filemanager/fs/dbfs/trash_navigator.go b/pkg/filemanager/fs/dbfs/trash_navigator.go index 1bf45c66..ede265ed 100644 --- a/pkg/filemanager/fs/dbfs/trash_navigator.go +++ b/pkg/filemanager/fs/dbfs/trash_navigator.go @@ -3,8 +3,11 @@ package dbfs import ( "context" "fmt" + + "github.com/cloudreve/Cloudreve/v4/application/constants" "github.com/cloudreve/Cloudreve/v4/ent" "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/cache" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" @@ -13,7 +16,26 @@ import ( "github.com/cloudreve/Cloudreve/v4/pkg/setting" ) -var trashNavigatorCapability = &boolset.BooleanSet{} +var ( + trashNavigatorCapability = &boolset.BooleanSet{} + defaultTrashView = &types.ExplorerView{ + View: "list", + Columns: []types.ListViewColumn{ + { + Type: 0, + }, + { + Type: 2, + }, + { + Type: 8, + }, + { + Type: 7, + }, + }, + } +) // NewTrashNavigator creates a navigator for user's "trash" file system. func NewTrashNavigator(u *ent.User, fileClient inventory.FileClient, l logging.Logger, config *setting.DBFS, @@ -135,3 +157,10 @@ func (n *trashNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *trashNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { return nil } + +func (n *trashNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView { + if view, ok := n.user.Settings.FsViewMap[string(constants.FileSystemTrash)]; ok { + return &view + } + return defaultTrashView +} diff --git a/pkg/filemanager/fs/fs.go b/pkg/filemanager/fs/fs.go index b6bd6c79..e6b1bde0 100644 --- a/pkg/filemanager/fs/fs.go +++ b/pkg/filemanager/fs/fs.go @@ -97,6 +97,8 @@ type ( GetFileFromDirectLink(ctx context.Context, dl *ent.DirectLink) (File, error) // TraverseFile traverses a file to its root file, return the file with linked root. TraverseFile(ctx context.Context, fileID int) (File, error) + // PatchProps patches the props of a file. + PatchProps(ctx context.Context, uri *URI, props *types.FileProps, delete bool) error } UploadManager interface { @@ -165,6 +167,7 @@ type ( FolderSummary() *FolderSummary Capabilities() *boolset.BooleanSet IsRootFolder() bool + View() *types.ExplorerView } Entities []Entity @@ -187,6 +190,7 @@ type ( StorageUsed int64 Shares []*ent.Share EntityStoragePolicies map[int]*ent.StoragePolicy + View *types.ExplorerView } FolderSummary struct { @@ -215,6 +219,7 @@ type ( MixedType bool SingleFileView bool StoragePolicy *ent.StoragePolicy + View *types.ExplorerView } // NavigatorProps is the properties of current filesystem. diff --git a/pkg/filemanager/manager/manager.go b/pkg/filemanager/manager/manager.go index 6735f4a6..b53f78e7 100644 --- a/pkg/filemanager/manager/manager.go +++ b/pkg/filemanager/manager/manager.go @@ -75,6 +75,8 @@ type ( CastStoragePolicyOnSlave(ctx context.Context, policy *ent.StoragePolicy) *ent.StoragePolicy // GetStorageDriver gets storage driver for given policy GetStorageDriver(ctx context.Context, policy *ent.StoragePolicy) (driver.Handler, error) + // PatchView patches the view setting of a file + PatchView(ctx context.Context, uri *fs.URI, view *types.ExplorerView) error } ShareManagement interface { @@ -111,6 +113,7 @@ type ( IsPrivate bool RemainDownloads int Expire *time.Time + ShareView bool } ) diff --git a/pkg/filemanager/manager/operation.go b/pkg/filemanager/manager/operation.go index a45d849a..1b44b624 100644 --- a/pkg/filemanager/manager/operation.go +++ b/pkg/filemanager/manager/operation.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/cloudreve/Cloudreve/v4/application/constants" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/inventory" "github.com/cloudreve/Cloudreve/v4/inventory/types" @@ -261,6 +262,10 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C password = util.RandString(8, util.RandomLowerCases) } + props := &types.ShareProps{ + ShareView: args.ShareView, + } + share, err := shareClient.Upsert(ctx, &inventory.CreateShareParams{ OwnerID: file.OwnerID(), FileID: file.ID(), @@ -268,6 +273,7 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C Expires: args.Expire, RemainDownloads: args.RemainDownloads, Existed: existed, + Props: props, }) if err != nil { @@ -281,6 +287,39 @@ func (m *manager) TraverseFile(ctx context.Context, fileID int) (fs.File, error) return m.fs.TraverseFile(ctx, fileID) } +func (m *manager) PatchView(ctx context.Context, uri *fs.URI, view *types.ExplorerView) error { + if uri.PathTrimmed() == "" && uri.FileSystem() != constants.FileSystemMy && uri.FileSystem() != constants.FileSystemShare { + if m.user.Settings.FsViewMap == nil { + m.user.Settings.FsViewMap = make(map[string]types.ExplorerView) + } + + if view == nil { + delete(m.user.Settings.FsViewMap, string(uri.FileSystem())) + } else { + m.user.Settings.FsViewMap[string(uri.FileSystem())] = *view + } + + if err := m.dep.UserClient().SaveSettings(ctx, m.user); err != nil { + return serializer.NewError(serializer.CodeDBError, "failed to save user settings", err) + } + + return nil + } + + patch := &types.FileProps{ + View: view, + } + isDelete := view == nil + if isDelete { + patch.View = &types.ExplorerView{} + } + if err := m.fs.PatchProps(ctx, uri, patch, isDelete); err != nil { + return err + } + + return nil +} + func getEntityDisplayName(f fs.File, e fs.Entity) string { switch e.Type() { case types.EntityTypeThumbnail: diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 2f6866ef..2f3d0490 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -388,3 +388,15 @@ func DeleteVersion(c *gin.Context) { c.JSON(200, serializer.Response{}) } + +func PatchView(c *gin.Context) { + service := ParametersFromContext[*explorer.PatchViewService](c, explorer.PatchViewParameterCtx{}) + err := service.Patch(c) + if err != nil { + c.JSON(200, serializer.Err(c, err)) + c.Abort() + return + } + + c.JSON(200, serializer.Response{}) +} diff --git a/routers/router.go b/routers/router.go index 6b17260e..3a6d9747 100644 --- a/routers/router.go +++ b/routers/router.go @@ -701,6 +701,11 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine { controllers.FromJSON[explorer.GetDirectLinkService](explorer.GetDirectLinkParamCtx{}), middleware.ValidateBatchFileCount(dep, explorer.GetDirectLinkParamCtx{}), controllers.GetSource) + // Patch view + file.PATCH("view", + controllers.FromJSON[explorer.PatchViewService](explorer.PatchViewParameterCtx{}), + controllers.PatchView, + ) } // 分享相关 diff --git a/service/explorer/file.go b/service/explorer/file.go index 615c9270..83aa8356 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -120,8 +120,6 @@ func (s *GetDirectLinkService) Get(c *gin.Context) ([]DirectLinkResponse, error) return BuildDirectLinkResponse(res), err } -const defaultPageSize = 100 - type ( // ListFileParameterCtx define key fore ListFileService ListFileParameterCtx struct{} @@ -130,7 +128,7 @@ type ( ListFileService struct { Uri string `uri:"uri" form:"uri" json:"uri" binding:"required"` Page int `uri:"page" form:"page" json:"page" binding:"min=0"` - PageSize int `uri:"page_size" form:"page_size" json:"page_size" binding:"min=10"` + PageSize int `uri:"page_size" form:"page_size" json:"page_size"` OrderBy string `uri:"order_by" form:"order_by" json:"order_by"` OrderDirection string `uri:"order_direction" form:"order_direction" json:"order_direction"` NextPageToken string `uri:"next_page_token" form:"next_page_token" json:"next_page_token"` @@ -150,10 +148,6 @@ func (service *ListFileService) List(c *gin.Context) (*ListResponse, error) { } pageSize := service.PageSize - if pageSize == 0 { - pageSize = defaultPageSize - } - streamed := false hasher := dep.HashIDEncoder() parent, res, err := m.List(c, uri, &manager.ListArgs{ @@ -670,3 +664,29 @@ func RedirectDirectLink(c *gin.Context, name string) error { c.Header("Cache-Control", fmt.Sprintf("public, max-age=%d", int(earliestExpire.Sub(time.Now()).Seconds()))) return nil } + +type ( + PatchViewParameterCtx struct{} + PatchViewService struct { + Uri string `json:"uri" binding:"required"` + View *types.ExplorerView `json:"view"` + } +) + +func (s *PatchViewService) Patch(c *gin.Context) error { + dep := dependency.FromContext(c) + user := inventory.UserFromContext(c) + m := manager.NewFileManager(dep, user) + defer m.Recycle() + + uri, err := fs.NewUriFromString(s.Uri) + if err != nil { + return serializer.NewError(serializer.CodeParamErr, "unknown uri", err) + } + + if err := m.PatchView(c, uri, s.View); err != nil { + return err + } + + return nil +} diff --git a/service/explorer/response.go b/service/explorer/response.go index 8f866f42..bcf0208c 100644 --- a/service/explorer/response.go +++ b/service/explorer/response.go @@ -207,11 +207,12 @@ type ListResponse struct { // It persists some intermedia state so that the following request don't need to query database again. // All the operations under this directory that supports context hint should carry this value in header // as X-Cr-Context-Hint. - ContextHint *uuid.UUID `json:"context_hint"` - RecursionLimitReached bool `json:"recursion_limit_reached,omitempty"` - MixedType bool `json:"mixed_type"` - SingleFileView bool `json:"single_file_view,omitempty"` - StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"` + ContextHint *uuid.UUID `json:"context_hint"` + RecursionLimitReached bool `json:"recursion_limit_reached,omitempty"` + MixedType bool `json:"mixed_type"` + SingleFileView bool `json:"single_file_view,omitempty"` + StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"` + View *types.ExplorerView `json:"view,omitempty"` } type FileResponse struct { @@ -233,10 +234,11 @@ type FileResponse struct { } type ExtendedInfo struct { - StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"` - StorageUsed int64 `json:"storage_used"` - Shares []Share `json:"shares,omitempty"` - Entities []Entity `json:"entities,omitempty"` + StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"` + StorageUsed int64 `json:"storage_used"` + Shares []Share `json:"shares,omitempty"` + Entities []Entity `json:"entities,omitempty"` + View *types.ExplorerView `json:"view,omitempty"` } type StoragePolicy struct { @@ -274,6 +276,7 @@ type Share struct { // Only viewable by owner IsPrivate bool `json:"is_private,omitempty"` Password string `json:"password,omitempty"` + ShareView bool `json:"share_view,omitempty"` // Only viewable if explicitly unlocked by owner SourceUri string `json:"source_uri,omitempty"` @@ -306,6 +309,7 @@ func BuildShare(s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *e if requester.ID == owner.ID { res.IsPrivate = s.Password != "" + res.ShareView = s.Props != nil && s.Props.ShareView } return &res @@ -323,6 +327,7 @@ func BuildListResponse(ctx context.Context, u *ent.User, parent fs.File, res *fs MixedType: res.MixedType, SingleFileView: res.SingleFileView, StoragePolicy: BuildStoragePolicy(res.StoragePolicy, hasher), + View: res.View, } if !res.Parent.IsNil() { @@ -382,7 +387,7 @@ func BuildExtendedInfo(ctx context.Context, u *ent.User, f fs.File, hasher hashi ext.Shares = lo.Map(extendedInfo.Shares, func(s *ent.Share, index int) Share { return *BuildShare(s, base, hasher, u, u, f.DisplayName(), f.Type(), true, false) }) - + ext.View = extendedInfo.View } return ext diff --git a/service/share/manage.go b/service/share/manage.go index 2d0de0a2..715fc008 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -22,6 +22,7 @@ type ( IsPrivate bool `json:"is_private"` RemainDownloads int `json:"downloads"` Expire int `json:"expire"` + ShareView bool `json:"share_view"` } ShareCreateParamCtx struct{} ) @@ -54,6 +55,7 @@ func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string, RemainDownloads: service.RemainDownloads, Expire: expires, ExistedShareID: existed, + ShareView: service.ShareView, }) if err != nil { return "", err diff --git a/service/user/response.go b/service/user/response.go index d4ceda39..e83e99ed 100644 --- a/service/user/response.go +++ b/service/user/response.go @@ -28,6 +28,7 @@ type UserSettings struct { Paswordless bool `json:"passwordless"` TwoFAEnabled bool `json:"two_fa_enabled"` Passkeys []Passkey `json:"passkeys,omitempty"` + DisableViewSync bool `json:"disable_view_sync"` } func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser) *UserSettings { @@ -40,6 +41,7 @@ func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Pa Passkeys: lo.Map(passkeys, func(item *ent.Passkey, index int) Passkey { return BuildPasskey(item) }), + DisableViewSync: u.Settings.DisableViewSync, } } @@ -95,17 +97,18 @@ type BuiltinLoginResponse struct { // User 用户序列化器 type User struct { - ID string `json:"id"` - Email string `json:"email,omitempty"` - Nickname string `json:"nickname"` - Status user.Status `json:"status,omitempty"` - Avatar string `json:"avatar,omitempty"` - CreatedAt time.Time `json:"created_at"` - PreferredTheme string `json:"preferred_theme,omitempty"` - Anonymous bool `json:"anonymous,omitempty"` - Group *Group `json:"group,omitempty"` - Pined []types.PinedFile `json:"pined,omitempty"` - Language string `json:"language,omitempty"` + ID string `json:"id"` + Email string `json:"email,omitempty"` + Nickname string `json:"nickname"` + Status user.Status `json:"status,omitempty"` + Avatar string `json:"avatar,omitempty"` + CreatedAt time.Time `json:"created_at"` + PreferredTheme string `json:"preferred_theme,omitempty"` + Anonymous bool `json:"anonymous,omitempty"` + Group *Group `json:"group,omitempty"` + Pined []types.PinedFile `json:"pined,omitempty"` + Language string `json:"language,omitempty"` + DisableViewSync bool `json:"disable_view_sync,omitempty"` } type Group struct { @@ -150,17 +153,18 @@ func BuildWebAuthnList(credentials []webauthn.Credential) []WebAuthnCredentials // BuildUser 序列化用户 func BuildUser(user *ent.User, idEncoder hashid.Encoder) User { return User{ - ID: hashid.EncodeUserID(idEncoder, user.ID), - Email: user.Email, - Nickname: user.Nick, - Status: user.Status, - Avatar: user.Avatar, - CreatedAt: user.CreatedAt, - PreferredTheme: user.Settings.PreferredTheme, - Anonymous: user.ID == 0, - Group: BuildGroup(user.Edges.Group, idEncoder), - Pined: user.Settings.Pined, - Language: user.Settings.Language, + ID: hashid.EncodeUserID(idEncoder, user.ID), + Email: user.Email, + Nickname: user.Nick, + Status: user.Status, + Avatar: user.Avatar, + CreatedAt: user.CreatedAt, + PreferredTheme: user.Settings.PreferredTheme, + Anonymous: user.ID == 0, + Group: BuildGroup(user.Edges.Group, idEncoder), + Pined: user.Settings.Pined, + Language: user.Settings.Language, + DisableViewSync: user.Settings.DisableViewSync, } } diff --git a/service/user/setting.go b/service/user/setting.go index 07cbcf03..c60085eb 100644 --- a/service/user/setting.go +++ b/service/user/setting.go @@ -4,6 +4,13 @@ import ( "context" "crypto/md5" "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/inventory" @@ -15,12 +22,6 @@ import ( "github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" ) const ( @@ -219,6 +220,7 @@ type ( NewPassword *string `json:"new_password" binding:"omitempty,min=6,max=128"` TwoFAEnabled *bool `json:"two_fa_enabled" binding:"omitempty"` TwoFACode *string `json:"two_fa_code" binding:"omitempty"` + DisableViewSync *bool `json:"disable_view_sync" binding:"omitempty"` } PatchUserSettingParamsCtx struct{} ) @@ -260,6 +262,11 @@ func (s *PatchUserSetting) Patch(c *gin.Context) error { saveSetting = true } + if s.DisableViewSync != nil { + u.Settings.DisableViewSync = *s.DisableViewSync + saveSetting = true + } + if s.CurrentPassword != nil && s.NewPassword != nil { if err := inventory.CheckPassword(u, *s.CurrentPassword); err != nil { return serializer.NewError(serializer.CodeIncorrectPassword, "Incorrect password", err)