@beechcms/api 0.4.0-preview.1 → 0.4.0-preview.11
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/assets/dashboard/BeechLogo.svg +18 -0
- package/assets/dashboard/BeechLogoLIght.svg +48 -0
- package/assets/dashboard/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/assets/dashboard/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/assets/dashboard/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/assets/dashboard/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/assets/dashboard/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/assets/dashboard/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/assets/dashboard/assets/index-9Ch2xWJr.js +554 -0
- package/assets/dashboard/assets/index-ye3325L9.css +1 -0
- package/assets/dashboard/beechLogoDark.svg +48 -0
- package/assets/dashboard/index.html +18 -0
- package/assets/dashboard/sol.svg +3 -0
- package/assets/dashboard/undraw_clouds_bmtk.svg +1 -0
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -0
- package/assets/dashboard/vite.svg +1 -0
- package/package.json +4 -3
- package/src/factory.ts +290 -21
- package/src/features/content/constants.ts +10 -0
- package/src/features/content/handlers/create.ts +163 -0
- package/src/features/content/handlers/delete.ts +85 -0
- package/src/features/content/handlers/facets.ts +45 -0
- package/src/features/content/handlers/get.ts +116 -0
- package/src/features/content/handlers/list.ts +88 -0
- package/src/features/content/handlers/update.ts +216 -0
- package/src/features/content/index.ts +20 -0
- package/src/features/draft/draft.handler.ts +203 -129
- package/src/features/password-reset/request.ts +1 -1
- package/src/features/schema/schema.handler.ts +16 -0
- package/src/features/settings/settings.handler.ts +18 -0
- package/src/index.ts +17 -337
- package/src/middleware/repository.middleware.ts +18 -0
- package/src/public/public-add.ts +72 -89
- package/src/public/public-edit.ts +51 -76
- package/src/public/public-read.ts +113 -114
- package/src/public/query-builder.ts +47 -136
- package/src/search-utils.ts +5 -1
- package/src/shared/base.repository.d1.ts +28 -0
- package/src/shared/content.repository.d1.ts +382 -0
- package/src/shared/idempotency.repository.d1.ts +45 -0
- package/src/types.ts +7 -1
- package/src/upload.ts +3 -7
- package/src/content.ts +0 -502
- package/src/features/draft/draft.test.ts +0 -315
- package/src/features/rotate-field/rotate-field.test.ts +0 -297
|
@@ -1,198 +1,272 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
import { Hono } from 'hono'
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
validateAndSanitizeSeedPayload,
|
|
5
|
+
resolvePolicies,
|
|
6
|
+
EntryNotFoundError
|
|
7
|
+
} from '@beechcms/core'
|
|
5
8
|
import { publicProblem } from '../../public/problem-details'
|
|
6
9
|
import { logActivity } from '../../shared/activity-logger'
|
|
7
10
|
import { cleanStr } from '../../shared/query-utils'
|
|
8
11
|
import { applyVisibility } from '../../shared/apply-policies'
|
|
12
|
+
import { AppEnv } from '../../types'
|
|
13
|
+
import { CONTENT_ERRORS } from '../content/constants'
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
type Variables = {
|
|
12
|
-
jwtPayload: { sub: string; email?: string }
|
|
13
|
-
getSeed: (slug: string) => Seed | null
|
|
14
|
-
seedRegistry: Record<string, Seed>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const draftApp = new Hono<{ Bindings: Bindings; Variables: Variables }>()
|
|
15
|
+
const draftApp = new Hono<AppEnv>()
|
|
18
16
|
|
|
19
17
|
function normalizeBody(raw: unknown): Record<string, unknown> {
|
|
20
18
|
return typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>) : {}
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
function draftNotAllowed(
|
|
24
|
-
return publicProblem(
|
|
25
|
-
type: 'draft-not-allowed',
|
|
21
|
+
function draftNotAllowed(context: any) {
|
|
22
|
+
return publicProblem(context, {
|
|
23
|
+
type: 'draft-not-allowed',
|
|
24
|
+
title: 'Method Not Allowed',
|
|
25
|
+
status: 405,
|
|
26
26
|
detail: 'This content type does not support pending drafts. Set allowDrafts: true on the Seed to enable.',
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// PUT /:slug/:id/draft — crea o sovrascrive la bozza in content_{slug}_drafts
|
|
31
|
-
draftApp.put('/:slug/:id/draft', async (
|
|
32
|
-
const slug =
|
|
33
|
-
const id =
|
|
34
|
-
|
|
35
|
-
const seed =
|
|
36
|
-
if (!seed)
|
|
37
|
-
|
|
31
|
+
draftApp.put('/:slug/:id/draft', async (context) => {
|
|
32
|
+
const slug = context.req.param('slug')
|
|
33
|
+
const id = context.req.param('id')
|
|
34
|
+
|
|
35
|
+
const seed = context.get('getSeed')(slug)
|
|
36
|
+
if (!seed) {
|
|
37
|
+
return publicProblem(context, {
|
|
38
|
+
type: 'content-seed-not-found',
|
|
39
|
+
title: 'Not Found',
|
|
40
|
+
status: 404,
|
|
41
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!seed.allowDrafts) return draftNotAllowed(context)
|
|
38
46
|
|
|
39
47
|
let body: Record<string, unknown>
|
|
40
48
|
try {
|
|
41
|
-
body = normalizeBody(await
|
|
49
|
+
body = normalizeBody(await context.req.json<unknown>())
|
|
42
50
|
} catch {
|
|
43
|
-
return publicProblem(
|
|
51
|
+
return publicProblem(context, {
|
|
52
|
+
type: 'content-invalid-json',
|
|
53
|
+
title: 'Bad Request',
|
|
54
|
+
status: 400,
|
|
55
|
+
detail: CONTENT_ERRORS.INVALID_JSON_BODY
|
|
56
|
+
})
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const repository = context.get('repository')
|
|
60
|
+
try {
|
|
61
|
+
// Verify entry existence
|
|
62
|
+
await repository.findById(seed, id)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof EntryNotFoundError) {
|
|
65
|
+
return publicProblem(context, {
|
|
66
|
+
type: 'content-not-found',
|
|
67
|
+
title: 'Not Found',
|
|
68
|
+
status: 404,
|
|
69
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
throw error
|
|
73
|
+
}
|
|
49
74
|
|
|
50
75
|
const sensitiveAliases = Object.keys(body).filter((alias) => {
|
|
51
76
|
const branch = seed.branches.find((b) => b.alias === alias)
|
|
52
77
|
return branch != null && resolvePolicies(branch).privacy !== 'plain'
|
|
53
78
|
})
|
|
79
|
+
|
|
54
80
|
if (sensitiveAliases.length > 0) {
|
|
55
|
-
return publicProblem(
|
|
81
|
+
return publicProblem(context, {
|
|
82
|
+
type: 'content-sensitive-field-edit',
|
|
83
|
+
title: 'Unprocessable Entity',
|
|
84
|
+
status: 422,
|
|
85
|
+
detail: `${CONTENT_ERRORS.SENSITIVE_FIELD_EDIT}: ${sensitiveAliases.join(', ')}`
|
|
86
|
+
})
|
|
56
87
|
}
|
|
57
88
|
|
|
58
89
|
const validation = validateAndSanitizeSeedPayload(seed, body, {
|
|
59
|
-
operation: 'update',
|
|
90
|
+
operation: 'update',
|
|
91
|
+
allowNull: true,
|
|
92
|
+
requireAtLeastOneValidField: true,
|
|
93
|
+
enforceRequiredFields: false,
|
|
60
94
|
})
|
|
95
|
+
|
|
61
96
|
if (validation.dangerousFields.length > 0) {
|
|
62
|
-
return publicProblem(
|
|
97
|
+
return publicProblem(context, {
|
|
98
|
+
type: 'content-dangerous-content',
|
|
99
|
+
title: 'Unprocessable Entity',
|
|
100
|
+
status: 422,
|
|
101
|
+
detail: `Dangerous markup in field '${validation.dangerousFields[0]}'`
|
|
102
|
+
})
|
|
63
103
|
}
|
|
104
|
+
|
|
64
105
|
if (validation.details.length > 0) {
|
|
65
|
-
return publicProblem(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const bindings: (string | number | null)[] = []
|
|
73
|
-
for (const branch of seed.branches) {
|
|
74
|
-
if (Object.hasOwn(validation.data, branch.alias)) {
|
|
75
|
-
cols.push(branch.alias)
|
|
76
|
-
placeholders.push('?')
|
|
77
|
-
bindings.push(serializeForDb(branch, validation.data[branch.alias]))
|
|
78
|
-
}
|
|
106
|
+
return publicProblem(context, {
|
|
107
|
+
type: 'content-validation-failed',
|
|
108
|
+
title: 'Bad Request',
|
|
109
|
+
status: 400,
|
|
110
|
+
detail: 'Validation failed',
|
|
111
|
+
errors: validation.details
|
|
112
|
+
})
|
|
79
113
|
}
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
await repository.saveDraft(seed, id, validation.data)
|
|
116
|
+
|
|
117
|
+
logActivity(context, {
|
|
118
|
+
action: 'update',
|
|
119
|
+
entityType: 'content',
|
|
120
|
+
entityId: id,
|
|
121
|
+
entitySlug: slug,
|
|
122
|
+
details: {
|
|
123
|
+
title: cleanStr(validation.data[seed.displayNameAlias]) ?? id,
|
|
124
|
+
note: 'draft saved'
|
|
125
|
+
},
|
|
92
126
|
})
|
|
93
127
|
|
|
94
|
-
return
|
|
128
|
+
return context.json({ success: true })
|
|
95
129
|
})
|
|
96
130
|
|
|
97
131
|
// GET /:slug/:id/draft — legge la bozza pendente
|
|
98
|
-
draftApp.get('/:slug/:id/draft', async (
|
|
99
|
-
const slug =
|
|
100
|
-
const id =
|
|
101
|
-
|
|
102
|
-
const seed =
|
|
103
|
-
if (!seed)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!row) {
|
|
112
|
-
// Verifica se entry esiste
|
|
113
|
-
const entry = await DB.prepare(`SELECT id FROM content_${slug} WHERE id = ?`).bind(id).first()
|
|
114
|
-
if (!entry) return publicProblem(c, { type: 'content-not-found', title: 'Not Found', status: 404, detail: 'Not found' })
|
|
115
|
-
return publicProblem(c, { type: 'draft-not-found', title: 'Not Found', status: 404, detail: 'No pending draft for this entry' })
|
|
132
|
+
draftApp.get('/:slug/:id/draft', async (context) => {
|
|
133
|
+
const slug = context.req.param('slug')
|
|
134
|
+
const id = context.req.param('id')
|
|
135
|
+
|
|
136
|
+
const seed = context.get('getSeed')(slug)
|
|
137
|
+
if (!seed) {
|
|
138
|
+
return publicProblem(context, {
|
|
139
|
+
type: 'content-seed-not-found',
|
|
140
|
+
title: 'Not Found',
|
|
141
|
+
status: 404,
|
|
142
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
143
|
+
})
|
|
116
144
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
|
|
146
|
+
if (!seed.allowDrafts) return draftNotAllowed(context)
|
|
147
|
+
|
|
148
|
+
const repository = context.get('repository')
|
|
149
|
+
const draft = await repository.getDraft(seed, id)
|
|
150
|
+
|
|
151
|
+
if (!draft) {
|
|
152
|
+
try {
|
|
153
|
+
await repository.findById(seed, id)
|
|
154
|
+
return publicProblem(context, {
|
|
155
|
+
type: 'draft-not-found',
|
|
156
|
+
title: 'Not Found',
|
|
157
|
+
status: 404,
|
|
158
|
+
detail: 'No pending draft for this entry'
|
|
159
|
+
})
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof EntryNotFoundError) {
|
|
162
|
+
return publicProblem(context, {
|
|
163
|
+
type: 'content-not-found',
|
|
164
|
+
title: 'Not Found',
|
|
165
|
+
status: 404,
|
|
166
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
throw error
|
|
123
170
|
}
|
|
124
171
|
}
|
|
125
172
|
|
|
126
|
-
return
|
|
173
|
+
return context.json({ data: applyVisibility(draft, seed) })
|
|
127
174
|
})
|
|
128
175
|
|
|
129
176
|
// POST /:slug/:id/draft/publish — promuove bozza → live atomicamente
|
|
130
|
-
draftApp.post('/:slug/:id/draft/publish', async (
|
|
131
|
-
const slug =
|
|
132
|
-
const id =
|
|
133
|
-
|
|
134
|
-
const seed =
|
|
135
|
-
if (!seed)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!draftRow) {
|
|
144
|
-
const entry = await DB.prepare(`SELECT id FROM content_${slug} WHERE id = ?`).bind(id).first()
|
|
145
|
-
if (!entry) return publicProblem(c, { type: 'content-not-found', title: 'Not Found', status: 404, detail: 'Not found' })
|
|
146
|
-
return publicProblem(c, { type: 'draft-not-found', title: 'Not Found', status: 404, detail: 'No pending draft to publish' })
|
|
177
|
+
draftApp.post('/:slug/:id/draft/publish', async (context) => {
|
|
178
|
+
const slug = context.req.param('slug')
|
|
179
|
+
const id = context.req.param('id')
|
|
180
|
+
|
|
181
|
+
const seed = context.get('getSeed')(slug)
|
|
182
|
+
if (!seed) {
|
|
183
|
+
return publicProblem(context, {
|
|
184
|
+
type: 'content-seed-not-found',
|
|
185
|
+
title: 'Not Found',
|
|
186
|
+
status: 404,
|
|
187
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
188
|
+
})
|
|
147
189
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
190
|
+
|
|
191
|
+
if (!seed.allowDrafts) return draftNotAllowed(context)
|
|
192
|
+
|
|
193
|
+
const repository = context.get('repository')
|
|
194
|
+
const draft = await repository.getDraft(seed, id)
|
|
195
|
+
|
|
196
|
+
if (!draft) {
|
|
197
|
+
try {
|
|
198
|
+
await repository.findById(seed, id)
|
|
199
|
+
return publicProblem(context, {
|
|
200
|
+
type: 'draft-not-found',
|
|
201
|
+
title: 'Not Found',
|
|
202
|
+
status: 404,
|
|
203
|
+
detail: 'No pending draft to publish'
|
|
204
|
+
})
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (error instanceof EntryNotFoundError) {
|
|
207
|
+
return publicProblem(context, {
|
|
208
|
+
type: 'content-not-found',
|
|
209
|
+
title: 'Not Found',
|
|
210
|
+
status: 404,
|
|
211
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
throw error
|
|
158
215
|
}
|
|
159
216
|
}
|
|
160
217
|
|
|
161
|
-
await
|
|
162
|
-
DB.prepare(`UPDATE content_${slug} SET ${setParts.join(', ')} WHERE id = ?`)
|
|
163
|
-
.bind(...setBindings, id),
|
|
164
|
-
DB.prepare(`DELETE FROM content_${slug}_drafts WHERE entry_id = ?`)
|
|
165
|
-
.bind(id),
|
|
166
|
-
])
|
|
218
|
+
await repository.publishDraft(seed, id)
|
|
167
219
|
|
|
168
|
-
|
|
169
|
-
const displayValue = draftRow[seed.displayNameAlias]
|
|
220
|
+
const displayValue = draft[seed.displayNameAlias]
|
|
170
221
|
const displayStr = typeof displayValue === 'string' ? displayValue : id
|
|
171
222
|
|
|
172
|
-
logActivity(
|
|
173
|
-
action: 'update',
|
|
223
|
+
logActivity(context, {
|
|
224
|
+
action: 'update',
|
|
225
|
+
entityType: 'content',
|
|
226
|
+
entityId: id,
|
|
227
|
+
entitySlug: slug,
|
|
174
228
|
details: { title: displayStr, note: 'draft published' },
|
|
175
229
|
})
|
|
176
230
|
|
|
177
|
-
return
|
|
231
|
+
return context.json({ success: true })
|
|
178
232
|
})
|
|
179
233
|
|
|
180
234
|
// DELETE /:slug/:id/draft — scarta la bozza pendente
|
|
181
|
-
draftApp.delete('/:slug/:id/draft', async (
|
|
182
|
-
const slug =
|
|
183
|
-
const id =
|
|
184
|
-
|
|
185
|
-
const seed =
|
|
186
|
-
if (!seed)
|
|
187
|
-
|
|
235
|
+
draftApp.delete('/:slug/:id/draft', async (context) => {
|
|
236
|
+
const slug = context.req.param('slug')
|
|
237
|
+
const id = context.req.param('id')
|
|
238
|
+
|
|
239
|
+
const seed = context.get('getSeed')(slug)
|
|
240
|
+
if (!seed) {
|
|
241
|
+
return publicProblem(context, {
|
|
242
|
+
type: 'content-seed-not-found',
|
|
243
|
+
title: 'Not Found',
|
|
244
|
+
status: 404,
|
|
245
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!seed.allowDrafts) return draftNotAllowed(context)
|
|
188
250
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
251
|
+
const repository = context.get('repository')
|
|
252
|
+
try {
|
|
253
|
+
await repository.findById(seed, id)
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error instanceof EntryNotFoundError) {
|
|
256
|
+
return publicProblem(context, {
|
|
257
|
+
type: 'content-not-found',
|
|
258
|
+
title: 'Not Found',
|
|
259
|
+
status: 404,
|
|
260
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
throw error
|
|
264
|
+
}
|
|
192
265
|
|
|
193
|
-
await
|
|
266
|
+
await repository.deleteDraft(seed, id)
|
|
194
267
|
|
|
195
|
-
return
|
|
268
|
+
return context.json({ success: true })
|
|
196
269
|
})
|
|
197
270
|
|
|
198
271
|
export { draftApp }
|
|
272
|
+
|
|
@@ -67,7 +67,7 @@ export async function requestPasswordReset(
|
|
|
67
67
|
.run()
|
|
68
68
|
|
|
69
69
|
const appUrl = (c.env.APP_URL ?? new URL(c.req.url).origin).replace(/\/$/, '')
|
|
70
|
-
const resetUrl = `${appUrl}/reset-password?token=${token}`
|
|
70
|
+
const resetUrl = `${appUrl}/admin/reset-password?token=${token}`
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
73
|
await sendPasswordResetEmail({
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { Hono } from 'hono'
|
|
3
|
+
import type { Env, Variables } from '../../types'
|
|
4
|
+
|
|
5
|
+
const schemaApp = new Hono<{ Bindings: Env; Variables: Variables }>()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ritorna l'intero schema del CMS (la lista dei Seed configurati).
|
|
9
|
+
* Usato dalla Dashboard per generare dinamicamente il menu e le form.
|
|
10
|
+
*/
|
|
11
|
+
schemaApp.get('/', async (c) => {
|
|
12
|
+
const registry = c.get('seedRegistry')
|
|
13
|
+
return c.json(Object.values(registry))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export { schemaApp }
|
|
@@ -43,6 +43,24 @@ type OrphanRow = {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
// GET /api/settings
|
|
47
|
+
settingsApp.get('/', async (c) => {
|
|
48
|
+
// General site configuration.
|
|
49
|
+
// In the future, these could be loaded from a 'system_settings' table in D1.
|
|
50
|
+
return c.json({
|
|
51
|
+
siteTitle: 'Beech CMS',
|
|
52
|
+
siteLogo: '/beechLogoDark.svg',
|
|
53
|
+
defaultLanguage: 'it',
|
|
54
|
+
dateFormat: c.env.DATE_FORMAT || 'DD-MM-YYYY',
|
|
55
|
+
features: {
|
|
56
|
+
drafts: true,
|
|
57
|
+
media: true,
|
|
58
|
+
search: true,
|
|
59
|
+
activityLog: true
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
46
64
|
// GET /api/settings/me
|
|
47
65
|
settingsApp.get('/me', async (c) => {
|
|
48
66
|
const { sub } = c.get('jwtPayload')
|