@beechcms/api 0.6.0-preview.2 → 0.6.0-preview.3
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/README.md +4 -4
- package/assets/dashboard/assets/chart-Cws4Yj6K.js +39 -0
- package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/assets/dashboard/assets/index-BjPpppHe.css +1 -0
- package/assets/dashboard/assets/index-CrW-zhkA.js +642 -0
- package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
- package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
- package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
- package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
- package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
- package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
- package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
- package/assets/dashboard/assets/pie-chart-recharts-CAxoDQp2.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-CixDW-LM.js +1 -0
- package/assets/dashboard/index.html +2 -2
- package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
- package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
- package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
- package/dist/auth/constants.d.ts +10 -0
- package/dist/auth/jose-token-service.d.ts +14 -0
- package/dist/auth/login.d.ts +32 -0
- package/dist/auth/refresh.d.ts +1 -0
- package/dist/factory.d.ts +39 -0
- package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/index.d.ts +13 -0
- package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
- package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
- package/dist/features/automations/automation-runner.d.ts +13 -0
- package/dist/features/automations/automation-runner.utils.d.ts +3 -0
- package/dist/features/automations/automations.handler.d.ts +7 -0
- package/dist/features/automations/automations.schema.d.ts +191 -0
- package/dist/features/automations/context-resolver.d.ts +8 -0
- package/dist/features/automations/cron-runner.d.ts +9 -0
- package/dist/features/automations/cron-runner.utils.d.ts +1 -0
- package/dist/features/automations/filter-translation.d.ts +9 -0
- package/dist/features/automations/index.d.ts +7 -0
- package/dist/features/automations/template-grammar.d.ts +73 -0
- package/dist/features/automations/var-access-resolver.d.ts +6 -0
- package/dist/features/automations/when-evaluator.d.ts +3 -0
- package/dist/features/automations/when-pushdown.d.ts +2 -0
- package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
- package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
- package/dist/features/backrefs/index.d.ts +1 -0
- package/dist/features/content/constants.d.ts +12 -0
- package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
- package/dist/features/content/handlers/create.d.ts +3 -0
- package/dist/features/content/handlers/delete.d.ts +3 -0
- package/dist/features/content/handlers/facets.d.ts +3 -0
- package/dist/features/content/handlers/get.d.ts +4 -0
- package/dist/features/content/handlers/helpers.d.ts +12 -0
- package/dist/features/content/handlers/list.d.ts +3 -0
- package/dist/features/content/handlers/update.d.ts +3 -0
- package/dist/features/content/index.d.ts +4 -0
- package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
- package/dist/features/dashboard-layout/index.d.ts +1 -0
- package/dist/features/draft/draft.handler.d.ts +4 -0
- package/dist/features/draft/draft.middleware.d.ts +9 -0
- package/dist/features/draft/index.d.ts +1 -0
- package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +19 -24
- package/dist/features/email/email.service.d.ts +13 -0
- package/dist/features/email/email.types.d.ts +94 -0
- package/{src/features/email/index.ts → dist/features/email/index.d.ts} +3 -16
- package/dist/features/email/providers/resend.d.ts +36 -0
- package/dist/features/email/providers/smtp.d.ts +11 -0
- package/dist/features/email/templates/automation-mail.d.ts +8 -0
- package/dist/features/email/templates/password-changed.d.ts +15 -0
- package/dist/features/email/templates/password-reset.d.ts +18 -0
- package/dist/features/email/templates/shell.d.ts +58 -0
- package/dist/features/notifications/index.d.ts +1 -0
- package/dist/features/notifications/notifications.handler.d.ts +15 -0
- package/dist/features/password-reset/index.d.ts +6 -0
- package/dist/features/password-reset/request.d.ts +10 -0
- package/dist/features/password-reset/reset.d.ts +9 -0
- package/dist/features/rotate-field/index.d.ts +1 -0
- package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
- package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
- package/dist/features/schema/schema.handler.d.ts +7 -0
- package/dist/features/seeds/index.d.ts +1 -0
- package/dist/features/seeds/seeds.handler.d.ts +6 -0
- package/dist/features/settings/settings.handler.d.ts +7 -0
- package/dist/features/setup/index.d.ts +7 -0
- package/dist/features/stats/index.d.ts +1 -0
- package/dist/features/stats/stats.handler.d.ts +7 -0
- package/dist/features/webhooks/index.d.ts +3 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9666 -0
- package/dist/media-utils.d.ts +25 -0
- package/dist/middleware/auth-providers.middleware.d.ts +8 -0
- package/dist/middleware/observability.middleware.d.ts +20 -0
- package/dist/middleware/rate-limit.middleware.d.ts +9 -0
- package/dist/middleware/repository.middleware.d.ts +34 -0
- package/dist/middleware/seed-registry.middleware.d.ts +10 -0
- package/dist/middleware/storage.middleware.d.ts +7 -0
- package/dist/middleware.d.ts +11 -0
- package/dist/public/access-policy.d.ts +12 -0
- package/dist/public/api-key-middleware.d.ts +6 -0
- package/dist/public/cache-utils.d.ts +10 -0
- package/dist/public/entry-projection.d.ts +2 -0
- package/dist/public/idempotency.d.ts +9 -0
- package/dist/public/index.d.ts +11 -0
- package/dist/public/problem-details.d.ts +31 -0
- package/dist/public/public-add.d.ts +3 -0
- package/dist/public/public-edit.d.ts +5 -0
- package/dist/public/public-errors.d.ts +13 -0
- package/dist/public/public-read.d.ts +3 -0
- package/dist/public/public-routes.d.ts +3 -0
- package/dist/public/query-builder.d.ts +28 -0
- package/dist/public/rate-limit-middleware.d.ts +3 -0
- package/dist/public/read-list.d.ts +26 -0
- package/dist/public/read-single.d.ts +22 -0
- package/dist/public/response-builder.d.ts +22 -0
- package/dist/public/sanitize.d.ts +23 -0
- package/{src/public/slug-utils.ts → dist/public/slug-utils.d.ts} +3 -10
- package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
- package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
- package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
- package/dist/search-utils.d.ts +54 -0
- package/dist/search.d.ts +6 -0
- package/dist/shared/apply-policies.d.ts +10 -0
- package/dist/shared/automations.repository.d1.d.ts +12 -0
- package/dist/shared/background-notification-service.d.ts +18 -0
- package/dist/shared/base.repository.d1.d.ts +18 -0
- package/dist/shared/content-utils.d.ts +21 -0
- package/dist/shared/content.repository.d1.d.ts +68 -0
- package/dist/shared/d1-activity-log.repository.d.ts +13 -0
- package/dist/shared/d1-activity-logger.d.ts +20 -0
- package/dist/shared/d1-analytics.repository.d.ts +20 -0
- package/dist/shared/d1-content-scan.repository.d.ts +7 -0
- package/dist/shared/d1-notification.repository.d.ts +17 -0
- package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
- package/dist/shared/d1-search.repository.d.ts +14 -0
- package/dist/shared/d1-session.repository.d.ts +12 -0
- package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
- package/dist/shared/d1-user.repository.d.ts +18 -0
- package/dist/shared/d1-widget.repository.d.ts +31 -0
- package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
- package/dist/shared/demo-data-sql.d.ts +1 -0
- package/dist/shared/demo-data.repository.d1.d.ts +6 -0
- package/dist/shared/execution-context-scheduler.d.ts +6 -0
- package/dist/shared/fixed-clock.d.ts +13 -0
- package/dist/shared/fts-sync.d.ts +1 -0
- package/dist/shared/idempotency.repository.d1.d.ts +16 -0
- package/dist/shared/in-memory-activity-logger.d.ts +11 -0
- package/dist/shared/in-memory-notification-service.d.ts +11 -0
- package/dist/shared/in-memory-seed.repository.d.ts +18 -0
- package/dist/shared/media.repository.d1.d.ts +35 -0
- package/dist/shared/qstash-notification-service.d.ts +18 -0
- package/dist/shared/query-utils.d.ts +36 -0
- package/dist/shared/request-utils.d.ts +13 -0
- package/dist/shared/schema-mutator.d1.d.ts +12 -0
- package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
- package/dist/shared/seed-registry-cache.d.ts +12 -0
- package/dist/shared/seed.repository.d1.d.ts +13 -0
- package/dist/shared/sequential-id-generator.d.ts +13 -0
- package/dist/shared/site-settings.repository.d1.d.ts +7 -0
- package/dist/shared/storage/factory.d.ts +8 -0
- package/dist/shared/storage/s3-bucket.d.ts +43 -0
- package/dist/shared/storage-utils.d.ts +7 -0
- package/dist/shared/system-stats.repository.d1.d.ts +25 -0
- package/dist/types.d.ts +85 -0
- package/dist/upload.d.ts +16 -0
- package/dist/widget.d.ts +7 -0
- package/package.json +27 -18
- package/assets/dashboard/assets/chart-BrJFdsGZ.js +0 -39
- package/assets/dashboard/assets/index-CIaoMxuB.css +0 -1
- package/assets/dashboard/assets/index-wTKgygsg.js +0 -636
- package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +0 -1
- package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +0 -1
- package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
- package/src/auth/__fixtures__/static-token-service.ts +0 -22
- package/src/auth/bcrypt-hash-provider.ts +0 -24
- package/src/auth/constants.ts +0 -14
- package/src/auth/generate-refresh-token.test.ts +0 -23
- package/src/auth/hash-provider.test.ts +0 -55
- package/src/auth/jose-token-service.ts +0 -62
- package/src/auth/jwt-claims-passthrough.test.ts +0 -87
- package/src/auth/login.test.ts +0 -100
- package/src/auth/login.ts +0 -78
- package/src/auth/refresh.ts +0 -11
- package/src/auth/token-service.test.ts +0 -90
- package/src/factory.ts +0 -370
- package/src/features/automations/__tests__/action-executors.test.ts +0 -255
- package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
- package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
- package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
- package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
- package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
- package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
- package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
- package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
- package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
- package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
- package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
- package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
- package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
- package/src/features/automations/action-executors/index.ts +0 -37
- package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
- package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
- package/src/features/automations/action-executors/webhook.executor.ts +0 -67
- package/src/features/automations/automation-runner.ts +0 -85
- package/src/features/automations/automation-runner.utils.ts +0 -47
- package/src/features/automations/automations.handler.ts +0 -197
- package/src/features/automations/automations.schema.ts +0 -177
- package/src/features/automations/context-resolver.ts +0 -152
- package/src/features/automations/cron-runner.ts +0 -140
- package/src/features/automations/cron-runner.utils.ts +0 -44
- package/src/features/automations/filter-translation.ts +0 -46
- package/src/features/automations/index.ts +0 -16
- package/src/features/automations/template-grammar.ts +0 -245
- package/src/features/automations/var-access-resolver.ts +0 -140
- package/src/features/automations/when-evaluator.ts +0 -87
- package/src/features/automations/when-pushdown.ts +0 -57
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
- package/src/features/backrefs/backrefs.handler.ts +0 -168
- package/src/features/backrefs/d1-backref.repository.ts +0 -84
- package/src/features/backrefs/index.ts +0 -5
- package/src/features/content/constants.ts +0 -16
- package/src/features/content/handlers/bulk.handler.ts +0 -185
- package/src/features/content/handlers/create.ts +0 -129
- package/src/features/content/handlers/delete.ts +0 -62
- package/src/features/content/handlers/facets.ts +0 -49
- package/src/features/content/handlers/get.ts +0 -120
- package/src/features/content/handlers/helpers.test.ts +0 -180
- package/src/features/content/handlers/helpers.ts +0 -93
- package/src/features/content/handlers/list.ts +0 -174
- package/src/features/content/handlers/update.ts +0 -174
- package/src/features/content/index.ts +0 -26
- package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +0 -388
- package/src/features/dashboard-layout/dashboard-layout.handler.ts +0 -206
- package/src/features/dashboard-layout/index.ts +0 -5
- package/src/features/draft/draft.handler.ts +0 -195
- package/src/features/draft/draft.middleware.ts +0 -66
- package/src/features/draft/index.ts +0 -5
- package/src/features/email/email.service.ts +0 -98
- package/src/features/email/email.types.ts +0 -119
- package/src/features/email/providers/resend.ts +0 -67
- package/src/features/email/providers/smtp.ts +0 -55
- package/src/features/email/templates/automation-mail.ts +0 -19
- package/src/features/email/templates/password-changed.ts +0 -63
- package/src/features/email/templates/password-reset.ts +0 -68
- package/src/features/email/templates/shell.ts +0 -96
- package/src/features/notifications/index.ts +0 -5
- package/src/features/notifications/notifications.handler.ts +0 -105
- package/src/features/password-reset/index.ts +0 -19
- package/src/features/password-reset/request.ts +0 -92
- package/src/features/password-reset/reset.ts +0 -102
- package/src/features/rotate-field/index.ts +0 -5
- package/src/features/rotate-field/rotate-field.handler.ts +0 -132
- package/src/features/rotate-field/rotate-field.schema.ts +0 -17
- package/src/features/schema/schema.handler.ts +0 -131
- package/src/features/seeds/index.ts +0 -5
- package/src/features/seeds/seeds.handler.test.ts +0 -1199
- package/src/features/seeds/seeds.handler.ts +0 -558
- package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
- package/src/features/settings/settings.handler.ts +0 -392
- package/src/features/setup/index.ts +0 -288
- package/src/features/stats/index.ts +0 -5
- package/src/features/stats/stats.handler.ts +0 -420
- package/src/features/webhooks/index.ts +0 -63
- package/src/index.ts +0 -65
- package/src/media-utils.ts +0 -82
- package/src/middleware/auth-providers.middleware.test.ts +0 -85
- package/src/middleware/auth-providers.middleware.ts +0 -45
- package/src/middleware/observability.middleware.test.ts +0 -93
- package/src/middleware/observability.middleware.ts +0 -70
- package/src/middleware/rate-limit.middleware.ts +0 -45
- package/src/middleware/repository.middleware.ts +0 -117
- package/src/middleware/seed-registry.middleware.test.ts +0 -97
- package/src/middleware/seed-registry.middleware.ts +0 -21
- package/src/middleware/storage.middleware.ts +0 -25
- package/src/middleware.ts +0 -45
- package/src/public/access-policy.ts +0 -27
- package/src/public/api-key-middleware.ts +0 -57
- package/src/public/cache-utils.ts +0 -38
- package/src/public/entry-projection.ts +0 -46
- package/src/public/idempotency.ts +0 -23
- package/src/public/index.ts +0 -16
- package/src/public/problem-details.ts +0 -124
- package/src/public/public-add.ts +0 -114
- package/src/public/public-edit.ts +0 -163
- package/src/public/public-errors.ts +0 -19
- package/src/public/public-read.ts +0 -63
- package/src/public/public-routes.ts +0 -88
- package/src/public/query-builder.test.ts +0 -224
- package/src/public/query-builder.ts +0 -156
- package/src/public/rate-limit-middleware.ts +0 -34
- package/src/public/read-list.ts +0 -54
- package/src/public/read-single.ts +0 -48
- package/src/public/response-builder.ts +0 -30
- package/src/public/sanitize.ts +0 -69
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
- package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
- package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
- package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
- package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
- package/src/rate-limit/no-op-rate-limiter.ts +0 -11
- package/src/search-utils.test.ts +0 -211
- package/src/search-utils.ts +0 -213
- package/src/search.ts +0 -65
- package/src/shared/apply-policies.test.ts +0 -81
- package/src/shared/apply-policies.ts +0 -67
- package/src/shared/automations.repository.d1.ts +0 -150
- package/src/shared/background-notification-service.test.ts +0 -62
- package/src/shared/background-notification-service.ts +0 -52
- package/src/shared/base.repository.d1.ts +0 -32
- package/src/shared/content-utils.test.ts +0 -165
- package/src/shared/content-utils.ts +0 -86
- package/src/shared/content.repository.d1.test.ts +0 -507
- package/src/shared/content.repository.d1.ts +0 -851
- package/src/shared/d1-activity-log.repository.test.ts +0 -140
- package/src/shared/d1-activity-log.repository.ts +0 -105
- package/src/shared/d1-activity-logger.test.ts +0 -86
- package/src/shared/d1-activity-logger.ts +0 -67
- package/src/shared/d1-analytics.repository.test.ts +0 -78
- package/src/shared/d1-analytics.repository.ts +0 -85
- package/src/shared/d1-content-scan.repository.test.ts +0 -80
- package/src/shared/d1-content-scan.repository.ts +0 -37
- package/src/shared/d1-notification.repository.test.ts +0 -128
- package/src/shared/d1-notification.repository.ts +0 -118
- package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
- package/src/shared/d1-password-reset-token.repository.ts +0 -56
- package/src/shared/d1-search.repository.test.ts +0 -87
- package/src/shared/d1-search.repository.ts +0 -88
- package/src/shared/d1-session.repository.test.ts +0 -125
- package/src/shared/d1-session.repository.ts +0 -102
- package/src/shared/d1-setup-checklist.repository.ts +0 -36
- package/src/shared/d1-user.repository.test.ts +0 -151
- package/src/shared/d1-user.repository.ts +0 -119
- package/src/shared/d1-widget.repository.test.ts +0 -267
- package/src/shared/d1-widget.repository.ts +0 -385
- package/src/shared/dashboard-layout.repository.d1.test.ts +0 -135
- package/src/shared/dashboard-layout.repository.d1.ts +0 -58
- package/src/shared/demo-data-sql.ts +0 -216
- package/src/shared/demo-data.repository.d1.ts +0 -20
- package/src/shared/execution-context-scheduler.ts +0 -13
- package/src/shared/fixed-clock.ts +0 -25
- package/src/shared/fts-sync.ts +0 -8
- package/src/shared/idempotency.repository.d1.test.ts +0 -83
- package/src/shared/idempotency.repository.d1.ts +0 -60
- package/src/shared/in-memory-activity-logger.ts +0 -19
- package/src/shared/in-memory-notification-service.ts +0 -19
- package/src/shared/in-memory-seed.repository.ts +0 -58
- package/src/shared/media.repository.d1.test.ts +0 -107
- package/src/shared/media.repository.d1.ts +0 -68
- package/src/shared/qstash-notification-service.test.ts +0 -67
- package/src/shared/qstash-notification-service.ts +0 -55
- package/src/shared/query-utils.ts +0 -141
- package/src/shared/request-utils.ts +0 -26
- package/src/shared/schema-mutator.d1.test.ts +0 -142
- package/src/shared/schema-mutator.d1.ts +0 -64
- package/src/shared/seed-layout.repository.d1.test.ts +0 -114
- package/src/shared/seed-layout.repository.d1.ts +0 -62
- package/src/shared/seed-registry-cache.test.ts +0 -68
- package/src/shared/seed-registry-cache.ts +0 -49
- package/src/shared/seed.repository.d1.test.ts +0 -143
- package/src/shared/seed.repository.d1.ts +0 -98
- package/src/shared/sequential-id-generator.ts +0 -33
- package/src/shared/site-settings.repository.d1.ts +0 -53
- package/src/shared/storage/factory.ts +0 -41
- package/src/shared/storage/s3-bucket.ts +0 -195
- package/src/shared/storage-utils.ts +0 -40
- package/src/shared/system-stats.repository.d1.test.ts +0 -58
- package/src/shared/system-stats.repository.d1.ts +0 -48
- package/src/types.ts +0 -90
- package/src/upload.ts +0 -198
- package/src/widget.test.ts +0 -85
- package/src/widget.ts +0 -251
|
@@ -1,558 +0,0 @@
|
|
|
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
|
-
/// <reference types="@cloudflare/workers-types" />
|
|
6
|
-
import { Hono } from 'hono'
|
|
7
|
-
import type { Branch, Seed } from '@beechcms/core'
|
|
8
|
-
import {
|
|
9
|
-
nextBranchId,
|
|
10
|
-
validateSeedDefinitions,
|
|
11
|
-
planCreateSeed,
|
|
12
|
-
planExtendSeed,
|
|
13
|
-
planFtsRebuild,
|
|
14
|
-
generateDropTable,
|
|
15
|
-
generateDropColumn,
|
|
16
|
-
generateRenameColumn,
|
|
17
|
-
generateRetypeColumn,
|
|
18
|
-
BRANCH_ALIAS_RE,
|
|
19
|
-
} from '@beechcms/core'
|
|
20
|
-
import { publicProblem, internalErrorDetail } from '../../public/problem-details'
|
|
21
|
-
import { deleteR2Objects } from '../../upload'
|
|
22
|
-
import { extractMediaKeysFromData } from '../../media-utils'
|
|
23
|
-
import type { Env, Variables } from '../../types'
|
|
24
|
-
|
|
25
|
-
const SLUG_RE = /^[a-z0-9_]+$/
|
|
26
|
-
|
|
27
|
-
function requireAdmin(context: any) {
|
|
28
|
-
const role = context.get('jwtPayload')?.role
|
|
29
|
-
if (role !== 'admin') {
|
|
30
|
-
return publicProblem(context, {
|
|
31
|
-
type: 'forbidden',
|
|
32
|
-
title: 'Forbidden',
|
|
33
|
-
status: 403,
|
|
34
|
-
detail: 'Seed management requires admin role.',
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
return null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function actorFromContext(context: any) {
|
|
41
|
-
const jwt = context.get('jwtPayload')
|
|
42
|
-
return {
|
|
43
|
-
id: jwt?.sub ?? 'unknown',
|
|
44
|
-
email: jwt?.email ?? 'unknown',
|
|
45
|
-
name: [jwt?.name, jwt?.surname].filter(Boolean).join(' ') || null,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function parseJsonBody(context: any): Promise<unknown> {
|
|
50
|
-
try { return await context.req.json() } catch {
|
|
51
|
-
return publicProblem(context, { type: 'invalid-json', title: 'Invalid JSON', status: 400, detail: 'Body must be valid JSON.' })
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function getActiveSeed(context: any, slug: string) {
|
|
56
|
-
const repo = context.get('seedRepository')
|
|
57
|
-
const existing = await repo.get(slug)
|
|
58
|
-
if (!existing || existing.status === 'deleted') {
|
|
59
|
-
return publicProblem(context, { type: 'seed-not-found', title: 'Seed not found', status: 404, detail: `No active seed with slug '${slug}'.` })
|
|
60
|
-
}
|
|
61
|
-
return existing
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function validateAndApplySeedDef(context: any, slug: string, candidate: Seed, action: 'create' | 'update', logDetails: any) {
|
|
65
|
-
const repo = context.get('seedRepository')
|
|
66
|
-
const activeSeeds = await repo.listActive()
|
|
67
|
-
const candidateSet = [...activeSeeds.filter((s: any) => s.slug !== slug), candidate]
|
|
68
|
-
const issues = validateSeedDefinitions(candidateSet)
|
|
69
|
-
const fatalIssues = issues.filter(i => i.fatal && i.slug === slug)
|
|
70
|
-
if (fatalIssues.length > 0) {
|
|
71
|
-
return publicProblem(context, {
|
|
72
|
-
type: 'validation-failed',
|
|
73
|
-
title: 'Validation failed',
|
|
74
|
-
status: 422,
|
|
75
|
-
detail: fatalIssues.flatMap(i => i.messages).join('; '),
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const schemaMutator = context.get('schemaMutator')
|
|
80
|
-
const tableName = `content_${slug}`
|
|
81
|
-
const existingCols = await schemaMutator.getColumns(tableName)
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const stmts = existingCols === null
|
|
85
|
-
? planCreateSeed(candidate)
|
|
86
|
-
: planExtendSeed(candidate, existingCols).statements
|
|
87
|
-
await schemaMutator.execDdl(stmts)
|
|
88
|
-
} catch (err) {
|
|
89
|
-
return publicProblem(context, {
|
|
90
|
-
type: 'ddl-failed',
|
|
91
|
-
title: 'DDL execution failed',
|
|
92
|
-
status: 422,
|
|
93
|
-
detail: internalErrorDetail(context.env, err),
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await repo.upsert(slug, candidate, 'runtime')
|
|
98
|
-
await repo.bumpRegistryVersion()
|
|
99
|
-
|
|
100
|
-
const actor = actorFromContext(context)
|
|
101
|
-
context.get('activityLogger').log({ action, entityType: 'seed', entityId: slug, details: logDetails, actor })
|
|
102
|
-
|
|
103
|
-
return null
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function applyDestructiveSeedDef(context: any, slug: string, updatedDef: Seed, stmts: string[], logDetails: any) {
|
|
107
|
-
const repo = context.get('seedRepository')
|
|
108
|
-
const schemaMutator = context.get('schemaMutator')
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
await schemaMutator.execDestructive(stmts)
|
|
112
|
-
} catch (err) {
|
|
113
|
-
return publicProblem(context, { type: 'ddl-failed', title: 'DDL failed', status: 422, detail: internalErrorDetail(context.env, err) })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await repo.upsert(slug, updatedDef, 'runtime')
|
|
117
|
-
await repo.bumpRegistryVersion()
|
|
118
|
-
|
|
119
|
-
const actor = actorFromContext(context)
|
|
120
|
-
context.get('activityLogger').log({ action: 'update', entityType: 'seed', entityId: slug, details: logDetails, actor })
|
|
121
|
-
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Typed confirm guard. Returns a 400 response on mismatch, null on success. */
|
|
126
|
-
function requireConfirm(context: any, expected: string, body: unknown) {
|
|
127
|
-
const confirm = (body as Record<string, unknown>)?.confirm
|
|
128
|
-
if (typeof confirm !== 'string' || confirm !== expected) {
|
|
129
|
-
return publicProblem(context, {
|
|
130
|
-
type: 'confirmation-required',
|
|
131
|
-
title: 'Confirmation required',
|
|
132
|
-
status: 400,
|
|
133
|
-
detail: `Destructive operation requires body field \`confirm\` equal to "${expected}".`,
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
return null
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function validateIncomingBranches(incomingBranches: Branch[], storedBranches: Branch[], slug: string, context: any) {
|
|
140
|
-
const storedById = new Map(storedBranches.map((b: Branch) => [b.id, b]))
|
|
141
|
-
|
|
142
|
-
for (const branch of incomingBranches) {
|
|
143
|
-
const stored = branch.id ? storedById.get(branch.id) : undefined
|
|
144
|
-
if (stored) {
|
|
145
|
-
if (branch.alias !== stored.alias) {
|
|
146
|
-
return publicProblem(context, {
|
|
147
|
-
type: 'alias-rename-not-supported',
|
|
148
|
-
title: 'Alias rename not supported',
|
|
149
|
-
status: 422,
|
|
150
|
-
detail: `Branch '${branch.id}' alias rename from '${stored.alias}' to '${branch.alias}' is irreversible. Use PATCH /api/seeds/${slug}/branches/${branch.id}/rename with a typed confirmation.`,
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
if (branch.type !== stored.type) {
|
|
154
|
-
return publicProblem(context, {
|
|
155
|
-
type: 'branch-type-change-not-supported',
|
|
156
|
-
title: 'Branch type change not supported',
|
|
157
|
-
status: 422,
|
|
158
|
-
detail: `Branch '${branch.id}' type change from '${stored.type}' to '${branch.type}' is irreversible. Use PATCH /api/seeds/${slug}/branches/${branch.id}/retype with a typed confirmation.`,
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
} else if (!branch.id) {
|
|
162
|
-
const accSeed = { branches: incomingBranches.filter(b => b.id) }
|
|
163
|
-
branch.id = nextBranchId(accSeed)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return null
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function deleteSeedMediaObjects(context: any, slug: string, seed: Seed, schemaMutator: any) {
|
|
170
|
-
const fileBranches = seed.branches.filter((b: Branch) => b.type === 'file')
|
|
171
|
-
if (fileBranches.length === 0) return
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const dbCols = await schemaMutator.getColumns(`content_${slug}`)
|
|
175
|
-
if (!dbCols) return
|
|
176
|
-
|
|
177
|
-
const colList = fileBranches.map((b: Branch) => b.alias).filter((a: string) => dbCols.has(a)).join(', ')
|
|
178
|
-
if (!colList) return
|
|
179
|
-
|
|
180
|
-
const rs = await context.env.DB.prepare(`SELECT ${colList} FROM content_${slug}`).all()
|
|
181
|
-
const rows = (rs.results as Record<string, unknown>[]) ?? []
|
|
182
|
-
const r2Keys: string[] = []
|
|
183
|
-
|
|
184
|
-
for (const row of rows) {
|
|
185
|
-
for (const b of fileBranches) {
|
|
186
|
-
const val = row[b.alias]
|
|
187
|
-
if (!val) continue
|
|
188
|
-
const keys = extractMediaKeysFromData(seed, { [b.alias]: val })
|
|
189
|
-
r2Keys.push(...keys)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (r2Keys.length > 0) {
|
|
194
|
-
await deleteR2Objects(context, r2Keys).catch(() => { /* non-fatal */ })
|
|
195
|
-
}
|
|
196
|
-
} catch { /* non-fatal: drop proceeds */ }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export const seedsApp = new Hono<{ Bindings: Env; Variables: Variables }>()
|
|
200
|
-
|
|
201
|
-
seedsApp.use('*', async (context, next) => {
|
|
202
|
-
const denied = requireAdmin(context)
|
|
203
|
-
if (denied) return denied
|
|
204
|
-
await next()
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
/** GET /api/seeds — list all seed records (active + deleted). Admin-only. */
|
|
208
|
-
seedsApp.get('/', async (context) => {
|
|
209
|
-
const records = await context.get('seedRepository').listAll()
|
|
210
|
-
return context.json(records)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
/** GET /api/seeds/:slug — single record by slug. Admin-only. */
|
|
214
|
-
seedsApp.get('/:slug', async (context) => {
|
|
215
|
-
const slug = context.req.param('slug')
|
|
216
|
-
const record = await context.get('seedRepository').get(slug)
|
|
217
|
-
if (!record) {
|
|
218
|
-
return publicProblem(context, {
|
|
219
|
-
type: 'seed-not-found',
|
|
220
|
-
title: 'Seed not found',
|
|
221
|
-
status: 404,
|
|
222
|
-
detail: `No seed with slug '${slug}'.`,
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
return context.json(record)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
/** POST /api/seeds — create a new content type. */
|
|
229
|
-
seedsApp.post('/', async (context) => {
|
|
230
|
-
const body = await parseJsonBody(context)
|
|
231
|
-
if (body instanceof Response) return body
|
|
232
|
-
|
|
233
|
-
const candidate = body as Seed
|
|
234
|
-
const slug = candidate?.slug
|
|
235
|
-
|
|
236
|
-
if (!slug || !SLUG_RE.test(slug)) {
|
|
237
|
-
return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `slug must match ${SLUG_RE.source}.` })
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const repo = context.get('seedRepository')
|
|
241
|
-
const existing = await repo.get(slug)
|
|
242
|
-
|
|
243
|
-
if (existing?.status === 'active') {
|
|
244
|
-
return publicProblem(context, {
|
|
245
|
-
type: 'slug-conflict',
|
|
246
|
-
title: 'Slug conflict',
|
|
247
|
-
status: 409,
|
|
248
|
-
detail: `An active seed with slug '${slug}' already exists.`,
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Assign ids to branches that are missing one
|
|
253
|
-
const accSeed: Pick<Seed, 'branches'> = { branches: [] }
|
|
254
|
-
const branches: Branch[] = Array.isArray(candidate.branches) ? candidate.branches : []
|
|
255
|
-
for (const branch of branches) {
|
|
256
|
-
if (!branch.id) {
|
|
257
|
-
branch.id = nextBranchId(accSeed)
|
|
258
|
-
}
|
|
259
|
-
accSeed.branches.push({ ...branch })
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Default displayNameAlias to first text branch if not provided
|
|
263
|
-
if (!candidate.displayNameAlias) {
|
|
264
|
-
const firstText = branches.find(b => b.type === 'text')
|
|
265
|
-
if (!firstText) {
|
|
266
|
-
return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: 'displayNameAlias is required (or include at least one text branch so it can be inferred).' })
|
|
267
|
-
}
|
|
268
|
-
candidate.displayNameAlias = firstText.alias
|
|
269
|
-
}
|
|
270
|
-
candidate.branches = branches
|
|
271
|
-
|
|
272
|
-
const error = await validateAndApplySeedDef(context, slug, candidate, 'create', { slug })
|
|
273
|
-
if (error) return error
|
|
274
|
-
|
|
275
|
-
return context.json({ slug }, 201)
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
/** PUT /api/seeds/:slug — replace definition (additive-only). */
|
|
279
|
-
seedsApp.put('/:slug', async (context) => {
|
|
280
|
-
const slug = context.req.param('slug')
|
|
281
|
-
const body = await parseJsonBody(context)
|
|
282
|
-
if (body instanceof Response) return body
|
|
283
|
-
|
|
284
|
-
const existing = await getActiveSeed(context, slug)
|
|
285
|
-
if (existing instanceof Response) return existing
|
|
286
|
-
|
|
287
|
-
const incoming = body as Seed
|
|
288
|
-
const storedBranches = existing.definition.branches
|
|
289
|
-
const incomingBranches: Branch[] = Array.isArray(incoming.branches) ? incoming.branches : []
|
|
290
|
-
|
|
291
|
-
const validationError = validateIncomingBranches(incomingBranches, storedBranches, slug, context)
|
|
292
|
-
if (validationError) return validationError
|
|
293
|
-
|
|
294
|
-
const candidate: Seed = { ...incoming, slug, branches: incomingBranches }
|
|
295
|
-
|
|
296
|
-
const error = await validateAndApplySeedDef(context, slug, candidate, 'update', { slug })
|
|
297
|
-
if (error) return error
|
|
298
|
-
|
|
299
|
-
return context.json({ slug })
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
/** POST /api/seeds/:slug/branches — add a single branch. */
|
|
303
|
-
seedsApp.post('/:slug/branches', async (context) => {
|
|
304
|
-
const slug = context.req.param('slug')
|
|
305
|
-
const body = await parseJsonBody(context)
|
|
306
|
-
if (body instanceof Response) return body
|
|
307
|
-
|
|
308
|
-
const existing = await getActiveSeed(context, slug)
|
|
309
|
-
if (existing instanceof Response) return existing
|
|
310
|
-
|
|
311
|
-
const newBranch = body as Branch
|
|
312
|
-
newBranch.id = nextBranchId(existing.definition)
|
|
313
|
-
|
|
314
|
-
const candidate: Seed = {
|
|
315
|
-
...existing.definition,
|
|
316
|
-
branches: [...existing.definition.branches, newBranch],
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const error = await validateAndApplySeedDef(context, slug, candidate, 'update', { addedBranch: newBranch.id })
|
|
320
|
-
if (error) return error
|
|
321
|
-
|
|
322
|
-
return context.json({ id: newBranch.id })
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
/** DELETE /api/seeds/:slug — soft-delete a content type. */
|
|
326
|
-
seedsApp.delete('/:slug', async (context) => {
|
|
327
|
-
const slug = context.req.param('slug')
|
|
328
|
-
const repo = context.get('seedRepository')
|
|
329
|
-
|
|
330
|
-
const existing = await getActiveSeed(context, slug)
|
|
331
|
-
if (existing instanceof Response) return existing
|
|
332
|
-
|
|
333
|
-
const backrefMap = context.get('backrefMap')
|
|
334
|
-
const inbound = backrefMap.get(slug)
|
|
335
|
-
if (inbound && inbound.length > 0) {
|
|
336
|
-
const referencers = [...new Set(inbound.map((r: any) => r.sourceSlug))]
|
|
337
|
-
return publicProblem(context, {
|
|
338
|
-
type: 'seed-referenced',
|
|
339
|
-
title: 'Seed referenced',
|
|
340
|
-
status: 409,
|
|
341
|
-
detail: `Seed '${slug}' is referenced by: ${referencers.join(', ')}. Remove those relations first.`,
|
|
342
|
-
})
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
await repo.softDelete(slug)
|
|
346
|
-
await repo.bumpRegistryVersion()
|
|
347
|
-
|
|
348
|
-
const actor = actorFromContext(context)
|
|
349
|
-
context.get('activityLogger').log({ action: 'delete', entityType: 'seed', entityId: slug, details: { slug }, actor })
|
|
350
|
-
|
|
351
|
-
return context.json({ success: true })
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
/** GET /api/seeds/:slug/orphans — columns in DB but absent from the definition. */
|
|
355
|
-
seedsApp.get('/:slug/orphans', async (context) => {
|
|
356
|
-
const slug = context.req.param('slug')
|
|
357
|
-
const existing = await getActiveSeed(context, slug)
|
|
358
|
-
if (existing instanceof Response) return existing
|
|
359
|
-
|
|
360
|
-
const schemaMutator = context.get('schemaMutator')
|
|
361
|
-
const dbCols = await schemaMutator.getColumns(`content_${slug}`)
|
|
362
|
-
if (!dbCols) return context.json({ orphans: [] })
|
|
363
|
-
|
|
364
|
-
const knownAliases = new Set([
|
|
365
|
-
'id', 'slug', 'status', 'created_at', 'updated_at',
|
|
366
|
-
...existing.definition.branches
|
|
367
|
-
.filter((b: Branch) => !(b.type === 'relation' && b.multiple === true))
|
|
368
|
-
.map((b: Branch) => b.alias),
|
|
369
|
-
])
|
|
370
|
-
const orphans = [...dbCols].filter((col: string) => !knownAliases.has(col))
|
|
371
|
-
return context.json({ orphans })
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
/** POST /api/seeds/:slug/fts/rebuild — rebuild FTS table + triggers. */
|
|
375
|
-
seedsApp.post('/:slug/fts/rebuild', async (context) => {
|
|
376
|
-
const slug = context.req.param('slug')
|
|
377
|
-
const existing = await getActiveSeed(context, slug)
|
|
378
|
-
if (existing instanceof Response) return existing
|
|
379
|
-
|
|
380
|
-
const stmts = planFtsRebuild(existing.definition)
|
|
381
|
-
if (stmts.length > 0) {
|
|
382
|
-
await context.get('schemaMutator').execDestructive(stmts)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const actor = actorFromContext(context)
|
|
386
|
-
context.get('activityLogger').log({ action: 'update', entityType: 'seed', entityId: slug, details: { op: 'fts-rebuild' }, actor })
|
|
387
|
-
|
|
388
|
-
return context.json({ success: true })
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
/** DELETE /api/seeds/:slug/hard — hard delete: drops tables + deletes row. */
|
|
392
|
-
seedsApp.delete('/:slug/hard', async (context) => {
|
|
393
|
-
const slug = context.req.param('slug')
|
|
394
|
-
const body = await parseJsonBody(context)
|
|
395
|
-
if (body instanceof Response) return body
|
|
396
|
-
|
|
397
|
-
const confirmErr = requireConfirm(context, slug, body)
|
|
398
|
-
if (confirmErr) return confirmErr
|
|
399
|
-
|
|
400
|
-
const existing = await getActiveSeed(context, slug)
|
|
401
|
-
if (existing instanceof Response) return existing
|
|
402
|
-
|
|
403
|
-
const backrefMap = context.get('backrefMap')
|
|
404
|
-
const inbound = backrefMap.get(slug)
|
|
405
|
-
if (inbound && inbound.length > 0) {
|
|
406
|
-
const referencers = [...new Set(inbound.map((r: any) => r.sourceSlug))]
|
|
407
|
-
return publicProblem(context, {
|
|
408
|
-
type: 'seed-referenced',
|
|
409
|
-
title: 'Seed referenced',
|
|
410
|
-
status: 409,
|
|
411
|
-
detail: `Seed '${slug}' is referenced by: ${referencers.join(', ')}. Remove those relations first.`,
|
|
412
|
-
})
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const repo = context.get('seedRepository')
|
|
416
|
-
const schemaMutator = context.get('schemaMutator')
|
|
417
|
-
const seed = existing.definition
|
|
418
|
-
|
|
419
|
-
await deleteSeedMediaObjects(context, slug, seed, schemaMutator)
|
|
420
|
-
|
|
421
|
-
await schemaMutator.execDestructive(generateDropTable(seed))
|
|
422
|
-
await repo.hardDelete(slug)
|
|
423
|
-
await repo.bumpRegistryVersion()
|
|
424
|
-
|
|
425
|
-
const actor = actorFromContext(context)
|
|
426
|
-
context.get('activityLogger').log({ action: 'delete', entityType: 'seed', entityId: slug, details: { op: 'hard-delete', slug }, actor })
|
|
427
|
-
|
|
428
|
-
return context.json({ success: true })
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
/** DELETE /api/seeds/:slug/branches/:branchId — drop a single field column. */
|
|
432
|
-
seedsApp.delete('/:slug/branches/:branchId', async (context) => {
|
|
433
|
-
const slug = context.req.param('slug')
|
|
434
|
-
const branchId = context.req.param('branchId')
|
|
435
|
-
const body = await parseJsonBody(context)
|
|
436
|
-
if (body instanceof Response) return body
|
|
437
|
-
|
|
438
|
-
const existing = await getActiveSeed(context, slug)
|
|
439
|
-
if (existing instanceof Response) return existing
|
|
440
|
-
|
|
441
|
-
const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
|
|
442
|
-
if (!branch) {
|
|
443
|
-
return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
|
|
447
|
-
if (confirmErr) return confirmErr
|
|
448
|
-
|
|
449
|
-
const updatedDef: Seed = {
|
|
450
|
-
...existing.definition,
|
|
451
|
-
branches: existing.definition.branches.filter((b: Branch) => b.id !== branchId),
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const stmts = generateDropColumn(existing.definition, branch.alias)
|
|
455
|
-
const error = await applyDestructiveSeedDef(context, slug, updatedDef, stmts, { op: 'drop-branch', branchId, alias: branch.alias })
|
|
456
|
-
if (error) return error
|
|
457
|
-
|
|
458
|
-
return context.json({ success: true })
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
/** PATCH /api/seeds/:slug/branches/:branchId/rename — rename a field alias. */
|
|
462
|
-
seedsApp.patch('/:slug/branches/:branchId/rename', async (context) => {
|
|
463
|
-
const slug = context.req.param('slug')
|
|
464
|
-
const branchId = context.req.param('branchId')
|
|
465
|
-
const body = await parseJsonBody(context)
|
|
466
|
-
if (body instanceof Response) return body
|
|
467
|
-
|
|
468
|
-
const newAlias = (body as Record<string, unknown>)?.newAlias
|
|
469
|
-
if (typeof newAlias !== 'string' || !BRANCH_ALIAS_RE.test(newAlias)) {
|
|
470
|
-
return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `newAlias must match ${BRANCH_ALIAS_RE.source} (lowercase letter followed by alphanumeric characters or underscores).` })
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const existing = await getActiveSeed(context, slug)
|
|
474
|
-
if (existing instanceof Response) return existing
|
|
475
|
-
|
|
476
|
-
const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
|
|
477
|
-
if (!branch) {
|
|
478
|
-
return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
|
|
482
|
-
if (confirmErr) return confirmErr
|
|
483
|
-
|
|
484
|
-
const renamedDef: Seed = {
|
|
485
|
-
...existing.definition,
|
|
486
|
-
branches: existing.definition.branches.map((b: Branch) =>
|
|
487
|
-
b.id === branchId ? { ...b, alias: newAlias } : b
|
|
488
|
-
),
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const renameStmts = generateRenameColumn(existing.definition, branch.alias, newAlias)
|
|
492
|
-
const ftsStmts = planFtsRebuild(renamedDef)
|
|
493
|
-
|
|
494
|
-
const error = await applyDestructiveSeedDef(context, slug, renamedDef, [...renameStmts, ...ftsStmts], { op: 'rename-branch', branchId, from: branch.alias, to: newAlias })
|
|
495
|
-
if (error) return error
|
|
496
|
-
|
|
497
|
-
let affectedAutomations: string[] = []
|
|
498
|
-
try {
|
|
499
|
-
const automationRepo = context.get('automationRepository')
|
|
500
|
-
if (automationRepo) {
|
|
501
|
-
const automations = await automationRepo.list(slug)
|
|
502
|
-
const oldAlias = branch.alias
|
|
503
|
-
affectedAutomations = automations
|
|
504
|
-
.filter((a: any) => JSON.stringify(a).includes(oldAlias))
|
|
505
|
-
.map((a: any) => a.id)
|
|
506
|
-
}
|
|
507
|
-
} catch { /* non-fatal */ }
|
|
508
|
-
|
|
509
|
-
return context.json({ success: true, affectedAutomations })
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
/** PATCH /api/seeds/:slug/branches/:branchId/retype — change field SQL type. */
|
|
513
|
-
seedsApp.patch('/:slug/branches/:branchId/retype', async (context) => {
|
|
514
|
-
const slug = context.req.param('slug')
|
|
515
|
-
const branchId = context.req.param('branchId')
|
|
516
|
-
const body = await parseJsonBody(context)
|
|
517
|
-
if (body instanceof Response) return body
|
|
518
|
-
|
|
519
|
-
const newType = (body as Record<string, unknown>)?.newType
|
|
520
|
-
const VALID_TYPES = new Set(['text','number','boolean','date','json','richtext','file','tags','relation','repeater'])
|
|
521
|
-
if (typeof newType !== 'string' || !VALID_TYPES.has(newType)) {
|
|
522
|
-
return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `newType must be one of: ${[...VALID_TYPES].join(', ')}.` })
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const existing = await getActiveSeed(context, slug)
|
|
526
|
-
if (existing instanceof Response) return existing
|
|
527
|
-
|
|
528
|
-
const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
|
|
529
|
-
if (!branch) {
|
|
530
|
-
return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (branch.type === 'repeater' || newType === 'repeater') {
|
|
534
|
-
return publicProblem(context, {
|
|
535
|
-
type: 'retype-not-supported',
|
|
536
|
-
title: 'Retype not supported',
|
|
537
|
-
status: 422,
|
|
538
|
-
detail: "Retyping to or from 'repeater' is not supported in v1.",
|
|
539
|
-
})
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
|
|
543
|
-
if (confirmErr) return confirmErr
|
|
544
|
-
|
|
545
|
-
const retypedBranch: Branch = { ...branch, type: newType as Branch['type'] }
|
|
546
|
-
const retypedDef: Seed = {
|
|
547
|
-
...existing.definition,
|
|
548
|
-
branches: existing.definition.branches.map((b: Branch) => b.id === branchId ? retypedBranch : b),
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const retypeStmts = generateRetypeColumn(existing.definition, retypedBranch)
|
|
552
|
-
const ftsStmts = planFtsRebuild(retypedDef)
|
|
553
|
-
|
|
554
|
-
const error = await applyDestructiveSeedDef(context, slug, retypedDef, [...retypeStmts, ...ftsStmts], { op: 'retype-branch', branchId, from: branch.type, to: newType })
|
|
555
|
-
if (error) return error
|
|
556
|
-
|
|
557
|
-
return context.json({ success: true })
|
|
558
|
-
})
|