@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.2
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 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
- package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
- package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
- package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/noResult.svg +42 -42
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +265 -265
- package/migrations/0029_automations.sql +14 -14
- package/migrations/0030_test_seeds.sql +125 -125
- package/package.json +2 -2
- package/src/auth/__fixtures__/in-memory-hash-provider.ts +17 -17
- package/src/auth/__fixtures__/static-token-service.ts +22 -22
- package/src/auth/bcrypt-hash-provider.ts +24 -24
- package/src/auth/constants.ts +14 -14
- package/src/auth/generate-refresh-token.test.ts +23 -23
- package/src/auth/hash-provider.test.ts +55 -55
- package/src/auth/jose-token-service.ts +62 -62
- package/src/auth/jwt-claims-passthrough.test.ts +87 -87
- package/src/auth/login.test.ts +100 -100
- package/src/auth/login.ts +78 -78
- package/src/auth/refresh.ts +11 -11
- package/src/auth/token-service.test.ts +90 -90
- package/src/factory.ts +370 -368
- package/src/features/automations/__tests__/action-executors.test.ts +255 -255
- package/src/features/automations/__tests__/automation-runner.test.ts +196 -196
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -60
- package/src/features/automations/__tests__/automations.handler.test.ts +264 -264
- package/src/features/automations/__tests__/automations.repository.test.ts +163 -163
- package/src/features/automations/__tests__/automations.schema.test.ts +224 -224
- package/src/features/automations/__tests__/context-resolver.test.ts +126 -126
- package/src/features/automations/__tests__/cron-runner.test.ts +267 -267
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -144
- package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -383
- package/src/features/automations/__tests__/template-grammar.test.ts +308 -308
- package/src/features/automations/__tests__/webhook.executor.test.ts +142 -142
- package/src/features/automations/__tests__/when-evaluator.test.ts +274 -274
- package/src/features/automations/__tests__/when-pushdown.test.ts +281 -281
- package/src/features/automations/action-executors/create-entry.executor.ts +27 -27
- package/src/features/automations/action-executors/edit-field.executor.ts +26 -26
- package/src/features/automations/action-executors/index.ts +37 -37
- package/src/features/automations/action-executors/send-mail.executor.ts +53 -53
- package/src/features/automations/action-executors/set-variable.executor.ts +159 -159
- package/src/features/automations/action-executors/webhook.executor.ts +67 -67
- package/src/features/automations/automation-runner.ts +85 -85
- package/src/features/automations/automation-runner.utils.ts +47 -47
- package/src/features/automations/automations.handler.ts +197 -197
- package/src/features/automations/automations.schema.ts +177 -177
- package/src/features/automations/context-resolver.ts +152 -152
- package/src/features/automations/cron-runner.ts +140 -140
- package/src/features/automations/cron-runner.utils.ts +44 -44
- package/src/features/automations/filter-translation.ts +46 -46
- package/src/features/automations/index.ts +16 -16
- package/src/features/automations/template-grammar.ts +245 -245
- package/src/features/automations/var-access-resolver.ts +140 -140
- package/src/features/automations/when-evaluator.ts +87 -87
- package/src/features/automations/when-pushdown.ts +57 -57
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -359
- package/src/features/backrefs/backrefs.handler.ts +168 -168
- package/src/features/backrefs/d1-backref.repository.ts +84 -84
- package/src/features/backrefs/index.ts +5 -5
- package/src/features/content/constants.ts +16 -16
- package/src/features/content/handlers/bulk.handler.ts +185 -185
- package/src/features/content/handlers/create.ts +129 -129
- package/src/features/content/handlers/delete.ts +62 -62
- package/src/features/content/handlers/facets.ts +49 -49
- package/src/features/content/handlers/get.ts +120 -120
- package/src/features/content/handlers/helpers.test.ts +180 -180
- package/src/features/content/handlers/helpers.ts +93 -93
- package/src/features/content/handlers/list.ts +174 -174
- package/src/features/content/handlers/update.ts +174 -174
- package/src/features/content/index.ts +26 -26
- package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
- package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
- package/src/features/dashboard-layout/index.ts +5 -0
- package/src/features/draft/draft.handler.ts +195 -195
- package/src/features/draft/draft.middleware.ts +66 -66
- package/src/features/draft/index.ts +5 -5
- package/src/features/email/email.provider.ts +42 -42
- package/src/features/email/email.service.ts +98 -98
- package/src/features/email/email.types.ts +119 -119
- package/src/features/email/index.ts +33 -33
- package/src/features/email/providers/resend.ts +67 -67
- package/src/features/email/providers/smtp.ts +55 -55
- package/src/features/email/templates/automation-mail.ts +19 -19
- package/src/features/email/templates/password-changed.ts +63 -63
- package/src/features/email/templates/password-reset.ts +68 -68
- package/src/features/email/templates/shell.ts +96 -96
- package/src/features/notifications/index.ts +5 -5
- package/src/features/notifications/notifications.handler.ts +105 -105
- package/src/features/password-reset/index.ts +19 -19
- package/src/features/password-reset/request.ts +92 -92
- package/src/features/password-reset/reset.ts +102 -102
- package/src/features/rotate-field/index.ts +5 -5
- package/src/features/rotate-field/rotate-field.handler.ts +132 -132
- package/src/features/rotate-field/rotate-field.schema.ts +17 -17
- package/src/features/schema/schema.handler.ts +131 -131
- package/src/features/seeds/index.ts +5 -5
- package/src/features/seeds/seeds.handler.test.ts +1199 -632
- package/src/features/seeds/seeds.handler.ts +558 -693
- package/src/features/settings/__tests__/settings.handler.test.ts +555 -555
- package/src/features/settings/settings.handler.ts +392 -392
- package/src/features/setup/index.ts +288 -204
- package/src/features/stats/index.ts +5 -5
- package/src/features/stats/stats.handler.ts +420 -420
- package/src/features/webhooks/index.ts +63 -63
- package/src/index.ts +65 -65
- package/src/media-utils.ts +82 -82
- package/src/middleware/auth-providers.middleware.test.ts +85 -0
- package/src/middleware/auth-providers.middleware.ts +45 -45
- package/src/middleware/observability.middleware.test.ts +93 -0
- package/src/middleware/observability.middleware.ts +70 -70
- package/src/middleware/rate-limit.middleware.ts +45 -45
- package/src/middleware/repository.middleware.ts +117 -114
- package/src/middleware/seed-registry.middleware.test.ts +97 -97
- package/src/middleware/seed-registry.middleware.ts +21 -21
- package/src/middleware/storage.middleware.ts +25 -25
- package/src/middleware.ts +45 -45
- package/src/public/access-policy.ts +27 -27
- package/src/public/api-key-middleware.ts +57 -57
- package/src/public/cache-utils.ts +38 -38
- package/src/public/entry-projection.ts +46 -46
- package/src/public/idempotency.ts +23 -23
- package/src/public/index.ts +16 -16
- package/src/public/problem-details.ts +124 -124
- package/src/public/public-add.ts +114 -114
- package/src/public/public-edit.ts +163 -163
- package/src/public/public-errors.ts +19 -19
- package/src/public/public-read.ts +63 -63
- package/src/public/public-routes.ts +88 -88
- package/src/public/query-builder.test.ts +224 -224
- package/src/public/query-builder.ts +156 -156
- package/src/public/rate-limit-middleware.ts +34 -34
- package/src/public/read-list.ts +54 -54
- package/src/public/read-single.ts +48 -48
- package/src/public/response-builder.ts +30 -30
- package/src/public/sanitize.ts +69 -69
- package/src/public/slug-utils.ts +18 -18
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -30
- package/src/rate-limit/cloudflare-rate-limiter.ts +15 -15
- package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -37
- package/src/rate-limit/in-memory-rate-limiter.ts +17 -17
- package/src/rate-limit/no-op-rate-limiter.test.ts +22 -22
- package/src/rate-limit/no-op-rate-limiter.ts +11 -11
- package/src/search-utils.test.ts +211 -211
- package/src/search-utils.ts +213 -213
- package/src/search.ts +65 -65
- package/src/shared/apply-policies.test.ts +81 -81
- package/src/shared/apply-policies.ts +67 -67
- package/src/shared/automations.repository.d1.ts +150 -150
- package/src/shared/background-notification-service.test.ts +62 -62
- package/src/shared/background-notification-service.ts +52 -52
- package/src/shared/base.repository.d1.ts +32 -32
- package/src/shared/content-utils.test.ts +165 -165
- package/src/shared/content-utils.ts +86 -86
- package/src/shared/content.repository.d1.test.ts +507 -458
- package/src/shared/content.repository.d1.ts +851 -851
- package/src/shared/d1-activity-log.repository.test.ts +140 -140
- package/src/shared/d1-activity-log.repository.ts +105 -105
- package/src/shared/d1-activity-logger.test.ts +86 -86
- package/src/shared/d1-activity-logger.ts +67 -67
- package/src/shared/d1-analytics.repository.test.ts +78 -78
- package/src/shared/d1-analytics.repository.ts +85 -85
- package/src/shared/d1-content-scan.repository.test.ts +80 -80
- package/src/shared/d1-content-scan.repository.ts +37 -37
- package/src/shared/d1-notification.repository.test.ts +128 -128
- package/src/shared/d1-notification.repository.ts +118 -118
- package/src/shared/d1-password-reset-token.repository.test.ts +81 -81
- package/src/shared/d1-password-reset-token.repository.ts +56 -56
- package/src/shared/d1-search.repository.test.ts +87 -87
- package/src/shared/d1-search.repository.ts +88 -88
- package/src/shared/d1-session.repository.test.ts +125 -125
- package/src/shared/d1-session.repository.ts +102 -102
- package/src/shared/d1-setup-checklist.repository.ts +36 -36
- package/src/shared/d1-user.repository.test.ts +151 -151
- package/src/shared/d1-user.repository.ts +119 -119
- package/src/shared/d1-widget.repository.test.ts +267 -221
- package/src/shared/d1-widget.repository.ts +385 -341
- package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
- package/src/shared/dashboard-layout.repository.d1.ts +58 -0
- package/src/shared/demo-data-sql.ts +216 -54
- package/src/shared/demo-data.repository.d1.ts +20 -20
- package/src/shared/execution-context-scheduler.ts +13 -13
- package/src/shared/fixed-clock.ts +25 -25
- package/src/shared/fts-sync.ts +8 -8
- package/src/shared/idempotency.repository.d1.test.ts +83 -83
- package/src/shared/idempotency.repository.d1.ts +60 -60
- package/src/shared/in-memory-activity-logger.ts +19 -19
- package/src/shared/in-memory-notification-service.ts +19 -19
- package/src/shared/in-memory-seed.repository.ts +58 -51
- package/src/shared/media.repository.d1.test.ts +107 -107
- package/src/shared/media.repository.d1.ts +68 -68
- package/src/shared/qstash-notification-service.test.ts +67 -67
- package/src/shared/qstash-notification-service.ts +55 -55
- package/src/shared/query-utils.ts +141 -141
- package/src/shared/request-utils.ts +26 -26
- package/src/shared/schema-mutator.d1.test.ts +142 -0
- package/src/shared/schema-mutator.d1.ts +64 -64
- package/src/shared/seed-layout.repository.d1.test.ts +114 -114
- package/src/shared/seed-layout.repository.d1.ts +62 -62
- package/src/shared/seed-registry-cache.test.ts +68 -68
- package/src/shared/seed-registry-cache.ts +49 -49
- package/src/shared/seed.repository.d1.test.ts +143 -143
- package/src/shared/seed.repository.d1.ts +98 -98
- package/src/shared/sequential-id-generator.ts +33 -33
- package/src/shared/site-settings.repository.d1.ts +53 -53
- package/src/shared/storage/factory.ts +41 -41
- package/src/shared/storage/s3-bucket.ts +195 -195
- package/src/shared/storage-utils.ts +40 -40
- package/src/shared/system-stats.repository.d1.test.ts +58 -58
- package/src/shared/system-stats.repository.d1.ts +48 -48
- package/src/types.ts +90 -87
- package/src/upload.ts +198 -198
- package/src/widget.test.ts +85 -0
- package/src/widget.ts +251 -212
- package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
- package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
-
// See LICENSE in the repository root for license terms.
|
|
4
|
-
|
|
5
|
-
import { Context } from 'hono'
|
|
6
|
-
import { deleteR2Objects } from '../../../upload'
|
|
7
|
-
import { extractMediaKeysFromData } from '../../../media-utils'
|
|
8
|
-
import { publicProblem } from '../../../public/problem-details'
|
|
9
|
-
import {
|
|
10
|
-
logContentActivity,
|
|
11
|
-
dispatchContentAutomation,
|
|
12
|
-
handleContentDatabaseError
|
|
13
|
-
} from './helpers'
|
|
14
|
-
import { CONTENT_ERRORS } from '../constants'
|
|
15
|
-
import { AppEnv } from '../../../types'
|
|
16
|
-
|
|
17
|
-
export async function deleteHandler(context: Context<AppEnv>) {
|
|
18
|
-
const schemaSlug = context.req.param('slug')
|
|
19
|
-
const entryId = context.req.param('id')
|
|
20
|
-
if (!schemaSlug || !entryId) {
|
|
21
|
-
return publicProblem(context, {
|
|
22
|
-
type: 'content-invalid-slug-or-id',
|
|
23
|
-
title: 'Bad Request',
|
|
24
|
-
status: 400,
|
|
25
|
-
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const seed = context.get('getSeed')(schemaSlug)
|
|
30
|
-
if (!seed) {
|
|
31
|
-
return publicProblem(context, {
|
|
32
|
-
type: 'content-seed-not-found',
|
|
33
|
-
title: 'Not Found',
|
|
34
|
-
status: 404,
|
|
35
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const repository = context.get('repository')
|
|
41
|
-
// Repository.delete returns the row data for cleanup
|
|
42
|
-
const { row } = await repository.delete(seed, entryId)
|
|
43
|
-
|
|
44
|
-
const title = row.title || row.name || entryId
|
|
45
|
-
|
|
46
|
-
logContentActivity(context, 'delete', entryId, schemaSlug, String(title))
|
|
47
|
-
dispatchContentAutomation(context, schemaSlug, 'delete', { ...row, id: entryId })
|
|
48
|
-
|
|
49
|
-
const r2ObjectKeys = extractMediaKeysFromData(seed, row)
|
|
50
|
-
if (r2ObjectKeys.length > 0) {
|
|
51
|
-
await deleteR2Objects(context, r2ObjectKeys).catch((error) => {
|
|
52
|
-
if (context.env.ENV !== 'production') {
|
|
53
|
-
console.warn('R2 cleanup on delete failed (orphaned files):', error)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return context.json({ success: true })
|
|
59
|
-
} catch (error) {
|
|
60
|
-
return handleContentDatabaseError(context, error)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
import { Context } from 'hono'
|
|
6
|
+
import { deleteR2Objects } from '../../../upload'
|
|
7
|
+
import { extractMediaKeysFromData } from '../../../media-utils'
|
|
8
|
+
import { publicProblem } from '../../../public/problem-details'
|
|
9
|
+
import {
|
|
10
|
+
logContentActivity,
|
|
11
|
+
dispatchContentAutomation,
|
|
12
|
+
handleContentDatabaseError
|
|
13
|
+
} from './helpers'
|
|
14
|
+
import { CONTENT_ERRORS } from '../constants'
|
|
15
|
+
import { AppEnv } from '../../../types'
|
|
16
|
+
|
|
17
|
+
export async function deleteHandler(context: Context<AppEnv>) {
|
|
18
|
+
const schemaSlug = context.req.param('slug')
|
|
19
|
+
const entryId = context.req.param('id')
|
|
20
|
+
if (!schemaSlug || !entryId) {
|
|
21
|
+
return publicProblem(context, {
|
|
22
|
+
type: 'content-invalid-slug-or-id',
|
|
23
|
+
title: 'Bad Request',
|
|
24
|
+
status: 400,
|
|
25
|
+
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const seed = context.get('getSeed')(schemaSlug)
|
|
30
|
+
if (!seed) {
|
|
31
|
+
return publicProblem(context, {
|
|
32
|
+
type: 'content-seed-not-found',
|
|
33
|
+
title: 'Not Found',
|
|
34
|
+
status: 404,
|
|
35
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const repository = context.get('repository')
|
|
41
|
+
// Repository.delete returns the row data for cleanup
|
|
42
|
+
const { row } = await repository.delete(seed, entryId)
|
|
43
|
+
|
|
44
|
+
const title = row.title || row.name || entryId
|
|
45
|
+
|
|
46
|
+
logContentActivity(context, 'delete', entryId, schemaSlug, String(title))
|
|
47
|
+
dispatchContentAutomation(context, schemaSlug, 'delete', { ...row, id: entryId })
|
|
48
|
+
|
|
49
|
+
const r2ObjectKeys = extractMediaKeysFromData(seed, row)
|
|
50
|
+
if (r2ObjectKeys.length > 0) {
|
|
51
|
+
await deleteR2Objects(context, r2ObjectKeys).catch((error) => {
|
|
52
|
+
if (context.env.ENV !== 'production') {
|
|
53
|
+
console.warn('R2 cleanup on delete failed (orphaned files):', error)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return context.json({ success: true })
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return handleContentDatabaseError(context, error)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
-
// See LICENSE in the repository root for license terms.
|
|
4
|
-
|
|
5
|
-
import { Context } from 'hono'
|
|
6
|
-
import { publicProblem } from '../../../public/problem-details'
|
|
7
|
-
import { CONTENT_ERRORS } from '../constants'
|
|
8
|
-
import { AppEnv } from '../../../types'
|
|
9
|
-
|
|
10
|
-
export async function facetsHandler(context: Context<AppEnv>) {
|
|
11
|
-
const slug = context.req.param('slug')
|
|
12
|
-
if (!slug) {
|
|
13
|
-
return publicProblem(context, {
|
|
14
|
-
type: 'content-invalid-slug',
|
|
15
|
-
title: 'Bad Request',
|
|
16
|
-
status: 400,
|
|
17
|
-
detail: CONTENT_ERRORS.INVALID_SLUG
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const seed = context.get('getSeed')(slug)
|
|
22
|
-
if (!seed) {
|
|
23
|
-
return publicProblem(context, {
|
|
24
|
-
type: 'content-seed-not-found',
|
|
25
|
-
title: 'Not Found',
|
|
26
|
-
status: 404,
|
|
27
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const repository = context.get('repository')
|
|
33
|
-
const { statuses, tagsByColumn } = await repository.getFacets(seed)
|
|
34
|
-
|
|
35
|
-
// Legacy format expected by the dashboard
|
|
36
|
-
return context.json({
|
|
37
|
-
statuses: Object.keys(statuses).sort((a, b) => a.localeCompare(b, 'it')),
|
|
38
|
-
tagsByColumnId: tagsByColumn,
|
|
39
|
-
})
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error('Content facets error:', error)
|
|
42
|
-
return publicProblem(context, {
|
|
43
|
-
type: 'content-database-error',
|
|
44
|
-
title: 'Internal Server Error',
|
|
45
|
-
status: 500,
|
|
46
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
import { Context } from 'hono'
|
|
6
|
+
import { publicProblem } from '../../../public/problem-details'
|
|
7
|
+
import { CONTENT_ERRORS } from '../constants'
|
|
8
|
+
import { AppEnv } from '../../../types'
|
|
9
|
+
|
|
10
|
+
export async function facetsHandler(context: Context<AppEnv>) {
|
|
11
|
+
const slug = context.req.param('slug')
|
|
12
|
+
if (!slug) {
|
|
13
|
+
return publicProblem(context, {
|
|
14
|
+
type: 'content-invalid-slug',
|
|
15
|
+
title: 'Bad Request',
|
|
16
|
+
status: 400,
|
|
17
|
+
detail: CONTENT_ERRORS.INVALID_SLUG
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const seed = context.get('getSeed')(slug)
|
|
22
|
+
if (!seed) {
|
|
23
|
+
return publicProblem(context, {
|
|
24
|
+
type: 'content-seed-not-found',
|
|
25
|
+
title: 'Not Found',
|
|
26
|
+
status: 404,
|
|
27
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const repository = context.get('repository')
|
|
33
|
+
const { statuses, tagsByColumn } = await repository.getFacets(seed)
|
|
34
|
+
|
|
35
|
+
// Legacy format expected by the dashboard
|
|
36
|
+
return context.json({
|
|
37
|
+
statuses: Object.keys(statuses).sort((a, b) => a.localeCompare(b, 'it')),
|
|
38
|
+
tagsByColumnId: tagsByColumn,
|
|
39
|
+
})
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Content facets error:', error)
|
|
42
|
+
return publicProblem(context, {
|
|
43
|
+
type: 'content-database-error',
|
|
44
|
+
title: 'Internal Server Error',
|
|
45
|
+
status: 500,
|
|
46
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
-
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
-
// See LICENSE in the repository root for license terms.
|
|
4
|
-
|
|
5
|
-
import { Context } from 'hono'
|
|
6
|
-
import { applyVisibility } from '../../../shared/apply-policies'
|
|
7
|
-
import { publicProblem } from '../../../public/problem-details'
|
|
8
|
-
import { CONTENT_ERRORS } from '../constants'
|
|
9
|
-
import { AppEnv } from '../../../types'
|
|
10
|
-
import { EntryNotFoundError } from '@beechcms/core'
|
|
11
|
-
|
|
12
|
-
export async function getByIdHandler(context: Context<AppEnv>) {
|
|
13
|
-
const slug = context.req.param('slug')
|
|
14
|
-
const id = context.req.param('id')
|
|
15
|
-
if (!slug || !id) {
|
|
16
|
-
return publicProblem(context, {
|
|
17
|
-
type: 'content-invalid-slug-or-id',
|
|
18
|
-
title: 'Bad Request',
|
|
19
|
-
status: 400,
|
|
20
|
-
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const seed = context.get('getSeed')(slug)
|
|
25
|
-
if (!seed) {
|
|
26
|
-
return publicProblem(context, {
|
|
27
|
-
type: 'content-seed-not-found',
|
|
28
|
-
title: 'Not Found',
|
|
29
|
-
status: 404,
|
|
30
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const repository = context.get('repository')
|
|
36
|
-
const item = await repository.findById(seed, id)
|
|
37
|
-
|
|
38
|
-
let hasPendingDraft = false
|
|
39
|
-
if (seed.allowDrafts) {
|
|
40
|
-
hasPendingDraft = await repository.hasDraft(seed, id)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return context.json({
|
|
44
|
-
...item,
|
|
45
|
-
has_pending_draft: hasPendingDraft,
|
|
46
|
-
data: applyVisibility(item, seed)
|
|
47
|
-
})
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (error instanceof EntryNotFoundError) {
|
|
50
|
-
return publicProblem(context, {
|
|
51
|
-
type: 'content-not-found',
|
|
52
|
-
title: 'Not Found',
|
|
53
|
-
status: 404,
|
|
54
|
-
detail: CONTENT_ERRORS.NOT_FOUND
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
console.error('Content detail error:', error)
|
|
58
|
-
return publicProblem(context, {
|
|
59
|
-
type: 'content-database-error',
|
|
60
|
-
title: 'Internal Server Error',
|
|
61
|
-
status: 500,
|
|
62
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function getBySlugHandler(context: Context<AppEnv>) {
|
|
68
|
-
const schemaSlug = context.req.param('schema_slug')
|
|
69
|
-
const entrySlug = context.req.param('entry_slug')
|
|
70
|
-
if (!schemaSlug || !entrySlug) {
|
|
71
|
-
return publicProblem(context, {
|
|
72
|
-
type: 'content-invalid-slug-or-id',
|
|
73
|
-
title: 'Bad Request',
|
|
74
|
-
status: 400,
|
|
75
|
-
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const seed = context.get('getSeed')(schemaSlug)
|
|
80
|
-
if (!seed) {
|
|
81
|
-
return publicProblem(context, {
|
|
82
|
-
type: 'content-seed-not-found',
|
|
83
|
-
title: 'Not Found',
|
|
84
|
-
status: 404,
|
|
85
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const repository = context.get('repository')
|
|
91
|
-
const item = await repository.findBySlug(seed, entrySlug)
|
|
92
|
-
|
|
93
|
-
let hasPendingDraft = false
|
|
94
|
-
if (seed.allowDrafts) {
|
|
95
|
-
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return context.json({
|
|
99
|
-
...item,
|
|
100
|
-
has_pending_draft: hasPendingDraft,
|
|
101
|
-
data: applyVisibility(item, seed)
|
|
102
|
-
})
|
|
103
|
-
} catch (error) {
|
|
104
|
-
if (error instanceof EntryNotFoundError) {
|
|
105
|
-
return publicProblem(context, {
|
|
106
|
-
type: 'content-not-found',
|
|
107
|
-
title: 'Not Found',
|
|
108
|
-
status: 404,
|
|
109
|
-
detail: CONTENT_ERRORS.NOT_FOUND
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
console.error('Content by-slug error:', error)
|
|
113
|
-
return publicProblem(context, {
|
|
114
|
-
type: 'content-database-error',
|
|
115
|
-
title: 'Internal Server Error',
|
|
116
|
-
status: 500,
|
|
117
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
}
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
|
|
3
|
+
// See LICENSE in the repository root for license terms.
|
|
4
|
+
|
|
5
|
+
import { Context } from 'hono'
|
|
6
|
+
import { applyVisibility } from '../../../shared/apply-policies'
|
|
7
|
+
import { publicProblem } from '../../../public/problem-details'
|
|
8
|
+
import { CONTENT_ERRORS } from '../constants'
|
|
9
|
+
import { AppEnv } from '../../../types'
|
|
10
|
+
import { EntryNotFoundError } from '@beechcms/core'
|
|
11
|
+
|
|
12
|
+
export async function getByIdHandler(context: Context<AppEnv>) {
|
|
13
|
+
const slug = context.req.param('slug')
|
|
14
|
+
const id = context.req.param('id')
|
|
15
|
+
if (!slug || !id) {
|
|
16
|
+
return publicProblem(context, {
|
|
17
|
+
type: 'content-invalid-slug-or-id',
|
|
18
|
+
title: 'Bad Request',
|
|
19
|
+
status: 400,
|
|
20
|
+
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const seed = context.get('getSeed')(slug)
|
|
25
|
+
if (!seed) {
|
|
26
|
+
return publicProblem(context, {
|
|
27
|
+
type: 'content-seed-not-found',
|
|
28
|
+
title: 'Not Found',
|
|
29
|
+
status: 404,
|
|
30
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const repository = context.get('repository')
|
|
36
|
+
const item = await repository.findById(seed, id)
|
|
37
|
+
|
|
38
|
+
let hasPendingDraft = false
|
|
39
|
+
if (seed.allowDrafts) {
|
|
40
|
+
hasPendingDraft = await repository.hasDraft(seed, id)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return context.json({
|
|
44
|
+
...item,
|
|
45
|
+
has_pending_draft: hasPendingDraft,
|
|
46
|
+
data: applyVisibility(item, seed)
|
|
47
|
+
})
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof EntryNotFoundError) {
|
|
50
|
+
return publicProblem(context, {
|
|
51
|
+
type: 'content-not-found',
|
|
52
|
+
title: 'Not Found',
|
|
53
|
+
status: 404,
|
|
54
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
console.error('Content detail error:', error)
|
|
58
|
+
return publicProblem(context, {
|
|
59
|
+
type: 'content-database-error',
|
|
60
|
+
title: 'Internal Server Error',
|
|
61
|
+
status: 500,
|
|
62
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getBySlugHandler(context: Context<AppEnv>) {
|
|
68
|
+
const schemaSlug = context.req.param('schema_slug')
|
|
69
|
+
const entrySlug = context.req.param('entry_slug')
|
|
70
|
+
if (!schemaSlug || !entrySlug) {
|
|
71
|
+
return publicProblem(context, {
|
|
72
|
+
type: 'content-invalid-slug-or-id',
|
|
73
|
+
title: 'Bad Request',
|
|
74
|
+
status: 400,
|
|
75
|
+
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const seed = context.get('getSeed')(schemaSlug)
|
|
80
|
+
if (!seed) {
|
|
81
|
+
return publicProblem(context, {
|
|
82
|
+
type: 'content-seed-not-found',
|
|
83
|
+
title: 'Not Found',
|
|
84
|
+
status: 404,
|
|
85
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const repository = context.get('repository')
|
|
91
|
+
const item = await repository.findBySlug(seed, entrySlug)
|
|
92
|
+
|
|
93
|
+
let hasPendingDraft = false
|
|
94
|
+
if (seed.allowDrafts) {
|
|
95
|
+
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return context.json({
|
|
99
|
+
...item,
|
|
100
|
+
has_pending_draft: hasPendingDraft,
|
|
101
|
+
data: applyVisibility(item, seed)
|
|
102
|
+
})
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof EntryNotFoundError) {
|
|
105
|
+
return publicProblem(context, {
|
|
106
|
+
type: 'content-not-found',
|
|
107
|
+
title: 'Not Found',
|
|
108
|
+
status: 404,
|
|
109
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
console.error('Content by-slug error:', error)
|
|
113
|
+
return publicProblem(context, {
|
|
114
|
+
type: 'content-database-error',
|
|
115
|
+
title: 'Internal Server Error',
|
|
116
|
+
status: 500,
|
|
117
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|