@beechcms/api 0.5.0 → 0.6.0-preview.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/assets/dashboard/Searching.svg +1 -0
- package/assets/dashboard/assets/index-BSjk0Sx9.js +634 -0
- package/assets/dashboard/assets/index-kRCYVN2B.css +1 -0
- package/assets/dashboard/index.html +2 -2
- package/assets/dashboard/noResult.svg +43 -0
- package/assets/dashboard/working.svg +1 -0
- package/migrations/0000_v040_base.sql +27 -0
- package/package.json +13 -9
- package/src/auth/{in-memory-hash-provider.ts → __fixtures__/in-memory-hash-provider.ts} +4 -0
- package/src/auth/{static-token-service.ts → __fixtures__/static-token-service.ts} +4 -0
- package/src/auth/bcrypt-hash-provider.ts +6 -2
- package/src/auth/constants.ts +4 -0
- package/src/auth/generate-refresh-token.test.ts +8 -4
- package/src/auth/hash-provider.test.ts +14 -5
- package/src/auth/jose-token-service.ts +7 -0
- package/src/auth/jwt-claims-passthrough.test.ts +87 -0
- package/src/auth/login.test.ts +9 -1
- package/src/auth/login.ts +5 -1
- package/src/auth/refresh.ts +7 -1
- package/src/auth/token-service.test.ts +9 -1
- package/src/factory.ts +37 -16
- package/src/features/automations/__tests__/action-executors.test.ts +59 -72
- package/src/features/automations/__tests__/automation-runner.test.ts +4 -0
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +4 -0
- package/src/features/automations/__tests__/automations.handler.test.ts +7 -3
- package/src/features/automations/__tests__/automations.repository.test.ts +4 -0
- package/src/features/automations/__tests__/automations.schema.test.ts +92 -2
- package/src/features/automations/__tests__/context-resolver.test.ts +4 -0
- package/src/features/automations/__tests__/cron-runner.test.ts +11 -7
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +4 -0
- package/src/features/automations/__tests__/set-variable.executor.test.ts +77 -0
- package/src/features/automations/__tests__/template-grammar.test.ts +4 -0
- package/src/features/automations/__tests__/webhook.executor.test.ts +142 -0
- package/src/features/automations/__tests__/when-evaluator.test.ts +4 -0
- package/src/features/automations/__tests__/when-pushdown.test.ts +5 -1
- package/src/features/automations/action-executors/create-entry.executor.ts +4 -0
- package/src/features/automations/action-executors/edit-field.executor.ts +4 -0
- package/src/features/automations/action-executors/index.ts +5 -1
- package/src/features/automations/action-executors/send-mail.executor.ts +13 -2
- package/src/features/automations/action-executors/set-variable.executor.ts +15 -2
- package/src/features/automations/action-executors/webhook.executor.ts +55 -13
- package/src/features/automations/automation-runner.ts +4 -0
- package/src/features/automations/automation-runner.utils.ts +4 -0
- package/src/features/automations/automations.handler.ts +4 -0
- package/src/features/automations/automations.schema.ts +19 -2
- package/src/features/automations/context-resolver.ts +4 -0
- package/src/features/automations/cron-runner.ts +4 -0
- package/src/features/automations/cron-runner.utils.ts +4 -0
- package/src/features/automations/filter-translation.ts +4 -0
- package/src/features/automations/index.ts +4 -0
- package/src/features/automations/template-grammar.ts +4 -0
- package/src/features/automations/var-access-resolver.ts +4 -0
- package/src/features/automations/when-evaluator.ts +4 -0
- package/src/features/automations/when-pushdown.ts +4 -0
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -0
- package/src/features/backrefs/backrefs.handler.ts +168 -0
- package/src/features/backrefs/d1-backref.repository.ts +84 -0
- package/src/features/backrefs/index.ts +5 -0
- package/src/features/content/constants.ts +6 -0
- package/src/features/content/handlers/bulk.handler.ts +185 -0
- package/src/features/content/handlers/create.ts +19 -55
- package/src/features/content/handlers/delete.ts +12 -37
- package/src/features/content/handlers/facets.ts +4 -0
- package/src/features/content/handlers/get.ts +4 -0
- package/src/features/content/handlers/helpers.test.ts +180 -0
- package/src/features/content/handlers/helpers.ts +93 -0
- package/src/features/content/handlers/list.ts +105 -19
- package/src/features/content/handlers/update.ts +19 -64
- package/src/features/content/index.ts +6 -0
- package/src/features/draft/draft.handler.ts +33 -8
- package/src/features/draft/draft.middleware.ts +4 -0
- package/src/features/draft/index.ts +4 -0
- package/src/features/email/email.provider.ts +4 -0
- package/src/features/email/email.service.ts +50 -45
- package/src/features/email/email.types.ts +12 -1
- package/src/features/email/index.ts +4 -0
- package/src/features/email/providers/resend.ts +4 -0
- package/src/features/email/providers/smtp.ts +55 -0
- package/src/features/email/templates/automation-mail.ts +4 -0
- package/src/features/email/templates/password-changed.ts +4 -0
- package/src/features/email/templates/password-reset.ts +4 -0
- package/src/features/email/templates/shell.ts +4 -0
- package/src/features/notifications/index.ts +4 -0
- package/src/features/notifications/notifications.handler.ts +4 -0
- package/src/features/password-reset/index.ts +5 -1
- package/src/features/password-reset/request.ts +12 -2
- package/src/features/password-reset/reset.ts +12 -2
- package/src/features/rotate-field/index.ts +4 -0
- package/src/features/rotate-field/rotate-field.handler.ts +4 -0
- package/src/features/rotate-field/rotate-field.schema.ts +4 -0
- package/src/features/schema/schema.handler.ts +118 -3
- package/src/features/seeds/index.ts +5 -0
- package/src/features/seeds/seeds.handler.test.ts +632 -0
- package/src/features/seeds/seeds.handler.ts +693 -0
- package/src/features/settings/__tests__/settings.handler.test.ts +555 -0
- package/src/features/settings/settings.handler.ts +98 -8
- package/src/features/setup/index.ts +124 -11
- package/src/features/stats/index.ts +4 -0
- package/src/features/stats/stats.handler.ts +11 -21
- package/src/features/webhooks/index.ts +63 -0
- package/src/index.ts +28 -19
- package/src/media-utils.ts +4 -0
- package/src/middleware/auth-providers.middleware.ts +13 -0
- package/src/middleware/observability.middleware.ts +21 -3
- package/src/middleware/rate-limit.middleware.ts +4 -0
- package/src/middleware/repository.middleware.ts +25 -2
- package/src/middleware/seed-registry.middleware.test.ts +97 -0
- package/src/middleware/seed-registry.middleware.ts +21 -0
- package/src/middleware/storage.middleware.ts +4 -0
- package/src/middleware.ts +5 -7
- package/src/public/access-policy.ts +4 -0
- package/src/public/api-key-middleware.ts +4 -0
- package/src/public/cache-utils.ts +4 -0
- package/src/public/entry-projection.ts +4 -0
- package/src/public/idempotency.ts +4 -0
- package/src/public/index.ts +4 -0
- package/src/public/problem-details.ts +71 -0
- package/src/public/public-add.ts +4 -0
- package/src/public/public-edit.ts +4 -0
- package/src/public/public-errors.ts +4 -0
- package/src/public/public-read.ts +4 -0
- package/src/public/public-routes.ts +4 -0
- package/src/public/query-builder.test.ts +4 -0
- package/src/public/query-builder.ts +4 -0
- package/src/public/rate-limit-middleware.ts +4 -0
- package/src/public/read-list.ts +4 -0
- package/src/public/read-single.ts +4 -0
- package/src/public/response-builder.ts +4 -0
- package/src/public/sanitize.ts +4 -0
- package/src/public/slug-utils.ts +4 -0
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +4 -0
- package/src/rate-limit/cloudflare-rate-limiter.ts +4 -0
- package/src/rate-limit/in-memory-rate-limiter.test.ts +4 -0
- package/src/rate-limit/in-memory-rate-limiter.ts +4 -0
- package/src/rate-limit/no-op-rate-limiter.test.ts +4 -0
- package/src/rate-limit/no-op-rate-limiter.ts +4 -0
- package/src/search-utils.test.ts +4 -0
- package/src/search-utils.ts +4 -0
- package/src/search.ts +4 -0
- package/src/shared/apply-policies.test.ts +4 -0
- package/src/shared/apply-policies.ts +4 -0
- package/src/shared/automations.repository.d1.ts +4 -0
- package/src/shared/background-notification-service.test.ts +4 -0
- package/src/shared/background-notification-service.ts +4 -0
- package/src/shared/base.repository.d1.ts +4 -0
- package/src/shared/content-utils.test.ts +4 -0
- package/src/shared/content-utils.ts +4 -0
- package/src/shared/content.repository.d1.test.ts +147 -1
- package/src/shared/content.repository.d1.ts +535 -66
- package/src/shared/d1-activity-log.repository.test.ts +4 -0
- package/src/shared/d1-activity-log.repository.ts +4 -0
- package/src/shared/d1-activity-logger.test.ts +4 -0
- package/src/shared/d1-activity-logger.ts +4 -0
- package/src/shared/d1-analytics.repository.test.ts +4 -0
- package/src/shared/d1-analytics.repository.ts +4 -0
- package/src/shared/d1-content-scan.repository.test.ts +9 -5
- package/src/shared/d1-content-scan.repository.ts +15 -7
- package/src/shared/d1-notification.repository.test.ts +4 -0
- package/src/shared/d1-notification.repository.ts +4 -0
- package/src/shared/d1-password-reset-token.repository.test.ts +4 -0
- package/src/shared/d1-password-reset-token.repository.ts +4 -0
- package/src/shared/d1-search.repository.test.ts +4 -0
- package/src/shared/d1-search.repository.ts +4 -0
- package/src/shared/d1-session.repository.test.ts +4 -0
- package/src/shared/d1-session.repository.ts +4 -0
- package/src/shared/d1-setup-checklist.repository.ts +36 -0
- package/src/shared/d1-user.repository.test.ts +8 -4
- package/src/shared/d1-user.repository.ts +15 -5
- package/src/shared/d1-widget.repository.test.ts +4 -0
- package/src/shared/d1-widget.repository.ts +4 -0
- package/src/shared/demo-data-sql.ts +54 -0
- package/src/shared/demo-data.repository.d1.ts +20 -0
- package/src/shared/execution-context-scheduler.ts +4 -0
- package/src/shared/fixed-clock.ts +4 -0
- package/src/shared/fts-sync.ts +4 -0
- package/src/shared/idempotency.repository.d1.test.ts +4 -0
- package/src/shared/idempotency.repository.d1.ts +4 -0
- package/src/shared/in-memory-activity-logger.ts +4 -0
- package/src/shared/in-memory-notification-service.ts +4 -0
- package/src/shared/in-memory-seed.repository.ts +51 -0
- package/src/shared/media.repository.d1.test.ts +4 -0
- package/src/shared/media.repository.d1.ts +4 -0
- package/src/shared/qstash-notification-service.test.ts +67 -0
- package/src/shared/qstash-notification-service.ts +55 -0
- package/src/shared/query-utils.ts +4 -0
- package/src/shared/request-utils.ts +4 -0
- package/src/shared/schema-mutator.d1.ts +64 -0
- package/src/shared/seed-layout.repository.d1.test.ts +114 -0
- package/src/shared/seed-layout.repository.d1.ts +62 -0
- package/src/shared/seed-registry-cache.test.ts +68 -0
- package/src/shared/seed-registry-cache.ts +49 -0
- package/src/shared/seed.repository.d1.test.ts +143 -0
- package/src/shared/seed.repository.d1.ts +98 -0
- package/src/shared/sequential-id-generator.ts +11 -0
- package/src/shared/site-settings.repository.d1.ts +53 -0
- package/src/shared/storage/factory.ts +16 -15
- package/src/shared/storage/s3-bucket.ts +34 -2
- package/src/shared/storage-utils.ts +4 -0
- package/src/shared/system-stats.repository.d1.test.ts +4 -0
- package/src/shared/system-stats.repository.d1.ts +5 -1
- package/src/types.ts +23 -3
- package/src/upload.ts +134 -123
- package/src/widget.ts +6 -2
- package/assets/dashboard/assets/index-B1mUfgiK.css +0 -1
- package/assets/dashboard/assets/index-CHxxc_id.js +0 -629
- package/src/shared/storage/r2-binding-bucket.ts +0 -81
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { createMiddleware } from 'hono/factory'
|
|
2
6
|
import type { Context } from 'hono'
|
|
3
7
|
import { D1ContentRepository } from '../shared/content.repository.d1'
|
|
@@ -13,8 +17,14 @@ import { D1WidgetRepository } from '../shared/d1-widget.repository'
|
|
|
13
17
|
import { D1SearchRepository } from '../shared/d1-search.repository'
|
|
14
18
|
import { D1AnalyticsRepository } from '../shared/d1-analytics.repository'
|
|
15
19
|
import { D1ContentScanRepository } from '../shared/d1-content-scan.repository'
|
|
20
|
+
import { D1SiteSettingsRepository } from '../shared/site-settings.repository.d1'
|
|
21
|
+
import { D1DemoDataRepository } from '../shared/demo-data.repository.d1'
|
|
22
|
+
import { D1SetupChecklistRepository } from '../shared/d1-setup-checklist.repository'
|
|
23
|
+
import { D1SeedLayoutRepository } from '../shared/seed-layout.repository.d1'
|
|
24
|
+
import { D1SeedRepository } from '../shared/seed.repository.d1'
|
|
25
|
+
import { D1SchemaMutator } from '../shared/schema-mutator.d1'
|
|
16
26
|
import { SystemClock, SystemIdGenerator } from '@beechcms/core'
|
|
17
|
-
import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler } from '@beechcms/core'
|
|
27
|
+
import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler, ISiteSettingsRepository, IDemoDataRepository, ISeedLayoutRepository, ISeedRepository, ISchemaMutator } from '@beechcms/core'
|
|
18
28
|
import { NoOpScheduler } from '@beechcms/core'
|
|
19
29
|
import { AutomationRunner } from '../features/automations'
|
|
20
30
|
import { D1AutomationRepository } from '../shared/automations.repository.d1'
|
|
@@ -40,6 +50,11 @@ interface RepositoryOverrides {
|
|
|
40
50
|
automationRepository?: IAutomationRepository
|
|
41
51
|
automationRunner?: IAutomationRunner
|
|
42
52
|
scheduler?: IScheduler
|
|
53
|
+
siteSettingsRepository?: ISiteSettingsRepository
|
|
54
|
+
demoDataRepository?: IDemoDataRepository
|
|
55
|
+
seedLayoutRepository?: ISeedLayoutRepository
|
|
56
|
+
seedRepository?: ISeedRepository
|
|
57
|
+
schemaMutator?: ISchemaMutator
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
function buildScheduler(context: Context): IScheduler {
|
|
@@ -75,17 +90,25 @@ export const repositoryMiddleware = (overrides?: RepositoryOverrides) => {
|
|
|
75
90
|
?? new D1AutomationRepository(database)
|
|
76
91
|
|
|
77
92
|
context.set('automationRepository', automationRepository)
|
|
93
|
+
// getSeed is set later by seedRegistryMiddleware — capture context by closure so the
|
|
94
|
+
// runner reads the live value at call time, not at middleware creation time.
|
|
78
95
|
context.set(
|
|
79
96
|
'automationRunner',
|
|
80
97
|
overrides?.automationRunner ?? new AutomationRunner({
|
|
81
98
|
automationRepository,
|
|
82
99
|
contentRepository: context.get('repository'),
|
|
83
|
-
getSeed: context.get('getSeed'),
|
|
100
|
+
getSeed: (slug: string) => context.get('getSeed')?.(slug) ?? null,
|
|
84
101
|
idGenerator: resolvedIdGenerator,
|
|
85
102
|
env: context.env as unknown as Record<string, string | undefined>,
|
|
86
103
|
}),
|
|
87
104
|
)
|
|
88
105
|
context.set('scheduler', overrides?.scheduler ?? buildScheduler(context))
|
|
106
|
+
context.set('siteSettingsRepository', overrides?.siteSettingsRepository ?? new D1SiteSettingsRepository(database))
|
|
107
|
+
context.set('demoDataRepository', overrides?.demoDataRepository ?? new D1DemoDataRepository(database))
|
|
108
|
+
context.set('setupChecklistRepository', new D1SetupChecklistRepository(database))
|
|
109
|
+
context.set('seedLayoutRepository', overrides?.seedLayoutRepository ?? new D1SeedLayoutRepository(database))
|
|
110
|
+
context.set('seedRepository', overrides?.seedRepository ?? new D1SeedRepository(database))
|
|
111
|
+
context.set('schemaMutator', overrides?.schemaMutator ?? new D1SchemaMutator(database))
|
|
89
112
|
await next()
|
|
90
113
|
})
|
|
91
114
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
6
|
+
import { Hono } from 'hono'
|
|
7
|
+
import { seedRegistryMiddleware } from './seed-registry.middleware'
|
|
8
|
+
import { __resetSeedRegistryCache } from '../shared/seed-registry-cache'
|
|
9
|
+
import type { ISeedRepository, Seed } from '@beechcms/core'
|
|
10
|
+
|
|
11
|
+
const mockSeed: Seed = {
|
|
12
|
+
slug: 'posts',
|
|
13
|
+
label: 'Posts',
|
|
14
|
+
displayNameAlias: 'title',
|
|
15
|
+
branches: [{ id: 'br_01', alias: 'title', label: 'Title', type: 'text' }],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeRepo(seeds: Seed[] = [mockSeed]): ISeedRepository {
|
|
19
|
+
return {
|
|
20
|
+
listActive: vi.fn().mockResolvedValue(seeds),
|
|
21
|
+
listAll: vi.fn().mockResolvedValue([]),
|
|
22
|
+
get: vi.fn().mockResolvedValue(null),
|
|
23
|
+
upsert: vi.fn().mockResolvedValue(undefined),
|
|
24
|
+
softDelete: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
getRegistryVersion: vi.fn().mockResolvedValue(1),
|
|
26
|
+
bumpRegistryVersion: vi.fn().mockResolvedValue(2),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('seedRegistryMiddleware', () => {
|
|
31
|
+
beforeEach(() => __resetSeedRegistryCache())
|
|
32
|
+
|
|
33
|
+
it('injects seedRegistry, getSeed, and backrefMap into context', async () => {
|
|
34
|
+
const app = new Hono()
|
|
35
|
+
const repo = makeRepo()
|
|
36
|
+
|
|
37
|
+
app.use('*', async (c, next) => {
|
|
38
|
+
c.set('seedRepository' as never, repo)
|
|
39
|
+
await next()
|
|
40
|
+
})
|
|
41
|
+
app.use('*', seedRegistryMiddleware())
|
|
42
|
+
app.get('/test', (c) => {
|
|
43
|
+
const registry = c.get('seedRegistry' as never) as any
|
|
44
|
+
const getSeed = c.get('getSeed' as never) as any
|
|
45
|
+
const backrefMap = c.get('backrefMap' as never) as any
|
|
46
|
+
return c.json({
|
|
47
|
+
hasRegistry: !!registry,
|
|
48
|
+
hasSeed: !!getSeed('posts'),
|
|
49
|
+
hasBackrefMap: !!backrefMap,
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const res = await app.request('/test')
|
|
54
|
+
const body = await res.json() as any
|
|
55
|
+
expect(body.hasRegistry).toBe(true)
|
|
56
|
+
expect(body.hasSeed).toBe(true)
|
|
57
|
+
expect(body.hasBackrefMap).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('getSeed returns null for unknown slug', async () => {
|
|
61
|
+
const app = new Hono()
|
|
62
|
+
const repo = makeRepo()
|
|
63
|
+
|
|
64
|
+
app.use('*', async (c, next) => {
|
|
65
|
+
c.set('seedRepository' as never, repo)
|
|
66
|
+
await next()
|
|
67
|
+
})
|
|
68
|
+
app.use('*', seedRegistryMiddleware())
|
|
69
|
+
app.get('/test', (c) => {
|
|
70
|
+
const getSeed = c.get('getSeed' as never) as any
|
|
71
|
+
return c.json({ result: getSeed('no-such-slug') })
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const res = await app.request('/test')
|
|
75
|
+
const body = await res.json() as any
|
|
76
|
+
expect(body.result).toBeNull()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('empty seed list yields empty registry without crashing', async () => {
|
|
80
|
+
const app = new Hono()
|
|
81
|
+
const repo = makeRepo([])
|
|
82
|
+
|
|
83
|
+
app.use('*', async (c, next) => {
|
|
84
|
+
c.set('seedRepository' as never, repo)
|
|
85
|
+
await next()
|
|
86
|
+
})
|
|
87
|
+
app.use('*', seedRegistryMiddleware())
|
|
88
|
+
app.get('/test', (c) => {
|
|
89
|
+
const registry = c.get('seedRegistry' as never) as any
|
|
90
|
+
return c.json({ count: registry.all().length })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const res = await app.request('/test')
|
|
94
|
+
const body = await res.json() as any
|
|
95
|
+
expect(body.count).toBe(0)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { createMiddleware } from 'hono/factory'
|
|
6
|
+
import type { Env, Variables } from '../types'
|
|
7
|
+
import { getHydratedRegistry } from '../shared/seed-registry-cache'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hydrates the seed registry from D1 and injects seedRegistry, getSeed, and backrefMap
|
|
11
|
+
* into the context. Must run AFTER repositoryMiddleware (needs seedRepository) and
|
|
12
|
+
* BEFORE any handler that reads the registry.
|
|
13
|
+
*/
|
|
14
|
+
export const seedRegistryMiddleware = () =>
|
|
15
|
+
createMiddleware<{ Bindings: Env; Variables: Variables }>(async (context, next) => {
|
|
16
|
+
const { registry, backrefMap } = await getHydratedRegistry(context.get('seedRepository'))
|
|
17
|
+
context.set('seedRegistry', registry)
|
|
18
|
+
context.set('getSeed', (slug: string) => registry.get(slug))
|
|
19
|
+
context.set('backrefMap', backrefMap)
|
|
20
|
+
await next()
|
|
21
|
+
})
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { createMiddleware } from 'hono/factory'
|
|
2
6
|
import { BeechBucket } from '@beechcms/core'
|
|
3
7
|
import { AppEnv } from '../types'
|
package/src/middleware.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
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
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import type { Context, Next } from 'hono'
|
|
3
7
|
import { HTTPException } from 'hono/http-exception'
|
|
4
8
|
import type { Env, Variables } from './types'
|
|
5
9
|
|
|
6
|
-
export type JwtPayload = {
|
|
7
|
-
sub: string
|
|
8
|
-
email?: string
|
|
9
|
-
name?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
10
|
const UNAUTHORIZED_JSON = JSON.stringify({ error: 'Unauthorized' })
|
|
13
11
|
|
|
14
12
|
function unauthorizedResponse() {
|
|
@@ -41,7 +39,7 @@ export function authMiddleware() {
|
|
|
41
39
|
throw new HTTPException(401, { res: unauthorizedResponse() })
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
c.set('jwtPayload', claims
|
|
42
|
+
c.set('jwtPayload', claims)
|
|
45
43
|
await next()
|
|
46
44
|
}
|
|
47
45
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Context, Next } from 'hono'
|
|
2
6
|
import { PUBLIC_ERRORS } from './public-errors'
|
|
3
7
|
import { publicProblem } from './problem-details'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { sha256hex } from '@beechcms/core'
|
|
2
6
|
|
|
3
7
|
export function parseIdempotencyKey(rawValue: string | undefined): string | null {
|
package/src/public/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
export { apiKeyMiddleware } from './api-key-middleware'
|
|
2
6
|
export { publicRateLimitMiddleware } from './rate-limit-middleware'
|
|
3
7
|
export { publicRoutes } from './public-routes'
|
|
@@ -1,5 +1,76 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Context } from 'hono'
|
|
2
6
|
|
|
7
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// FK / SQLite error detection
|
|
9
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
type FkViolationKind = 'relation-in-use' | 'relation-target-not-found' | 'foreign-key-violation'
|
|
12
|
+
|
|
13
|
+
function isFkError(error: unknown): boolean {
|
|
14
|
+
if (!(error instanceof Error)) return false
|
|
15
|
+
// D1 surfaces the error in .message
|
|
16
|
+
if (/FOREIGN KEY constraint failed/i.test(error.message)) return true
|
|
17
|
+
// Some runtimes expose cause.code
|
|
18
|
+
const cause = (error as { cause?: { code?: string } }).cause
|
|
19
|
+
if (cause?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') return true
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Classify a SQLite FK error by HTTP method.
|
|
25
|
+
* - DELETE → the referenced row is still used → relation-in-use
|
|
26
|
+
* - POST/PUT/PATCH → referenced parent does not exist → relation-target-not-found
|
|
27
|
+
* - anything else → generic foreign-key-violation
|
|
28
|
+
*/
|
|
29
|
+
function classifyFkError(method: string): FkViolationKind {
|
|
30
|
+
const m = method.toUpperCase()
|
|
31
|
+
if (m === 'DELETE') return 'relation-in-use'
|
|
32
|
+
if (m === 'POST' || m === 'PUT' || m === 'PATCH') return 'relation-target-not-found'
|
|
33
|
+
return 'foreign-key-violation'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const FK_PROBLEM: Record<FkViolationKind, { title: string; status: 409 | 422; detail: string }> = {
|
|
37
|
+
'relation-in-use': {
|
|
38
|
+
title: 'Relation In Use',
|
|
39
|
+
status: 409,
|
|
40
|
+
detail: 'The record cannot be deleted because it is referenced by one or more related entries.',
|
|
41
|
+
},
|
|
42
|
+
'relation-target-not-found': {
|
|
43
|
+
title: 'Relation Target Not Found',
|
|
44
|
+
status: 422,
|
|
45
|
+
detail: 'The provided relation id does not match any existing record in the referenced collection.',
|
|
46
|
+
},
|
|
47
|
+
'foreign-key-violation': {
|
|
48
|
+
title: 'Foreign Key Violation',
|
|
49
|
+
status: 409,
|
|
50
|
+
detail: 'The operation violates a referential integrity constraint.',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns a Problem+JSON response when the error is a SQLite FK violation,
|
|
56
|
+
* or null when the error is unrelated (let the caller handle it).
|
|
57
|
+
*
|
|
58
|
+
* @param c Hono context
|
|
59
|
+
* @param error The caught error
|
|
60
|
+
* @param method HTTP method of the current request (e.g. 'DELETE', 'POST')
|
|
61
|
+
*/
|
|
62
|
+
export function fkProblemOrNull(c: Context, error: unknown, method: string): Response | null {
|
|
63
|
+
if (!isFkError(error)) return null
|
|
64
|
+
const kind = classifyFkError(method)
|
|
65
|
+
const meta = FK_PROBLEM[kind]
|
|
66
|
+
return publicProblem(c, {
|
|
67
|
+
type: `https://beechcms.dev/problems/${kind}`,
|
|
68
|
+
title: meta.title,
|
|
69
|
+
status: meta.status,
|
|
70
|
+
detail: meta.detail,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
3
74
|
export interface PublicProblemDetailItem {
|
|
4
75
|
field: string
|
|
5
76
|
expected: string
|
package/src/public/public-add.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { isValidContentStatus, SlugConflictError } from '@beechcms/core'
|
|
2
6
|
import type { Context } from 'hono'
|
|
3
7
|
import { cleanStr } from '../shared/query-utils'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { isValidContentStatus, resolvePolicies, EntryNotFoundError } from '@beechcms/core'
|
|
2
6
|
import type { Seed } from '@beechcms/core'
|
|
3
7
|
import type { Context } from 'hono'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Context } from 'hono'
|
|
2
6
|
import { cleanStr } from '../shared/query-utils'
|
|
3
7
|
import { checkPublicOperation } from './access-policy'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { Hono } from 'hono'
|
|
2
6
|
import type { AppEnv } from '../types'
|
|
3
7
|
import { publicReadHandler } from './public-read'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Seed, FilterGroup, FilterOperator, FilterType, BranchType } from '@beechcms/core'
|
|
2
6
|
import { parsePositiveInt } from '../shared/query-utils'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Context, Next } from 'hono'
|
|
2
6
|
import type { AppEnv } from '../types'
|
|
3
7
|
import { publicProblem } from './problem-details'
|
package/src/public/read-list.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { Seed, ContentRepository } from '@beechcms/core'
|
|
2
6
|
import { cleanStr } from '../shared/query-utils'
|
|
3
7
|
import { toFlatPublicEntry } from './entry-projection'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { EntryNotFoundError } from '@beechcms/core'
|
|
2
6
|
import type { Seed, ContentRepository } from '@beechcms/core'
|
|
3
7
|
import { toFlatPublicEntry } from './entry-projection'
|
package/src/public/sanitize.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { validateAndSanitizeSeedPayload } from '@beechcms/core'
|
|
2
6
|
import type { Seed, ValidationDetail } from '@beechcms/core'
|
|
3
7
|
|
package/src/public/slug-utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
6
|
import { CloudflareRateLimiter } from './cloudflare-rate-limiter'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import type { IRateLimiter, RateLimitResult } from '@beechcms/core'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { InMemoryRateLimiter } from './in-memory-rate-limiter'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { IRateLimiter, RateLimitResult } from '@beechcms/core'
|
|
2
6
|
|
|
3
7
|
export class InMemoryRateLimiter implements IRateLimiter {
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { NoOpRateLimiter } from './no-op-rate-limiter'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import type { IRateLimiter, RateLimitResult } from '@beechcms/core'
|
|
2
6
|
|
|
3
7
|
export class NoOpRateLimiter implements IRateLimiter {
|
package/src/search-utils.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { encodeCursor, decodeCursor, buildFtsQuery, mapFtsRow } from './search-utils'
|
|
3
7
|
import type { Seed } from '@beechcms/core'
|
package/src/search-utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
// apps/api/src/search-utils.ts
|
|
2
6
|
// Pure functions — zero Hono dependencies, importable from Vitest.
|
|
3
7
|
// v0.4.0: FTS is per-seed (fts_{slug}), joined with content_{slug} for metadata.
|
package/src/search.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect } from 'vitest'
|
|
2
6
|
import { applyPrivacy, applyVisibility, PrivacyPolicyError } from './apply-policies'
|
|
3
7
|
import type { Seed } from '@beechcms/core'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import { resolvePolicies, sha256hex } from '@beechcms/core'
|
|
3
7
|
import type { Seed } from '@beechcms/core'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
6
|
import { BackgroundNotificationService } from './background-notification-service'
|
|
3
7
|
import type { INotificationRepository, NotificationRecord } from '@beechcms/core'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import { RepositoryError } from '@beechcms/core'
|
|
3
7
|
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
6
|
import { rowToApiData, rowToEntry, buildInsertBindings, buildUpdateBindings, hasDraft } from './content-utils'
|
|
3
7
|
import type { Seed } from '@beechcms/core'
|
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
6
|
import { deserializeFromDb, serializeForDb } from '@beechcms/core'
|
|
3
7
|
import type { Seed } from '@beechcms/core'
|