@crombolo/api-management 0.12.1 → 0.13.1
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/package.json +3 -3
- package/src/handlers/document.ts +28 -1
- package/src/ports-content.ts +23 -0
- package/src/ports.ts +3 -0
- package/src/routes.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/api-management",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Umbraco-compatible Management API handlers for crombolo.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Crombolo",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@crombolo/core": "0.
|
|
32
|
-
"@crombolo/contracts": "0.
|
|
31
|
+
"@crombolo/core": "0.13.1",
|
|
32
|
+
"@crombolo/contracts": "0.13.1"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/handlers/document.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
ROOT_ACCESS,
|
|
21
21
|
} from '@crombolo/core'
|
|
22
22
|
import type { PreviewPort } from '../ports.ts'
|
|
23
|
-
import type { DocumentPort, PortFailure, SaveDocument } from '../ports-content.ts'
|
|
23
|
+
import type { ApprovalGatePort, DocumentPort, PortFailure, SaveDocument } from '../ports-content.ts'
|
|
24
24
|
import type { ManagementApiRouter, RequestContext } from '../router.ts'
|
|
25
25
|
import { created, paging } from './content-type.ts'
|
|
26
26
|
import {
|
|
@@ -277,7 +277,14 @@ export function registerDocumentHandlers(
|
|
|
277
277
|
router: ManagementApiRouter,
|
|
278
278
|
port: DocumentPort,
|
|
279
279
|
preview?: PreviewPort,
|
|
280
|
+
approvals?: ApprovalGatePort,
|
|
280
281
|
): void {
|
|
282
|
+
/** A page a workflow covers goes through it: the gate's refusal, as a problem. */
|
|
283
|
+
const needsApproval = (title: string, detail: string) =>
|
|
284
|
+
problemResponse(
|
|
285
|
+
problemDetails({ title, status: 400, detail, operationStatus: 'NeedsApproval' }),
|
|
286
|
+
)
|
|
287
|
+
const principalOf = (ctx: RequestContext) => ctx.principal as NonNullable<typeof ctx.principal>
|
|
281
288
|
const trees: TreeSource = {
|
|
282
289
|
treeRoot: (paging) => port.treeRoot(paging),
|
|
283
290
|
treeChildren: (key, paging) => port.treeChildren(key, paging),
|
|
@@ -330,6 +337,12 @@ export function registerDocumentHandlers(
|
|
|
330
337
|
const body = await readBody(ctx)
|
|
331
338
|
const key = typeof body.id === 'string' && body.id ? body.id : crypto.randomUUID()
|
|
332
339
|
const input = toSaveDocument(key, body)
|
|
340
|
+
const refusal = await approvals?.newPageRefusal(
|
|
341
|
+
principalOf(ctx),
|
|
342
|
+
input.parentKey,
|
|
343
|
+
input.contentTypeKey,
|
|
344
|
+
)
|
|
345
|
+
if (refusal) return needsApproval('The document needs approval before it is published', refusal)
|
|
333
346
|
const result = await port.createAndPublish(
|
|
334
347
|
input,
|
|
335
348
|
readCultures(body),
|
|
@@ -346,6 +359,10 @@ export function registerDocumentHandlers(
|
|
|
346
359
|
const body = await readBody(ctx)
|
|
347
360
|
const key = ctx.params.id as string
|
|
348
361
|
const input = toSaveDocument(key, body)
|
|
362
|
+
const refusal =
|
|
363
|
+
(await approvals?.beforeSave(principalOf(ctx), key)) ??
|
|
364
|
+
(await approvals?.publishRefusal(principalOf(ctx), key, 'publish'))
|
|
365
|
+
if (refusal) return needsApproval('The document needs approval before it is published', refusal)
|
|
349
366
|
const result = await port.updateAndPublish(
|
|
350
367
|
key,
|
|
351
368
|
input,
|
|
@@ -479,6 +496,8 @@ export function registerDocumentHandlers(
|
|
|
479
496
|
existing: DocumentAggregate,
|
|
480
497
|
body: Record<string, unknown>,
|
|
481
498
|
) => {
|
|
499
|
+
const refusal = await approvals?.beforeSave(principalOf(ctx), key)
|
|
500
|
+
if (refusal) return needsApproval('The document is waiting for approval', refusal)
|
|
482
501
|
const result = await port.update(
|
|
483
502
|
key,
|
|
484
503
|
{
|
|
@@ -497,6 +516,7 @@ export function registerDocumentHandlers(
|
|
|
497
516
|
}),
|
|
498
517
|
)
|
|
499
518
|
}
|
|
519
|
+
await approvals?.afterSave(principalOf(ctx), key)
|
|
500
520
|
ctx.notifications.push({ message: 'Document saved', category: 'Content', type: 'Success' })
|
|
501
521
|
return new Response(null, { status: 200 })
|
|
502
522
|
}
|
|
@@ -548,6 +568,8 @@ export function registerDocumentHandlers(
|
|
|
548
568
|
if (!(await port.byKey(key)))
|
|
549
569
|
return problemResponse(notFound('The document could not be found'))
|
|
550
570
|
const body = await readBody(ctx)
|
|
571
|
+
const refusal = await approvals?.publishRefusal(principalOf(ctx), key, 'publish')
|
|
572
|
+
if (refusal) return needsApproval('The document needs approval before it is published', refusal)
|
|
551
573
|
const schedules = readSchedules(body)
|
|
552
574
|
if (schedules === 'invalid')
|
|
553
575
|
return problemResponse(
|
|
@@ -592,6 +614,8 @@ export function registerDocumentHandlers(
|
|
|
592
614
|
const key = ctx.params.id as string
|
|
593
615
|
if (!(await port.byKey(key)))
|
|
594
616
|
return problemResponse(notFound('The document could not be found'))
|
|
617
|
+
const refusal = await approvals?.publishRefusal(principalOf(ctx), key, 'unpublish')
|
|
618
|
+
if (refusal) return needsApproval('Taking the document down needs approval', refusal)
|
|
595
619
|
const result = await port.unpublish(key, readCultures(await readBody(ctx)))
|
|
596
620
|
if (!result.ok) {
|
|
597
621
|
return problemResponse(
|
|
@@ -710,6 +734,9 @@ export function registerDocumentHandlers(
|
|
|
710
734
|
const branchTasks = new Map<string, { documentKey: string }>()
|
|
711
735
|
router.handle('PutDocumentByIdPublishWithDescendants', async (ctx) => {
|
|
712
736
|
const key = ctx.params.id as string
|
|
737
|
+
const refusal = await approvals?.branchRefusal(principalOf(ctx), key)
|
|
738
|
+
if (refusal)
|
|
739
|
+
return needsApproval('Part of this branch needs approval before it is published', refusal)
|
|
713
740
|
const body = await readBody(ctx)
|
|
714
741
|
const cultures = Array.isArray(body.cultures) ? (body.cultures as unknown[]).map(String) : []
|
|
715
742
|
const result = await port.publishBranch(
|
package/src/ports-content.ts
CHANGED
|
@@ -591,3 +591,26 @@ export interface TemplateQueryPort {
|
|
|
591
591
|
documentTypeAliases(): Promise<string[]>
|
|
592
592
|
execute(request: TemplateQueryRequest): Promise<{ items: TemplateQueryItem[]; total: number }>
|
|
593
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Approval before publishing (story 028): asked before a publish, an unpublish
|
|
597
|
+
* or a save, so a page a workflow covers goes through it. Each answer is
|
|
598
|
+
* undefined to go ahead, or what to tell the person.
|
|
599
|
+
*/
|
|
600
|
+
export interface ApprovalGatePort {
|
|
601
|
+
publishRefusal(
|
|
602
|
+
principal: Principal,
|
|
603
|
+
key: string,
|
|
604
|
+
action: 'publish' | 'unpublish',
|
|
605
|
+
): Promise<string | undefined>
|
|
606
|
+
/** A branch publish: the root and every page below it. */
|
|
607
|
+
branchRefusal(principal: Principal, key: string): Promise<string | undefined>
|
|
608
|
+
/** A page created and published in one go, by where it will sit and its type. */
|
|
609
|
+
newPageRefusal(
|
|
610
|
+
principal: Principal,
|
|
611
|
+
parentKey: string | null,
|
|
612
|
+
contentTypeKey: string,
|
|
613
|
+
): Promise<string | undefined>
|
|
614
|
+
beforeSave(principal: Principal, key: string): Promise<string | undefined>
|
|
615
|
+
afterSave(principal: Principal, key: string): Promise<void>
|
|
616
|
+
}
|
package/src/ports.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { ResponseOf } from '@crombolo/contracts'
|
|
11
11
|
import type { UploadSettings } from '@crombolo/core'
|
|
12
12
|
import type {
|
|
13
|
+
ApprovalGatePort,
|
|
13
14
|
BlueprintPort,
|
|
14
15
|
ContentTypePort,
|
|
15
16
|
DataTypePort,
|
|
@@ -265,6 +266,8 @@ export interface ManagementApiDeps {
|
|
|
265
266
|
dataTypes?: DataTypePort
|
|
266
267
|
templates?: TemplatePort
|
|
267
268
|
documents?: DocumentPort
|
|
269
|
+
/** Approval workflows: the gate on publishing and saving a page a workflow covers. */
|
|
270
|
+
approvals?: ApprovalGatePort
|
|
268
271
|
dictionary?: DictionaryPort
|
|
269
272
|
userManagement?: UserPort
|
|
270
273
|
userGroups?: UserGroupPort
|
package/src/routes.ts
CHANGED
|
@@ -96,7 +96,7 @@ export function createManagementApiRouter(options: ManagementApiOptions = {}): M
|
|
|
96
96
|
if (deps.dataTypes) registerDataTypeHandlers(router, deps.dataTypes)
|
|
97
97
|
if (deps.templates) registerTemplateHandlers(router, deps.templates)
|
|
98
98
|
if (deps.templateQuery) registerTemplateQueryHandlers(router, deps.templateQuery)
|
|
99
|
-
if (deps.documents) registerDocumentHandlers(router, deps.documents, deps.preview)
|
|
99
|
+
if (deps.documents) registerDocumentHandlers(router, deps.documents, deps.preview, deps.approvals)
|
|
100
100
|
if (deps.blueprints) registerDocumentBlueprintHandlers(router, deps.blueprints)
|
|
101
101
|
if (deps.temporaryFiles) registerTemporaryFileHandlers(router, deps.temporaryFiles)
|
|
102
102
|
if (deps.media) registerMediaHandlers(router, deps.media)
|