@orthacms/content-server 0.4.2 → 0.5.0
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/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -1
- package/dist/lib/content.module.d.ts.map +1 -1
- package/dist/lib/content.module.js +31 -1
- package/dist/lib/copilot/entry-proposal.applier.d.ts.map +1 -1
- package/dist/lib/copilot/entry-proposal.applier.js +5 -4
- package/dist/lib/docs/describe-content-api.d.ts +7 -0
- package/dist/lib/docs/describe-content-api.d.ts.map +1 -1
- package/dist/lib/docs/describe-content-api.js +177 -53
- package/dist/lib/docs/describe-views-api.d.ts +19 -0
- package/dist/lib/docs/describe-views-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-views-api.js +76 -0
- package/dist/lib/docs/field-schema.d.ts.map +1 -1
- package/dist/lib/docs/field-schema.js +36 -2
- package/dist/lib/docs/openapi-writer.d.ts +49 -0
- package/dist/lib/docs/openapi-writer.d.ts.map +1 -0
- package/dist/lib/docs/openapi-writer.js +76 -0
- package/dist/lib/docs/public-api-schemas.d.ts +45 -0
- package/dist/lib/docs/public-api-schemas.d.ts.map +1 -0
- package/dist/lib/docs/public-api-schemas.js +377 -0
- package/dist/lib/docs/views-schemas.d.ts +22 -0
- package/dist/lib/docs/views-schemas.d.ts.map +1 -0
- package/dist/lib/docs/views-schemas.js +156 -0
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts +8 -2
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.js +19 -4
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts +2 -2
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.js +14 -4
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts +6 -8
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.js +10 -4
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts +2 -5
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.js +6 -1
- package/dist/lib/entries/domain/entry.d.ts +8 -1
- package/dist/lib/entries/domain/entry.d.ts.map +1 -1
- package/dist/lib/entries/domain/entry.js +12 -12
- package/dist/lib/entries/domain/events/entry-events.d.ts +47 -5
- package/dist/lib/entries/domain/events/entry-events.d.ts.map +1 -1
- package/dist/lib/entries/domain/events/entry-events.js +26 -10
- package/dist/lib/entries/entries.constants.d.ts +10 -2
- package/dist/lib/entries/entries.constants.d.ts.map +1 -1
- package/dist/lib/entries/entries.constants.js +11 -2
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts +2 -2
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts.map +1 -1
- package/dist/lib/entries/http/controllers/bulk-entries.controller.js +8 -6
- package/dist/lib/entries/http/controllers/create-entry.controller.d.ts.map +1 -1
- package/dist/lib/entries/http/controllers/create-entry.controller.js +1 -1
- package/dist/lib/entries/http/controllers/update-entry.controller.d.ts.map +1 -1
- package/dist/lib/entries/http/controllers/update-entry.controller.js +1 -1
- package/dist/lib/entries/http/dto/extension-bag.validator.d.ts +14 -0
- package/dist/lib/entries/http/dto/extension-bag.validator.d.ts.map +1 -0
- package/dist/lib/entries/http/dto/extension-bag.validator.js +42 -0
- package/dist/lib/entries/http/dto/save-entry.dto.d.ts +16 -0
- package/dist/lib/entries/http/dto/save-entry.dto.d.ts.map +1 -1
- package/dist/lib/entries/http/dto/save-entry.dto.js +30 -0
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +50 -8
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -1
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +183 -32
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts +38 -5
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts.map +1 -1
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.js +38 -6
- package/dist/lib/entries/infrastructure/queries/entries.service.d.ts +3 -1
- package/dist/lib/entries/infrastructure/queries/entries.service.d.ts.map +1 -1
- package/dist/lib/entries/infrastructure/queries/entries.service.js +17 -5
- package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts +82 -0
- package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entry-match.query.js +152 -0
- package/dist/lib/extension/entry-filter-provider.d.ts +79 -0
- package/dist/lib/extension/entry-filter-provider.d.ts.map +1 -0
- package/dist/lib/extension/entry-filter-provider.js +149 -0
- package/dist/lib/extension/entry-write-extension.d.ts +185 -0
- package/dist/lib/extension/entry-write-extension.d.ts.map +1 -0
- package/dist/lib/extension/entry-write-extension.js +182 -0
- package/dist/lib/extension/read-scope.d.ts +107 -0
- package/dist/lib/extension/read-scope.d.ts.map +1 -0
- package/dist/lib/extension/read-scope.js +131 -0
- package/dist/lib/insights/docs/describe-content-insights-api.d.ts +23 -0
- package/dist/lib/insights/docs/describe-content-insights-api.d.ts.map +1 -0
- package/dist/lib/insights/docs/describe-content-insights-api.js +329 -0
- package/dist/lib/mcp/content-tools.provider.d.ts.map +1 -1
- package/dist/lib/mcp/content-tools.provider.js +10 -9
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts +14 -13
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts.map +1 -1
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.js +62 -46
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts.map +1 -1
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.js +8 -0
- package/dist/lib/public-api/infrastructure/public-entries.query.d.ts +14 -1
- package/dist/lib/public-api/infrastructure/public-entries.query.d.ts.map +1 -1
- package/dist/lib/public-api/infrastructure/public-entries.query.js +20 -3
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts +10 -9
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts.map +1 -1
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.js +23 -24
- package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts +16 -4
- package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts.map +1 -1
- package/dist/lib/public-api/infrastructure/public-expansion.query.js +29 -9
- package/dist/lib/public-api/infrastructure/token-actor.d.ts +37 -0
- package/dist/lib/public-api/infrastructure/token-actor.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/token-actor.js +58 -0
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -1
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +23 -1
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts +7 -1
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts.map +1 -1
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.js +9 -2
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts +28 -0
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.js +52 -0
- package/dist/lib/revisions/types/revision-view.d.ts +11 -0
- package/dist/lib/revisions/types/revision-view.d.ts.map +1 -1
- package/dist/lib/utils/content-plugin.d.ts.map +1 -1
- package/dist/lib/utils/content-plugin.js +10 -1
- package/dist/lib/utils/content-views-plugin.d.ts +28 -0
- package/dist/lib/utils/content-views-plugin.d.ts.map +1 -0
- package/dist/lib/utils/content-views-plugin.js +37 -0
- package/dist/lib/views/application/queries/saved-views.query.d.ts +16 -0
- package/dist/lib/views/application/queries/saved-views.query.d.ts.map +1 -0
- package/dist/lib/views/application/queries/saved-views.query.js +46 -0
- package/dist/lib/views/application/saved-view-access.service.d.ts +30 -0
- package/dist/lib/views/application/saved-view-access.service.d.ts.map +1 -0
- package/dist/lib/views/application/saved-view-access.service.js +55 -0
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts +30 -0
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts.map +1 -0
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.js +81 -0
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts +18 -0
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts.map +1 -0
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.js +56 -0
- package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts +23 -0
- package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts.map +1 -0
- package/dist/lib/views/application/use-cases/set-default-view.use-case.js +51 -0
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts +24 -0
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts.map +1 -0
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.js +90 -0
- package/dist/lib/views/application/view-scope.service.d.ts +25 -0
- package/dist/lib/views/application/view-scope.service.d.ts.map +1 -0
- package/dist/lib/views/application/view-scope.service.js +45 -0
- package/dist/lib/views/content-views.module.d.ts +22 -0
- package/dist/lib/views/content-views.module.d.ts.map +1 -0
- package/dist/lib/views/content-views.module.js +63 -0
- package/dist/lib/views/domain/errors/index.d.ts +6 -0
- package/dist/lib/views/domain/errors/index.d.ts.map +1 -0
- package/dist/lib/views/domain/errors/index.js +12 -0
- package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts +10 -0
- package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts.map +1 -0
- package/dist/lib/views/domain/errors/saved-view-forbidden.error.js +16 -0
- package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts +9 -0
- package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts.map +1 -0
- package/dist/lib/views/domain/errors/saved-view-limit-error.js +16 -0
- package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts +5 -0
- package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts.map +1 -0
- package/dist/lib/views/domain/errors/saved-view-name-taken.error.js +11 -0
- package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts +9 -0
- package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts.map +1 -0
- package/dist/lib/views/domain/errors/saved-view-not-found.error.js +15 -0
- package/dist/lib/views/domain/events/saved-view-events.d.ts +28 -0
- package/dist/lib/views/domain/events/saved-view-events.d.ts.map +1 -0
- package/dist/lib/views/domain/events/saved-view-events.js +38 -0
- package/dist/lib/views/domain/saved-view.d.ts +66 -0
- package/dist/lib/views/domain/saved-view.d.ts.map +1 -0
- package/dist/lib/views/domain/saved-view.js +18 -0
- package/dist/lib/views/domain/saved-view.repository.d.ts +77 -0
- package/dist/lib/views/domain/saved-view.repository.d.ts.map +1 -0
- package/dist/lib/views/domain/saved-view.repository.js +5 -0
- package/dist/lib/views/http/controllers/saved-views.controller.d.ts +45 -0
- package/dist/lib/views/http/controllers/saved-views.controller.d.ts.map +1 -0
- package/dist/lib/views/http/controllers/saved-views.controller.js +216 -0
- package/dist/lib/views/http/controllers/to-http-error.d.ts +10 -0
- package/dist/lib/views/http/controllers/to-http-error.d.ts.map +1 -0
- package/dist/lib/views/http/controllers/to-http-error.js +26 -0
- package/dist/lib/views/http/dto/save-view.dto.d.ts +54 -0
- package/dist/lib/views/http/dto/save-view.dto.d.ts.map +1 -0
- package/dist/lib/views/http/dto/save-view.dto.js +230 -0
- package/dist/lib/views/http/dto/view-payload.validator.d.ts +20 -0
- package/dist/lib/views/http/dto/view-payload.validator.d.ts.map +1 -0
- package/dist/lib/views/http/dto/view-payload.validator.js +52 -0
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts +26 -0
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts.map +1 -0
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.js +135 -0
- package/dist/lib/views/infrastructure/schema/external-refs.d.ts +68 -0
- package/dist/lib/views/infrastructure/schema/external-refs.d.ts.map +1 -0
- package/dist/lib/views/infrastructure/schema/external-refs.js +29 -0
- package/dist/lib/views/infrastructure/schema/index.d.ts +8 -0
- package/dist/lib/views/infrastructure/schema/index.d.ts.map +1 -0
- package/dist/lib/views/infrastructure/schema/index.js +13 -0
- package/dist/lib/views/infrastructure/schema/saved-views.d.ts +275 -0
- package/dist/lib/views/infrastructure/schema/saved-views.d.ts.map +1 -0
- package/dist/lib/views/infrastructure/schema/saved-views.js +87 -0
- package/dist/lib/views/views.constants.d.ts +26 -0
- package/dist/lib/views/views.constants.d.ts.map +1 -0
- package/dist/lib/views/views.constants.js +28 -0
- package/dist/lib/views/views.tokens.d.ts +17 -0
- package/dist/lib/views/views.tokens.d.ts.map +1 -0
- package/dist/lib/views/views.tokens.js +21 -0
- package/migrations/0000_saved_views.sql +29 -0
- package/migrations/meta/0000_snapshot.json +256 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +13 -12
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts +0 -26
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/create-entry.controller.d.ts +0 -20
- package/dist/lib/entries/controllers/create-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts +0 -24
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/get-entry.controller.d.ts +0 -34
- package/dist/lib/entries/controllers/get-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/list-entries.controller.d.ts +0 -20
- package/dist/lib/entries/controllers/list-entries.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts +0 -19
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/resolve-type.d.ts +0 -9
- package/dist/lib/entries/controllers/resolve-type.d.ts.map +0 -1
- package/dist/lib/entries/controllers/update-entry.controller.d.ts +0 -18
- package/dist/lib/entries/controllers/update-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts +0 -11
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts.map +0 -1
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts +0 -46
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts.map +0 -1
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts +0 -19
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts.map +0 -1
- package/dist/lib/entries/dto/save-entry.dto.d.ts +0 -46
- package/dist/lib/entries/dto/save-entry.dto.d.ts.map +0 -1
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts +0 -24
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts.map +0 -1
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts +0 -40
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts.map +0 -1
- package/dist/lib/entries/services/entries.service.d.ts +0 -59
- package/dist/lib/entries/services/entries.service.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-counter.service.d.ts +0 -31
- package/dist/lib/entries/services/entry-counter.service.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-filter-schema.d.ts +0 -32
- package/dist/lib/entries/services/entry-filter-schema.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-row.d.ts +0 -56
- package/dist/lib/entries/services/entry-row.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-writer.service.d.ts +0 -193
- package/dist/lib/entries/services/entry-writer.service.d.ts.map +0 -1
- package/dist/lib/entries/services/relation-link.service.d.ts +0 -115
- package/dist/lib/entries/services/relation-link.service.d.ts.map +0 -1
- package/dist/lib/extension/per-locale-relation.d.ts +0 -18
- package/dist/lib/extension/per-locale-relation.d.ts.map +0 -1
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts +0 -26
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts.map +0 -1
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts +0 -24
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts.map +0 -1
- package/dist/lib/public/controllers/public-schema.controller.d.ts +0 -18
- package/dist/lib/public/controllers/public-schema.controller.d.ts.map +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AccessPolicy, PermissionsService, type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { type ViewVisibility } from '../domain/saved-view';
|
|
3
|
+
import type { SavedViewRecord } from '../domain/saved-view.repository';
|
|
4
|
+
/**
|
|
5
|
+
* The two authorization rules saved views add on top of the route guards.
|
|
6
|
+
*
|
|
7
|
+
* The guards already establish that the caller is authenticated, a member of
|
|
8
|
+
* the open workspace, and may read content. What is left is per-row and
|
|
9
|
+
* per-visibility, so it cannot live in a decorator:
|
|
10
|
+
*
|
|
11
|
+
* - **Only the owner writes.** Editing or deleting someone else's view is a
|
|
12
|
+
* 403 even for an administrator — a shared view is its author's, and the
|
|
13
|
+
* remedy for disagreeing with one is "Save as new", not a silent rewrite.
|
|
14
|
+
* - **Sharing is a permission.** Making a view workspace-visible needs
|
|
15
|
+
* `views:share`; a member without it still saves as many private views as
|
|
16
|
+
* they like.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SavedViewAccessService {
|
|
19
|
+
private readonly permissions;
|
|
20
|
+
private readonly accessPolicy;
|
|
21
|
+
constructor(permissions: PermissionsService, accessPolicy: AccessPolicy);
|
|
22
|
+
/** Throws unless `user` owns `view`. */
|
|
23
|
+
assertOwned(view: SavedViewRecord, user: PublicUser): void;
|
|
24
|
+
/**
|
|
25
|
+
* Throws unless `user` may store a view at `visibility`. Private views need
|
|
26
|
+
* nothing beyond membership; workspace-visible ones need `views:share`.
|
|
27
|
+
*/
|
|
28
|
+
assertCanUseVisibility(visibility: ViewVisibility, user: PublicUser): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=saved-view-access.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saved-view-access.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/views/application/saved-view-access.service.ts"],"names":[],"mappings":"AACA,OAAO,EACH,YAAY,EAGZ,kBAAkB,EAClB,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAGvE;;;;;;;;;;;;;GAaG;AACH,qBACa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBADZ,WAAW,EAAE,kBAAkB,EAC/B,YAAY,EAAE,YAAY;IAG/C,wCAAwC;IACxC,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI;IAQ1D;;;OAGG;IACG,sBAAsB,CACxB,UAAU,EAAE,cAAc,EAC1B,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,IAAI,CAAC;CAanB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SavedViewAccessService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
7
|
+
const saved_view_1 = require("../domain/saved-view");
|
|
8
|
+
const errors_1 = require("../domain/errors");
|
|
9
|
+
/**
|
|
10
|
+
* The two authorization rules saved views add on top of the route guards.
|
|
11
|
+
*
|
|
12
|
+
* The guards already establish that the caller is authenticated, a member of
|
|
13
|
+
* the open workspace, and may read content. What is left is per-row and
|
|
14
|
+
* per-visibility, so it cannot live in a decorator:
|
|
15
|
+
*
|
|
16
|
+
* - **Only the owner writes.** Editing or deleting someone else's view is a
|
|
17
|
+
* 403 even for an administrator — a shared view is its author's, and the
|
|
18
|
+
* remedy for disagreeing with one is "Save as new", not a silent rewrite.
|
|
19
|
+
* - **Sharing is a permission.** Making a view workspace-visible needs
|
|
20
|
+
* `views:share`; a member without it still saves as many private views as
|
|
21
|
+
* they like.
|
|
22
|
+
*/
|
|
23
|
+
let SavedViewAccessService = class SavedViewAccessService {
|
|
24
|
+
permissions;
|
|
25
|
+
accessPolicy;
|
|
26
|
+
constructor(permissions, accessPolicy) {
|
|
27
|
+
this.permissions = permissions;
|
|
28
|
+
this.accessPolicy = accessPolicy;
|
|
29
|
+
}
|
|
30
|
+
/** Throws unless `user` owns `view`. */
|
|
31
|
+
assertOwned(view, user) {
|
|
32
|
+
if (view.ownerId !== user.id) {
|
|
33
|
+
throw new errors_1.SavedViewForbiddenError('Only the person who saved a view can change it. Use “Save as new” instead.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Throws unless `user` may store a view at `visibility`. Private views need
|
|
38
|
+
* nothing beyond membership; workspace-visible ones need `views:share`.
|
|
39
|
+
*/
|
|
40
|
+
async assertCanUseVisibility(visibility, user) {
|
|
41
|
+
if (visibility !== saved_view_1.VIEW_VISIBILITY.Workspace)
|
|
42
|
+
return;
|
|
43
|
+
const granted = await this.permissions.forRole(user.roleId);
|
|
44
|
+
const allowed = this.accessPolicy.can({ userId: user.id, grantedPermissions: new Set(granted) }, identity_server_1.Permission.create(identity_server_1.PERMISSIONS.VIEWS_SHARE));
|
|
45
|
+
if (!allowed) {
|
|
46
|
+
throw new errors_1.SavedViewForbiddenError('Sharing a view with the workspace requires the “views:share” permission.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.SavedViewAccessService = SavedViewAccessService;
|
|
51
|
+
exports.SavedViewAccessService = SavedViewAccessService = tslib_1.__decorate([
|
|
52
|
+
(0, common_1.Injectable)(),
|
|
53
|
+
tslib_1.__metadata("design:paramtypes", [identity_server_1.PermissionsService,
|
|
54
|
+
identity_server_1.AccessPolicy])
|
|
55
|
+
], SavedViewAccessService);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { OutboxWriter, UnitOfWork } from '@orthacms/database';
|
|
3
|
+
import type { SavedView, SavedViewPayload, ViewVisibility } from '../../domain/saved-view';
|
|
4
|
+
import { type SavedViewRepository } from '../../domain/saved-view.repository';
|
|
5
|
+
import { SavedViewAccessService } from '../saved-view-access.service';
|
|
6
|
+
/** Input for {@link CreateSavedViewUseCase}. */
|
|
7
|
+
export interface CreateSavedViewInput {
|
|
8
|
+
/** The workspace the view belongs to. */
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
/** The list it is over (`content:<typeName>`). */
|
|
11
|
+
scope: string;
|
|
12
|
+
/** Display name, already trimmed by the DTO. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Who may see it. */
|
|
15
|
+
visibility: ViewVisibility;
|
|
16
|
+
/** The slice it restores. */
|
|
17
|
+
payload: SavedViewPayload;
|
|
18
|
+
/** Whether to make it the caller's default for this scope. */
|
|
19
|
+
makeDefault?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Saves a new view for the calling user. */
|
|
22
|
+
export declare class CreateSavedViewUseCase {
|
|
23
|
+
private readonly repository;
|
|
24
|
+
private readonly access;
|
|
25
|
+
private readonly uow;
|
|
26
|
+
private readonly outbox;
|
|
27
|
+
constructor(repository: SavedViewRepository, access: SavedViewAccessService, uow: UnitOfWork, outbox: OutboxWriter);
|
|
28
|
+
execute(input: CreateSavedViewInput, user: PublicUser): Promise<SavedView>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=create-saved-view.use-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-saved-view.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/use-cases/create-saved-view.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAEH,YAAY,EACZ,UAAU,EACb,MAAM,oBAAoB,CAAC;AAM5B,OAAO,KAAK,EACR,SAAS,EACT,gBAAgB,EAChB,cAAc,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAM5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACjC,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,UAAU,EAAE,cAAc,CAAC;IAC3B,6BAA6B;IAC7B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,6CAA6C;AAC7C,qBACa,sBAAsB;IAG3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAHN,UAAU,EAAE,mBAAmB,EAC/B,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,YAAY;IAGnC,OAAO,CACT,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,CAAC;CAiExB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CreateSavedViewUseCase = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const database_1 = require("@orthacms/database");
|
|
7
|
+
const saved_view_events_1 = require("../../domain/events/saved-view-events");
|
|
8
|
+
const utils_server_1 = require("@orthacms/utils-server");
|
|
9
|
+
const saved_view_repository_1 = require("../../domain/saved-view.repository");
|
|
10
|
+
const errors_1 = require("../../domain/errors");
|
|
11
|
+
const views_constants_1 = require("../../views.constants");
|
|
12
|
+
const saved_view_access_service_1 = require("../saved-view-access.service");
|
|
13
|
+
const saved_views_query_1 = require("../queries/saved-views.query");
|
|
14
|
+
/** Saves a new view for the calling user. */
|
|
15
|
+
let CreateSavedViewUseCase = class CreateSavedViewUseCase {
|
|
16
|
+
repository;
|
|
17
|
+
access;
|
|
18
|
+
uow;
|
|
19
|
+
outbox;
|
|
20
|
+
constructor(repository, access, uow, outbox) {
|
|
21
|
+
this.repository = repository;
|
|
22
|
+
this.access = access;
|
|
23
|
+
this.uow = uow;
|
|
24
|
+
this.outbox = outbox;
|
|
25
|
+
}
|
|
26
|
+
async execute(input, user) {
|
|
27
|
+
await this.access.assertCanUseVisibility(input.visibility, user);
|
|
28
|
+
const held = await this.repository.countForOwner(input.workspaceId, input.scope, user.id);
|
|
29
|
+
if (held >= views_constants_1.VIEW_MAX_PER_SCOPE)
|
|
30
|
+
throw new errors_1.SavedViewLimitError();
|
|
31
|
+
// The unique index is the arbiter, not the count above: two concurrent
|
|
32
|
+
// saves of the same name both pass a read-then-write check and one of
|
|
33
|
+
// them has to lose at the constraint. Translate that loss into the same
|
|
34
|
+
// 409 a sequential duplicate gets.
|
|
35
|
+
let created;
|
|
36
|
+
try {
|
|
37
|
+
created = await this.uow.run(async () => {
|
|
38
|
+
const row = await this.repository.create({
|
|
39
|
+
workspaceId: input.workspaceId,
|
|
40
|
+
scope: input.scope,
|
|
41
|
+
ownerId: user.id,
|
|
42
|
+
visibility: input.visibility,
|
|
43
|
+
name: input.name,
|
|
44
|
+
payload: input.payload
|
|
45
|
+
});
|
|
46
|
+
await this.outbox.append((0, database_1.attachActor)([
|
|
47
|
+
(0, saved_view_events_1.savedViewEvent)(saved_view_events_1.SAVED_VIEW_EVENT_KINDS.CREATED, row.id, {
|
|
48
|
+
workspaceId: input.workspaceId,
|
|
49
|
+
scope: input.scope,
|
|
50
|
+
name: input.name,
|
|
51
|
+
// The half that makes the row worth
|
|
52
|
+
// keeping: a `workspace` view is shared
|
|
53
|
+
// state everyone in the switcher sees.
|
|
54
|
+
visibility: input.visibility
|
|
55
|
+
})
|
|
56
|
+
], { id: user.id, email: user.email ?? null }));
|
|
57
|
+
return row;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if ((0, utils_server_1.isUniqueViolation)(error)) {
|
|
62
|
+
throw new errors_1.SavedViewNameTakenError(input.name);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
if (input.makeDefault) {
|
|
67
|
+
// Outside the run on purpose: a personal default raises no event
|
|
68
|
+
// (see `saved-view-events.ts`), so it has nothing to commit with.
|
|
69
|
+
await this.repository.setDefault(user.id, input.scope, created.id);
|
|
70
|
+
}
|
|
71
|
+
return (0, saved_views_query_1.toSavedView)(created, user.id, input.makeDefault ? created.id : null);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
exports.CreateSavedViewUseCase = CreateSavedViewUseCase;
|
|
75
|
+
exports.CreateSavedViewUseCase = CreateSavedViewUseCase = tslib_1.__decorate([
|
|
76
|
+
(0, common_1.Injectable)(),
|
|
77
|
+
tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
|
|
78
|
+
tslib_1.__metadata("design:paramtypes", [Object, saved_view_access_service_1.SavedViewAccessService,
|
|
79
|
+
database_1.UnitOfWork,
|
|
80
|
+
database_1.OutboxWriter])
|
|
81
|
+
], CreateSavedViewUseCase);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { OutboxWriter, UnitOfWork } from '@orthacms/database';
|
|
3
|
+
import { type SavedViewRepository } from '../../domain/saved-view.repository';
|
|
4
|
+
import { SavedViewAccessService } from '../saved-view-access.service';
|
|
5
|
+
/**
|
|
6
|
+
* Deletes a view the caller owns. Anyone else's default pointing at it is
|
|
7
|
+
* cascaded away by the FK, so they fall back to the unfiltered list rather than
|
|
8
|
+
* to a dangling id.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DeleteSavedViewUseCase {
|
|
11
|
+
private readonly repository;
|
|
12
|
+
private readonly access;
|
|
13
|
+
private readonly uow;
|
|
14
|
+
private readonly outbox;
|
|
15
|
+
constructor(repository: SavedViewRepository, access: SavedViewAccessService, uow: UnitOfWork, outbox: OutboxWriter);
|
|
16
|
+
execute(id: string, workspaceId: string, user: PublicUser): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=delete-saved-view.use-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-saved-view.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/use-cases/delete-saved-view.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAEH,YAAY,EACZ,UAAU,EACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAEtE;;;;GAIG;AACH,qBACa,sBAAsB;IAG3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAHN,UAAU,EAAE,mBAAmB,EAC/B,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,YAAY;IAGnC,OAAO,CACT,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,IAAI,CAAC;CA0BnB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeleteSavedViewUseCase = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const database_1 = require("@orthacms/database");
|
|
7
|
+
const saved_view_events_1 = require("../../domain/events/saved-view-events");
|
|
8
|
+
const saved_view_repository_1 = require("../../domain/saved-view.repository");
|
|
9
|
+
const errors_1 = require("../../domain/errors");
|
|
10
|
+
const saved_view_access_service_1 = require("../saved-view-access.service");
|
|
11
|
+
/**
|
|
12
|
+
* Deletes a view the caller owns. Anyone else's default pointing at it is
|
|
13
|
+
* cascaded away by the FK, so they fall back to the unfiltered list rather than
|
|
14
|
+
* to a dangling id.
|
|
15
|
+
*/
|
|
16
|
+
let DeleteSavedViewUseCase = class DeleteSavedViewUseCase {
|
|
17
|
+
repository;
|
|
18
|
+
access;
|
|
19
|
+
uow;
|
|
20
|
+
outbox;
|
|
21
|
+
constructor(repository, access, uow, outbox) {
|
|
22
|
+
this.repository = repository;
|
|
23
|
+
this.access = access;
|
|
24
|
+
this.uow = uow;
|
|
25
|
+
this.outbox = outbox;
|
|
26
|
+
}
|
|
27
|
+
async execute(id, workspaceId, user) {
|
|
28
|
+
const existing = await this.repository.findById(id);
|
|
29
|
+
if (!existing || existing.workspaceId !== workspaceId) {
|
|
30
|
+
throw new errors_1.SavedViewNotFoundError(id);
|
|
31
|
+
}
|
|
32
|
+
this.access.assertOwned(existing, user);
|
|
33
|
+
await this.uow.run(async () => {
|
|
34
|
+
await this.repository.delete(id);
|
|
35
|
+
// The view's own details, because this is the last place they
|
|
36
|
+
// exist — and a shared view leaving everyone's switcher is a
|
|
37
|
+
// change to what a team can see, not just to one person's list.
|
|
38
|
+
await this.outbox.append((0, database_1.attachActor)([
|
|
39
|
+
(0, saved_view_events_1.savedViewEvent)(saved_view_events_1.SAVED_VIEW_EVENT_KINDS.DELETED, id, {
|
|
40
|
+
workspaceId,
|
|
41
|
+
scope: existing.scope,
|
|
42
|
+
name: existing.name,
|
|
43
|
+
visibility: existing.visibility
|
|
44
|
+
})
|
|
45
|
+
], { id: user.id, email: user.email ?? null }));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
exports.DeleteSavedViewUseCase = DeleteSavedViewUseCase;
|
|
50
|
+
exports.DeleteSavedViewUseCase = DeleteSavedViewUseCase = tslib_1.__decorate([
|
|
51
|
+
(0, common_1.Injectable)(),
|
|
52
|
+
tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
|
|
53
|
+
tslib_1.__metadata("design:paramtypes", [Object, saved_view_access_service_1.SavedViewAccessService,
|
|
54
|
+
database_1.UnitOfWork,
|
|
55
|
+
database_1.OutboxWriter])
|
|
56
|
+
], DeleteSavedViewUseCase);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { type SavedViewRepository } from '../../domain/saved-view.repository';
|
|
3
|
+
/**
|
|
4
|
+
* Points a user's default for one list at a view, or clears it.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately **not** owner-gated: a default is the reader's own landing
|
|
7
|
+
* choice, so anyone who can see a shared view may land on it. What is gated is
|
|
8
|
+
* visibility — the view has to be one this caller can actually read, which is
|
|
9
|
+
* what the ownership-or-shared check below establishes.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SetDefaultViewUseCase {
|
|
12
|
+
private readonly repository;
|
|
13
|
+
constructor(repository: SavedViewRepository);
|
|
14
|
+
execute(id: string, workspaceId: string, user: PublicUser): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Clears the caller's default for the scope `id` belongs to. Addressed by
|
|
17
|
+
* view id rather than by scope so the route sits beside its `PUT`
|
|
18
|
+
* counterpart instead of colliding with `DELETE /views/:id`. Idempotent:
|
|
19
|
+
* clearing when the default points elsewhere, or nowhere, still succeeds.
|
|
20
|
+
*/
|
|
21
|
+
executeClear(id: string, workspaceId: string, user: PublicUser): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=set-default-view.use-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-default-view.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/use-cases/set-default-view.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAG5C;;;;;;;GAOG;AACH,qBACa,qBAAqB;IAG1B,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,mBAAmB;IAG9C,OAAO,CACT,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,IAAI,CAAC;IAWhB;;;;;OAKG;IACG,YAAY,CACd,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,IAAI,CAAC;CAOnB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SetDefaultViewUseCase = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const saved_view_1 = require("../../domain/saved-view");
|
|
7
|
+
const saved_view_repository_1 = require("../../domain/saved-view.repository");
|
|
8
|
+
const errors_1 = require("../../domain/errors");
|
|
9
|
+
/**
|
|
10
|
+
* Points a user's default for one list at a view, or clears it.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately **not** owner-gated: a default is the reader's own landing
|
|
13
|
+
* choice, so anyone who can see a shared view may land on it. What is gated is
|
|
14
|
+
* visibility — the view has to be one this caller can actually read, which is
|
|
15
|
+
* what the ownership-or-shared check below establishes.
|
|
16
|
+
*/
|
|
17
|
+
let SetDefaultViewUseCase = class SetDefaultViewUseCase {
|
|
18
|
+
repository;
|
|
19
|
+
constructor(repository) {
|
|
20
|
+
this.repository = repository;
|
|
21
|
+
}
|
|
22
|
+
async execute(id, workspaceId, user) {
|
|
23
|
+
const existing = await this.repository.findById(id);
|
|
24
|
+
const readable = existing &&
|
|
25
|
+
existing.workspaceId === workspaceId &&
|
|
26
|
+
(existing.ownerId === user.id ||
|
|
27
|
+
existing.visibility === saved_view_1.VIEW_VISIBILITY.Workspace);
|
|
28
|
+
if (!existing || !readable)
|
|
29
|
+
throw new errors_1.SavedViewNotFoundError(id);
|
|
30
|
+
await this.repository.setDefault(user.id, existing.scope, id);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Clears the caller's default for the scope `id` belongs to. Addressed by
|
|
34
|
+
* view id rather than by scope so the route sits beside its `PUT`
|
|
35
|
+
* counterpart instead of colliding with `DELETE /views/:id`. Idempotent:
|
|
36
|
+
* clearing when the default points elsewhere, or nowhere, still succeeds.
|
|
37
|
+
*/
|
|
38
|
+
async executeClear(id, workspaceId, user) {
|
|
39
|
+
const existing = await this.repository.findById(id);
|
|
40
|
+
if (!existing || existing.workspaceId !== workspaceId) {
|
|
41
|
+
throw new errors_1.SavedViewNotFoundError(id);
|
|
42
|
+
}
|
|
43
|
+
await this.repository.clearDefault(user.id, existing.scope);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.SetDefaultViewUseCase = SetDefaultViewUseCase;
|
|
47
|
+
exports.SetDefaultViewUseCase = SetDefaultViewUseCase = tslib_1.__decorate([
|
|
48
|
+
(0, common_1.Injectable)(),
|
|
49
|
+
tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
|
|
50
|
+
tslib_1.__metadata("design:paramtypes", [Object])
|
|
51
|
+
], SetDefaultViewUseCase);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { OutboxWriter, UnitOfWork } from '@orthacms/database';
|
|
3
|
+
import type { SavedView, SavedViewPayload, ViewVisibility } from '../../domain/saved-view';
|
|
4
|
+
import { type SavedViewRepository } from '../../domain/saved-view.repository';
|
|
5
|
+
import { SavedViewAccessService } from '../saved-view-access.service';
|
|
6
|
+
/** The fields `PATCH /api/views/:id` may change. */
|
|
7
|
+
export interface UpdateSavedViewInput {
|
|
8
|
+
/** New display name. */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** New visibility. */
|
|
11
|
+
visibility?: ViewVisibility;
|
|
12
|
+
/** New slice. */
|
|
13
|
+
payload?: SavedViewPayload;
|
|
14
|
+
}
|
|
15
|
+
/** Renames, re-shares, or re-captures an existing view. */
|
|
16
|
+
export declare class UpdateSavedViewUseCase {
|
|
17
|
+
private readonly repository;
|
|
18
|
+
private readonly access;
|
|
19
|
+
private readonly uow;
|
|
20
|
+
private readonly outbox;
|
|
21
|
+
constructor(repository: SavedViewRepository, access: SavedViewAccessService, uow: UnitOfWork, outbox: OutboxWriter);
|
|
22
|
+
execute(id: string, workspaceId: string, input: UpdateSavedViewInput, user: PublicUser): Promise<SavedView>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=update-saved-view.use-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-saved-view.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/use-cases/update-saved-view.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAEH,YAAY,EACZ,UAAU,EACb,MAAM,oBAAoB,CAAC;AAM5B,OAAO,KAAK,EACR,SAAS,EACT,gBAAgB,EAChB,cAAc,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACjC,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,iBAAiB;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,2DAA2D;AAC3D,qBACa,sBAAsB;IAG3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAHN,UAAU,EAAE,mBAAmB,EAC/B,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,YAAY;IAGnC,OAAO,CACT,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,CAAC;CA0ExB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UpdateSavedViewUseCase = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const database_1 = require("@orthacms/database");
|
|
7
|
+
const saved_view_events_1 = require("../../domain/events/saved-view-events");
|
|
8
|
+
const utils_server_1 = require("@orthacms/utils-server");
|
|
9
|
+
const saved_view_repository_1 = require("../../domain/saved-view.repository");
|
|
10
|
+
const errors_1 = require("../../domain/errors");
|
|
11
|
+
const saved_view_access_service_1 = require("../saved-view-access.service");
|
|
12
|
+
const saved_views_query_1 = require("../queries/saved-views.query");
|
|
13
|
+
/** Renames, re-shares, or re-captures an existing view. */
|
|
14
|
+
let UpdateSavedViewUseCase = class UpdateSavedViewUseCase {
|
|
15
|
+
repository;
|
|
16
|
+
access;
|
|
17
|
+
uow;
|
|
18
|
+
outbox;
|
|
19
|
+
constructor(repository, access, uow, outbox) {
|
|
20
|
+
this.repository = repository;
|
|
21
|
+
this.access = access;
|
|
22
|
+
this.uow = uow;
|
|
23
|
+
this.outbox = outbox;
|
|
24
|
+
}
|
|
25
|
+
async execute(id, workspaceId, input, user) {
|
|
26
|
+
const existing = await this.repository.findById(id);
|
|
27
|
+
// The workspace check is part of the not-found decision, not a separate
|
|
28
|
+
// 403: an id from another workspace must read exactly like an unknown
|
|
29
|
+
// one, or the endpoint becomes a probe for views elsewhere.
|
|
30
|
+
if (!existing || existing.workspaceId !== workspaceId) {
|
|
31
|
+
throw new errors_1.SavedViewNotFoundError(id);
|
|
32
|
+
}
|
|
33
|
+
this.access.assertOwned(existing, user);
|
|
34
|
+
if (input.visibility !== undefined) {
|
|
35
|
+
await this.access.assertCanUseVisibility(input.visibility, user);
|
|
36
|
+
}
|
|
37
|
+
// Every field is optional, so `{}` is a valid body — and it would reach
|
|
38
|
+
// Drizzle as `.set({})`, which throws rather than updating nothing.
|
|
39
|
+
// Answer the row unchanged instead: a patch that asks for no change has
|
|
40
|
+
// already got what it asked for.
|
|
41
|
+
const changes = input.name !== undefined ||
|
|
42
|
+
input.visibility !== undefined ||
|
|
43
|
+
input.payload !== undefined;
|
|
44
|
+
if (!changes) {
|
|
45
|
+
const currentDefault = await this.repository.findDefaultId(user.id, existing.scope);
|
|
46
|
+
return (0, saved_views_query_1.toSavedView)(existing, user.id, currentDefault);
|
|
47
|
+
}
|
|
48
|
+
let updated;
|
|
49
|
+
try {
|
|
50
|
+
updated = await this.uow.run(async () => {
|
|
51
|
+
const row = await this.repository.update(id, input);
|
|
52
|
+
await this.outbox.append((0, database_1.attachActor)([
|
|
53
|
+
(0, saved_view_events_1.savedViewEvent)(saved_view_events_1.SAVED_VIEW_EVENT_KINDS.UPDATED, id, {
|
|
54
|
+
workspaceId,
|
|
55
|
+
scope: existing.scope,
|
|
56
|
+
name: row.name,
|
|
57
|
+
// The keys the caller actually sent — a
|
|
58
|
+
// patch, so "what changed" is the request.
|
|
59
|
+
fields: Object.keys(input),
|
|
60
|
+
// Called out on its own: sharing is a
|
|
61
|
+
// permission of its own, and unsharing
|
|
62
|
+
// takes a view out of every member's
|
|
63
|
+
// switcher without telling them.
|
|
64
|
+
visibility: {
|
|
65
|
+
from: existing.visibility,
|
|
66
|
+
to: row.visibility
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
], { id: user.id, email: user.email ?? null }));
|
|
70
|
+
return row;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if ((0, utils_server_1.isUniqueViolation)(error)) {
|
|
75
|
+
throw new errors_1.SavedViewNameTakenError(input.name ?? existing.name);
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
const defaultId = await this.repository.findDefaultId(user.id, updated.scope);
|
|
80
|
+
return (0, saved_views_query_1.toSavedView)(updated, user.id, defaultId);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
exports.UpdateSavedViewUseCase = UpdateSavedViewUseCase;
|
|
84
|
+
exports.UpdateSavedViewUseCase = UpdateSavedViewUseCase = tslib_1.__decorate([
|
|
85
|
+
(0, common_1.Injectable)(),
|
|
86
|
+
tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
|
|
87
|
+
tslib_1.__metadata("design:paramtypes", [Object, saved_view_access_service_1.SavedViewAccessService,
|
|
88
|
+
database_1.UnitOfWork,
|
|
89
|
+
database_1.OutboxWriter])
|
|
90
|
+
], UpdateSavedViewUseCase);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ContentTypeRegistry } from '../../registry/content-type-registry';
|
|
2
|
+
import { WorkspaceGrantsQuery } from '../../content-types/queries/workspace-grants.query';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a view's `scope` to a content type the open workspace may actually
|
|
5
|
+
* reach.
|
|
6
|
+
*
|
|
7
|
+
* Without this the views endpoint would be a way around
|
|
8
|
+
* {@link ContentGrantGuard}: `scope` names a content type, and an unchecked one
|
|
9
|
+
* lets a caller confirm that `content:salaries` exists in some workspace by
|
|
10
|
+
* saving a view over it. So the answer for an unknown type and for an ungranted
|
|
11
|
+
* one is the **same 404** the entries routes give — grants read before any
|
|
12
|
+
* registry decision, same status, same message.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ViewScopeService {
|
|
15
|
+
private readonly registry;
|
|
16
|
+
private readonly grants;
|
|
17
|
+
constructor(registry: ContentTypeRegistry, grants: WorkspaceGrantsQuery);
|
|
18
|
+
/**
|
|
19
|
+
* Throws `NotFoundException` unless `scope` names a content type this
|
|
20
|
+
* workspace was granted. The DTO has already enforced the
|
|
21
|
+
* `content:<typeName>` shape.
|
|
22
|
+
*/
|
|
23
|
+
assertReachable(scope: string, workspaceId: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=view-scope.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-scope.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/views/application/view-scope.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAC;AAG1F;;;;;;;;;;GAUG;AACH,qBACa,gBAAgB;IAGrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,oBAAoB;IAGjD;;;;OAIG;IACG,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAO3E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ViewScopeService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const views_tokens_1 = require("../views.tokens");
|
|
7
|
+
const workspace_grants_query_1 = require("../../content-types/queries/workspace-grants.query");
|
|
8
|
+
const views_constants_1 = require("../views.constants");
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a view's `scope` to a content type the open workspace may actually
|
|
11
|
+
* reach.
|
|
12
|
+
*
|
|
13
|
+
* Without this the views endpoint would be a way around
|
|
14
|
+
* {@link ContentGrantGuard}: `scope` names a content type, and an unchecked one
|
|
15
|
+
* lets a caller confirm that `content:salaries` exists in some workspace by
|
|
16
|
+
* saving a view over it. So the answer for an unknown type and for an ungranted
|
|
17
|
+
* one is the **same 404** the entries routes give — grants read before any
|
|
18
|
+
* registry decision, same status, same message.
|
|
19
|
+
*/
|
|
20
|
+
let ViewScopeService = class ViewScopeService {
|
|
21
|
+
registry;
|
|
22
|
+
grants;
|
|
23
|
+
constructor(registry, grants) {
|
|
24
|
+
this.registry = registry;
|
|
25
|
+
this.grants = grants;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Throws `NotFoundException` unless `scope` names a content type this
|
|
29
|
+
* workspace was granted. The DTO has already enforced the
|
|
30
|
+
* `content:<typeName>` shape.
|
|
31
|
+
*/
|
|
32
|
+
async assertReachable(scope, workspaceId) {
|
|
33
|
+
const typeName = scope.slice(views_constants_1.CONTENT_SCOPE_PREFIX.length);
|
|
34
|
+
const granted = await this.grants.grantedSlugs(workspaceId);
|
|
35
|
+
if (!granted.has(typeName) || !this.registry.get(typeName)) {
|
|
36
|
+
throw new common_1.NotFoundException(`Unknown content type "${typeName}".`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.ViewScopeService = ViewScopeService;
|
|
41
|
+
exports.ViewScopeService = ViewScopeService = tslib_1.__decorate([
|
|
42
|
+
(0, common_1.Injectable)(),
|
|
43
|
+
tslib_1.__param(0, (0, views_tokens_1.InjectViewContentRegistry)()),
|
|
44
|
+
tslib_1.__metadata("design:paramtypes", [Function, workspace_grants_query_1.WorkspaceGrantsQuery])
|
|
45
|
+
], ViewScopeService);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { ContentTypeRegistry } from '../registry/content-type-registry';
|
|
3
|
+
/**
|
|
4
|
+
* The saved-views module.
|
|
5
|
+
*
|
|
6
|
+
* A **second** module from this package rather than a branch of
|
|
7
|
+
* `ContentModule`, because it needs its own `ServerPlugin` entry — the contract
|
|
8
|
+
* carries exactly one `migrations` descriptor per plugin, and content's is
|
|
9
|
+
* already spent on the host-owned generated collection tables. Splitting the
|
|
10
|
+
* module keeps that seam honest: the views feature owns fixed tables it ships
|
|
11
|
+
* migrations for, and the content model's tables stay the host's.
|
|
12
|
+
*
|
|
13
|
+
* It provides the registry the plugin factory hands it under its **own** token
|
|
14
|
+
* (`VIEW_CONTENT_REGISTRY`), so `ViewScopeService` can resolve a scope without
|
|
15
|
+
* importing `ContentModule` — which would make the two plugins' load order
|
|
16
|
+
* load-bearing — and without two global modules both claiming
|
|
17
|
+
* `CONTENT_REGISTRY`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ContentViewsModule {
|
|
20
|
+
static forRoot(registry: ContentTypeRegistry): DynamicModule;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=content-views.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-views.module.d.ts","sourceRoot":"","sources":["../../../src/lib/views/content-views.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAa7E;;;;;;;;;;;;;;;GAeG;AACH,qBACa,kBAAkB;IAC3B,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,mBAAmB,GAAG,aAAa;CAuB/D"}
|