@webiny/api-aco 6.6.0-alpha.4 → 6.6.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AcoFeature.js +2 -0
- package/AcoFeature.js.map +1 -1
- package/exports/api/aco/flp.d.ts +1 -0
- package/exports/api/aco/flp.js +1 -0
- package/features/flp/GetFlp/decorators/GetFlpWithCodeFlps.d.ts +13 -0
- package/features/flp/GetFlp/decorators/GetFlpWithCodeFlps.js +26 -0
- package/features/flp/GetFlp/decorators/GetFlpWithCodeFlps.js.map +1 -0
- package/features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.d.ts +13 -0
- package/features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.js +27 -0
- package/features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.js.map +1 -0
- package/features/flp/shared/CodeFlpMerger.d.ts +26 -0
- package/features/flp/shared/CodeFlpMerger.js +20 -0
- package/features/flp/shared/CodeFlpMerger.js.map +1 -0
- package/features/flp/shared/CodeFlpPath.d.ts +15 -0
- package/features/flp/shared/CodeFlpPath.js +29 -0
- package/features/flp/shared/CodeFlpPath.js.map +1 -0
- package/features/flp/shared/CodeFlpTarget.d.ts +10 -0
- package/features/flp/shared/CodeFlpTarget.js +26 -0
- package/features/flp/shared/CodeFlpTarget.js.map +1 -0
- package/features/flp/shared/CodeFlpsProvider.d.ts +14 -0
- package/features/flp/shared/CodeFlpsProvider.js +33 -0
- package/features/flp/shared/CodeFlpsProvider.js.map +1 -0
- package/features/flp/shared/abstractions.d.ts +77 -0
- package/features/flp/shared/abstractions.js +19 -0
- package/features/flp/shared/abstractions.js.map +1 -0
- package/features/flp/shared/feature.d.ts +5 -0
- package/features/flp/shared/feature.js +17 -0
- package/features/flp/shared/feature.js.map +1 -0
- package/features/flp/shared/index.d.ts +4 -0
- package/features/flp/shared/index.js +4 -0
- package/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.d.ts +9 -1
- package/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.js +20 -3
- package/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.js.map +1 -1
- package/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.d.ts +14 -1
- package/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.js +52 -4
- package/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.js.map +1 -1
- package/folder/createFolderTypeDefs.js +4 -1
- package/folder/createFolderTypeDefs.js.map +1 -1
- package/package.json +19 -19
package/AcoFeature.js
CHANGED
|
@@ -36,6 +36,7 @@ import { EnsureFolderIsEmptyOnDeleteFeature } from "./features/folder/EnsureFold
|
|
|
36
36
|
import { AcoFilterCrud, AcoFlpCrud, FilterStorageOperations, FlpStorageOperations } from "./features/folder/shared/abstractions.js";
|
|
37
37
|
import { ListFlpsFeature } from "./features/flp/ListFlps/feature.js";
|
|
38
38
|
import { GetFlpFeature } from "./features/flp/GetFlp/feature.js";
|
|
39
|
+
import { CodeFlpsFeature } from "./features/flp/shared/feature.js";
|
|
39
40
|
import { ListFolderLevelPermissionsTargetsFeature } from "./features/folder/ListFolderLevelPermissionsTargets/feature.js";
|
|
40
41
|
import { CreateFlpOnFolderCreatedFeature } from "./features/flp/CreateFlpOnFolderCreated/index.js";
|
|
41
42
|
import { EnsureFolderIsEmptyFeature } from "./features/folder/EnsureFolderIsEmpty/feature.js";
|
|
@@ -76,6 +77,7 @@ const AcoFeature = createFeature({
|
|
|
76
77
|
DeleteFlpFeature.register(container);
|
|
77
78
|
ListFlpsFeature.register(container);
|
|
78
79
|
GetFlpFeature.register(container);
|
|
80
|
+
CodeFlpsFeature.register(container);
|
|
79
81
|
CreateFlpOnFolderCreatedFeature.register(container);
|
|
80
82
|
UpdateFlpOnFolderUpdatedFeature.register(container);
|
|
81
83
|
DeleteFlpOnFolderDeletedFeature.register(container);
|
package/AcoFeature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcoFeature.js","sources":["../src/AcoFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { GraphQLSchemaFactory } from \"@webiny/api-graphql/graphql/abstractions.js\";\nimport type { IGraphQLSchemaBuilder } from \"@webiny/api-graphql/features/GraphQLSchemaBuilder/abstractions.js\";\nimport type { Container } from \"@webiny/di\";\nimport { IdentityContext } from \"@webiny/api-core/features/security/IdentityContext/abstractions.js\";\nimport { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport { HeadlessCms } from \"@webiny/api-headless-cms/features/shared/abstractions.js\";\nimport { addAcoBaseSchema } from \"./createAcoGraphQL.js\";\nimport { addFilterSchema } from \"~/filter/filter.gql.js\";\nimport { AcoFolderSchemaFactory } from \"./AcoFolderSchemaFactory.js\";\nimport { FolderModelProvider } from \"~/features/folder/FolderModelProvider.js\";\nimport { CmsFlpFeature } from \"~/features/cms/index.js\";\nimport { FeatureFlags } from \"@webiny/api-core/features/featureFlags/abstractions.js\";\nimport { CreateFlpTask } from \"~/flp/tasks/createFlp.task.js\";\nimport { UpdateFlpTask } from \"~/flp/tasks/updateFlp.task.js\";\nimport { DeleteFlpTask } from \"~/flp/tasks/deleteFlp.task.js\";\nimport { SyncFlpTask } from \"~/flp/tasks/syncFlp.task.js\";\nimport type { AcoStorageOperations } from \"~/types.js\";\nimport type { Tenant } from \"@webiny/api-core/types/tenancy.js\";\nimport { FolderModel } from \"~/domain/folder/folder.model.js\";\nimport { FilterPrivateModel } from \"~/filter/filter.model.js\";\n// Facade wiring (moved here from createAcoContext so the ACO facade can be a lazy DI factory).\nimport { createFilterOperations } from \"~/filter/filter.so.js\";\nimport { createFilterCrudMethods } from \"~/filter/filter.crud.js\";\nimport { createFlpCrudMethods } from \"~/flp/index.js\";\nimport {\n FolderLevelPermissions,\n FolderLevelPermissionsFeature\n} from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { UpdateFolderFeature } from \"~/features/folder/UpdateFolder/index.js\";\nimport { DeleteFolderFeature } from \"~/features/folder/DeleteFolder/index.js\";\nimport { CreateFolderFeature } from \"~/features/folder/CreateFolder/index.js\";\nimport { GetFolderFeature } from \"~/features/folder/GetFolder/index.js\";\nimport { ListFoldersFeature } from \"~/features/folder/ListFolders/index.js\";\nimport { GetFolderHierarchyFeature } from \"~/features/folder/GetFolderHierarchy/index.js\";\nimport { GetAncestorsFeature } from \"~/features/folder/GetAncestors/index.js\";\nimport { UpdateFlpOnFolderUpdatedFeature } from \"~/features/flp/UpdateFlpOnFolderUpdated/index.js\";\nimport { DeleteFlpOnFolderDeletedFeature } from \"~/features/flp/DeleteFlpOnFolderDeleted/index.js\";\nimport { EnsureHcmsFolderIsEmptyOnDeleteFeature } from \"~/features/folder/EnsureHcmsFolderIsEmptyOnDelete/index.js\";\nimport { CreateFlpFeature } from \"~/features/flp/CreateFlp/index.js\";\nimport { DeleteFlpFeature } from \"~/features/flp/DeleteFlp/index.js\";\nimport { UpdateFlpFeature } from \"~/features/flp/UpdateFlp/index.js\";\nimport { EnsureFolderIsEmptyOnDeleteFeature } from \"~/features/folder/EnsureFolderIsEmptyOnDelete/index.js\";\nimport {\n AcoFilterCrud,\n AcoFlpCrud,\n FilterStorageOperations,\n FlpStorageOperations\n} from \"~/features/folder/shared/abstractions.js\";\nimport { ListFlpsFeature } from \"~/features/flp/ListFlps/feature.js\";\nimport { GetFlpFeature } from \"~/features/flp/GetFlp/feature.js\";\nimport { ListFolderLevelPermissionsTargetsFeature } from \"~/features/folder/ListFolderLevelPermissionsTargets/feature.js\";\nimport { CreateFlpOnFolderCreatedFeature } from \"~/features/flp/CreateFlpOnFolderCreated/index.js\";\nimport { EnsureFolderIsEmptyFeature } from \"~/features/folder/EnsureFolderIsEmpty/feature.js\";\nimport { AcoAiToolsFeature } from \"~/features/ai/index.js\";\n\nclass AcoSchemaFactoryImpl implements GraphQLSchemaFactory.Interface {\n async execute(builder: IGraphQLSchemaBuilder): Promise<IGraphQLSchemaBuilder> {\n // Static ACO base + filter schema (DI-native contributors declaring their deps). The\n // dynamic, per-tenant folder schema is built by AcoFolderSchemaFactory.\n addAcoBaseSchema(builder);\n addFilterSchema(builder);\n return builder;\n }\n}\n\nconst AcoSchemaFactory = GraphQLSchemaFactory.createImplementation({\n implementation: AcoSchemaFactoryImpl,\n dependencies: []\n});\n\nexport const AcoFeature = createFeature({\n name: \"Aco\",\n register(container: Container) {\n container.register(FolderModel);\n AcoAiToolsFeature.register(container);\n container.register(FilterPrivateModel);\n\n // Background task definitions — pure wiring, no tenant/identity needed.\n container.register(CreateFlpTask);\n container.register(UpdateFlpTask);\n container.register(DeleteFlpTask);\n container.register(SyncFlpTask);\n\n // Folder + FLP use-case features — pure DI wiring (all lazy). Moved out of the per-request\n // initializer so they're registered for every event, not only GraphQL requests.\n FolderLevelPermissionsFeature.register(container);\n CreateFolderFeature.register(container);\n UpdateFolderFeature.register(container);\n DeleteFolderFeature.register(container);\n GetFolderFeature.register(container);\n ListFoldersFeature.register(container);\n ListFolderLevelPermissionsTargetsFeature.register(container);\n GetFolderHierarchyFeature.register(container);\n GetAncestorsFeature.register(container);\n EnsureFolderIsEmptyFeature.register(container);\n CreateFlpFeature.register(container);\n UpdateFlpFeature.register(container);\n DeleteFlpFeature.register(container);\n ListFlpsFeature.register(container);\n GetFlpFeature.register(container);\n CreateFlpOnFolderCreatedFeature.register(container);\n UpdateFlpOnFolderUpdatedFeature.register(container);\n DeleteFlpOnFolderDeletedFeature.register(container);\n EnsureFolderIsEmptyOnDeleteFeature.register(container);\n EnsureHcmsFolderIsEmptyOnDeleteFeature.register(container);\n\n // ===== Lazy ACO facade =====\n // Built on first resolve (post-auth) so the HeadlessCms facade (also lazy) is resolved on\n // demand. Memoised per request container. Consumers resolve AcoFlpCrud / AcoFilterCrud /\n // FilterStorageOperations via DI.\n const getTenant = (): Tenant => container.resolve(TenantContext).getTenant();\n\n let storageOperations: AcoStorageOperations | undefined;\n const getStorageOperations = (): AcoStorageOperations => {\n if (!storageOperations) {\n const cms = container.resolve(HeadlessCms);\n const identityContext = container.resolve(IdentityContext);\n storageOperations = {\n filter: createFilterOperations({ cms, identityContext, container }),\n flp: container.resolve(FlpStorageOperations)\n };\n }\n return storageOperations;\n };\n\n let flpCrudMethods: ReturnType<typeof createFlpCrudMethods> | undefined;\n const getFlpCrudMethods = () => {\n if (!flpCrudMethods) {\n flpCrudMethods = createFlpCrudMethods({\n getTenant,\n storageOperations: getStorageOperations()\n });\n }\n return flpCrudMethods;\n };\n container.registerFactory(AcoFlpCrud, () => getFlpCrudMethods());\n container.registerFactory(FilterStorageOperations, () => getStorageOperations().filter);\n\n let filterCrudMethods: ReturnType<typeof createFilterCrudMethods> | undefined;\n container.registerFactory(AcoFilterCrud, () => {\n if (!filterCrudMethods) {\n filterCrudMethods = createFilterCrudMethods({\n container,\n getTenant,\n storageOperations: getStorageOperations(),\n folderLevelPermissions: container.resolve(FolderLevelPermissions)\n });\n }\n return filterCrudMethods;\n });\n\n // The per-tenant folder model is resolved on demand — see FolderModelProvider.\n container.register(FolderModelProvider);\n\n // CMS-entry folder-level-permission decorators, feature-flag gated. These must be\n // registered before any CMS-entry abstraction they wrap is first resolved, which is why\n // they used to sit in a pre-resolver initializer. Register time is strictly earlier and the\n // gate is valid here: the WCP license is refreshed before any feature registers (see\n // registerApiRequestStack).\n if (\n container\n .resolve(FeatureFlags)\n .get()\n .isEnabled(\"advancedAccessControlLayer.folderLevelPermissions\")\n ) {\n CmsFlpFeature.register(container);\n }\n\n container.register(AcoSchemaFactory);\n container.register(AcoFolderSchemaFactory);\n }\n});\n"],"names":["AcoSchemaFactoryImpl","builder","addAcoBaseSchema","addFilterSchema","AcoSchemaFactory","GraphQLSchemaFactory","AcoFeature","createFeature","container","FolderModel","AcoAiToolsFeature","FilterPrivateModel","CreateFlpTask","UpdateFlpTask","DeleteFlpTask","SyncFlpTask","FolderLevelPermissionsFeature","CreateFolderFeature","UpdateFolderFeature","DeleteFolderFeature","GetFolderFeature","ListFoldersFeature","ListFolderLevelPermissionsTargetsFeature","GetFolderHierarchyFeature","GetAncestorsFeature","EnsureFolderIsEmptyFeature","CreateFlpFeature","UpdateFlpFeature","DeleteFlpFeature","ListFlpsFeature","GetFlpFeature","CreateFlpOnFolderCreatedFeature","UpdateFlpOnFolderUpdatedFeature","DeleteFlpOnFolderDeletedFeature","EnsureFolderIsEmptyOnDeleteFeature","EnsureHcmsFolderIsEmptyOnDeleteFeature","getTenant","TenantContext","storageOperations","getStorageOperations","cms","HeadlessCms","identityContext","IdentityContext","createFilterOperations","FlpStorageOperations","flpCrudMethods","getFlpCrudMethods","createFlpCrudMethods","AcoFlpCrud","FilterStorageOperations","filterCrudMethods","AcoFilterCrud","createFilterCrudMethods","FolderLevelPermissions","FolderModelProvider","FeatureFlags","CmsFlpFeature","AcoFolderSchemaFactory"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAMA;IACF,MAAM,QAAQC,OAA8B,EAAkC;QAG1EC,iBAAiBD;QACjBE,gBAAgBF;QAChB,OAAOA;IACX;AACJ;AAEA,MAAMG,mBAAmBC,qBAAqB,oBAAoB,CAAC;IAC/D,gBAAgBL;IAChB,cAAc,EAAE;AACpB;AAEO,MAAMM,aAAaC,cAAc;IACpC,MAAM;IACN,UAASC,SAAoB;QACzBA,UAAU,QAAQ,CAACC;QACnBC,kBAAkB,QAAQ,CAACF;QAC3BA,UAAU,QAAQ,CAACG;QAGnBH,UAAU,QAAQ,CAACI;QACnBJ,UAAU,QAAQ,CAACK;QACnBL,UAAU,QAAQ,CAACM;QACnBN,UAAU,QAAQ,CAACO;QAInBC,8BAA8B,QAAQ,CAACR;QACvCS,oBAAoB,QAAQ,CAACT;QAC7BU,oBAAoB,QAAQ,CAACV;QAC7BW,oBAAoB,QAAQ,CAACX;QAC7BY,iBAAiB,QAAQ,CAACZ;QAC1Ba,mBAAmB,QAAQ,CAACb;QAC5Bc,yCAAyC,QAAQ,CAACd;QAClDe,0BAA0B,QAAQ,CAACf;QACnCgB,oBAAoB,QAAQ,CAAChB;QAC7BiB,2BAA2B,QAAQ,CAACjB;QACpCkB,iBAAiB,QAAQ,CAAClB;QAC1BmB,iBAAiB,QAAQ,CAACnB;QAC1BoB,iBAAiB,QAAQ,CAACpB;QAC1BqB,gBAAgB,QAAQ,CAACrB;QACzBsB,cAAc,QAAQ,CAACtB;QACvBuB,gCAAgC,QAAQ,CAACvB;QACzCwB,gCAAgC,QAAQ,CAACxB;QACzCyB,gCAAgC,QAAQ,CAACzB;QACzC0B,mCAAmC,QAAQ,CAAC1B;QAC5C2B,uCAAuC,QAAQ,CAAC3B;QAMhD,MAAM4B,YAAY,IAAc5B,UAAU,OAAO,CAAC6B,eAAe,SAAS;QAE1E,IAAIC;QACJ,MAAMC,uBAAuB;YACzB,IAAI,CAACD,mBAAmB;gBACpB,MAAME,MAAMhC,UAAU,OAAO,CAACiC;gBAC9B,MAAMC,kBAAkBlC,UAAU,OAAO,CAACmC;gBAC1CL,oBAAoB;oBAChB,QAAQM,uBAAuB;wBAAEJ;wBAAKE;wBAAiBlC;oBAAU;oBACjE,KAAKA,UAAU,OAAO,CAACqC;gBAC3B;YACJ;YACA,OAAOP;QACX;QAEA,IAAIQ;QACJ,MAAMC,oBAAoB;YACtB,IAAI,CAACD,gBACDA,iBAAiBE,qBAAqB;gBAClCZ;gBACA,mBAAmBG;YACvB;YAEJ,OAAOO;QACX;QACAtC,UAAU,eAAe,CAACyC,YAAY,IAAMF;QAC5CvC,UAAU,eAAe,CAAC0C,yBAAyB,IAAMX,uBAAuB,MAAM;QAEtF,IAAIY;QACJ3C,UAAU,eAAe,CAAC4C,eAAe;YACrC,IAAI,CAACD,mBACDA,oBAAoBE,wBAAwB;gBACxC7C;gBACA4B;gBACA,mBAAmBG;gBACnB,wBAAwB/B,UAAU,OAAO,CAAC8C;YAC9C;YAEJ,OAAOH;QACX;QAGA3C,UAAU,QAAQ,CAAC+C;QAOnB,IACI/C,UACK,OAAO,CAACgD,cACR,GAAG,GACH,SAAS,CAAC,sDAEfC,cAAc,QAAQ,CAACjD;QAG3BA,UAAU,QAAQ,CAACJ;QACnBI,UAAU,QAAQ,CAACkD;IACvB;AACJ"}
|
|
1
|
+
{"version":3,"file":"AcoFeature.js","sources":["../src/AcoFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { GraphQLSchemaFactory } from \"@webiny/api-graphql/graphql/abstractions.js\";\nimport type { IGraphQLSchemaBuilder } from \"@webiny/api-graphql/features/GraphQLSchemaBuilder/abstractions.js\";\nimport type { Container } from \"@webiny/di\";\nimport { IdentityContext } from \"@webiny/api-core/features/security/IdentityContext/abstractions.js\";\nimport { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport { HeadlessCms } from \"@webiny/api-headless-cms/features/shared/abstractions.js\";\nimport { addAcoBaseSchema } from \"./createAcoGraphQL.js\";\nimport { addFilterSchema } from \"~/filter/filter.gql.js\";\nimport { AcoFolderSchemaFactory } from \"./AcoFolderSchemaFactory.js\";\nimport { FolderModelProvider } from \"~/features/folder/FolderModelProvider.js\";\nimport { CmsFlpFeature } from \"~/features/cms/index.js\";\nimport { FeatureFlags } from \"@webiny/api-core/features/featureFlags/abstractions.js\";\nimport { CreateFlpTask } from \"~/flp/tasks/createFlp.task.js\";\nimport { UpdateFlpTask } from \"~/flp/tasks/updateFlp.task.js\";\nimport { DeleteFlpTask } from \"~/flp/tasks/deleteFlp.task.js\";\nimport { SyncFlpTask } from \"~/flp/tasks/syncFlp.task.js\";\nimport type { AcoStorageOperations } from \"~/types.js\";\nimport type { Tenant } from \"@webiny/api-core/types/tenancy.js\";\nimport { FolderModel } from \"~/domain/folder/folder.model.js\";\nimport { FilterPrivateModel } from \"~/filter/filter.model.js\";\n// Facade wiring (moved here from createAcoContext so the ACO facade can be a lazy DI factory).\nimport { createFilterOperations } from \"~/filter/filter.so.js\";\nimport { createFilterCrudMethods } from \"~/filter/filter.crud.js\";\nimport { createFlpCrudMethods } from \"~/flp/index.js\";\nimport {\n FolderLevelPermissions,\n FolderLevelPermissionsFeature\n} from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { UpdateFolderFeature } from \"~/features/folder/UpdateFolder/index.js\";\nimport { DeleteFolderFeature } from \"~/features/folder/DeleteFolder/index.js\";\nimport { CreateFolderFeature } from \"~/features/folder/CreateFolder/index.js\";\nimport { GetFolderFeature } from \"~/features/folder/GetFolder/index.js\";\nimport { ListFoldersFeature } from \"~/features/folder/ListFolders/index.js\";\nimport { GetFolderHierarchyFeature } from \"~/features/folder/GetFolderHierarchy/index.js\";\nimport { GetAncestorsFeature } from \"~/features/folder/GetAncestors/index.js\";\nimport { UpdateFlpOnFolderUpdatedFeature } from \"~/features/flp/UpdateFlpOnFolderUpdated/index.js\";\nimport { DeleteFlpOnFolderDeletedFeature } from \"~/features/flp/DeleteFlpOnFolderDeleted/index.js\";\nimport { EnsureHcmsFolderIsEmptyOnDeleteFeature } from \"~/features/folder/EnsureHcmsFolderIsEmptyOnDelete/index.js\";\nimport { CreateFlpFeature } from \"~/features/flp/CreateFlp/index.js\";\nimport { DeleteFlpFeature } from \"~/features/flp/DeleteFlp/index.js\";\nimport { UpdateFlpFeature } from \"~/features/flp/UpdateFlp/index.js\";\nimport { EnsureFolderIsEmptyOnDeleteFeature } from \"~/features/folder/EnsureFolderIsEmptyOnDelete/index.js\";\nimport {\n AcoFilterCrud,\n AcoFlpCrud,\n FilterStorageOperations,\n FlpStorageOperations\n} from \"~/features/folder/shared/abstractions.js\";\nimport { ListFlpsFeature } from \"~/features/flp/ListFlps/feature.js\";\nimport { GetFlpFeature } from \"~/features/flp/GetFlp/feature.js\";\nimport { CodeFlpsFeature } from \"~/features/flp/shared/feature.js\";\nimport { ListFolderLevelPermissionsTargetsFeature } from \"~/features/folder/ListFolderLevelPermissionsTargets/feature.js\";\nimport { CreateFlpOnFolderCreatedFeature } from \"~/features/flp/CreateFlpOnFolderCreated/index.js\";\nimport { EnsureFolderIsEmptyFeature } from \"~/features/folder/EnsureFolderIsEmpty/feature.js\";\nimport { AcoAiToolsFeature } from \"~/features/ai/index.js\";\n\nclass AcoSchemaFactoryImpl implements GraphQLSchemaFactory.Interface {\n async execute(builder: IGraphQLSchemaBuilder): Promise<IGraphQLSchemaBuilder> {\n // Static ACO base + filter schema (DI-native contributors declaring their deps). The\n // dynamic, per-tenant folder schema is built by AcoFolderSchemaFactory.\n addAcoBaseSchema(builder);\n addFilterSchema(builder);\n return builder;\n }\n}\n\nconst AcoSchemaFactory = GraphQLSchemaFactory.createImplementation({\n implementation: AcoSchemaFactoryImpl,\n dependencies: []\n});\n\nexport const AcoFeature = createFeature({\n name: \"Aco\",\n register(container: Container) {\n container.register(FolderModel);\n AcoAiToolsFeature.register(container);\n container.register(FilterPrivateModel);\n\n // Background task definitions — pure wiring, no tenant/identity needed.\n container.register(CreateFlpTask);\n container.register(UpdateFlpTask);\n container.register(DeleteFlpTask);\n container.register(SyncFlpTask);\n\n // Folder + FLP use-case features — pure DI wiring (all lazy). Moved out of the per-request\n // initializer so they're registered for every event, not only GraphQL requests.\n FolderLevelPermissionsFeature.register(container);\n CreateFolderFeature.register(container);\n UpdateFolderFeature.register(container);\n DeleteFolderFeature.register(container);\n GetFolderFeature.register(container);\n ListFoldersFeature.register(container);\n ListFolderLevelPermissionsTargetsFeature.register(container);\n GetFolderHierarchyFeature.register(container);\n GetAncestorsFeature.register(container);\n EnsureFolderIsEmptyFeature.register(container);\n CreateFlpFeature.register(container);\n UpdateFlpFeature.register(container);\n DeleteFlpFeature.register(container);\n ListFlpsFeature.register(container);\n GetFlpFeature.register(container);\n // Must come after ListFlps/GetFlp — it decorates both.\n CodeFlpsFeature.register(container);\n CreateFlpOnFolderCreatedFeature.register(container);\n UpdateFlpOnFolderUpdatedFeature.register(container);\n DeleteFlpOnFolderDeletedFeature.register(container);\n EnsureFolderIsEmptyOnDeleteFeature.register(container);\n EnsureHcmsFolderIsEmptyOnDeleteFeature.register(container);\n\n // ===== Lazy ACO facade =====\n // Built on first resolve (post-auth) so the HeadlessCms facade (also lazy) is resolved on\n // demand. Memoised per request container. Consumers resolve AcoFlpCrud / AcoFilterCrud /\n // FilterStorageOperations via DI.\n const getTenant = (): Tenant => container.resolve(TenantContext).getTenant();\n\n let storageOperations: AcoStorageOperations | undefined;\n const getStorageOperations = (): AcoStorageOperations => {\n if (!storageOperations) {\n const cms = container.resolve(HeadlessCms);\n const identityContext = container.resolve(IdentityContext);\n storageOperations = {\n filter: createFilterOperations({ cms, identityContext, container }),\n flp: container.resolve(FlpStorageOperations)\n };\n }\n return storageOperations;\n };\n\n let flpCrudMethods: ReturnType<typeof createFlpCrudMethods> | undefined;\n const getFlpCrudMethods = () => {\n if (!flpCrudMethods) {\n flpCrudMethods = createFlpCrudMethods({\n getTenant,\n storageOperations: getStorageOperations()\n });\n }\n return flpCrudMethods;\n };\n container.registerFactory(AcoFlpCrud, () => getFlpCrudMethods());\n container.registerFactory(FilterStorageOperations, () => getStorageOperations().filter);\n\n let filterCrudMethods: ReturnType<typeof createFilterCrudMethods> | undefined;\n container.registerFactory(AcoFilterCrud, () => {\n if (!filterCrudMethods) {\n filterCrudMethods = createFilterCrudMethods({\n container,\n getTenant,\n storageOperations: getStorageOperations(),\n folderLevelPermissions: container.resolve(FolderLevelPermissions)\n });\n }\n return filterCrudMethods;\n });\n\n // The per-tenant folder model is resolved on demand — see FolderModelProvider.\n container.register(FolderModelProvider);\n\n // CMS-entry folder-level-permission decorators, feature-flag gated. These must be\n // registered before any CMS-entry abstraction they wrap is first resolved, which is why\n // they used to sit in a pre-resolver initializer. Register time is strictly earlier and the\n // gate is valid here: the WCP license is refreshed before any feature registers (see\n // registerApiRequestStack).\n if (\n container\n .resolve(FeatureFlags)\n .get()\n .isEnabled(\"advancedAccessControlLayer.folderLevelPermissions\")\n ) {\n CmsFlpFeature.register(container);\n }\n\n container.register(AcoSchemaFactory);\n container.register(AcoFolderSchemaFactory);\n }\n});\n"],"names":["AcoSchemaFactoryImpl","builder","addAcoBaseSchema","addFilterSchema","AcoSchemaFactory","GraphQLSchemaFactory","AcoFeature","createFeature","container","FolderModel","AcoAiToolsFeature","FilterPrivateModel","CreateFlpTask","UpdateFlpTask","DeleteFlpTask","SyncFlpTask","FolderLevelPermissionsFeature","CreateFolderFeature","UpdateFolderFeature","DeleteFolderFeature","GetFolderFeature","ListFoldersFeature","ListFolderLevelPermissionsTargetsFeature","GetFolderHierarchyFeature","GetAncestorsFeature","EnsureFolderIsEmptyFeature","CreateFlpFeature","UpdateFlpFeature","DeleteFlpFeature","ListFlpsFeature","GetFlpFeature","CodeFlpsFeature","CreateFlpOnFolderCreatedFeature","UpdateFlpOnFolderUpdatedFeature","DeleteFlpOnFolderDeletedFeature","EnsureFolderIsEmptyOnDeleteFeature","EnsureHcmsFolderIsEmptyOnDeleteFeature","getTenant","TenantContext","storageOperations","getStorageOperations","cms","HeadlessCms","identityContext","IdentityContext","createFilterOperations","FlpStorageOperations","flpCrudMethods","getFlpCrudMethods","createFlpCrudMethods","AcoFlpCrud","FilterStorageOperations","filterCrudMethods","AcoFilterCrud","createFilterCrudMethods","FolderLevelPermissions","FolderModelProvider","FeatureFlags","CmsFlpFeature","AcoFolderSchemaFactory"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,MAAMA;IACF,MAAM,QAAQC,OAA8B,EAAkC;QAG1EC,iBAAiBD;QACjBE,gBAAgBF;QAChB,OAAOA;IACX;AACJ;AAEA,MAAMG,mBAAmBC,qBAAqB,oBAAoB,CAAC;IAC/D,gBAAgBL;IAChB,cAAc,EAAE;AACpB;AAEO,MAAMM,aAAaC,cAAc;IACpC,MAAM;IACN,UAASC,SAAoB;QACzBA,UAAU,QAAQ,CAACC;QACnBC,kBAAkB,QAAQ,CAACF;QAC3BA,UAAU,QAAQ,CAACG;QAGnBH,UAAU,QAAQ,CAACI;QACnBJ,UAAU,QAAQ,CAACK;QACnBL,UAAU,QAAQ,CAACM;QACnBN,UAAU,QAAQ,CAACO;QAInBC,8BAA8B,QAAQ,CAACR;QACvCS,oBAAoB,QAAQ,CAACT;QAC7BU,oBAAoB,QAAQ,CAACV;QAC7BW,oBAAoB,QAAQ,CAACX;QAC7BY,iBAAiB,QAAQ,CAACZ;QAC1Ba,mBAAmB,QAAQ,CAACb;QAC5Bc,yCAAyC,QAAQ,CAACd;QAClDe,0BAA0B,QAAQ,CAACf;QACnCgB,oBAAoB,QAAQ,CAAChB;QAC7BiB,2BAA2B,QAAQ,CAACjB;QACpCkB,iBAAiB,QAAQ,CAAClB;QAC1BmB,iBAAiB,QAAQ,CAACnB;QAC1BoB,iBAAiB,QAAQ,CAACpB;QAC1BqB,gBAAgB,QAAQ,CAACrB;QACzBsB,cAAc,QAAQ,CAACtB;QAEvBuB,gBAAgB,QAAQ,CAACvB;QACzBwB,gCAAgC,QAAQ,CAACxB;QACzCyB,gCAAgC,QAAQ,CAACzB;QACzC0B,gCAAgC,QAAQ,CAAC1B;QACzC2B,mCAAmC,QAAQ,CAAC3B;QAC5C4B,uCAAuC,QAAQ,CAAC5B;QAMhD,MAAM6B,YAAY,IAAc7B,UAAU,OAAO,CAAC8B,eAAe,SAAS;QAE1E,IAAIC;QACJ,MAAMC,uBAAuB;YACzB,IAAI,CAACD,mBAAmB;gBACpB,MAAME,MAAMjC,UAAU,OAAO,CAACkC;gBAC9B,MAAMC,kBAAkBnC,UAAU,OAAO,CAACoC;gBAC1CL,oBAAoB;oBAChB,QAAQM,uBAAuB;wBAAEJ;wBAAKE;wBAAiBnC;oBAAU;oBACjE,KAAKA,UAAU,OAAO,CAACsC;gBAC3B;YACJ;YACA,OAAOP;QACX;QAEA,IAAIQ;QACJ,MAAMC,oBAAoB;YACtB,IAAI,CAACD,gBACDA,iBAAiBE,qBAAqB;gBAClCZ;gBACA,mBAAmBG;YACvB;YAEJ,OAAOO;QACX;QACAvC,UAAU,eAAe,CAAC0C,YAAY,IAAMF;QAC5CxC,UAAU,eAAe,CAAC2C,yBAAyB,IAAMX,uBAAuB,MAAM;QAEtF,IAAIY;QACJ5C,UAAU,eAAe,CAAC6C,eAAe;YACrC,IAAI,CAACD,mBACDA,oBAAoBE,wBAAwB;gBACxC9C;gBACA6B;gBACA,mBAAmBG;gBACnB,wBAAwBhC,UAAU,OAAO,CAAC+C;YAC9C;YAEJ,OAAOH;QACX;QAGA5C,UAAU,QAAQ,CAACgD;QAOnB,IACIhD,UACK,OAAO,CAACiD,cACR,GAAG,GACH,SAAS,CAAC,sDAEfC,cAAc,QAAQ,CAAClD;QAG3BA,UAAU,QAAQ,CAACJ;QACnBI,UAAU,QAAQ,CAACmD;IACvB;AACJ"}
|
package/exports/api/aco/flp.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { FlpFactory } from "../../../features/flp/shared/abstractions.js";
|
|
1
2
|
export { CreateFlpUseCase } from "../../../features/flp/CreateFlp/abstractions.js";
|
|
2
3
|
export { DeleteFlpUseCase } from "../../../features/flp/DeleteFlp/abstractions.js";
|
|
3
4
|
export { FolderLevelPermissions } from "../../../features/flp/FolderLevelPermissions/abstractions.js";
|
package/exports/api/aco/flp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { FlpFactory } from "../../../features/flp/shared/abstractions.js";
|
|
1
2
|
export { CreateFlpUseCase } from "../../../features/flp/CreateFlp/abstractions.js";
|
|
2
3
|
export { DeleteFlpUseCase } from "../../../features/flp/DeleteFlp/abstractions.js";
|
|
3
4
|
export { FolderLevelPermissions } from "../../../features/flp/FolderLevelPermissions/abstractions.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GetFlpUseCase } from "../abstractions.js";
|
|
2
|
+
import { CodeFlpsProvider } from "../../../../features/flp/shared/index.js";
|
|
3
|
+
import type { FolderLevelPermission } from "../../../../flp/flp.types.js";
|
|
4
|
+
declare class GetFlpWithCodeFlpsImpl implements GetFlpUseCase.Interface {
|
|
5
|
+
private codeFlpsProvider;
|
|
6
|
+
private decoratee;
|
|
7
|
+
constructor(codeFlpsProvider: CodeFlpsProvider.Interface, decoratee: GetFlpUseCase.Interface);
|
|
8
|
+
execute(id: string): Promise<FolderLevelPermission | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare const GetFlpWithCodeFlps: typeof GetFlpWithCodeFlpsImpl & {
|
|
11
|
+
__abstraction: import("@webiny/di").Abstraction<import("../abstractions.js").IGetFolderPermission>;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GetFlpUseCase } from "../abstractions.js";
|
|
2
|
+
import { CodeFlpMerger, CodeFlpsProvider } from "../../shared/index.js";
|
|
3
|
+
class GetFlpWithCodeFlpsImpl {
|
|
4
|
+
constructor(codeFlpsProvider, decoratee){
|
|
5
|
+
this.codeFlpsProvider = codeFlpsProvider;
|
|
6
|
+
this.decoratee = decoratee;
|
|
7
|
+
}
|
|
8
|
+
async execute(id) {
|
|
9
|
+
const flp = await this.decoratee.execute(id);
|
|
10
|
+
if (!flp) return null;
|
|
11
|
+
const codePermissions = await this.codeFlpsProvider.getPermissions({
|
|
12
|
+
type: flp.type,
|
|
13
|
+
path: flp.path
|
|
14
|
+
});
|
|
15
|
+
return CodeFlpMerger.merge(flp, codePermissions);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const GetFlpWithCodeFlps = GetFlpUseCase.createDecorator({
|
|
19
|
+
decorator: GetFlpWithCodeFlpsImpl,
|
|
20
|
+
dependencies: [
|
|
21
|
+
CodeFlpsProvider
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
export { GetFlpWithCodeFlps };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=GetFlpWithCodeFlps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/GetFlp/decorators/GetFlpWithCodeFlps.js","sources":["../../../../../src/features/flp/GetFlp/decorators/GetFlpWithCodeFlps.ts"],"sourcesContent":["import { GetFlpUseCase } from \"../abstractions.js\";\nimport { CodeFlpMerger, CodeFlpsProvider } from \"~/features/flp/shared/index.js\";\nimport type { FolderLevelPermission } from \"~/flp/flp.types.js\";\n\nclass GetFlpWithCodeFlpsImpl implements GetFlpUseCase.Interface {\n constructor(\n private codeFlpsProvider: CodeFlpsProvider.Interface,\n private decoratee: GetFlpUseCase.Interface\n ) {}\n\n async execute(id: string): Promise<FolderLevelPermission | null> {\n const flp = await this.decoratee.execute(id);\n\n if (!flp) {\n return null;\n }\n\n const codePermissions = await this.codeFlpsProvider.getPermissions({\n type: flp.type,\n path: flp.path\n });\n\n return CodeFlpMerger.merge(flp, codePermissions);\n }\n}\n\nexport const GetFlpWithCodeFlps = GetFlpUseCase.createDecorator({\n decorator: GetFlpWithCodeFlpsImpl,\n dependencies: [CodeFlpsProvider]\n});\n"],"names":["GetFlpWithCodeFlpsImpl","codeFlpsProvider","decoratee","id","flp","codePermissions","CodeFlpMerger","GetFlpWithCodeFlps","GetFlpUseCase","CodeFlpsProvider"],"mappings":";;AAIA,MAAMA;IACF,YACYC,gBAA4C,EAC5CC,SAAkC,CAC5C;aAFUD,gBAAgB,GAAhBA;aACAC,SAAS,GAATA;IACT;IAEH,MAAM,QAAQC,EAAU,EAAyC;QAC7D,MAAMC,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACD;QAEzC,IAAI,CAACC,KACD,OAAO;QAGX,MAAMC,kBAAkB,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;YAC/D,MAAMD,IAAI,IAAI;YACd,MAAMA,IAAI,IAAI;QAClB;QAEA,OAAOE,cAAc,KAAK,CAACF,KAAKC;IACpC;AACJ;AAEO,MAAME,qBAAqBC,cAAc,eAAe,CAAC;IAC5D,WAAWR;IACX,cAAc;QAACS;KAAiB;AACpC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ListFlpsUseCase } from "../abstractions.js";
|
|
2
|
+
import { CodeFlpsProvider } from "../../../../features/flp/shared/index.js";
|
|
3
|
+
import type { FolderLevelPermission, ListFlpsParams } from "../../../../flp/flp.types.js";
|
|
4
|
+
declare class ListFlpsWithCodeFlpsImpl implements ListFlpsUseCase.Interface {
|
|
5
|
+
private codeFlpsProvider;
|
|
6
|
+
private decoratee;
|
|
7
|
+
constructor(codeFlpsProvider: CodeFlpsProvider.Interface, decoratee: ListFlpsUseCase.Interface);
|
|
8
|
+
execute(params: ListFlpsParams): Promise<FolderLevelPermission[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare const ListFlpsWithCodeFlps: typeof ListFlpsWithCodeFlpsImpl & {
|
|
11
|
+
__abstraction: import("@webiny/di").Abstraction<import("../abstractions.js").IListFlps>;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ListFlpsUseCase } from "../abstractions.js";
|
|
2
|
+
import { CodeFlpMerger, CodeFlpsProvider } from "../../shared/index.js";
|
|
3
|
+
class ListFlpsWithCodeFlpsImpl {
|
|
4
|
+
constructor(codeFlpsProvider, decoratee){
|
|
5
|
+
this.codeFlpsProvider = codeFlpsProvider;
|
|
6
|
+
this.decoratee = decoratee;
|
|
7
|
+
}
|
|
8
|
+
async execute(params) {
|
|
9
|
+
const flps = await this.decoratee.execute(params);
|
|
10
|
+
return Promise.all(flps.map(async (flp)=>{
|
|
11
|
+
const codePermissions = await this.codeFlpsProvider.getPermissions({
|
|
12
|
+
type: flp.type,
|
|
13
|
+
path: flp.path
|
|
14
|
+
});
|
|
15
|
+
return CodeFlpMerger.merge(flp, codePermissions);
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const ListFlpsWithCodeFlps = ListFlpsUseCase.createDecorator({
|
|
20
|
+
decorator: ListFlpsWithCodeFlpsImpl,
|
|
21
|
+
dependencies: [
|
|
22
|
+
CodeFlpsProvider
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
export { ListFlpsWithCodeFlps };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=ListFlpsWithCodeFlps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.js","sources":["../../../../../src/features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.ts"],"sourcesContent":["import { ListFlpsUseCase } from \"../abstractions.js\";\nimport { CodeFlpMerger, CodeFlpsProvider } from \"~/features/flp/shared/index.js\";\nimport type { FolderLevelPermission, ListFlpsParams } from \"~/flp/flp.types.js\";\n\nclass ListFlpsWithCodeFlpsImpl implements ListFlpsUseCase.Interface {\n constructor(\n private codeFlpsProvider: CodeFlpsProvider.Interface,\n private decoratee: ListFlpsUseCase.Interface\n ) {}\n\n async execute(params: ListFlpsParams): Promise<FolderLevelPermission[]> {\n const flps = await this.decoratee.execute(params);\n\n return Promise.all(\n flps.map(async flp => {\n const codePermissions = await this.codeFlpsProvider.getPermissions({\n type: flp.type,\n path: flp.path\n });\n\n return CodeFlpMerger.merge(flp, codePermissions);\n })\n );\n }\n}\n\nexport const ListFlpsWithCodeFlps = ListFlpsUseCase.createDecorator({\n decorator: ListFlpsWithCodeFlpsImpl,\n dependencies: [CodeFlpsProvider]\n});\n"],"names":["ListFlpsWithCodeFlpsImpl","codeFlpsProvider","decoratee","params","flps","Promise","flp","codePermissions","CodeFlpMerger","ListFlpsWithCodeFlps","ListFlpsUseCase","CodeFlpsProvider"],"mappings":";;AAIA,MAAMA;IACF,YACYC,gBAA4C,EAC5CC,SAAoC,CAC9C;aAFUD,gBAAgB,GAAhBA;aACAC,SAAS,GAATA;IACT;IAEH,MAAM,QAAQC,MAAsB,EAAoC;QACpE,MAAMC,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACD;QAE1C,OAAOE,QAAQ,GAAG,CACdD,KAAK,GAAG,CAAC,OAAME;YACX,MAAMC,kBAAkB,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;gBAC/D,MAAMD,IAAI,IAAI;gBACd,MAAMA,IAAI,IAAI;YAClB;YAEA,OAAOE,cAAc,KAAK,CAACF,KAAKC;QACpC;IAER;AACJ;AAEO,MAAME,uBAAuBC,gBAAgB,eAAe,CAAC;IAChE,WAAWV;IACX,cAAc;QAACW;KAAiB;AACpC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FolderLevelPermission, FolderPermission } from "../../../flp/flp.types.js";
|
|
2
|
+
export declare class CodeFlpMerger {
|
|
3
|
+
/**
|
|
4
|
+
* Merge code-defined permissions into a stored FLP record.
|
|
5
|
+
*
|
|
6
|
+
* A code-defined permission always wins over a stored one for the same target, and that takes
|
|
7
|
+
* both steps below — ordering alone is not enough, because the two downstream consumers pick a
|
|
8
|
+
* target's permission in different ways:
|
|
9
|
+
*
|
|
10
|
+
* - `CanAccessFolder` takes the first match (`permissions.find(p => p.target === ...)`), so code
|
|
11
|
+
* permissions go first.
|
|
12
|
+
* - `DefaultPermissionsMerger` instead collects *every* permission for the identity and reduces
|
|
13
|
+
* them by access level, preferring `no-access` and then `owner`. A leftover duplicate would
|
|
14
|
+
* join that reduce and could outrank the code-defined entry — a stored `owner` would beat a
|
|
15
|
+
* code-defined `viewer`. Dropping the stored permission for a code-defined target is what
|
|
16
|
+
* prevents it.
|
|
17
|
+
*
|
|
18
|
+
* So: exactly one permission per target, and for code-defined targets it is the code one.
|
|
19
|
+
*/
|
|
20
|
+
static merge(flp: FolderLevelPermission, codePermissions: FolderPermission[]): FolderLevelPermission;
|
|
21
|
+
/**
|
|
22
|
+
* The same merge, for callers that hold a plain permission list rather than a stored FLP record
|
|
23
|
+
* — the write paths, which resolve code permissions from the folder's own type and path.
|
|
24
|
+
*/
|
|
25
|
+
static mergePermissions(permissions: FolderPermission[], codePermissions: FolderPermission[]): FolderPermission[];
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class CodeFlpMerger {
|
|
2
|
+
static merge(flp, codePermissions) {
|
|
3
|
+
if (!codePermissions.length) return flp;
|
|
4
|
+
return {
|
|
5
|
+
...flp,
|
|
6
|
+
permissions: CodeFlpMerger.mergePermissions(flp.permissions, codePermissions)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
static mergePermissions(permissions, codePermissions) {
|
|
10
|
+
if (!codePermissions.length) return permissions;
|
|
11
|
+
const codeTargets = new Set(codePermissions.map((permission)=>permission.target));
|
|
12
|
+
return [
|
|
13
|
+
...codePermissions,
|
|
14
|
+
...permissions.filter((permission)=>!codeTargets.has(permission.target))
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export { CodeFlpMerger };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=CodeFlpMerger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/CodeFlpMerger.js","sources":["../../../../src/features/flp/shared/CodeFlpMerger.ts"],"sourcesContent":["import type { FolderLevelPermission, FolderPermission } from \"~/flp/flp.types.js\";\n\nexport class CodeFlpMerger {\n /**\n * Merge code-defined permissions into a stored FLP record.\n *\n * A code-defined permission always wins over a stored one for the same target, and that takes\n * both steps below — ordering alone is not enough, because the two downstream consumers pick a\n * target's permission in different ways:\n *\n * - `CanAccessFolder` takes the first match (`permissions.find(p => p.target === ...)`), so code\n * permissions go first.\n * - `DefaultPermissionsMerger` instead collects *every* permission for the identity and reduces\n * them by access level, preferring `no-access` and then `owner`. A leftover duplicate would\n * join that reduce and could outrank the code-defined entry — a stored `owner` would beat a\n * code-defined `viewer`. Dropping the stored permission for a code-defined target is what\n * prevents it.\n *\n * So: exactly one permission per target, and for code-defined targets it is the code one.\n */\n static merge(\n flp: FolderLevelPermission,\n codePermissions: FolderPermission[]\n ): FolderLevelPermission {\n if (!codePermissions.length) {\n return flp;\n }\n\n return {\n ...flp,\n permissions: CodeFlpMerger.mergePermissions(flp.permissions, codePermissions)\n };\n }\n\n /**\n * The same merge, for callers that hold a plain permission list rather than a stored FLP record\n * — the write paths, which resolve code permissions from the folder's own type and path.\n */\n static mergePermissions(\n permissions: FolderPermission[],\n codePermissions: FolderPermission[]\n ): FolderPermission[] {\n if (!codePermissions.length) {\n return permissions;\n }\n\n const codeTargets = new Set(codePermissions.map(permission => permission.target));\n\n return [\n ...codePermissions,\n ...permissions.filter(permission => !codeTargets.has(permission.target))\n ];\n }\n}\n"],"names":["CodeFlpMerger","flp","codePermissions","permissions","codeTargets","Set","permission"],"mappings":"AAEO,MAAMA;IAkBT,OAAO,MACHC,GAA0B,EAC1BC,eAAmC,EACd;QACrB,IAAI,CAACA,gBAAgB,MAAM,EACvB,OAAOD;QAGX,OAAO;YACH,GAAGA,GAAG;YACN,aAAaD,cAAc,gBAAgB,CAACC,IAAI,WAAW,EAAEC;QACjE;IACJ;IAMA,OAAO,iBACHC,WAA+B,EAC/BD,eAAmC,EACjB;QAClB,IAAI,CAACA,gBAAgB,MAAM,EACvB,OAAOC;QAGX,MAAMC,cAAc,IAAIC,IAAIH,gBAAgB,GAAG,CAACI,CAAAA,aAAcA,WAAW,MAAM;QAE/E,OAAO;eACAJ;eACAC,YAAY,MAAM,CAACG,CAAAA,aAAc,CAACF,YAAY,GAAG,CAACE,WAAW,MAAM;SACzE;IACL;AACJ"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ParsedPath {
|
|
2
|
+
/** Normalized path, without the wildcard suffix, e.g. `root/marketing`. */
|
|
3
|
+
base: string;
|
|
4
|
+
/** Whether the rule was written with a trailing `/*`, meaning "this folder and its subtree". */
|
|
5
|
+
subtree: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Folder paths are stored as `root/<slug>[/<slug>...]` (see `~/utils/Path.ts`). Code-defined FLPs
|
|
9
|
+
* are authored by hand, so we accept the friendlier `/marketing` form as well and normalize it.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CodeFlpPath {
|
|
12
|
+
static parse(path: string): ParsedPath;
|
|
13
|
+
static matches(rulePath: string, folderPath: string): boolean;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ROOT_FOLDER } from "@webiny/shared-aco";
|
|
2
|
+
class CodeFlpPath {
|
|
3
|
+
static parse(path) {
|
|
4
|
+
let value = path.trim();
|
|
5
|
+
let subtree = false;
|
|
6
|
+
if (value.endsWith("*")) {
|
|
7
|
+
subtree = true;
|
|
8
|
+
value = value.slice(0, -1);
|
|
9
|
+
}
|
|
10
|
+
value = value.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
11
|
+
if (value === ROOT_FOLDER || "" === value) return {
|
|
12
|
+
base: ROOT_FOLDER,
|
|
13
|
+
subtree: "" === value ? true : subtree
|
|
14
|
+
};
|
|
15
|
+
const base = value.startsWith(`${ROOT_FOLDER}/`) ? value : `${ROOT_FOLDER}/${value}`;
|
|
16
|
+
return {
|
|
17
|
+
base,
|
|
18
|
+
subtree
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
static matches(rulePath, folderPath) {
|
|
22
|
+
const { base, subtree } = CodeFlpPath.parse(rulePath);
|
|
23
|
+
if (folderPath === base) return true;
|
|
24
|
+
return subtree ? folderPath.startsWith(`${base}/`) : false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export { CodeFlpPath };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=CodeFlpPath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/CodeFlpPath.js","sources":["../../../../src/features/flp/shared/CodeFlpPath.ts"],"sourcesContent":["import { ROOT_FOLDER } from \"@webiny/shared-aco\";\n\ninterface ParsedPath {\n /** Normalized path, without the wildcard suffix, e.g. `root/marketing`. */\n base: string;\n /** Whether the rule was written with a trailing `/*`, meaning \"this folder and its subtree\". */\n subtree: boolean;\n}\n\n/**\n * Folder paths are stored as `root/<slug>[/<slug>...]` (see `~/utils/Path.ts`). Code-defined FLPs\n * are authored by hand, so we accept the friendlier `/marketing` form as well and normalize it.\n */\nexport class CodeFlpPath {\n static parse(path: string): ParsedPath {\n let value = path.trim();\n let subtree = false;\n\n // A trailing \"*\" (with or without the separator) means \"this folder and everything below\".\n if (value.endsWith(\"*\")) {\n subtree = true;\n value = value.slice(0, -1);\n }\n\n // Strip surrounding separators, so \"/marketing/\" and \"marketing\" normalize the same way.\n value = value.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n if (value === ROOT_FOLDER || value === \"\") {\n return { base: ROOT_FOLDER, subtree: value === \"\" ? true : subtree };\n }\n\n // The \"root\" prefix is an implementation detail of stored paths — make it optional.\n const base = value.startsWith(`${ROOT_FOLDER}/`) ? value : `${ROOT_FOLDER}/${value}`;\n\n return { base, subtree };\n }\n\n static matches(rulePath: string, folderPath: string): boolean {\n const { base, subtree } = CodeFlpPath.parse(rulePath);\n\n if (folderPath === base) {\n return true;\n }\n\n return subtree ? folderPath.startsWith(`${base}/`) : false;\n }\n}\n"],"names":["CodeFlpPath","path","value","subtree","ROOT_FOLDER","base","rulePath","folderPath"],"mappings":";AAaO,MAAMA;IACT,OAAO,MAAMC,IAAY,EAAc;QACnC,IAAIC,QAAQD,KAAK,IAAI;QACrB,IAAIE,UAAU;QAGd,IAAID,MAAM,QAAQ,CAAC,MAAM;YACrBC,UAAU;YACVD,QAAQA,MAAM,KAAK,CAAC,GAAG;QAC3B;QAGAA,QAAQA,MAAM,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ;QAElD,IAAIA,UAAUE,eAAeF,AAAU,OAAVA,OACzB,OAAO;YAAE,MAAME;YAAa,SAASF,AAAU,OAAVA,QAAe,OAAOC;QAAQ;QAIvE,MAAME,OAAOH,MAAM,UAAU,CAAC,GAAGE,YAAY,CAAC,CAAC,IAAIF,QAAQ,GAAGE,YAAY,CAAC,EAAEF,OAAO;QAEpF,OAAO;YAAEG;YAAMF;QAAQ;IAC3B;IAEA,OAAO,QAAQG,QAAgB,EAAEC,UAAkB,EAAW;QAC1D,MAAM,EAAEF,IAAI,EAAEF,OAAO,EAAE,GAAGH,YAAY,KAAK,CAACM;QAE5C,IAAIC,eAAeF,MACf,OAAO;QAGX,OAAOF,UAAUI,WAAW,UAAU,CAAC,GAAGF,KAAK,CAAC,CAAC,IAAI;IACzD;AACJ"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CodeFolderPermission } from "./abstractions.js";
|
|
2
|
+
import type { FolderPermission } from "../../../flp/flp.types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Code-defined permissions are authored as `{ team: "editors" }` or `{ user: "<id>" }`. Internally,
|
|
5
|
+
* a permission's target is a single string — `team:editors` or `admin:<id>` — so this is where the
|
|
6
|
+
* authored form is turned into the stored one.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CodeFlpTarget {
|
|
9
|
+
static resolve(permission: CodeFolderPermission): FolderPermission;
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WebinyError } from "@webiny/error";
|
|
2
|
+
class CodeFlpTarget {
|
|
3
|
+
static resolve(permission) {
|
|
4
|
+
const { team, user, level } = permission;
|
|
5
|
+
if (void 0 !== team && void 0 !== user) throw new WebinyError("A code-defined folder permission must target either a team or a user, not both.", "CODE_FLP_AMBIGUOUS_TARGET", {
|
|
6
|
+
team,
|
|
7
|
+
user
|
|
8
|
+
});
|
|
9
|
+
if (void 0 !== team) return {
|
|
10
|
+
target: `team:${team}`,
|
|
11
|
+
level,
|
|
12
|
+
plugin: true
|
|
13
|
+
};
|
|
14
|
+
if (void 0 !== user) return {
|
|
15
|
+
target: `admin:${user}`,
|
|
16
|
+
level,
|
|
17
|
+
plugin: true
|
|
18
|
+
};
|
|
19
|
+
throw new WebinyError('A code-defined folder permission must specify either "team" or "user".', "CODE_FLP_MISSING_TARGET", {
|
|
20
|
+
permission
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export { CodeFlpTarget };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=CodeFlpTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/CodeFlpTarget.js","sources":["../../../../src/features/flp/shared/CodeFlpTarget.ts"],"sourcesContent":["import { WebinyError } from \"@webiny/error\";\nimport type { CodeFolderPermission } from \"./abstractions.js\";\nimport type { FolderPermission } from \"~/flp/flp.types.js\";\n\n/**\n * Code-defined permissions are authored as `{ team: \"editors\" }` or `{ user: \"<id>\" }`. Internally,\n * a permission's target is a single string — `team:editors` or `admin:<id>` — so this is where the\n * authored form is turned into the stored one.\n */\nexport class CodeFlpTarget {\n static resolve(permission: CodeFolderPermission): FolderPermission {\n const { team, user, level } = permission;\n\n if (team !== undefined && user !== undefined) {\n throw new WebinyError(\n `A code-defined folder permission must target either a team or a user, not both.`,\n \"CODE_FLP_AMBIGUOUS_TARGET\",\n { team, user }\n );\n }\n\n if (team !== undefined) {\n return { target: `team:${team}`, level, plugin: true };\n }\n\n if (user !== undefined) {\n return { target: `admin:${user}`, level, plugin: true };\n }\n\n // Without a target we'd silently produce a permission that matches nobody. For a rule that\n // denies access, dropping it quietly would mean accidental exposure — so fail loudly.\n throw new WebinyError(\n `A code-defined folder permission must specify either \"team\" or \"user\".`,\n \"CODE_FLP_MISSING_TARGET\",\n { permission }\n );\n }\n}\n"],"names":["CodeFlpTarget","permission","team","user","level","undefined","WebinyError"],"mappings":";AASO,MAAMA;IACT,OAAO,QAAQC,UAAgC,EAAoB;QAC/D,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAEC,KAAK,EAAE,GAAGH;QAE9B,IAAIC,AAASG,WAATH,QAAsBC,AAASE,WAATF,MACtB,MAAM,IAAIG,YACN,mFACA,6BACA;YAAEJ;YAAMC;QAAK;QAIrB,IAAID,AAASG,WAATH,MACA,OAAO;YAAE,QAAQ,CAAC,KAAK,EAAEA,MAAM;YAAEE;YAAO,QAAQ;QAAK;QAGzD,IAAID,AAASE,WAATF,MACA,OAAO;YAAE,QAAQ,CAAC,MAAM,EAAEA,MAAM;YAAEC;YAAO,QAAQ;QAAK;QAK1D,MAAM,IAAIE,YACN,0EACA,2BACA;YAAEL;QAAW;IAErB;AACJ"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CodeFlpsProvider as CodeFlpsProviderAbstraction, FlpFactory, type GetCodePermissionsParams } from "./abstractions.js";
|
|
2
|
+
import type { FolderPermission } from "../../../flp/flp.types.js";
|
|
3
|
+
declare class CodeFlpsProviderImpl implements CodeFlpsProviderAbstraction.Interface {
|
|
4
|
+
private flpFactories;
|
|
5
|
+
private cache;
|
|
6
|
+
constructor(flpFactories: FlpFactory.Interface[]);
|
|
7
|
+
getPermissions({ type, path }: GetCodePermissionsParams): Promise<FolderPermission[]>;
|
|
8
|
+
/** All code-defined FLPs, collected from every registered `FlpFactory`. */
|
|
9
|
+
private getCodeFlps;
|
|
10
|
+
}
|
|
11
|
+
export declare const CodeFlpsProvider: typeof CodeFlpsProviderImpl & {
|
|
12
|
+
__abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").ICodeFlpsProvider>;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CodeFlpsProvider, FlpFactory } from "./abstractions.js";
|
|
2
|
+
import { CodeFlpPath } from "./CodeFlpPath.js";
|
|
3
|
+
import { CodeFlpTarget } from "./CodeFlpTarget.js";
|
|
4
|
+
class CodeFlpsProviderImpl {
|
|
5
|
+
constructor(flpFactories){
|
|
6
|
+
this.flpFactories = flpFactories;
|
|
7
|
+
}
|
|
8
|
+
async getPermissions({ type, path }) {
|
|
9
|
+
const codeFlps = await this.getCodeFlps();
|
|
10
|
+
return codeFlps.filter((flp)=>flp.type === type && CodeFlpPath.matches(flp.path, path)).flatMap((flp)=>flp.permissions.map((permission)=>CodeFlpTarget.resolve(permission)));
|
|
11
|
+
}
|
|
12
|
+
async getCodeFlps() {
|
|
13
|
+
if (void 0 === this.cache) {
|
|
14
|
+
const results = await Promise.all(this.flpFactories.map((factory)=>factory.execute()));
|
|
15
|
+
this.cache = results.flat();
|
|
16
|
+
}
|
|
17
|
+
return this.cache;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const CodeFlpsProvider_CodeFlpsProvider = CodeFlpsProvider.createImplementation({
|
|
21
|
+
implementation: CodeFlpsProviderImpl,
|
|
22
|
+
dependencies: [
|
|
23
|
+
[
|
|
24
|
+
FlpFactory,
|
|
25
|
+
{
|
|
26
|
+
multiple: true
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
export { CodeFlpsProvider_CodeFlpsProvider as CodeFlpsProvider };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=CodeFlpsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/CodeFlpsProvider.js","sources":["../../../../src/features/flp/shared/CodeFlpsProvider.ts"],"sourcesContent":["import {\n type CodeFlp,\n CodeFlpsProvider as CodeFlpsProviderAbstraction,\n FlpFactory,\n type GetCodePermissionsParams\n} from \"./abstractions.js\";\nimport { CodeFlpPath } from \"./CodeFlpPath.js\";\nimport { CodeFlpTarget } from \"./CodeFlpTarget.js\";\nimport type { FolderPermission } from \"~/flp/flp.types.js\";\n\nclass CodeFlpsProviderImpl implements CodeFlpsProviderAbstraction.Interface {\n private cache: CodeFlp[] | undefined;\n\n constructor(private flpFactories: FlpFactory.Interface[]) {}\n\n async getPermissions({ type, path }: GetCodePermissionsParams): Promise<FolderPermission[]> {\n const codeFlps = await this.getCodeFlps();\n\n return codeFlps\n .filter(flp => flp.type === type && CodeFlpPath.matches(flp.path, path))\n .flatMap<FolderPermission>(flp => {\n return flp.permissions.map(permission => CodeFlpTarget.resolve(permission));\n });\n }\n\n /** All code-defined FLPs, collected from every registered `FlpFactory`. */\n private async getCodeFlps(): Promise<CodeFlp[]> {\n if (this.cache === undefined) {\n const results = await Promise.all(this.flpFactories.map(factory => factory.execute()));\n this.cache = results.flat();\n }\n\n return this.cache;\n }\n}\n\nexport const CodeFlpsProvider = CodeFlpsProviderAbstraction.createImplementation({\n implementation: CodeFlpsProviderImpl,\n dependencies: [[FlpFactory, { multiple: true }]]\n});\n"],"names":["CodeFlpsProviderImpl","flpFactories","type","path","codeFlps","flp","CodeFlpPath","permission","CodeFlpTarget","undefined","results","Promise","factory","CodeFlpsProvider","CodeFlpsProviderAbstraction","FlpFactory"],"mappings":";;;AAUA,MAAMA;IAGF,YAAoBC,YAAoC,CAAE;aAAtCA,YAAY,GAAZA;IAAuC;IAE3D,MAAM,eAAe,EAAEC,IAAI,EAAEC,IAAI,EAA4B,EAA+B;QACxF,MAAMC,WAAW,MAAM,IAAI,CAAC,WAAW;QAEvC,OAAOA,SACF,MAAM,CAACC,CAAAA,MAAOA,IAAI,IAAI,KAAKH,QAAQI,YAAY,OAAO,CAACD,IAAI,IAAI,EAAEF,OACjE,OAAO,CAAmBE,CAAAA,MAChBA,IAAI,WAAW,CAAC,GAAG,CAACE,CAAAA,aAAcC,cAAc,OAAO,CAACD;IAE3E;IAGA,MAAc,cAAkC;QAC5C,IAAI,AAAeE,WAAf,IAAI,CAAC,KAAK,EAAgB;YAC1B,MAAMC,UAAU,MAAMC,QAAQ,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAACC,CAAAA,UAAWA,QAAQ,OAAO;YAClF,IAAI,CAAC,KAAK,GAAGF,QAAQ,IAAI;QAC7B;QAEA,OAAO,IAAI,CAAC,KAAK;IACrB;AACJ;AAEO,MAAMG,oCAAmBC,iBAAAA,oBAAgD,CAAC;IAC7E,gBAAgBd;IAChB,cAAc;QAAC;YAACe;YAAY;gBAAE,UAAU;YAAK;SAAE;KAAC;AACpD"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FolderAccessLevel, FolderPermission } from "../../../flp/flp.types.js";
|
|
2
|
+
interface CodeTeamPermission {
|
|
3
|
+
/** Slug of the team the permission is granted to. */
|
|
4
|
+
team: string;
|
|
5
|
+
user?: never;
|
|
6
|
+
level: FolderAccessLevel;
|
|
7
|
+
}
|
|
8
|
+
interface CodeUserPermission {
|
|
9
|
+
/** ID of the admin user the permission is granted to. */
|
|
10
|
+
user: string;
|
|
11
|
+
team?: never;
|
|
12
|
+
level: FolderAccessLevel;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A permission grants a level to exactly one target: either a team or a single admin user. The
|
|
16
|
+
* `team:` / `admin:` target strings used internally are generated from these.
|
|
17
|
+
*
|
|
18
|
+
* Prefer `team` — team slugs are authored by hand and stable, while user IDs are generated at
|
|
19
|
+
* runtime and differ per environment.
|
|
20
|
+
*/
|
|
21
|
+
export type CodeFolderPermission = CodeTeamPermission | CodeUserPermission;
|
|
22
|
+
export type CodeFlp = {
|
|
23
|
+
/**
|
|
24
|
+
* Folder type the rule applies to. Use `FlpFactory.FolderType` to build the built-in ones
|
|
25
|
+
* instead of writing the string by hand.
|
|
26
|
+
*/
|
|
27
|
+
type: string;
|
|
28
|
+
/**
|
|
29
|
+
* Folder path the rule binds to. The leading `root` segment is optional, so both `/marketing`
|
|
30
|
+
* and `root/marketing` are accepted. A trailing `/*` matches the folder and its whole subtree.
|
|
31
|
+
*/
|
|
32
|
+
path: string;
|
|
33
|
+
permissions: CodeFolderPermission[];
|
|
34
|
+
};
|
|
35
|
+
export interface IFlpFactory {
|
|
36
|
+
execute(): Promise<CodeFlp[]>;
|
|
37
|
+
}
|
|
38
|
+
/** Provide code-defined folder-level permissions. */
|
|
39
|
+
export declare const FlpFactory: import("@webiny/di").Abstraction<IFlpFactory> & {
|
|
40
|
+
FolderType: {
|
|
41
|
+
/** File Manager folders. */
|
|
42
|
+
FileManager: {
|
|
43
|
+
/** Folders holding files. */
|
|
44
|
+
Files: string;
|
|
45
|
+
};
|
|
46
|
+
/** Headless CMS folders. */
|
|
47
|
+
Cms: {
|
|
48
|
+
/** Folders holding entries of a content model, addressed by its `modelId`. */
|
|
49
|
+
Entries(modelId: string): string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export declare namespace FlpFactory {
|
|
54
|
+
type Interface = IFlpFactory;
|
|
55
|
+
type Return = Promise<CodeFlp[]>;
|
|
56
|
+
type Flp = CodeFlp;
|
|
57
|
+
type Permission = CodeFolderPermission;
|
|
58
|
+
}
|
|
59
|
+
export interface GetCodePermissionsParams {
|
|
60
|
+
type: string;
|
|
61
|
+
path: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ICodeFlpsProvider {
|
|
64
|
+
/**
|
|
65
|
+
* Return all code-defined permissions that apply to the given folder type and path.
|
|
66
|
+
*/
|
|
67
|
+
getPermissions(params: GetCodePermissionsParams): Promise<FolderPermission[]>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve code-defined folder-level permissions for a given folder. Only permissions contributed by
|
|
71
|
+
* an `FlpFactory` are considered — the ones stored in the database are loaded separately.
|
|
72
|
+
*/
|
|
73
|
+
export declare const CodeFlpsProvider: import("@webiny/di").Abstraction<ICodeFlpsProvider>;
|
|
74
|
+
export declare namespace CodeFlpsProvider {
|
|
75
|
+
type Interface = ICodeFlpsProvider;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createAbstraction } from "@webiny/feature/api";
|
|
2
|
+
import { FM_FILE_TYPE } from "../../../constants.js";
|
|
3
|
+
const FolderType = {
|
|
4
|
+
FileManager: {
|
|
5
|
+
Files: FM_FILE_TYPE
|
|
6
|
+
},
|
|
7
|
+
Cms: {
|
|
8
|
+
Entries (modelId) {
|
|
9
|
+
return `cms:${modelId}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const FlpFactory = Object.assign(createAbstraction("FlpFactory"), {
|
|
14
|
+
FolderType: FolderType
|
|
15
|
+
});
|
|
16
|
+
const CodeFlpsProvider = createAbstraction("CodeFlpsProvider");
|
|
17
|
+
export { CodeFlpsProvider, FlpFactory };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=abstractions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/abstractions.js","sources":["../../../../src/features/flp/shared/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\nimport { FM_FILE_TYPE } from \"~/constants.js\";\nimport type { FolderAccessLevel, FolderPermission } from \"~/flp/flp.types.js\";\n\ninterface CodeTeamPermission {\n /** Slug of the team the permission is granted to. */\n team: string;\n user?: never;\n level: FolderAccessLevel;\n}\n\ninterface CodeUserPermission {\n /** ID of the admin user the permission is granted to. */\n user: string;\n team?: never;\n level: FolderAccessLevel;\n}\n\n/**\n * A permission grants a level to exactly one target: either a team or a single admin user. The\n * `team:` / `admin:` target strings used internally are generated from these.\n *\n * Prefer `team` — team slugs are authored by hand and stable, while user IDs are generated at\n * runtime and differ per environment.\n */\nexport type CodeFolderPermission = CodeTeamPermission | CodeUserPermission;\n\nexport type CodeFlp = {\n /**\n * Folder type the rule applies to. Use `FlpFactory.FolderType` to build the built-in ones\n * instead of writing the string by hand.\n */\n type: string;\n /**\n * Folder path the rule binds to. The leading `root` segment is optional, so both `/marketing`\n * and `root/marketing` are accepted. A trailing `/*` matches the folder and its whole subtree.\n */\n path: string;\n permissions: CodeFolderPermission[];\n};\n\nexport interface IFlpFactory {\n execute(): Promise<CodeFlp[]>;\n}\n\n/**\n * The folder types shipped with Webiny, grouped by the app that owns them, so `CodeFlp.type` does\n * not have to be written by hand.\n *\n * Folder types stay open-ended — every app registering folders picks its own type — so `type`\n * remains a plain `string` and a custom app can still pass its own value directly.\n */\nconst FolderType = {\n /** File Manager folders. */\n FileManager: {\n /** Folders holding files. */\n Files: FM_FILE_TYPE as string\n },\n /** Headless CMS folders. */\n Cms: {\n /** Folders holding entries of a content model, addressed by its `modelId`. */\n Entries(modelId: string): string {\n return `cms:${modelId}`;\n }\n }\n};\n\n/** Provide code-defined folder-level permissions. */\nexport const FlpFactory = Object.assign(createAbstraction<IFlpFactory>(\"FlpFactory\"), {\n FolderType\n});\n\nexport namespace FlpFactory {\n export type Interface = IFlpFactory;\n export type Return = Promise<CodeFlp[]>;\n export type Flp = CodeFlp;\n export type Permission = CodeFolderPermission;\n}\n\nexport interface GetCodePermissionsParams {\n type: string;\n path: string;\n}\n\nexport interface ICodeFlpsProvider {\n /**\n * Return all code-defined permissions that apply to the given folder type and path.\n */\n getPermissions(params: GetCodePermissionsParams): Promise<FolderPermission[]>;\n}\n\n/**\n * Resolve code-defined folder-level permissions for a given folder. Only permissions contributed by\n * an `FlpFactory` are considered — the ones stored in the database are loaded separately.\n */\nexport const CodeFlpsProvider = createAbstraction<ICodeFlpsProvider>(\"CodeFlpsProvider\");\n\nexport namespace CodeFlpsProvider {\n export type Interface = ICodeFlpsProvider;\n}\n"],"names":["FolderType","FM_FILE_TYPE","modelId","FlpFactory","Object","createAbstraction","CodeFlpsProvider"],"mappings":";;AAoDA,MAAMA,aAAa;IAEf,aAAa;QAET,OAAOC;IACX;IAEA,KAAK;QAED,SAAQC,OAAe;YACnB,OAAO,CAAC,IAAI,EAAEA,SAAS;QAC3B;IACJ;AACJ;AAGO,MAAMC,aAAaC,OAAO,MAAM,CAACC,kBAA+B,eAAe;IAClFL,YAAAA;AACJ;AAyBO,MAAMM,mBAAmBD,kBAAqC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/api";
|
|
2
|
+
import { FeatureFlags } from "@webiny/api-core/features/featureFlags/abstractions.js";
|
|
3
|
+
import { CodeFlpsProvider } from "./CodeFlpsProvider.js";
|
|
4
|
+
import { GetFlpWithCodeFlps } from "../GetFlp/decorators/GetFlpWithCodeFlps.js";
|
|
5
|
+
import { ListFlpsWithCodeFlps } from "../ListFlps/decorators/ListFlpsWithCodeFlps.js";
|
|
6
|
+
const CodeFlpsFeature = createFeature({
|
|
7
|
+
name: "CodeFlps",
|
|
8
|
+
register (container) {
|
|
9
|
+
if (!container.resolve(FeatureFlags).get().isEnabled("advancedAccessControlLayer.folderLevelPermissions")) return;
|
|
10
|
+
container.register(CodeFlpsProvider).inSingletonScope();
|
|
11
|
+
container.registerDecorator(GetFlpWithCodeFlps);
|
|
12
|
+
container.registerDecorator(ListFlpsWithCodeFlps);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
export { CodeFlpsFeature };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/flp/shared/feature.js","sources":["../../../../src/features/flp/shared/feature.ts"],"sourcesContent":["import { type Container, createFeature } from \"@webiny/feature/api\";\nimport { FeatureFlags } from \"@webiny/api-core/features/featureFlags/abstractions.js\";\nimport { CodeFlpsProvider } from \"./CodeFlpsProvider.js\";\nimport { GetFlpWithCodeFlps } from \"~/features/flp/GetFlp/decorators/GetFlpWithCodeFlps.js\";\nimport { ListFlpsWithCodeFlps } from \"~/features/flp/ListFlps/decorators/ListFlpsWithCodeFlps.js\";\n\nexport const CodeFlpsFeature = createFeature({\n name: \"CodeFlps\",\n register(container: Container) {\n // Code-defined FLPs are part of folder-level permissions, which is license-gated. Check at\n // register time so nothing wires up — and no code permission is ever enforced — without the\n // entitlement.\n if (\n !container\n .resolve(FeatureFlags)\n .get()\n .isEnabled(\"advancedAccessControlLayer.folderLevelPermissions\")\n ) {\n return;\n }\n\n container.register(CodeFlpsProvider).inSingletonScope();\n container.registerDecorator(GetFlpWithCodeFlps);\n container.registerDecorator(ListFlpsWithCodeFlps);\n }\n});\n"],"names":["CodeFlpsFeature","createFeature","container","FeatureFlags","CodeFlpsProvider","GetFlpWithCodeFlps","ListFlpsWithCodeFlps"],"mappings":";;;;;AAMO,MAAMA,kBAAkBC,cAAc;IACzC,MAAM;IACN,UAASC,SAAoB;QAIzB,IACI,CAACA,UACI,OAAO,CAACC,cACR,GAAG,GACH,SAAS,CAAC,sDAEf;QAGJD,UAAU,QAAQ,CAACE,kBAAkB,gBAAgB;QACrDF,UAAU,iBAAiB,CAACG;QAC5BH,UAAU,iBAAiB,CAACI;IAChC;AACJ"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { FolderLevelPermissions } from "../../../../features/flp/FolderLevelPermissions/index.js";
|
|
2
2
|
import { CreateFolderUseCase } from "../abstractions.js";
|
|
3
3
|
import type { CreateFolderParams } from "../../../../folder/folder.types.js";
|
|
4
|
+
import { CodeFlpsProvider } from "../../../../features/flp/shared/index.js";
|
|
4
5
|
declare class CreateFolderWithFolderLevelPermissionsImpl implements CreateFolderUseCase.Interface {
|
|
5
6
|
private folderLevelPermissions;
|
|
6
7
|
private readonly decoretee;
|
|
7
|
-
|
|
8
|
+
private readonly codeFlpsProvider?;
|
|
9
|
+
constructor(folderLevelPermissions: FolderLevelPermissions.Interface, codeFlpsProvider: CodeFlpsProvider.Interface | undefined, decoretee: CreateFolderUseCase.Interface);
|
|
8
10
|
execute(params: CreateFolderParams): CreateFolderUseCase.Return;
|
|
11
|
+
/**
|
|
12
|
+
* Code-defined permissions are resolved from the folder's own type and path rather than the FLP
|
|
13
|
+
* catalog: the record for a folder this new is written by an event handler and may not exist
|
|
14
|
+
* yet. Without this the response would omit them until something refetched the folder.
|
|
15
|
+
*/
|
|
16
|
+
private withCodePermissions;
|
|
9
17
|
}
|
|
10
18
|
export declare const CreateFolderWithFolderLevelPermissions: typeof CreateFolderWithFolderLevelPermissionsImpl;
|
|
11
19
|
export {};
|
|
@@ -2,9 +2,11 @@ import { FolderLevelPermissions } from "../../../flp/FolderLevelPermissions/inde
|
|
|
2
2
|
import { CreateFolderUseCase } from "../abstractions.js";
|
|
3
3
|
import { Result, createDecorator } from "@webiny/feature/api";
|
|
4
4
|
import { FolderNotAuthorizedError } from "../../../../domain/folder/errors.js";
|
|
5
|
+
import { CodeFlpMerger, CodeFlpsProvider } from "../../../flp/shared/index.js";
|
|
5
6
|
class CreateFolderWithFolderLevelPermissionsImpl {
|
|
6
|
-
constructor(folderLevelPermissions, decoretee){
|
|
7
|
+
constructor(folderLevelPermissions, codeFlpsProvider, decoretee){
|
|
7
8
|
this.folderLevelPermissions = folderLevelPermissions;
|
|
9
|
+
this.codeFlpsProvider = codeFlpsProvider;
|
|
8
10
|
this.decoretee = decoretee;
|
|
9
11
|
}
|
|
10
12
|
async execute(params) {
|
|
@@ -20,18 +22,33 @@ class CreateFolderWithFolderLevelPermissionsImpl {
|
|
|
20
22
|
const result = await this.decoretee.execute(params);
|
|
21
23
|
if (result.isFail()) return Result.fail(result.error);
|
|
22
24
|
const folder = result.value;
|
|
23
|
-
const
|
|
25
|
+
const permissions = await this.withCodePermissions(folder, folder?.permissions ?? []);
|
|
26
|
+
const permissionsWithDefaults = await this.folderLevelPermissions.getDefaultPermissions(permissions);
|
|
24
27
|
return Result.ok({
|
|
25
28
|
...folder,
|
|
26
29
|
permissions: permissionsWithDefaults
|
|
27
30
|
});
|
|
28
31
|
}
|
|
32
|
+
async withCodePermissions(folder, permissions) {
|
|
33
|
+
if (!this.codeFlpsProvider) return permissions;
|
|
34
|
+
const codePermissions = await this.codeFlpsProvider.getPermissions({
|
|
35
|
+
type: folder.type,
|
|
36
|
+
path: folder.path
|
|
37
|
+
});
|
|
38
|
+
return CodeFlpMerger.mergePermissions(permissions, codePermissions);
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
const CreateFolderWithFolderLevelPermissions = createDecorator({
|
|
31
42
|
abstraction: CreateFolderUseCase,
|
|
32
43
|
decorator: CreateFolderWithFolderLevelPermissionsImpl,
|
|
33
44
|
dependencies: [
|
|
34
|
-
FolderLevelPermissions
|
|
45
|
+
FolderLevelPermissions,
|
|
46
|
+
[
|
|
47
|
+
CodeFlpsProvider,
|
|
48
|
+
{
|
|
49
|
+
optional: true
|
|
50
|
+
}
|
|
51
|
+
]
|
|
35
52
|
]
|
|
36
53
|
});
|
|
37
54
|
export { CreateFolderWithFolderLevelPermissions };
|
package/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.js","sources":["../../../../../src/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.ts"],"sourcesContent":["import { FolderLevelPermissions } from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { CreateFolderUseCase } from \"../abstractions.js\";\nimport type { CreateFolderParams } from \"~/folder/folder.types.js\";\nimport { createDecorator, Result } from \"@webiny/feature/api\";\nimport { FolderNotAuthorizedError } from \"~/domain/folder/errors.js\";\n\nclass CreateFolderWithFolderLevelPermissionsImpl implements CreateFolderUseCase.Interface {\n private folderLevelPermissions: FolderLevelPermissions.Interface;\n private readonly decoretee: CreateFolderUseCase.Interface;\n\n constructor(\n folderLevelPermissions: FolderLevelPermissions.Interface,\n decoretee: CreateFolderUseCase.Interface\n ) {\n this.folderLevelPermissions = folderLevelPermissions;\n this.decoretee = decoretee;\n }\n\n async execute(params: CreateFolderParams): CreateFolderUseCase.Return {\n let canCreateFolder: boolean;\n if (params.parentId) {\n const permissions = await this.folderLevelPermissions.getFolderLevelPermissions(\n params.parentId\n );\n canCreateFolder = await this.folderLevelPermissions.canAccessFolder({\n permissions,\n rwd: \"w\"\n });\n } else {\n canCreateFolder = this.folderLevelPermissions.canCreateFolderInRoot();\n }\n\n if (!canCreateFolder) {\n return Result.fail(new FolderNotAuthorizedError());\n }\n\n const result = await this.decoretee.execute(params);\n\n if (result.isFail()) {\n return Result.fail(result.error);\n }\n\n const folder = result.value;\n\n // Let's set default permissions based on the current user.\n const permissionsWithDefaults
|
|
1
|
+
{"version":3,"file":"features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.js","sources":["../../../../../src/features/folder/CreateFolder/decorators/CreateFolderWithFolderLevelPermissions.ts"],"sourcesContent":["import { FolderLevelPermissions } from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { CreateFolderUseCase } from \"../abstractions.js\";\nimport type { CreateFolderParams } from \"~/folder/folder.types.js\";\nimport type { Folder } from \"~/folder/folder.types.js\";\nimport type { FolderPermission } from \"~/flp/flp.types.js\";\nimport { createDecorator, Result } from \"@webiny/feature/api\";\nimport { FolderNotAuthorizedError } from \"~/domain/folder/errors.js\";\nimport { CodeFlpMerger } from \"~/features/flp/shared/index.js\";\nimport { CodeFlpsProvider } from \"~/features/flp/shared/index.js\";\n\nclass CreateFolderWithFolderLevelPermissionsImpl implements CreateFolderUseCase.Interface {\n private folderLevelPermissions: FolderLevelPermissions.Interface;\n private readonly decoretee: CreateFolderUseCase.Interface;\n private readonly codeFlpsProvider?: CodeFlpsProvider.Interface;\n\n constructor(\n folderLevelPermissions: FolderLevelPermissions.Interface,\n codeFlpsProvider: CodeFlpsProvider.Interface | undefined,\n decoretee: CreateFolderUseCase.Interface\n ) {\n this.folderLevelPermissions = folderLevelPermissions;\n this.codeFlpsProvider = codeFlpsProvider;\n this.decoretee = decoretee;\n }\n\n async execute(params: CreateFolderParams): CreateFolderUseCase.Return {\n let canCreateFolder: boolean;\n if (params.parentId) {\n const permissions = await this.folderLevelPermissions.getFolderLevelPermissions(\n params.parentId\n );\n canCreateFolder = await this.folderLevelPermissions.canAccessFolder({\n permissions,\n rwd: \"w\"\n });\n } else {\n canCreateFolder = this.folderLevelPermissions.canCreateFolderInRoot();\n }\n\n if (!canCreateFolder) {\n return Result.fail(new FolderNotAuthorizedError());\n }\n\n const result = await this.decoretee.execute(params);\n\n if (result.isFail()) {\n return Result.fail(result.error);\n }\n\n const folder = result.value;\n\n const permissions = await this.withCodePermissions(folder, folder?.permissions ?? []);\n\n // Let's set default permissions based on the current user.\n const permissionsWithDefaults =\n await this.folderLevelPermissions.getDefaultPermissions(permissions);\n\n return Result.ok({\n ...folder,\n permissions: permissionsWithDefaults\n });\n }\n\n /**\n * Code-defined permissions are resolved from the folder's own type and path rather than the FLP\n * catalog: the record for a folder this new is written by an event handler and may not exist\n * yet. Without this the response would omit them until something refetched the folder.\n */\n private async withCodePermissions(\n folder: Pick<Folder, \"type\" | \"path\">,\n permissions: FolderPermission[]\n ): Promise<FolderPermission[]> {\n if (!this.codeFlpsProvider) {\n return permissions;\n }\n\n const codePermissions = await this.codeFlpsProvider.getPermissions({\n type: folder.type,\n path: folder.path\n });\n\n return CodeFlpMerger.mergePermissions(permissions, codePermissions);\n }\n}\n\nexport const CreateFolderWithFolderLevelPermissions = createDecorator({\n abstraction: CreateFolderUseCase,\n decorator: CreateFolderWithFolderLevelPermissionsImpl,\n // `CodeFlpsProvider` is optional: `CodeFlpsFeature` does not register it unless the project has\n // the folder-level permissions entitlement.\n dependencies: [FolderLevelPermissions, [CodeFlpsProvider, { optional: true }]]\n});\n"],"names":["CreateFolderWithFolderLevelPermissionsImpl","folderLevelPermissions","codeFlpsProvider","decoretee","params","canCreateFolder","permissions","Result","FolderNotAuthorizedError","result","folder","permissionsWithDefaults","codePermissions","CodeFlpMerger","CreateFolderWithFolderLevelPermissions","createDecorator","CreateFolderUseCase","FolderLevelPermissions","CodeFlpsProvider"],"mappings":";;;;;AAUA,MAAMA;IAKF,YACIC,sBAAwD,EACxDC,gBAAwD,EACxDC,SAAwC,CAC1C;QACE,IAAI,CAAC,sBAAsB,GAAGF;QAC9B,IAAI,CAAC,gBAAgB,GAAGC;QACxB,IAAI,CAAC,SAAS,GAAGC;IACrB;IAEA,MAAM,QAAQC,MAA0B,EAA8B;QAClE,IAAIC;QACJ,IAAID,OAAO,QAAQ,EAAE;YACjB,MAAME,cAAc,MAAM,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CAC3EF,OAAO,QAAQ;YAEnBC,kBAAkB,MAAM,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC;gBAChEC;gBACA,KAAK;YACT;QACJ,OACID,kBAAkB,IAAI,CAAC,sBAAsB,CAAC,qBAAqB;QAGvE,IAAI,CAACA,iBACD,OAAOE,OAAO,IAAI,CAAC,IAAIC;QAG3B,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACL;QAE5C,IAAIK,OAAO,MAAM,IACb,OAAOF,OAAO,IAAI,CAACE,OAAO,KAAK;QAGnC,MAAMC,SAASD,OAAO,KAAK;QAE3B,MAAMH,cAAc,MAAM,IAAI,CAAC,mBAAmB,CAACI,QAAQA,QAAQ,eAAe,EAAE;QAGpF,MAAMC,0BACF,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAACL;QAE5D,OAAOC,OAAO,EAAE,CAAC;YACb,GAAGG,MAAM;YACT,aAAaC;QACjB;IACJ;IAOA,MAAc,oBACVD,MAAqC,EACrCJ,WAA+B,EACJ;QAC3B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EACtB,OAAOA;QAGX,MAAMM,kBAAkB,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;YAC/D,MAAMF,OAAO,IAAI;YACjB,MAAMA,OAAO,IAAI;QACrB;QAEA,OAAOG,cAAc,gBAAgB,CAACP,aAAaM;IACvD;AACJ;AAEO,MAAME,yCAAyCC,gBAAgB;IAClE,aAAaC;IACb,WAAWhB;IAGX,cAAc;QAACiB;QAAwB;YAACC;YAAkB;gBAAE,UAAU;YAAK;SAAE;KAAC;AAClF"}
|
|
@@ -2,12 +2,25 @@ import type { UpdateFolderParams } from "../../../../folder/folder.types.js";
|
|
|
2
2
|
import { FolderLevelPermissions } from "../../../../features/flp/FolderLevelPermissions/index.js";
|
|
3
3
|
import { UpdateFolderUseCase } from "../abstractions.js";
|
|
4
4
|
import { GetFolderUseCase } from "../../../../features/folder/GetFolder/index.js";
|
|
5
|
+
import { CodeFlpsProvider } from "../../../../features/flp/shared/index.js";
|
|
5
6
|
declare class UpdateFolderWithFolderLevelPermissionsImpl implements UpdateFolderUseCase.Interface {
|
|
6
7
|
private getFolder;
|
|
7
8
|
private folderLevelPermissions;
|
|
8
9
|
private readonly decoretee;
|
|
9
|
-
|
|
10
|
+
private readonly codeFlpsProvider?;
|
|
11
|
+
constructor(getFolder: GetFolderUseCase.Interface, folderLevelPermissions: FolderLevelPermissions.Interface, codeFlpsProvider: CodeFlpsProvider.Interface | undefined, decoretee: UpdateFolderUseCase.Interface);
|
|
10
12
|
execute(id: string, params: UpdateFolderParams): UpdateFolderUseCase.Return;
|
|
13
|
+
/**
|
|
14
|
+
* The type and path the folder will have after this update, computed the same way
|
|
15
|
+
* `UpdateFolderRepository` computes them: the submitted slug and parent, falling back to the
|
|
16
|
+
* current ones.
|
|
17
|
+
*/
|
|
18
|
+
private getTargetFolder;
|
|
19
|
+
/**
|
|
20
|
+
* Code-defined permissions are resolved from the folder's own type and path rather than the FLP
|
|
21
|
+
* catalog, so they apply even when the stored record is missing or not yet written.
|
|
22
|
+
*/
|
|
23
|
+
private withCodePermissions;
|
|
11
24
|
}
|
|
12
25
|
export declare const UpdateFolderWithFolderLevelPermissions: typeof UpdateFolderWithFolderLevelPermissionsImpl;
|
|
13
26
|
export {};
|
|
@@ -3,10 +3,13 @@ import { FolderLevelPermissions } from "../../../flp/FolderLevelPermissions/inde
|
|
|
3
3
|
import { UpdateFolderUseCase } from "../abstractions.js";
|
|
4
4
|
import { GetFolderUseCase } from "../../GetFolder/index.js";
|
|
5
5
|
import { FolderCannotMoveToNewParent, FolderValidationError } from "../../../../domain/folder/errors.js";
|
|
6
|
+
import { CodeFlpMerger, CodeFlpsProvider } from "../../../flp/shared/index.js";
|
|
7
|
+
import { Path } from "../../../../utils/Path.js";
|
|
6
8
|
class UpdateFolderWithFolderLevelPermissionsImpl {
|
|
7
|
-
constructor(getFolder, folderLevelPermissions, decoretee){
|
|
9
|
+
constructor(getFolder, folderLevelPermissions, codeFlpsProvider, decoretee){
|
|
8
10
|
this.getFolder = getFolder;
|
|
9
11
|
this.folderLevelPermissions = folderLevelPermissions;
|
|
12
|
+
this.codeFlpsProvider = codeFlpsProvider;
|
|
10
13
|
this.decoretee = decoretee;
|
|
11
14
|
}
|
|
12
15
|
async execute(id, params) {
|
|
@@ -18,7 +21,9 @@ class UpdateFolderWithFolderLevelPermissionsImpl {
|
|
|
18
21
|
permissions: originalPermissions,
|
|
19
22
|
rwd: "w"
|
|
20
23
|
});
|
|
21
|
-
const
|
|
24
|
+
const targetFolder = await this.getTargetFolder(original, params);
|
|
25
|
+
const submittedPermissions = await this.withCodePermissions(targetFolder, params.permissions ?? []);
|
|
26
|
+
const permissions = await this.folderLevelPermissions.getDefaultPermissions(submittedPermissions);
|
|
22
27
|
const stillHasAccess = await this.folderLevelPermissions.canAccessFolder({
|
|
23
28
|
permissions,
|
|
24
29
|
rwd: "w"
|
|
@@ -28,6 +33,7 @@ class UpdateFolderWithFolderLevelPermissionsImpl {
|
|
|
28
33
|
const targetIsValid = permission.target.startsWith("admin:") || permission.target.startsWith("team:");
|
|
29
34
|
if (!targetIsValid) return Result.fail(new FolderValidationError(`Permission target "${permission.target}" is not valid.`));
|
|
30
35
|
if (permission.inheritedFrom) return Result.fail(new FolderValidationError('Permission "inheritedFrom" cannot be set manually.'));
|
|
36
|
+
if (permission.plugin) return Result.fail(new FolderValidationError('Permission "plugin" cannot be set manually. Code-defined permissions are managed via an "FlpFactory".'));
|
|
31
37
|
}
|
|
32
38
|
if (params.parentId && params.parentId !== original.parentId) {
|
|
33
39
|
const parentPermissions = await this.folderLevelPermissions.getFolderLevelPermissions(params.parentId);
|
|
@@ -39,18 +45,60 @@ class UpdateFolderWithFolderLevelPermissionsImpl {
|
|
|
39
45
|
}
|
|
40
46
|
const result = await this.decoretee.execute(id, params);
|
|
41
47
|
if (result.isFail()) return Result.fail(result.error);
|
|
48
|
+
const updatedPermissions = await this.withCodePermissions(result.value, params.permissions ?? []);
|
|
49
|
+
const updatedPermissionsWithDefaults = await this.folderLevelPermissions.getDefaultPermissions(updatedPermissions);
|
|
42
50
|
return Result.ok({
|
|
43
51
|
...result.value,
|
|
44
|
-
permissions
|
|
52
|
+
permissions: updatedPermissionsWithDefaults
|
|
45
53
|
});
|
|
46
54
|
}
|
|
55
|
+
async getTargetFolder(original, params) {
|
|
56
|
+
const slug = params.slug || original.slug;
|
|
57
|
+
const parentId = void 0 !== params.parentId ? params.parentId : original.parentId;
|
|
58
|
+
if (parentId === original.parentId) {
|
|
59
|
+
const parentPath = original.path.slice(0, -(original.slug.length + 1));
|
|
60
|
+
const path = Path.create(slug, parentPath);
|
|
61
|
+
return {
|
|
62
|
+
type: original.type,
|
|
63
|
+
path
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!parentId) {
|
|
67
|
+
const path = Path.create(slug);
|
|
68
|
+
return {
|
|
69
|
+
type: original.type,
|
|
70
|
+
path
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const parentResult = await this.getFolder.execute(parentId);
|
|
74
|
+
if (parentResult.isFail()) return original;
|
|
75
|
+
const path = Path.create(slug, parentResult.value.path);
|
|
76
|
+
return {
|
|
77
|
+
type: original.type,
|
|
78
|
+
path
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async withCodePermissions(folder, permissions) {
|
|
82
|
+
if (!this.codeFlpsProvider) return permissions;
|
|
83
|
+
const codePermissions = await this.codeFlpsProvider.getPermissions({
|
|
84
|
+
type: folder.type,
|
|
85
|
+
path: folder.path
|
|
86
|
+
});
|
|
87
|
+
return CodeFlpMerger.mergePermissions(permissions, codePermissions);
|
|
88
|
+
}
|
|
47
89
|
}
|
|
48
90
|
const UpdateFolderWithFolderLevelPermissions = createDecorator({
|
|
49
91
|
abstraction: UpdateFolderUseCase,
|
|
50
92
|
decorator: UpdateFolderWithFolderLevelPermissionsImpl,
|
|
51
93
|
dependencies: [
|
|
52
94
|
GetFolderUseCase,
|
|
53
|
-
FolderLevelPermissions
|
|
95
|
+
FolderLevelPermissions,
|
|
96
|
+
[
|
|
97
|
+
CodeFlpsProvider,
|
|
98
|
+
{
|
|
99
|
+
optional: true
|
|
100
|
+
}
|
|
101
|
+
]
|
|
54
102
|
]
|
|
55
103
|
});
|
|
56
104
|
export { UpdateFolderWithFolderLevelPermissions };
|
package/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.js","sources":["../../../../../src/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.ts"],"sourcesContent":["import { createDecorator, Result } from \"@webiny/feature/api\";\nimport type { UpdateFolderParams } from \"~/folder/folder.types.js\";\nimport { FolderLevelPermissions } from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { UpdateFolderUseCase } from \"../abstractions.js\";\nimport { GetFolderUseCase } from \"~/features/folder/GetFolder/index.js\";\nimport { FolderCannotMoveToNewParent, FolderValidationError } from \"~/domain/folder/errors.js\";\n\nclass UpdateFolderWithFolderLevelPermissionsImpl implements UpdateFolderUseCase.Interface {\n private folderLevelPermissions: FolderLevelPermissions.Interface;\n private readonly decoretee: UpdateFolderUseCase.Interface;\n\n constructor(\n private getFolder: GetFolderUseCase.Interface,\n folderLevelPermissions: FolderLevelPermissions.Interface,\n decoretee: UpdateFolderUseCase.Interface\n ) {\n this.folderLevelPermissions = folderLevelPermissions;\n this.decoretee = decoretee;\n }\n\n async execute(id: string, params: UpdateFolderParams): UpdateFolderUseCase.Return {\n const originalResult = await this.getFolder.execute(id);\n\n if (originalResult.isFail()) {\n return Result.fail(originalResult.error);\n }\n\n const original = originalResult.value;\n\n const originalPermissions = await this.folderLevelPermissions.getFolderLevelPermissions(id);\n\n // Let's ensure current identity's permission allows the update operation.\n await this.folderLevelPermissions.ensureCanAccessFolder({\n permissions: originalPermissions,\n rwd: \"w\"\n });\n\n const permissions = await this.folderLevelPermissions.getDefaultPermissions(\n params.permissions ?? []\n );\n\n // Check if the user still has access to the folder with the provided permissions.\n const stillHasAccess = await this.folderLevelPermissions.canAccessFolder({\n permissions,\n rwd: \"w\"\n });\n\n if (!stillHasAccess) {\n return Result.fail(\n new FolderValidationError(\n `Cannot continue because you would loose access to this folder.`\n )\n );\n }\n\n // Validate data.\n if (Array.isArray(params.permissions)) {\n for (const permission of params.permissions) {\n const targetIsValid =\n permission.target.startsWith(\"admin:\") || permission.target.startsWith(\"team:\");\n if (!targetIsValid) {\n return Result.fail(\n new FolderValidationError(\n `Permission target \"${permission.target}\" is not valid.`\n )\n );\n }\n\n if (permission.inheritedFrom) {\n return Result.fail(\n new FolderValidationError(\n `Permission \"inheritedFrom\" cannot be set manually.`\n )\n );\n }\n }\n }\n\n // Parent change is not allowed if the user doesn't have access to the new parent.\n if (params.parentId && params.parentId !== original.parentId) {\n // Getting the parent folder permissions will throw an error if the user doesn't have access.\n const parentPermissions = await this.folderLevelPermissions.getFolderLevelPermissions(\n params.parentId\n );\n\n const canAccessFolder = await this.folderLevelPermissions.canAccessFolder({\n permissions: parentPermissions,\n rwd: \"w\"\n });\n\n if (!canAccessFolder) {\n return Result.fail(new FolderCannotMoveToNewParent());\n }\n }\n\n const result = await this.decoretee.execute(id, params);\n\n if (result.isFail()) {\n return Result.fail(result.error);\n }\n\n return Result.ok({\n ...result.value,\n permissions\n });\n }\n}\n\nexport const UpdateFolderWithFolderLevelPermissions = createDecorator({\n abstraction: UpdateFolderUseCase,\n decorator: UpdateFolderWithFolderLevelPermissionsImpl,\n dependencies: [GetFolderUseCase, FolderLevelPermissions]\n});\n"],"names":["UpdateFolderWithFolderLevelPermissionsImpl","getFolder","folderLevelPermissions","decoretee","id","params","originalResult","Result","original","originalPermissions","permissions","stillHasAccess","FolderValidationError","Array","permission","targetIsValid","parentPermissions","canAccessFolder","FolderCannotMoveToNewParent","result","UpdateFolderWithFolderLevelPermissions","createDecorator","UpdateFolderUseCase","GetFolderUseCase","FolderLevelPermissions"],"mappings":";;;;;AAOA,MAAMA;IAIF,YACYC,SAAqC,EAC7CC,sBAAwD,EACxDC,SAAwC,CAC1C;aAHUF,SAAS,GAATA;QAIR,IAAI,CAAC,sBAAsB,GAAGC;QAC9B,IAAI,CAAC,SAAS,GAAGC;IACrB;IAEA,MAAM,QAAQC,EAAU,EAAEC,MAA0B,EAA8B;QAC9E,MAAMC,iBAAiB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACF;QAEpD,IAAIE,eAAe,MAAM,IACrB,OAAOC,OAAO,IAAI,CAACD,eAAe,KAAK;QAG3C,MAAME,WAAWF,eAAe,KAAK;QAErC,MAAMG,sBAAsB,MAAM,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CAACL;QAGxF,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAAC;YACpD,aAAaK;YACb,KAAK;QACT;QAEA,MAAMC,cAAc,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CACvEL,OAAO,WAAW,IAAI,EAAE;QAI5B,MAAMM,iBAAiB,MAAM,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC;YACrED;YACA,KAAK;QACT;QAEA,IAAI,CAACC,gBACD,OAAOJ,OAAO,IAAI,CACd,IAAIK,sBACA;QAMZ,IAAIC,MAAM,OAAO,CAACR,OAAO,WAAW,GAChC,KAAK,MAAMS,cAAcT,OAAO,WAAW,CAAE;YACzC,MAAMU,gBACFD,WAAW,MAAM,CAAC,UAAU,CAAC,aAAaA,WAAW,MAAM,CAAC,UAAU,CAAC;YAC3E,IAAI,CAACC,eACD,OAAOR,OAAO,IAAI,CACd,IAAIK,sBACA,CAAC,mBAAmB,EAAEE,WAAW,MAAM,CAAC,eAAe,CAAC;YAKpE,IAAIA,WAAW,aAAa,EACxB,OAAOP,OAAO,IAAI,CACd,IAAIK,sBACA;QAIhB;QAIJ,IAAIP,OAAO,QAAQ,IAAIA,OAAO,QAAQ,KAAKG,SAAS,QAAQ,EAAE;YAE1D,MAAMQ,oBAAoB,MAAM,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CACjFX,OAAO,QAAQ;YAGnB,MAAMY,kBAAkB,MAAM,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC;gBACtE,aAAaD;gBACb,KAAK;YACT;YAEA,IAAI,CAACC,iBACD,OAAOV,OAAO,IAAI,CAAC,IAAIW;QAE/B;QAEA,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACf,IAAIC;QAEhD,IAAIc,OAAO,MAAM,IACb,OAAOZ,OAAO,IAAI,CAACY,OAAO,KAAK;QAGnC,OAAOZ,OAAO,EAAE,CAAC;YACb,GAAGY,OAAO,KAAK;YACfT;QACJ;IACJ;AACJ;AAEO,MAAMU,yCAAyCC,gBAAgB;IAClE,aAAaC;IACb,WAAWtB;IACX,cAAc;QAACuB;QAAkBC;KAAuB;AAC5D"}
|
|
1
|
+
{"version":3,"file":"features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.js","sources":["../../../../../src/features/folder/UpdateFolder/decorators/UpdateFolderWithFolderLevelPermissions.ts"],"sourcesContent":["import { createDecorator, Result } from \"@webiny/feature/api\";\nimport type { Folder } from \"~/folder/folder.types.js\";\nimport type { UpdateFolderParams } from \"~/folder/folder.types.js\";\nimport type { FolderPermission } from \"~/flp/flp.types.js\";\nimport { FolderLevelPermissions } from \"~/features/flp/FolderLevelPermissions/index.js\";\nimport { UpdateFolderUseCase } from \"../abstractions.js\";\nimport { GetFolderUseCase } from \"~/features/folder/GetFolder/index.js\";\nimport { FolderCannotMoveToNewParent, FolderValidationError } from \"~/domain/folder/errors.js\";\nimport { CodeFlpMerger } from \"~/features/flp/shared/index.js\";\nimport { CodeFlpsProvider } from \"~/features/flp/shared/index.js\";\nimport { Path } from \"~/utils/Path.js\";\n\nclass UpdateFolderWithFolderLevelPermissionsImpl implements UpdateFolderUseCase.Interface {\n private folderLevelPermissions: FolderLevelPermissions.Interface;\n private readonly decoretee: UpdateFolderUseCase.Interface;\n private readonly codeFlpsProvider?: CodeFlpsProvider.Interface;\n\n constructor(\n private getFolder: GetFolderUseCase.Interface,\n folderLevelPermissions: FolderLevelPermissions.Interface,\n codeFlpsProvider: CodeFlpsProvider.Interface | undefined,\n decoretee: UpdateFolderUseCase.Interface\n ) {\n this.folderLevelPermissions = folderLevelPermissions;\n this.codeFlpsProvider = codeFlpsProvider;\n this.decoretee = decoretee;\n }\n\n async execute(id: string, params: UpdateFolderParams): UpdateFolderUseCase.Return {\n const originalResult = await this.getFolder.execute(id);\n\n if (originalResult.isFail()) {\n return Result.fail(originalResult.error);\n }\n\n const original = originalResult.value;\n\n const originalPermissions = await this.folderLevelPermissions.getFolderLevelPermissions(id);\n\n // Let's ensure current identity's permission allows the update operation.\n await this.folderLevelPermissions.ensureCanAccessFolder({\n permissions: originalPermissions,\n rwd: \"w\"\n });\n\n // The guard below asks whether the user keeps access once the update is saved. A new slug or\n // a new parent changes the path, and code-defined rules match on the path, so resolve them\n // against where the folder is going rather than where it is now.\n const targetFolder = await this.getTargetFolder(original, params);\n\n const submittedPermissions = await this.withCodePermissions(\n targetFolder,\n params.permissions ?? []\n );\n\n const permissions =\n await this.folderLevelPermissions.getDefaultPermissions(submittedPermissions);\n\n // Check if the user still has access to the folder with the provided permissions.\n const stillHasAccess = await this.folderLevelPermissions.canAccessFolder({\n permissions,\n rwd: \"w\"\n });\n\n if (!stillHasAccess) {\n return Result.fail(\n new FolderValidationError(\n `Cannot continue because you would loose access to this folder.`\n )\n );\n }\n\n // Validate data.\n if (Array.isArray(params.permissions)) {\n for (const permission of params.permissions) {\n const targetIsValid =\n permission.target.startsWith(\"admin:\") || permission.target.startsWith(\"team:\");\n if (!targetIsValid) {\n return Result.fail(\n new FolderValidationError(\n `Permission target \"${permission.target}\" is not valid.`\n )\n );\n }\n\n if (permission.inheritedFrom) {\n return Result.fail(\n new FolderValidationError(\n `Permission \"inheritedFrom\" cannot be set manually.`\n )\n );\n }\n\n if (permission.plugin) {\n return Result.fail(\n new FolderValidationError(\n `Permission \"plugin\" cannot be set manually. Code-defined permissions are managed via an \"FlpFactory\".`\n )\n );\n }\n }\n }\n\n // Parent change is not allowed if the user doesn't have access to the new parent.\n if (params.parentId && params.parentId !== original.parentId) {\n // Getting the parent folder permissions will throw an error if the user doesn't have access.\n const parentPermissions = await this.folderLevelPermissions.getFolderLevelPermissions(\n params.parentId\n );\n\n const canAccessFolder = await this.folderLevelPermissions.canAccessFolder({\n permissions: parentPermissions,\n rwd: \"w\"\n });\n\n if (!canAccessFolder) {\n return Result.fail(new FolderCannotMoveToNewParent());\n }\n }\n\n const result = await this.decoretee.execute(id, params);\n\n if (result.isFail()) {\n return Result.fail(result.error);\n }\n\n // Resolved again against the updated folder: a rename or a move changes the path, and with\n // it which code-defined rules apply.\n const updatedPermissions = await this.withCodePermissions(\n result.value,\n params.permissions ?? []\n );\n\n const updatedPermissionsWithDefaults =\n await this.folderLevelPermissions.getDefaultPermissions(updatedPermissions);\n\n return Result.ok({\n ...result.value,\n permissions: updatedPermissionsWithDefaults\n });\n }\n\n /**\n * The type and path the folder will have after this update, computed the same way\n * `UpdateFolderRepository` computes them: the submitted slug and parent, falling back to the\n * current ones.\n */\n private async getTargetFolder(\n original: Folder,\n params: UpdateFolderParams\n ): Promise<Pick<Folder, \"type\" | \"path\">> {\n const slug = params.slug || original.slug;\n const parentId = params.parentId !== undefined ? params.parentId : original.parentId;\n\n // Same parent: only the last segment of the path can change, so it is derived from the\n // current path. This avoids reading the parent, which the user may not have access to.\n if (parentId === original.parentId) {\n const parentPath = original.path.slice(0, -(original.slug.length + 1));\n const path = Path.create(slug, parentPath);\n\n return { type: original.type, path };\n }\n\n if (!parentId) {\n const path = Path.create(slug);\n\n return { type: original.type, path };\n }\n\n // A move. If the destination cannot be read, keep the current path: the parent check further\n // down rejects the move with its own error.\n const parentResult = await this.getFolder.execute(parentId);\n if (parentResult.isFail()) {\n return original;\n }\n\n const path = Path.create(slug, parentResult.value.path);\n\n return { type: original.type, path };\n }\n\n /**\n * Code-defined permissions are resolved from the folder's own type and path rather than the FLP\n * catalog, so they apply even when the stored record is missing or not yet written.\n */\n private async withCodePermissions(\n folder: Pick<Folder, \"type\" | \"path\">,\n permissions: FolderPermission[]\n ): Promise<FolderPermission[]> {\n if (!this.codeFlpsProvider) {\n return permissions;\n }\n\n const codePermissions = await this.codeFlpsProvider.getPermissions({\n type: folder.type,\n path: folder.path\n });\n\n return CodeFlpMerger.mergePermissions(permissions, codePermissions);\n }\n}\n\nexport const UpdateFolderWithFolderLevelPermissions = createDecorator({\n abstraction: UpdateFolderUseCase,\n decorator: UpdateFolderWithFolderLevelPermissionsImpl,\n // `CodeFlpsProvider` is optional: `CodeFlpsFeature` does not register it unless the project has\n // the folder-level permissions entitlement.\n dependencies: [GetFolderUseCase, FolderLevelPermissions, [CodeFlpsProvider, { optional: true }]]\n});\n"],"names":["UpdateFolderWithFolderLevelPermissionsImpl","getFolder","folderLevelPermissions","codeFlpsProvider","decoretee","id","params","originalResult","Result","original","originalPermissions","targetFolder","submittedPermissions","permissions","stillHasAccess","FolderValidationError","Array","permission","targetIsValid","parentPermissions","canAccessFolder","FolderCannotMoveToNewParent","result","updatedPermissions","updatedPermissionsWithDefaults","slug","parentId","undefined","parentPath","path","Path","parentResult","folder","codePermissions","CodeFlpMerger","UpdateFolderWithFolderLevelPermissions","createDecorator","UpdateFolderUseCase","GetFolderUseCase","FolderLevelPermissions","CodeFlpsProvider"],"mappings":";;;;;;;AAYA,MAAMA;IAKF,YACYC,SAAqC,EAC7CC,sBAAwD,EACxDC,gBAAwD,EACxDC,SAAwC,CAC1C;aAJUH,SAAS,GAATA;QAKR,IAAI,CAAC,sBAAsB,GAAGC;QAC9B,IAAI,CAAC,gBAAgB,GAAGC;QACxB,IAAI,CAAC,SAAS,GAAGC;IACrB;IAEA,MAAM,QAAQC,EAAU,EAAEC,MAA0B,EAA8B;QAC9E,MAAMC,iBAAiB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACF;QAEpD,IAAIE,eAAe,MAAM,IACrB,OAAOC,OAAO,IAAI,CAACD,eAAe,KAAK;QAG3C,MAAME,WAAWF,eAAe,KAAK;QAErC,MAAMG,sBAAsB,MAAM,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CAACL;QAGxF,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAAC;YACpD,aAAaK;YACb,KAAK;QACT;QAKA,MAAMC,eAAe,MAAM,IAAI,CAAC,eAAe,CAACF,UAAUH;QAE1D,MAAMM,uBAAuB,MAAM,IAAI,CAAC,mBAAmB,CACvDD,cACAL,OAAO,WAAW,IAAI,EAAE;QAG5B,MAAMO,cACF,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAACD;QAG5D,MAAME,iBAAiB,MAAM,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC;YACrED;YACA,KAAK;QACT;QAEA,IAAI,CAACC,gBACD,OAAON,OAAO,IAAI,CACd,IAAIO,sBACA;QAMZ,IAAIC,MAAM,OAAO,CAACV,OAAO,WAAW,GAChC,KAAK,MAAMW,cAAcX,OAAO,WAAW,CAAE;YACzC,MAAMY,gBACFD,WAAW,MAAM,CAAC,UAAU,CAAC,aAAaA,WAAW,MAAM,CAAC,UAAU,CAAC;YAC3E,IAAI,CAACC,eACD,OAAOV,OAAO,IAAI,CACd,IAAIO,sBACA,CAAC,mBAAmB,EAAEE,WAAW,MAAM,CAAC,eAAe,CAAC;YAKpE,IAAIA,WAAW,aAAa,EACxB,OAAOT,OAAO,IAAI,CACd,IAAIO,sBACA;YAKZ,IAAIE,WAAW,MAAM,EACjB,OAAOT,OAAO,IAAI,CACd,IAAIO,sBACA;QAIhB;QAIJ,IAAIT,OAAO,QAAQ,IAAIA,OAAO,QAAQ,KAAKG,SAAS,QAAQ,EAAE;YAE1D,MAAMU,oBAAoB,MAAM,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CACjFb,OAAO,QAAQ;YAGnB,MAAMc,kBAAkB,MAAM,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC;gBACtE,aAAaD;gBACb,KAAK;YACT;YAEA,IAAI,CAACC,iBACD,OAAOZ,OAAO,IAAI,CAAC,IAAIa;QAE/B;QAEA,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACjB,IAAIC;QAEhD,IAAIgB,OAAO,MAAM,IACb,OAAOd,OAAO,IAAI,CAACc,OAAO,KAAK;QAKnC,MAAMC,qBAAqB,MAAM,IAAI,CAAC,mBAAmB,CACrDD,OAAO,KAAK,EACZhB,OAAO,WAAW,IAAI,EAAE;QAG5B,MAAMkB,iCACF,MAAM,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAACD;QAE5D,OAAOf,OAAO,EAAE,CAAC;YACb,GAAGc,OAAO,KAAK;YACf,aAAaE;QACjB;IACJ;IAOA,MAAc,gBACVf,QAAgB,EAChBH,MAA0B,EACY;QACtC,MAAMmB,OAAOnB,OAAO,IAAI,IAAIG,SAAS,IAAI;QACzC,MAAMiB,WAAWpB,AAAoBqB,WAApBrB,OAAO,QAAQ,GAAiBA,OAAO,QAAQ,GAAGG,SAAS,QAAQ;QAIpF,IAAIiB,aAAajB,SAAS,QAAQ,EAAE;YAChC,MAAMmB,aAAanB,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,CAAEA,CAAAA,SAAS,IAAI,CAAC,MAAM,GAAG;YACnE,MAAMoB,OAAOC,KAAK,MAAM,CAACL,MAAMG;YAE/B,OAAO;gBAAE,MAAMnB,SAAS,IAAI;gBAAEoB;YAAK;QACvC;QAEA,IAAI,CAACH,UAAU;YACX,MAAMG,OAAOC,KAAK,MAAM,CAACL;YAEzB,OAAO;gBAAE,MAAMhB,SAAS,IAAI;gBAAEoB;YAAK;QACvC;QAIA,MAAME,eAAe,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAACL;QAClD,IAAIK,aAAa,MAAM,IACnB,OAAOtB;QAGX,MAAMoB,OAAOC,KAAK,MAAM,CAACL,MAAMM,aAAa,KAAK,CAAC,IAAI;QAEtD,OAAO;YAAE,MAAMtB,SAAS,IAAI;YAAEoB;QAAK;IACvC;IAMA,MAAc,oBACVG,MAAqC,EACrCnB,WAA+B,EACJ;QAC3B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EACtB,OAAOA;QAGX,MAAMoB,kBAAkB,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;YAC/D,MAAMD,OAAO,IAAI;YACjB,MAAMA,OAAO,IAAI;QACrB;QAEA,OAAOE,cAAc,gBAAgB,CAACrB,aAAaoB;IACvD;AACJ;AAEO,MAAME,yCAAyCC,gBAAgB;IAClE,aAAaC;IACb,WAAWrC;IAGX,cAAc;QAACsC;QAAkBC;QAAwB;YAACC;YAAkB;gBAAE,UAAU;YAAK;SAAE;KAAC;AACpG"}
|
|
@@ -163,8 +163,11 @@ const createFolderTypeDefs = (params)=>{
|
|
|
163
163
|
|
|
164
164
|
extend type AcoFolder_Permissions {
|
|
165
165
|
inheritedFrom: ID
|
|
166
|
+
# Set on permissions contributed from code, via an "FlpFactory". Read-only by design:
|
|
167
|
+
# deliberately absent from the input type below, so it cannot be assigned via the API.
|
|
168
|
+
plugin: Boolean
|
|
166
169
|
}
|
|
167
|
-
|
|
170
|
+
|
|
168
171
|
extend input AcoFolder_PermissionsInput {
|
|
169
172
|
inheritedFrom: ID
|
|
170
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder/createFolderTypeDefs.js","sources":["../../src/folder/createFolderTypeDefs.ts"],"sourcesContent":["import type { CmsModel, CmsModelField } from \"@webiny/api-headless-cms/types/index.js\";\nimport { renderFields } from \"@webiny/api-headless-cms/utils/renderFields.js\";\nimport { renderInputFields } from \"@webiny/api-headless-cms/utils/renderInputFields.js\";\nimport type { CmsModelFieldToGraphQLRegistry } from \"@webiny/api-headless-cms/exports/api/cms/graphql.js\";\n\nexport interface CreateFolderTypeDefsParams {\n model: CmsModel;\n models: CmsModel[];\n fieldRegistry: CmsModelFieldToGraphQLRegistry.Interface;\n}\n\nconst removeFieldRequiredValidation = (field: CmsModelField) => {\n if (field.validation) {\n field.validation = field.validation.filter(validation => validation.name !== \"required\");\n }\n if (field.listValidation) {\n field.listValidation = field.listValidation.filter(v => v.name !== \"required\");\n }\n return field;\n};\n\nconst createUpdateFields = (fields: CmsModelField[]): CmsModelField[] => {\n return fields.reduce<CmsModelField[]>((collection, field) => {\n collection.push(removeFieldRequiredValidation({ ...field }));\n return collection;\n }, []);\n};\n\nexport const createFolderTypeDefs = (params: CreateFolderTypeDefsParams): string => {\n const { model, models, fieldRegistry } = params;\n const { fields } = model;\n\n const fieldTypes = renderFields({\n models,\n model,\n fields,\n type: \"manage\",\n fieldRegistry\n });\n const inputCreateFields = renderInputFields({\n models,\n model,\n fields,\n fieldRegistry\n });\n const inputUpdateFields = renderInputFields({\n models,\n model,\n fields: createUpdateFields(fields),\n fieldRegistry\n });\n\n return /* GraphQL */ `\n ${fieldTypes.map(f => f.typeDefs).join(\"\\n\")}\n \n type Folder {\n id: ID!\n # Tells us if the current user can manage folder structure.\n canManageStructure: Boolean\n\n # Tells us if the current user can manage folder permissions.\n canManagePermissions: Boolean\n\n # Tells us if the current user can manage folder content.\n canManageContent: Boolean\n\n # Tells us if the folder contains non-inherited permissions.\n hasNonInheritedPermissions: Boolean\n\n createdOn: DateTime\n modifiedOn: DateTime\n savedOn: DateTime\n createdBy: AcoUser\n modifiedBy: AcoUser\n savedBy: AcoUser\n \n ${fieldTypes.map(f => f.fields).join(\"\\n\")}\n }\n\n ${inputCreateFields.map(f => f.typeDefs).join(\"\\n\")}\n \n input FolderCreateInput {\n # Pass an ID if you want to create a folder with a specific ID.\n id: ID \n \n ${inputCreateFields.map(f => f.fields).join(\"\\n\")}\n }\n \n input FolderUpdateInput {\n ${inputUpdateFields.map(f => f.fields).join(\"\\n\")}\n }\n \n input FoldersListWhereInput {\n type: String!\n parentId: ID\n parentId_in: [ID]\n slug: String\n slug_not: String\n slug_contains: String\n slug_not_contains: String\n slug_in: [String]\n slug_not_in: [String]\n slug_startsWith: String\n slug_not_startsWith: String\n path: String\n path_not: String\n path_contains: String\n path_not_contains: String\n path_in: [String]\n path_not_in: [String]\n path_startsWith: String\n path_not_startsWith: String\n createdBy: ID\n }\n \n type FolderResponse {\n data: Folder\n error: AcoError\n }\n\n type FoldersListResponse {\n data: [Folder]\n error: AcoError\n meta: AcoMeta\n }\n\n type FoldersHierarchyData {\n parents: [Folder]\n siblings: [Folder]\n }\n\n type FoldersHierarchyResponse {\n data: FoldersHierarchyData\n error: AcoError\n }\n\n type FolderLevelPermissionsTarget {\n id: ID!\n type: String!\n target: ID!\n name: String!\n meta: JSON\n }\n\n type FolderLevelPermissionsTargetsListMeta {\n totalCount: Int!\n }\n\n type FolderLevelPermissionsTargetsListResponse {\n data: [FolderLevelPermissionsTarget]\n meta: FolderLevelPermissionsTargetsListMeta\n error: AcoError\n }\n \n type FolderModelResponse {\n data: JSON\n error: AcoError\n }\n\n extend type AcoQuery {\n getFolderModel: FolderModelResponse!\n getFolder(id: ID!): FolderResponse\n listFolders(\n where: FoldersListWhereInput!\n limit: Int\n after: String\n sort: AcoSort\n ): FoldersListResponse\n getFolderHierarchy(type: String!, id: ID!): FoldersHierarchyResponse\n listFolderLevelPermissionsTargets: FolderLevelPermissionsTargetsListResponse\n }\n\n extend type AcoMutation {\n createFolder(data: FolderCreateInput!): FolderResponse\n updateFolder(id: ID!, data: FolderUpdateInput!): FolderResponse\n deleteFolder(id: ID!): AcoBooleanResponse\n }\n \n # The base type is dynamically generated from a Folder model\n \n extend type AcoFolder_Permissions {\n inheritedFrom: ID\n }\n
|
|
1
|
+
{"version":3,"file":"folder/createFolderTypeDefs.js","sources":["../../src/folder/createFolderTypeDefs.ts"],"sourcesContent":["import type { CmsModel, CmsModelField } from \"@webiny/api-headless-cms/types/index.js\";\nimport { renderFields } from \"@webiny/api-headless-cms/utils/renderFields.js\";\nimport { renderInputFields } from \"@webiny/api-headless-cms/utils/renderInputFields.js\";\nimport type { CmsModelFieldToGraphQLRegistry } from \"@webiny/api-headless-cms/exports/api/cms/graphql.js\";\n\nexport interface CreateFolderTypeDefsParams {\n model: CmsModel;\n models: CmsModel[];\n fieldRegistry: CmsModelFieldToGraphQLRegistry.Interface;\n}\n\nconst removeFieldRequiredValidation = (field: CmsModelField) => {\n if (field.validation) {\n field.validation = field.validation.filter(validation => validation.name !== \"required\");\n }\n if (field.listValidation) {\n field.listValidation = field.listValidation.filter(v => v.name !== \"required\");\n }\n return field;\n};\n\nconst createUpdateFields = (fields: CmsModelField[]): CmsModelField[] => {\n return fields.reduce<CmsModelField[]>((collection, field) => {\n collection.push(removeFieldRequiredValidation({ ...field }));\n return collection;\n }, []);\n};\n\nexport const createFolderTypeDefs = (params: CreateFolderTypeDefsParams): string => {\n const { model, models, fieldRegistry } = params;\n const { fields } = model;\n\n const fieldTypes = renderFields({\n models,\n model,\n fields,\n type: \"manage\",\n fieldRegistry\n });\n const inputCreateFields = renderInputFields({\n models,\n model,\n fields,\n fieldRegistry\n });\n const inputUpdateFields = renderInputFields({\n models,\n model,\n fields: createUpdateFields(fields),\n fieldRegistry\n });\n\n return /* GraphQL */ `\n ${fieldTypes.map(f => f.typeDefs).join(\"\\n\")}\n \n type Folder {\n id: ID!\n # Tells us if the current user can manage folder structure.\n canManageStructure: Boolean\n\n # Tells us if the current user can manage folder permissions.\n canManagePermissions: Boolean\n\n # Tells us if the current user can manage folder content.\n canManageContent: Boolean\n\n # Tells us if the folder contains non-inherited permissions.\n hasNonInheritedPermissions: Boolean\n\n createdOn: DateTime\n modifiedOn: DateTime\n savedOn: DateTime\n createdBy: AcoUser\n modifiedBy: AcoUser\n savedBy: AcoUser\n \n ${fieldTypes.map(f => f.fields).join(\"\\n\")}\n }\n\n ${inputCreateFields.map(f => f.typeDefs).join(\"\\n\")}\n \n input FolderCreateInput {\n # Pass an ID if you want to create a folder with a specific ID.\n id: ID \n \n ${inputCreateFields.map(f => f.fields).join(\"\\n\")}\n }\n \n input FolderUpdateInput {\n ${inputUpdateFields.map(f => f.fields).join(\"\\n\")}\n }\n \n input FoldersListWhereInput {\n type: String!\n parentId: ID\n parentId_in: [ID]\n slug: String\n slug_not: String\n slug_contains: String\n slug_not_contains: String\n slug_in: [String]\n slug_not_in: [String]\n slug_startsWith: String\n slug_not_startsWith: String\n path: String\n path_not: String\n path_contains: String\n path_not_contains: String\n path_in: [String]\n path_not_in: [String]\n path_startsWith: String\n path_not_startsWith: String\n createdBy: ID\n }\n \n type FolderResponse {\n data: Folder\n error: AcoError\n }\n\n type FoldersListResponse {\n data: [Folder]\n error: AcoError\n meta: AcoMeta\n }\n\n type FoldersHierarchyData {\n parents: [Folder]\n siblings: [Folder]\n }\n\n type FoldersHierarchyResponse {\n data: FoldersHierarchyData\n error: AcoError\n }\n\n type FolderLevelPermissionsTarget {\n id: ID!\n type: String!\n target: ID!\n name: String!\n meta: JSON\n }\n\n type FolderLevelPermissionsTargetsListMeta {\n totalCount: Int!\n }\n\n type FolderLevelPermissionsTargetsListResponse {\n data: [FolderLevelPermissionsTarget]\n meta: FolderLevelPermissionsTargetsListMeta\n error: AcoError\n }\n \n type FolderModelResponse {\n data: JSON\n error: AcoError\n }\n\n extend type AcoQuery {\n getFolderModel: FolderModelResponse!\n getFolder(id: ID!): FolderResponse\n listFolders(\n where: FoldersListWhereInput!\n limit: Int\n after: String\n sort: AcoSort\n ): FoldersListResponse\n getFolderHierarchy(type: String!, id: ID!): FoldersHierarchyResponse\n listFolderLevelPermissionsTargets: FolderLevelPermissionsTargetsListResponse\n }\n\n extend type AcoMutation {\n createFolder(data: FolderCreateInput!): FolderResponse\n updateFolder(id: ID!, data: FolderUpdateInput!): FolderResponse\n deleteFolder(id: ID!): AcoBooleanResponse\n }\n \n # The base type is dynamically generated from a Folder model\n \n extend type AcoFolder_Permissions {\n inheritedFrom: ID\n # Set on permissions contributed from code, via an \"FlpFactory\". Read-only by design:\n # deliberately absent from the input type below, so it cannot be assigned via the API.\n plugin: Boolean\n }\n\n extend input AcoFolder_PermissionsInput {\n inheritedFrom: ID\n }\n `;\n};\n"],"names":["removeFieldRequiredValidation","field","validation","v","createUpdateFields","fields","collection","createFolderTypeDefs","params","model","models","fieldRegistry","fieldTypes","renderFields","inputCreateFields","renderInputFields","inputUpdateFields","f"],"mappings":";;AAWA,MAAMA,gCAAgC,CAACC;IACnC,IAAIA,MAAM,UAAU,EAChBA,MAAM,UAAU,GAAGA,MAAM,UAAU,CAAC,MAAM,CAACC,CAAAA,aAAcA,AAAoB,eAApBA,WAAW,IAAI;IAE5E,IAAID,MAAM,cAAc,EACpBA,MAAM,cAAc,GAAGA,MAAM,cAAc,CAAC,MAAM,CAACE,CAAAA,IAAKA,AAAW,eAAXA,EAAE,IAAI;IAElE,OAAOF;AACX;AAEA,MAAMG,qBAAqB,CAACC,SACjBA,OAAO,MAAM,CAAkB,CAACC,YAAYL;QAC/CK,WAAW,IAAI,CAACN,8BAA8B;YAAE,GAAGC,KAAK;QAAC;QACzD,OAAOK;IACX,GAAG,EAAE;AAGF,MAAMC,uBAAuB,CAACC;IACjC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,aAAa,EAAE,GAAGH;IACzC,MAAM,EAAEH,MAAM,EAAE,GAAGI;IAEnB,MAAMG,aAAaC,aAAa;QAC5BH;QACAD;QACAJ;QACA,MAAM;QACNM;IACJ;IACA,MAAMG,oBAAoBC,kBAAkB;QACxCL;QACAD;QACAJ;QACAM;IACJ;IACA,MAAMK,oBAAoBD,kBAAkB;QACxCL;QACAD;QACA,QAAQL,mBAAmBC;QAC3BM;IACJ;IAEA,OAAqB,CAAC;QAClB,EAAEC,WAAW,GAAG,CAACK,CAAAA,IAAKA,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;YAuBzC,EAAEL,WAAW,GAAG,CAACK,CAAAA,IAAKA,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;;;QAG/C,EAAEH,kBAAkB,GAAG,CAACG,CAAAA,IAAKA,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM;;;;;;aAM/C,EAAEH,kBAAkB,GAAG,CAACG,CAAAA,IAAKA,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;;;;YAInD,EAAED,kBAAkB,GAAG,CAACC,CAAAA,IAAKA,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqG1D,CAAC;AACL"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/api-aco",
|
|
3
|
-
"version": "6.6.0-alpha.
|
|
3
|
+
"version": "6.6.0-alpha.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aco:base"
|
|
@@ -21,29 +21,29 @@
|
|
|
21
21
|
".": "./index.js"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webiny/api-core": "6.6.0-alpha.
|
|
25
|
-
"@webiny/api-graphql": "6.6.0-alpha.
|
|
26
|
-
"@webiny/api-headless-cms": "6.6.0-alpha.
|
|
27
|
-
"@webiny/background-tasks": "6.6.0-alpha.
|
|
24
|
+
"@webiny/api-core": "6.6.0-alpha.6",
|
|
25
|
+
"@webiny/api-graphql": "6.6.0-alpha.6",
|
|
26
|
+
"@webiny/api-headless-cms": "6.6.0-alpha.6",
|
|
27
|
+
"@webiny/background-tasks": "6.6.0-alpha.6",
|
|
28
28
|
"@webiny/di": "1.1.0",
|
|
29
|
-
"@webiny/error": "6.6.0-alpha.
|
|
30
|
-
"@webiny/event-handler-core": "6.6.0-alpha.
|
|
31
|
-
"@webiny/feature": "6.6.0-alpha.
|
|
32
|
-
"@webiny/handler": "6.6.0-alpha.
|
|
33
|
-
"@webiny/shared-aco": "6.6.0-alpha.
|
|
34
|
-
"@webiny/utils": "6.6.0-alpha.
|
|
35
|
-
"@webiny/validation": "6.6.0-alpha.
|
|
29
|
+
"@webiny/error": "6.6.0-alpha.6",
|
|
30
|
+
"@webiny/event-handler-core": "6.6.0-alpha.6",
|
|
31
|
+
"@webiny/feature": "6.6.0-alpha.6",
|
|
32
|
+
"@webiny/handler": "6.6.0-alpha.6",
|
|
33
|
+
"@webiny/shared-aco": "6.6.0-alpha.6",
|
|
34
|
+
"@webiny/utils": "6.6.0-alpha.6",
|
|
35
|
+
"@webiny/validation": "6.6.0-alpha.6",
|
|
36
36
|
"lodash": "4.18.1",
|
|
37
37
|
"zod": "4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@webiny/api": "6.6.0-alpha.
|
|
41
|
-
"@webiny/api-core-testing": "6.6.0-alpha.
|
|
42
|
-
"@webiny/api-file-manager": "6.6.0-alpha.
|
|
43
|
-
"@webiny/api-headless-cms-testing": "6.6.0-alpha.
|
|
44
|
-
"@webiny/build-tools": "6.6.0-alpha.
|
|
45
|
-
"@webiny/plugins": "6.6.0-alpha.
|
|
46
|
-
"@webiny/wcp": "6.6.0-alpha.
|
|
40
|
+
"@webiny/api": "6.6.0-alpha.6",
|
|
41
|
+
"@webiny/api-core-testing": "6.6.0-alpha.6",
|
|
42
|
+
"@webiny/api-file-manager": "6.6.0-alpha.6",
|
|
43
|
+
"@webiny/api-headless-cms-testing": "6.6.0-alpha.6",
|
|
44
|
+
"@webiny/build-tools": "6.6.0-alpha.6",
|
|
45
|
+
"@webiny/plugins": "6.6.0-alpha.6",
|
|
46
|
+
"@webiny/wcp": "6.6.0-alpha.6",
|
|
47
47
|
"graphql": "16.14.2",
|
|
48
48
|
"rimraf": "6.1.3",
|
|
49
49
|
"typescript": "7.0.2",
|