@orthacms/content-server 0.4.2 → 0.4.3
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 +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -1
- package/dist/lib/content.module.d.ts.map +1 -1
- package/dist/lib/content.module.js +23 -1
- 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 +28 -2
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -1
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +112 -15
- 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-access-source.d.ts +112 -0
- package/dist/lib/extension/entry-access-source.d.ts.map +1 -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/entry-write-hook.d.ts +98 -0
- package/dist/lib/extension/entry-write-hook.d.ts.map +1 -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/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-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/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 +12 -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/types/revision-view.d.ts +11 -0
- package/dist/lib/revisions/types/revision-view.d.ts.map +1 -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 +29 -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 +27 -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 +57 -0
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts +15 -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 +35 -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 +21 -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 +61 -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/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 +17 -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 +125 -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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The **read scope port** — the seam a downstream plugin uses to narrow what
|
|
4
|
+
* the public content API returns, without this package knowing why.
|
|
5
|
+
*
|
|
6
|
+
* Content-server declares the port and consults it from `PublicEntriesQuery`
|
|
7
|
+
* and `PublicExpansionQuery`; an implementing plugin
|
|
8
|
+
* (`@orthacms/segments-server`, for reader entitlements) registers a scope
|
|
9
|
+
* through {@link contentReadScopeRegistrar}. Every fragment returned is AND-ed
|
|
10
|
+
* onto the visibility predicate the public read already states, so a scope can
|
|
11
|
+
* only ever *subtract* rows — there is no shape of return value that widens a
|
|
12
|
+
* read.
|
|
13
|
+
*
|
|
14
|
+
* ## Why a registry rather than a DI token
|
|
15
|
+
*
|
|
16
|
+
* The port takes any number of independent narrowings, and Nest has **no
|
|
17
|
+
* multi-provider**: two dynamic modules binding the same token do not merge,
|
|
18
|
+
* the second silently replaces the first. A second scoping plugin would
|
|
19
|
+
* therefore switch the first one off, with nothing to notice it — which for a
|
|
20
|
+
* visibility rule means content quietly becoming visible. So registration is a
|
|
21
|
+
* runtime `register(...)` call against a registry this module owns, the same
|
|
22
|
+
* shape and the same reason as `copilotToolsRegistrar`.
|
|
23
|
+
*
|
|
24
|
+
* ## Why not `CONTENT_ENTRY_EXTENSION`
|
|
25
|
+
*
|
|
26
|
+
* That port is documented as a **single binding**: one provider per app, and
|
|
27
|
+
* i18n already holds it. Its own note suggests binding a composite when a
|
|
28
|
+
* second extension appears, and for a write-pipeline extension that composes
|
|
29
|
+
* cleanly — but "narrow a read" and "extend a write" are different
|
|
30
|
+
* responsibilities with different failure modes. A composite would fuse i18n
|
|
31
|
+
* and entitlements into one provider, where a fault in either silently drops
|
|
32
|
+
* the other's clause.
|
|
33
|
+
*
|
|
34
|
+
* ## Two constraints on an implementation
|
|
35
|
+
*
|
|
36
|
+
* **Synchronous.** The predicate is assembled inside the query builder, and
|
|
37
|
+
* making that path async would ripple through every public read for the benefit
|
|
38
|
+
* of one provider. A scope that needs I/O — resolving who the caller is, say —
|
|
39
|
+
* must do it earlier in the request (a guard, an interceptor, middleware) and
|
|
40
|
+
* read the result from its own request-scoped state here.
|
|
41
|
+
*
|
|
42
|
+
* **Public reads only.** These fragments are not applied to the admin's own
|
|
43
|
+
* entries list. An admin caller is a member of the workspace looking at their
|
|
44
|
+
* own CMS, while a reader entitlement is about who may *consume* published
|
|
45
|
+
* content, and scoping the editor's list by it would hide from an author the
|
|
46
|
+
* very rows they are responsible for. The same split content grants already
|
|
47
|
+
* make.
|
|
48
|
+
*/
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.ContentReadScopeRegistry = void 0;
|
|
51
|
+
exports.contentReadScopeRegistrar = contentReadScopeRegistrar;
|
|
52
|
+
const tslib_1 = require("tslib");
|
|
53
|
+
const common_1 = require("@nestjs/common");
|
|
54
|
+
/**
|
|
55
|
+
* Every registered narrowing, in registration order.
|
|
56
|
+
*
|
|
57
|
+
* Order does not affect the result — the fragments are AND-ed — but it is kept
|
|
58
|
+
* stable so a query log reads the same way twice.
|
|
59
|
+
*/
|
|
60
|
+
let ContentReadScopeRegistry = class ContentReadScopeRegistry {
|
|
61
|
+
scopes = [];
|
|
62
|
+
/** Adds a scope. Registering the same instance twice is a no-op. */
|
|
63
|
+
register(scope) {
|
|
64
|
+
if (!this.scopes.includes(scope)) {
|
|
65
|
+
this.scopes.push(scope);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Every registered scope. Empty on an installation with none. */
|
|
69
|
+
all() {
|
|
70
|
+
return this.scopes;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The fragments for one read, ready to be spread into `and(...)`.
|
|
74
|
+
*
|
|
75
|
+
* Returns an empty array — not a fragment matching everything — when
|
|
76
|
+
* nothing is registered, which is the state every installation without a
|
|
77
|
+
* scoping plugin stays in.
|
|
78
|
+
*/
|
|
79
|
+
fragments(context) {
|
|
80
|
+
if (!this.scopes.length) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return this.scopes.map((scope) => scope.scope(context));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
exports.ContentReadScopeRegistry = ContentReadScopeRegistry;
|
|
87
|
+
exports.ContentReadScopeRegistry = ContentReadScopeRegistry = tslib_1.__decorate([
|
|
88
|
+
(0, common_1.Injectable)()
|
|
89
|
+
], ContentReadScopeRegistry);
|
|
90
|
+
/** Registers a plugin's read scopes with content at bootstrap. */
|
|
91
|
+
class ReadScopeBootstrapper {
|
|
92
|
+
label;
|
|
93
|
+
registry;
|
|
94
|
+
scopes;
|
|
95
|
+
logger = new common_1.Logger(ReadScopeBootstrapper.name);
|
|
96
|
+
constructor(label, registry, scopes) {
|
|
97
|
+
this.label = label;
|
|
98
|
+
this.registry = registry;
|
|
99
|
+
this.scopes = scopes;
|
|
100
|
+
}
|
|
101
|
+
onApplicationBootstrap() {
|
|
102
|
+
// A deployment that does not register `ContentPlugin` is not a real
|
|
103
|
+
// configuration, but a binding plugin should still boot rather than
|
|
104
|
+
// fail on an injection it cannot influence.
|
|
105
|
+
if (!this.registry) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const scope of this.scopes) {
|
|
109
|
+
this.registry.register(scope);
|
|
110
|
+
}
|
|
111
|
+
this.logger.log(`Registered the ${this.label} read scope with the content API.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Builds the DI provider that registers `scopes` with content's read-scope
|
|
116
|
+
* registry at bootstrap.
|
|
117
|
+
*
|
|
118
|
+
* A factory with an explicit `inject` list rather than a class with reflected
|
|
119
|
+
* parameters, for the reason `copilotToolsRegistrar` documents: an optional
|
|
120
|
+
* dependency typed as `Foo | null` emits `Object` for `design:paramtypes`, Nest
|
|
121
|
+
* injects `undefined` with no error, and the plugin ends up registering
|
|
122
|
+
* nothing. Here that failure would mean a visibility rule that silently stopped
|
|
123
|
+
* being applied.
|
|
124
|
+
*/
|
|
125
|
+
function contentReadScopeRegistrar(label, ...scopes) {
|
|
126
|
+
return {
|
|
127
|
+
provide: `CONTENT_READ_SCOPE_REGISTRAR_${label.toUpperCase()}`,
|
|
128
|
+
useFactory: (registry, ...resolved) => new ReadScopeBootstrapper(label, registry, resolved),
|
|
129
|
+
inject: [{ token: ContentReadScopeRegistry, optional: true }, ...scopes]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Database } from '@orthacms/database';
|
|
2
2
|
import { type ContentEntryExtension } from '../../extension/entry-extension';
|
|
3
|
+
import { ContentReadScopeRegistry } from '../../extension/read-scope';
|
|
3
4
|
import { type AnyContentType } from '../../types/content-type';
|
|
4
5
|
import { RelationLinkService } from '../../entries/infrastructure/persistence/relation-link.service';
|
|
5
6
|
import type { PublicMediaFieldView, PublicRelationFieldView } from '../types/public-expansion';
|
|
@@ -49,7 +50,8 @@ export declare class PublicEntriesQuery {
|
|
|
49
50
|
private readonly expansion;
|
|
50
51
|
private readonly relationLinks;
|
|
51
52
|
private readonly extension?;
|
|
52
|
-
|
|
53
|
+
private readonly readScopes?;
|
|
54
|
+
constructor(db: Database, expansion: PublicExpansionQuery, relationLinks: RelationLinkService, extension?: ContentEntryExtension | undefined, readScopes?: ContentReadScopeRegistry | undefined);
|
|
53
55
|
/**
|
|
54
56
|
* One page of a type's publicly readable entries. Count and page share the
|
|
55
57
|
* WHERE and run concurrently, so the request pays the slower of the two.
|
|
@@ -225,6 +227,17 @@ export declare class PublicEntriesQuery {
|
|
|
225
227
|
* visibility rule in exactly one respect and inherits the rest verbatim.
|
|
226
228
|
*/
|
|
227
229
|
private liveWhere;
|
|
230
|
+
/**
|
|
231
|
+
* The fragments contributed by the bound `CONTENT_READ_SCOPE` providers.
|
|
232
|
+
*
|
|
233
|
+
* Applied here, in {@link liveWhere}, rather than in `readableWhere`: this
|
|
234
|
+
* is the clause every public read shares — the list, the single-entry read
|
|
235
|
+
* and the translation lookup that deliberately steps around the locale
|
|
236
|
+
* scope all pass through it — so a scope cannot be missed by a read that
|
|
237
|
+
* chose the narrower helper. AND-ed like everything else around them, which
|
|
238
|
+
* is what makes a scope structurally incapable of widening a read.
|
|
239
|
+
*/
|
|
240
|
+
private readScopeWhere;
|
|
228
241
|
/**
|
|
229
242
|
* The publish-state clause. `published` — the default and everything a
|
|
230
243
|
* read-only token can ask for — is the API's headline rule; the other two
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-entries.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-entries.query.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAEH,KAAK,qBAAqB,EAC7B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AACrG,OAAO,KAAK,EACR,oBAAoB,EACpB,uBAAuB,EAC1B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAGH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EACjC,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAc9E;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAClB;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvB;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBACa,kBAAkB;IAEL,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAK9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"public-entries.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-entries.query.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ const drizzle_orm_1 = require("drizzle-orm");
|
|
|
7
7
|
const database_1 = require("@orthacms/database");
|
|
8
8
|
const utils_server_1 = require("@orthacms/utils-server");
|
|
9
9
|
const entry_extension_1 = require("../../extension/entry-extension");
|
|
10
|
+
const read_scope_1 = require("../../extension/read-scope");
|
|
10
11
|
const content_type_1 = require("../../types/content-type");
|
|
11
12
|
const fields_1 = require("../../types/fields");
|
|
12
13
|
const relation_link_service_1 = require("../../entries/infrastructure/persistence/relation-link.service");
|
|
@@ -48,11 +49,13 @@ let PublicEntriesQuery = class PublicEntriesQuery {
|
|
|
48
49
|
expansion;
|
|
49
50
|
relationLinks;
|
|
50
51
|
extension;
|
|
51
|
-
|
|
52
|
+
readScopes;
|
|
53
|
+
constructor(db, expansion, relationLinks, extension, readScopes) {
|
|
52
54
|
this.db = db;
|
|
53
55
|
this.expansion = expansion;
|
|
54
56
|
this.relationLinks = relationLinks;
|
|
55
57
|
this.extension = extension;
|
|
58
|
+
this.readScopes = readScopes;
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
61
|
* One page of a type's publicly readable entries. Count and page share the
|
|
@@ -500,7 +503,20 @@ let PublicEntriesQuery = class PublicEntriesQuery {
|
|
|
500
503
|
*/
|
|
501
504
|
liveWhere(type, workspaceId, visibility = 'published') {
|
|
502
505
|
const table = type.table;
|
|
503
|
-
return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), this.statusWhere(type, visibility), type.paranoid ? (0, drizzle_orm_1.isNull)(table['deletedAt']) : undefined);
|
|
506
|
+
return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), this.statusWhere(type, visibility), type.paranoid ? (0, drizzle_orm_1.isNull)(table['deletedAt']) : undefined, ...this.readScopeWhere(type, workspaceId));
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* The fragments contributed by the bound `CONTENT_READ_SCOPE` providers.
|
|
510
|
+
*
|
|
511
|
+
* Applied here, in {@link liveWhere}, rather than in `readableWhere`: this
|
|
512
|
+
* is the clause every public read shares — the list, the single-entry read
|
|
513
|
+
* and the translation lookup that deliberately steps around the locale
|
|
514
|
+
* scope all pass through it — so a scope cannot be missed by a read that
|
|
515
|
+
* chose the narrower helper. AND-ed like everything else around them, which
|
|
516
|
+
* is what makes a scope structurally incapable of widening a read.
|
|
517
|
+
*/
|
|
518
|
+
readScopeWhere(type, workspaceId) {
|
|
519
|
+
return this.readScopes?.fragments({ type, workspaceId }) ?? [];
|
|
504
520
|
}
|
|
505
521
|
/**
|
|
506
522
|
* The publish-state clause. `published` — the default and everything a
|
|
@@ -552,6 +568,7 @@ exports.PublicEntriesQuery = PublicEntriesQuery = tslib_1.__decorate([
|
|
|
552
568
|
tslib_1.__param(0, (0, database_1.InjectDatabase)()),
|
|
553
569
|
tslib_1.__param(3, (0, common_1.Optional)()),
|
|
554
570
|
tslib_1.__param(3, (0, common_1.Inject)(entry_extension_1.CONTENT_ENTRY_EXTENSION)),
|
|
571
|
+
tslib_1.__param(4, (0, common_1.Optional)()),
|
|
555
572
|
tslib_1.__metadata("design:paramtypes", [Object, public_expansion_query_1.PublicExpansionQuery,
|
|
556
|
-
relation_link_service_1.RelationLinkService, Object])
|
|
573
|
+
relation_link_service_1.RelationLinkService, Object, read_scope_1.ContentReadScopeRegistry])
|
|
557
574
|
], PublicEntriesQuery);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Database } from '@orthacms/database';
|
|
2
2
|
import { type AnyContentType } from '../../types/content-type';
|
|
3
3
|
import { type MediaAssetResolver } from '../../extension/media-asset-resolver';
|
|
4
|
+
import { ContentReadScopeRegistry } from '../../extension/read-scope';
|
|
4
5
|
import { RelationLinkService } from '../../entries/infrastructure/persistence/relation-link.service';
|
|
5
6
|
import type { PublicMediaFieldView, PublicRelationFieldView } from '../types/public-expansion';
|
|
6
7
|
/** A generated content row seen as a bag of values by property name. */
|
|
@@ -31,7 +32,16 @@ export declare class PublicExpansionQuery {
|
|
|
31
32
|
private readonly db;
|
|
32
33
|
private readonly relationLinks;
|
|
33
34
|
private readonly media?;
|
|
34
|
-
|
|
35
|
+
private readonly readScopes?;
|
|
36
|
+
constructor(db: Database, relationLinks: RelationLinkService, media?: MediaAssetResolver | undefined, readScopes?: ContentReadScopeRegistry | undefined);
|
|
37
|
+
/**
|
|
38
|
+
* The read-scope fragments for one hop's target type.
|
|
39
|
+
*
|
|
40
|
+
* The target is a different content type from the one the request named, so
|
|
41
|
+
* the scope is asked about **it** — a scope keyed to the requested type
|
|
42
|
+
* would leave every relation target unguarded.
|
|
43
|
+
*/
|
|
44
|
+
private readScopeWhere;
|
|
35
45
|
/**
|
|
36
46
|
* Validate a `?relationFields=` list: every name must be a relation field
|
|
37
47
|
* on the type, and its target must be a content type the workspace was
|
|
@@ -47,9 +57,11 @@ export declare class PublicExpansionQuery {
|
|
|
47
57
|
* One capped page of links per requested relation field, for every row on
|
|
48
58
|
* the page, keyed by entry id.
|
|
49
59
|
*
|
|
50
|
-
* `publishedOnly` is the public difference: a draft target
|
|
51
|
-
*
|
|
52
|
-
* reach.
|
|
60
|
+
* `publishedOnly` is the public difference: a draft target — or one a bound
|
|
61
|
+
* read scope hides from this reader — is neither shown **nor counted**, so
|
|
62
|
+
* `total` is the number of links this caller can actually reach. Counting
|
|
63
|
+
* them would leak the cardinality of what is hidden: "5 links, 2 visible"
|
|
64
|
+
* says three restricted records exist here.
|
|
53
65
|
*/
|
|
54
66
|
relationsForRows(type: AnyContentType, fields: string[], rows: Row[], workspaceId: string, limit?: number): Promise<Map<string, Record<string, PublicRelationFieldView>>>;
|
|
55
67
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-expansion.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-expansion.query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"public-expansion.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-expansion.query.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAErG,OAAO,KAAK,EACR,oBAAoB,EAEpB,uBAAuB,EAC1B,MAAM,2BAA2B,CAAC;AAInC,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAKnC;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAwCtC;;;;;;;;;;;;GAYG;AACH,qBACa,oBAAoB;IAEP,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAG9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAKvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBATO,EAAE,EAAE,QAAQ,EAC9B,aAAa,EAAE,mBAAmB,EAGlC,KAAK,CAAC,EAAE,kBAAkB,YAAA,EAK1B,UAAU,CAAC,EAAE,wBAAwB,YAAA;IAG1D;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAOtB;;;;;;;OAOG;IACH,mBAAmB,CACf,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,MAAM,EAAE;IA8BX,yEAAyE;IACzE,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE;IAiBzE;;;;;;;;;OASG;IACG,gBAAgB,CAClB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IA6ChE;;;;OAIG;IACG,oBAAoB,CACtB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EACjE,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAqBnD;;;;;;;;OAQG;YACW,iBAAiB;IAuD/B;;;;;;OAMG;IACG,YAAY,CACd,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IA+F7D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAgBvB,4EAA4E;IAC5E,OAAO,CAAC,WAAW;CA+BtB"}
|
|
@@ -9,6 +9,7 @@ const content_domain_1 = require("@orthacms/content-domain");
|
|
|
9
9
|
const content_type_1 = require("../../types/content-type");
|
|
10
10
|
const fields_1 = require("../../types/fields");
|
|
11
11
|
const media_asset_resolver_1 = require("../../extension/media-asset-resolver");
|
|
12
|
+
const read_scope_1 = require("../../extension/read-scope");
|
|
12
13
|
const relation_link_service_1 = require("../../entries/infrastructure/persistence/relation-link.service");
|
|
13
14
|
const public_list_entries_query_dto_1 = require("../http/dto/public-list-entries-query.dto");
|
|
14
15
|
const public_entry_row_1 = require("./public-entry-row");
|
|
@@ -73,10 +74,22 @@ let PublicExpansionQuery = class PublicExpansionQuery {
|
|
|
73
74
|
db;
|
|
74
75
|
relationLinks;
|
|
75
76
|
media;
|
|
76
|
-
|
|
77
|
+
readScopes;
|
|
78
|
+
constructor(db, relationLinks, media, readScopes) {
|
|
77
79
|
this.db = db;
|
|
78
80
|
this.relationLinks = relationLinks;
|
|
79
81
|
this.media = media;
|
|
82
|
+
this.readScopes = readScopes;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The read-scope fragments for one hop's target type.
|
|
86
|
+
*
|
|
87
|
+
* The target is a different content type from the one the request named, so
|
|
88
|
+
* the scope is asked about **it** — a scope keyed to the requested type
|
|
89
|
+
* would leave every relation target unguarded.
|
|
90
|
+
*/
|
|
91
|
+
readScopeWhere(type, workspaceId) {
|
|
92
|
+
return this.readScopes?.fragments({ type, workspaceId }) ?? [];
|
|
80
93
|
}
|
|
81
94
|
/**
|
|
82
95
|
* Validate a `?relationFields=` list: every name must be a relation field
|
|
@@ -121,9 +134,11 @@ let PublicExpansionQuery = class PublicExpansionQuery {
|
|
|
121
134
|
* One capped page of links per requested relation field, for every row on
|
|
122
135
|
* the page, keyed by entry id.
|
|
123
136
|
*
|
|
124
|
-
* `publishedOnly` is the public difference: a draft target
|
|
125
|
-
*
|
|
126
|
-
* reach.
|
|
137
|
+
* `publishedOnly` is the public difference: a draft target — or one a bound
|
|
138
|
+
* read scope hides from this reader — is neither shown **nor counted**, so
|
|
139
|
+
* `total` is the number of links this caller can actually reach. Counting
|
|
140
|
+
* them would leak the cardinality of what is hidden: "5 links, 2 visible"
|
|
141
|
+
* says three restricted records exist here.
|
|
127
142
|
*/
|
|
128
143
|
async relationsForRows(type, fields, rows, workspaceId, limit = public_list_entries_query_dto_1.DEFAULT_EXPANSION_LIMIT) {
|
|
129
144
|
const out = new Map();
|
|
@@ -140,9 +155,13 @@ let PublicExpansionQuery = class PublicExpansionQuery {
|
|
|
140
155
|
for (const [field, value] of Object.entries(byField)) {
|
|
141
156
|
const linked = entriesById.get(field);
|
|
142
157
|
view[field] = {
|
|
143
|
-
// Preserve the preview's link order
|
|
144
|
-
//
|
|
145
|
-
//
|
|
158
|
+
// Preserve the preview's link order. Nothing should drop
|
|
159
|
+
// here any more — `previewForEntries` applies the same
|
|
160
|
+
// visibility (published-only **and** the bound read scopes)
|
|
161
|
+
// inside its window, so an unreachable target is already
|
|
162
|
+
// out of both `items` and `total`. The filter stays as a
|
|
163
|
+
// belt: a hydration miss must never leave a hole in the
|
|
164
|
+
// array, and it is cheap to keep honest.
|
|
146
165
|
items: value.items
|
|
147
166
|
.map((ref) => linked?.get(ref.id))
|
|
148
167
|
.filter((entry) => !!entry),
|
|
@@ -209,7 +228,7 @@ let PublicExpansionQuery = class PublicExpansionQuery {
|
|
|
209
228
|
? (0, drizzle_orm_1.eq)(cols['status'], content_type_1.ENTRY_STATUS.Published)
|
|
210
229
|
: undefined, target.paranoid
|
|
211
230
|
? (0, drizzle_orm_1.isNull)(cols['deletedAt'])
|
|
212
|
-
: undefined)));
|
|
231
|
+
: undefined, ...this.readScopeWhere(target, workspaceId))));
|
|
213
232
|
out.set(field, new Map(rows.map((row) => [
|
|
214
233
|
row['id'],
|
|
215
234
|
(0, public_entry_row_1.toPublicEntry)(target, row)
|
|
@@ -358,7 +377,8 @@ exports.PublicExpansionQuery = PublicExpansionQuery = tslib_1.__decorate([
|
|
|
358
377
|
tslib_1.__param(0, (0, database_1.InjectDatabase)()),
|
|
359
378
|
tslib_1.__param(2, (0, common_1.Optional)()),
|
|
360
379
|
tslib_1.__param(2, (0, media_asset_resolver_1.InjectMediaAssetResolver)()),
|
|
361
|
-
tslib_1.
|
|
380
|
+
tslib_1.__param(3, (0, common_1.Optional)()),
|
|
381
|
+
tslib_1.__metadata("design:paramtypes", [Object, relation_link_service_1.RelationLinkService, Object, read_scope_1.ContentReadScopeRegistry])
|
|
362
382
|
], PublicExpansionQuery);
|
|
363
383
|
/** The asset ids stored in a media field — one id, or an ordered list. */
|
|
364
384
|
function mediaRefsOf(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restore-revision.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/application/use-cases/restore-revision.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AACtG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAEH,KAAK,aAAa,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,qBACa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACA,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAD5B,MAAM,EAAE,kBAAkB,EACH,KAAK,EAAE,aAAa;IAG1D,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM;IACnB;;;;OAIG;IACH,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,OAAO,CAAC,EAAE;QACN;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;KAC5B,GACF,OAAO,CAAC,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"restore-revision.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/application/use-cases/restore-revision.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AACtG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAEH,KAAK,aAAa,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,qBACa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACA,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAD5B,MAAM,EAAE,kBAAkB,EACH,KAAK,EAAE,aAAa;IAG1D,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM;IACnB;;;;OAIG;IACH,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,OAAO,CAAC,EAAE;QACN;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;KAC5B,GACF,OAAO,CAAC,WAAW,CAAC;CA0C1B"}
|
|
@@ -46,7 +46,18 @@ let RestoreRevisionUseCase = class RestoreRevisionUseCase {
|
|
|
46
46
|
values[field] = ids;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
return this.writer.update(type, id, values, workspaceId, undefined, actor,
|
|
49
|
+
return this.writer.update(type, id, values, workspaceId, undefined, actor, {
|
|
50
|
+
...options,
|
|
51
|
+
// Put back whatever the bound extensions held when this version was
|
|
52
|
+
// captured — segments' audiences, today. Without it "go back to
|
|
53
|
+
// Tuesday" would restore Tuesday's words in front of today's
|
|
54
|
+
// readers, which is the half of a restore nobody would think to
|
|
55
|
+
// check. A version captured before any extension existed carries no
|
|
56
|
+
// bag, and an absent bag changes nothing (an omitted key is left
|
|
57
|
+
// alone) — which is the right reading: that version knows nothing
|
|
58
|
+
// about audiences, it does not assert the entry had none.
|
|
59
|
+
extensions: detail.snapshot.extra
|
|
60
|
+
});
|
|
50
61
|
}
|
|
51
62
|
};
|
|
52
63
|
exports.RestoreRevisionUseCase = RestoreRevisionUseCase;
|
|
@@ -11,7 +11,13 @@ type Row = Record<string, unknown>;
|
|
|
11
11
|
* `relations` is read separately from the join tables — those links never travel
|
|
12
12
|
* in the `values` bag — inside the save transaction, so the snapshot reflects
|
|
13
13
|
* exactly what committed.
|
|
14
|
+
*
|
|
15
|
+
* `extra` is the same idea one plugin further out: state a **bound entry-write
|
|
16
|
+
* extension** owns in a table this package knows nothing about (segments'
|
|
17
|
+
* audiences), read back on the same transaction. It is omitted when there is
|
|
18
|
+
* none, so a snapshot on an installation with no extensions is byte-for-byte
|
|
19
|
+
* what it was before the port existed.
|
|
14
20
|
*/
|
|
15
|
-
export declare function buildSnapshot(type: AnyContentType, row: Row, relations: Record<string, string[]>): RevisionSnapshot;
|
|
21
|
+
export declare function buildSnapshot(type: AnyContentType, row: Row, relations: Record<string, string[]>, extra?: Record<string, unknown>): RevisionSnapshot;
|
|
16
22
|
export {};
|
|
17
23
|
//# sourceMappingURL=revision-snapshot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revision-snapshot.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/infrastructure/persistence/revision-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC
|
|
1
|
+
{"version":3,"file":"revision-snapshot.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/infrastructure/persistence/revision-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,gBAAgB,CAMlB"}
|
|
@@ -11,10 +11,17 @@ const entry_row_1 = require("../../../entries/infrastructure/persistence/entry-r
|
|
|
11
11
|
* `relations` is read separately from the join tables — those links never travel
|
|
12
12
|
* in the `values` bag — inside the save transaction, so the snapshot reflects
|
|
13
13
|
* exactly what committed.
|
|
14
|
+
*
|
|
15
|
+
* `extra` is the same idea one plugin further out: state a **bound entry-write
|
|
16
|
+
* extension** owns in a table this package knows nothing about (segments'
|
|
17
|
+
* audiences), read back on the same transaction. It is omitted when there is
|
|
18
|
+
* none, so a snapshot on an installation with no extensions is byte-for-byte
|
|
19
|
+
* what it was before the port existed.
|
|
14
20
|
*/
|
|
15
|
-
function buildSnapshot(type, row, relations) {
|
|
21
|
+
function buildSnapshot(type, row, relations, extra) {
|
|
16
22
|
return {
|
|
17
23
|
values: (0, entry_row_1.toRecord)(type, row).values,
|
|
18
|
-
relations
|
|
24
|
+
relations,
|
|
25
|
+
...(extra ? { extra } : {})
|
|
19
26
|
};
|
|
20
27
|
}
|
|
@@ -17,6 +17,17 @@ export interface RevisionSnapshot {
|
|
|
17
17
|
* never travel in {@link values}.
|
|
18
18
|
*/
|
|
19
19
|
relations: Record<string, string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* What each bound **entry-write extension** held for the entry at capture
|
|
22
|
+
* time, keyed by its `key` — state a different plugin owns in a table this
|
|
23
|
+
* package knows nothing about (`segments` records who could read the entry).
|
|
24
|
+
*
|
|
25
|
+
* Opaque here: content-server stores it, hands it back on a restore, and
|
|
26
|
+
* never looks inside. Absent on an installation with no extensions, and on
|
|
27
|
+
* every version captured before one was installed — so a reader must treat
|
|
28
|
+
* a missing bag as "nothing is known", never as "the entry had none".
|
|
29
|
+
*/
|
|
30
|
+
extra?: Record<string, unknown>;
|
|
20
31
|
}
|
|
21
32
|
/**
|
|
22
33
|
* One revision as served to the admin timeline — the metadata, without the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revision-view.d.ts","sourceRoot":"","sources":["../../../../src/lib/revisions/types/revision-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EACR,QAAQ,EACR,WAAW,EACd,MAAM,qCAAqC,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"revision-view.d.ts","sourceRoot":"","sources":["../../../../src/lib/revisions/types/revision-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EACR,QAAQ,EACR,WAAW,EACd,MAAM,qCAAqC,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,wDAAwD;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAe,SAAQ,eAAe;IACnD,6BAA6B;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CAC1C;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ServerPlugin } from '@orthacms/bootstrap-server';
|
|
2
|
+
import type { ContentServerPlugin } from './content-plugin';
|
|
3
|
+
/** Options for {@link ContentViewsPlugin}. */
|
|
4
|
+
export interface ContentViewsPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The content plugin this rides on — its registry is what turns a view's
|
|
7
|
+
* `content:<typeName>` scope back into a known content type. Taking the
|
|
8
|
+
* whole plugin (rather than a bare registry) makes the dependency visible
|
|
9
|
+
* at the composition root: the entry has to be listed *after* the content
|
|
10
|
+
* one, which is also the migration order its foreign keys need.
|
|
11
|
+
*/
|
|
12
|
+
content: ContentServerPlugin;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Saved list views — the named filter/sort/column slices an editor returns to.
|
|
16
|
+
*
|
|
17
|
+
* A **separate `ServerPlugin` entry from the same package** as `ContentPlugin`,
|
|
18
|
+
* and deliberately so. `ServerPlugin.migrations` is one descriptor, and
|
|
19
|
+
* content's is already carrying the HOST's generated collection tables; a
|
|
20
|
+
* second entry is how this package ships its own fixed tables without touching
|
|
21
|
+
* that contract. The host applies both under their own tracking tables.
|
|
22
|
+
*
|
|
23
|
+
* Order matters at the composition root: `saved_views` has foreign keys into
|
|
24
|
+
* identity's `users` and workspaces' `workspaces`, and nothing declares that
|
|
25
|
+
* dependency — list this after both, and after `ContentPlugin` itself.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ContentViewsPlugin(options: ContentViewsPluginOptions): ServerPlugin;
|
|
28
|
+
//# sourceMappingURL=content-views-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-views-plugin.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/content-views-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,8CAA8C;AAC9C,MAAM,WAAW,yBAAyB;IACtC;;;;;;OAMG;IACH,OAAO,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,OAAO,EAAE,yBAAyB,GACnC,YAAY,CAUd"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentViewsPlugin = ContentViewsPlugin;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const content_views_module_1 = require("../views/content-views.module");
|
|
6
|
+
/**
|
|
7
|
+
* Saved list views — the named filter/sort/column slices an editor returns to.
|
|
8
|
+
*
|
|
9
|
+
* A **separate `ServerPlugin` entry from the same package** as `ContentPlugin`,
|
|
10
|
+
* and deliberately so. `ServerPlugin.migrations` is one descriptor, and
|
|
11
|
+
* content's is already carrying the HOST's generated collection tables; a
|
|
12
|
+
* second entry is how this package ships its own fixed tables without touching
|
|
13
|
+
* that contract. The host applies both under their own tracking tables.
|
|
14
|
+
*
|
|
15
|
+
* Order matters at the composition root: `saved_views` has foreign keys into
|
|
16
|
+
* identity's `users` and workspaces' `workspaces`, and nothing declares that
|
|
17
|
+
* dependency — list this after both, and after `ContentPlugin` itself.
|
|
18
|
+
*/
|
|
19
|
+
function ContentViewsPlugin(options) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'content-views',
|
|
22
|
+
module: content_views_module_1.ContentViewsModule.forRoot(options.content.registry),
|
|
23
|
+
// src/lib/utils → ../../../migrations = <pkg>/migrations.
|
|
24
|
+
migrations: {
|
|
25
|
+
dir: () => (0, node_path_1.join)(__dirname, '../../../migrations'),
|
|
26
|
+
table: '__drizzle_migrations_content_views'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SavedView } from '../../domain/saved-view';
|
|
2
|
+
import { type SavedViewRecord, type SavedViewRepository } from '../../domain/saved-view.repository';
|
|
3
|
+
/**
|
|
4
|
+
* The read side: every view a caller may see for one list, with the two
|
|
5
|
+
* per-caller flags the switcher renders from (`isOwn` gates Save and Delete,
|
|
6
|
+
* `isDefault` marks the row and drives the landing view).
|
|
7
|
+
*/
|
|
8
|
+
export declare class SavedViewsQuery {
|
|
9
|
+
private readonly repository;
|
|
10
|
+
constructor(repository: SavedViewRepository);
|
|
11
|
+
/** Views visible to `readerId` in this workspace + scope. */
|
|
12
|
+
list(workspaceId: string, scope: string, readerId: string): Promise<SavedView[]>;
|
|
13
|
+
}
|
|
14
|
+
/** Projects a stored row into the API shape for one reader. */
|
|
15
|
+
export declare function toSavedView(record: SavedViewRecord, readerId: string, defaultId: string | null): SavedView;
|
|
16
|
+
//# sourceMappingURL=saved-views.query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saved-views.query.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/queries/saved-views.query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAEH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C;;;;GAIG;AACH,qBACa,eAAe;IAGpB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,mBAAmB;IAGpD,6DAA6D;IACvD,IAAI,CACN,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC;CAS1B;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CACvB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GACzB,SAAS,CAYX"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SavedViewsQuery = void 0;
|
|
4
|
+
exports.toSavedView = toSavedView;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const saved_view_repository_1 = require("../../domain/saved-view.repository");
|
|
8
|
+
/**
|
|
9
|
+
* The read side: every view a caller may see for one list, with the two
|
|
10
|
+
* per-caller flags the switcher renders from (`isOwn` gates Save and Delete,
|
|
11
|
+
* `isDefault` marks the row and drives the landing view).
|
|
12
|
+
*/
|
|
13
|
+
let SavedViewsQuery = class SavedViewsQuery {
|
|
14
|
+
repository;
|
|
15
|
+
constructor(repository) {
|
|
16
|
+
this.repository = repository;
|
|
17
|
+
}
|
|
18
|
+
/** Views visible to `readerId` in this workspace + scope. */
|
|
19
|
+
async list(workspaceId, scope, readerId) {
|
|
20
|
+
const [records, defaultId] = await Promise.all([
|
|
21
|
+
this.repository.listVisible(workspaceId, scope, readerId),
|
|
22
|
+
this.repository.findDefaultId(readerId, scope)
|
|
23
|
+
]);
|
|
24
|
+
return records.map((record) => toSavedView(record, readerId, defaultId));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.SavedViewsQuery = SavedViewsQuery;
|
|
28
|
+
exports.SavedViewsQuery = SavedViewsQuery = tslib_1.__decorate([
|
|
29
|
+
(0, common_1.Injectable)(),
|
|
30
|
+
tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
|
|
31
|
+
tslib_1.__metadata("design:paramtypes", [Object])
|
|
32
|
+
], SavedViewsQuery);
|
|
33
|
+
/** Projects a stored row into the API shape for one reader. */
|
|
34
|
+
function toSavedView(record, readerId, defaultId) {
|
|
35
|
+
return {
|
|
36
|
+
id: record.id,
|
|
37
|
+
scope: record.scope,
|
|
38
|
+
name: record.name,
|
|
39
|
+
visibility: record.visibility,
|
|
40
|
+
ownerId: record.ownerId,
|
|
41
|
+
isOwn: record.ownerId === readerId,
|
|
42
|
+
isDefault: record.id === defaultId,
|
|
43
|
+
payload: record.payload,
|
|
44
|
+
updatedAt: record.updatedAt.toISOString()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -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
|