@crombolo/api-management 0.0.0-stage → 0.10.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +69 -0
  3. package/package.json +32 -4
  4. package/src/authorization.ts +453 -0
  5. package/src/handlers/content-type.ts +784 -0
  6. package/src/handlers/data-type.ts +267 -0
  7. package/src/handlers/dictionary.ts +245 -0
  8. package/src/handlers/document-blueprint.ts +193 -0
  9. package/src/handlers/document.ts +961 -0
  10. package/src/handlers/editor-services.ts +59 -0
  11. package/src/handlers/element.ts +492 -0
  12. package/src/handlers/file-systems.ts +207 -0
  13. package/src/handlers/import.ts +46 -0
  14. package/src/handlers/localization.ts +116 -0
  15. package/src/handlers/log-viewer.ts +125 -0
  16. package/src/handlers/manifest.ts +15 -0
  17. package/src/handlers/media.ts +453 -0
  18. package/src/handlers/member-group.ts +87 -0
  19. package/src/handlers/member.ts +189 -0
  20. package/src/handlers/models-builder.ts +29 -0
  21. package/src/handlers/package.ts +193 -0
  22. package/src/handlers/public-access.ts +107 -0
  23. package/src/handlers/published-cache.ts +27 -0
  24. package/src/handlers/redirects.ts +78 -0
  25. package/src/handlers/references.ts +44 -0
  26. package/src/handlers/server.ts +25 -0
  27. package/src/handlers/settings-stubs.ts +76 -0
  28. package/src/handlers/start-node-trees.ts +84 -0
  29. package/src/handlers/template-query.ts +191 -0
  30. package/src/handlers/template.ts +150 -0
  31. package/src/handlers/temporary-file.ts +62 -0
  32. package/src/handlers/user.ts +604 -0
  33. package/src/index.ts +17 -0
  34. package/src/ports-content.ts +564 -0
  35. package/src/ports-files.ts +51 -0
  36. package/src/ports-logs.ts +38 -0
  37. package/src/ports-members.ts +111 -0
  38. package/src/ports-models-builder.ts +26 -0
  39. package/src/ports-packages.ts +59 -0
  40. package/src/ports-users.ts +230 -0
  41. package/src/ports.ts +225 -0
  42. package/src/router.ts +216 -0
  43. package/src/routes.ts +99 -0
  44. package/src/server-events.ts +71 -0
  45. package/README.md +0 -3
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Document blueprints: their tree and folders in Settings, the editor, "Create
3
+ * Document Blueprint" from a page, and the scaffold a new page starts from.
4
+ */
5
+ import type { DocumentAggregate, TreeItem } from '@crombolo/core'
6
+ import { invalidSkipTake, notFound, problemDetails, problemResponse } from '@crombolo/core'
7
+ import type { BlueprintPort, PortFailure } from '../ports-content.ts'
8
+ import type { ManagementApiRouter, RequestContext } from '../router.ts'
9
+ import { created, paging, registerFolderHandlers } from './content-type.ts'
10
+ import { auditLogResponse, readBody, toDocumentResponse, toSaveDocument } from './document.ts'
11
+
12
+ const API = '/umbraco/management/api/v1'
13
+
14
+ function toBlueprintResponse(aggregate: DocumentAggregate, id = aggregate.key) {
15
+ const document = toDocumentResponse(aggregate)
16
+ return {
17
+ id,
18
+ documentType: document.documentType,
19
+ values: document.values,
20
+ variants: document.variants.map((variant) => ({ ...variant, id, state: 'Draft' })),
21
+ flags: [],
22
+ }
23
+ }
24
+
25
+ function toBlueprintTreeItem(item: TreeItem) {
26
+ return {
27
+ id: item.key,
28
+ name: item.name,
29
+ isFolder: item.isFolder,
30
+ noAccess: false,
31
+ hasChildren: item.hasChildren,
32
+ parent: item.parentKey ? { id: item.parentKey } : null,
33
+ documentType: item.isFolder
34
+ ? null
35
+ : { id: item.contentTypeKey ?? '', icon: item.icon ?? 'icon-blueprint', collection: null },
36
+ flags: [],
37
+ }
38
+ }
39
+
40
+ const refId = (value: unknown): string | null => {
41
+ if (!value || typeof value !== 'object') return null
42
+ const id = (value as Record<string, unknown>).id
43
+ return typeof id === 'string' && id ? id : null
44
+ }
45
+
46
+ const failed = (title: string, result: PortFailure) =>
47
+ problemResponse(problemDetails({ title, status: result.status ?? 400, detail: result.reason }))
48
+
49
+ export function registerDocumentBlueprintHandlers(
50
+ router: ManagementApiRouter,
51
+ port: BlueprintPort,
52
+ ): void {
53
+ const principal = (ctx: RequestContext) => ctx.principal as NonNullable<typeof ctx.principal>
54
+ const notifyDone = (ctx: RequestContext, message: string) =>
55
+ ctx.notifications.push({ message, category: 'Document Blueprint', type: 'Success' })
56
+
57
+ router.handle('GetDocumentBlueprintById', async ({ params }) => {
58
+ const blueprint = await port.byKey(params.id as string)
59
+ if (!blueprint) return problemResponse(notFound('The blueprint could not be found'))
60
+ return Response.json(toBlueprintResponse(blueprint))
61
+ })
62
+
63
+ /** A fresh copy to start a page from: the blueprint's values under a new id. */
64
+ router.handle('GetDocumentBlueprintByIdScaffold', async ({ params }) => {
65
+ const blueprint = await port.byKey(params.id as string)
66
+ if (!blueprint) return problemResponse(notFound('The blueprint could not be found'))
67
+ return Response.json(toBlueprintResponse(blueprint, crypto.randomUUID()))
68
+ })
69
+
70
+ router.handle('PostDocumentBlueprint', async (ctx) => {
71
+ const body = await readBody(ctx)
72
+ const key = typeof body.id === 'string' && body.id ? body.id : crypto.randomUUID()
73
+ const result = await port.create(toSaveDocument(key, body), principal(ctx))
74
+ if (!result.ok) return failed('Could not create the blueprint', result)
75
+ notifyDone(ctx, 'Document Blueprint saved')
76
+ return created(`${ctx.url.origin}${API}/document-blueprint/${key}`, key)
77
+ })
78
+
79
+ router.handle('PutDocumentBlueprintById', async (ctx) => {
80
+ const key = ctx.params.id as string
81
+ const existing = await port.byKey(key)
82
+ if (!existing) return problemResponse(notFound('The blueprint could not be found'))
83
+ const body = await readBody(ctx)
84
+ const result = await port.update(
85
+ key,
86
+ {
87
+ ...toSaveDocument(key, body),
88
+ contentTypeKey: existing.contentTypeKey,
89
+ parentKey: existing.parentKey,
90
+ },
91
+ principal(ctx),
92
+ )
93
+ if (!result.ok) return failed('Could not save the blueprint', result)
94
+ notifyDone(ctx, 'Document Blueprint saved')
95
+ return new Response(null, { status: 200 })
96
+ })
97
+
98
+ router.handle('DeleteDocumentBlueprintById', async (ctx) => {
99
+ const removed = await port.remove(ctx.params.id as string)
100
+ if (!removed) return problemResponse(notFound('The blueprint could not be found'))
101
+ return new Response(null, { status: 200 })
102
+ })
103
+
104
+ router.handle('PostDocumentBlueprintFromDocument', async (ctx) => {
105
+ const body = await readBody(ctx)
106
+ const documentKey = refId(body.document)
107
+ const name = String(body.name ?? '').trim()
108
+ if (!documentKey || !name)
109
+ return problemResponse(
110
+ problemDetails({ title: 'A document and a name are required', status: 400 }),
111
+ )
112
+ const key = typeof body.id === 'string' && body.id ? body.id : crypto.randomUUID()
113
+ const result = await port.fromDocument(
114
+ documentKey,
115
+ { key, name, parentKey: refId(body.parent) },
116
+ principal(ctx),
117
+ )
118
+ if (!result.ok) return failed('Could not create the blueprint', result)
119
+ notifyDone(ctx, 'Document Blueprint created')
120
+ return created(`${ctx.url.origin}${API}/document-blueprint/${result.key}`, result.key)
121
+ })
122
+
123
+ router.handle('PutDocumentBlueprintByIdMove', async (ctx) => {
124
+ const result = await port.move(ctx.params.id as string, refId((await readBody(ctx)).target))
125
+ if (!result.ok) return failed('Could not move the blueprint', result)
126
+ return new Response(null, { status: 200 })
127
+ })
128
+
129
+ router.handle('GetDocumentBlueprintByIdAuditLog', (ctx) => auditLogResponse(ctx, port))
130
+
131
+ router.handle('GetDocumentTypeByIdBlueprint', async (ctx) => {
132
+ const page = paging(ctx)
133
+ if (!page) return problemResponse(invalidSkipTake())
134
+ const result = await port.forContentType(ctx.params.id as string, page)
135
+ return Response.json({
136
+ total: result.total,
137
+ items: result.items.map((b) => ({ id: b.key, name: b.name, flags: [] })),
138
+ })
139
+ })
140
+
141
+ router.handle('GetItemDocumentBlueprint', async (ctx) => {
142
+ const found = await port.items(ctx.url.searchParams.getAll('id'))
143
+ return Response.json(
144
+ found.map((b) => ({
145
+ id: b.key,
146
+ name: b.name,
147
+ documentType: { id: b.contentTypeKey, icon: b.icon, collection: null },
148
+ flags: [],
149
+ })),
150
+ )
151
+ })
152
+
153
+ const foldersOnly = (ctx: RequestContext) => ctx.url.searchParams.get('foldersOnly') === 'true'
154
+ router.handle('GetTreeDocumentBlueprintRoot', async (ctx) => {
155
+ const page = paging(ctx)
156
+ if (!page) return problemResponse(invalidSkipTake())
157
+ const result = await port.treeRoot({ ...page, foldersOnly: foldersOnly(ctx) })
158
+ return Response.json({ total: result.total, items: result.items.map(toBlueprintTreeItem) })
159
+ })
160
+ router.handle('GetTreeDocumentBlueprintChildren', async (ctx) => {
161
+ const page = paging(ctx)
162
+ if (!page) return problemResponse(invalidSkipTake())
163
+ const parentId = ctx.url.searchParams.get('parentId')
164
+ if (!parentId)
165
+ return problemResponse(problemDetails({ title: 'parentId is required', status: 400 }))
166
+ const result = await port.treeChildren(parentId, { ...page, foldersOnly: foldersOnly(ctx) })
167
+ return Response.json({ total: result.total, items: result.items.map(toBlueprintTreeItem) })
168
+ })
169
+ router.handle('GetTreeDocumentBlueprintAncestors', async (ctx) => {
170
+ const descendantId = ctx.url.searchParams.get('descendantId')
171
+ if (!descendantId) return Response.json([])
172
+ return Response.json((await port.treeAncestors(descendantId)).map(toBlueprintTreeItem))
173
+ })
174
+ router.handle('GetTreeDocumentBlueprintSiblings', async (ctx) => {
175
+ const target = ctx.url.searchParams.get('target')
176
+ if (!target)
177
+ return problemResponse(problemDetails({ title: 'target is required', status: 400 }))
178
+ const window = await port.treeSiblings(
179
+ target,
180
+ Number(ctx.url.searchParams.get('before') ?? 0),
181
+ Number(ctx.url.searchParams.get('after') ?? 0),
182
+ foldersOnly(ctx),
183
+ )
184
+ if (!window) return problemResponse(notFound('The item could not be found'))
185
+ return Response.json({
186
+ totalBefore: window.totalBefore,
187
+ totalAfter: window.totalAfter,
188
+ items: window.items.map(toBlueprintTreeItem),
189
+ })
190
+ })
191
+
192
+ registerFolderHandlers(router, 'DocumentBlueprint', port.folders, toBlueprintTreeItem)
193
+ }