@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.
Files changed (375) hide show
  1. package/README.md +4 -4
  2. package/assets/dashboard/assets/chart-Cws4Yj6K.js +39 -0
  3. package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  4. package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  5. package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  6. package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  7. package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  8. package/assets/dashboard/assets/index-BjPpppHe.css +1 -0
  9. package/assets/dashboard/assets/index-CrW-zhkA.js +642 -0
  10. package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
  11. package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
  12. package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
  13. package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
  14. package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
  15. package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
  16. package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
  17. package/assets/dashboard/assets/pie-chart-recharts-CAxoDQp2.js +1 -0
  18. package/assets/dashboard/assets/timeseries-chart-recharts-CixDW-LM.js +1 -0
  19. package/assets/dashboard/index.html +2 -2
  20. package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
  21. package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
  22. package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
  23. package/dist/auth/constants.d.ts +10 -0
  24. package/dist/auth/jose-token-service.d.ts +14 -0
  25. package/dist/auth/login.d.ts +32 -0
  26. package/dist/auth/refresh.d.ts +1 -0
  27. package/dist/factory.d.ts +39 -0
  28. package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
  29. package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
  30. package/dist/features/automations/action-executors/index.d.ts +13 -0
  31. package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
  32. package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
  33. package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
  34. package/dist/features/automations/automation-runner.d.ts +13 -0
  35. package/dist/features/automations/automation-runner.utils.d.ts +3 -0
  36. package/dist/features/automations/automations.handler.d.ts +7 -0
  37. package/dist/features/automations/automations.schema.d.ts +191 -0
  38. package/dist/features/automations/context-resolver.d.ts +8 -0
  39. package/dist/features/automations/cron-runner.d.ts +9 -0
  40. package/dist/features/automations/cron-runner.utils.d.ts +1 -0
  41. package/dist/features/automations/filter-translation.d.ts +9 -0
  42. package/dist/features/automations/index.d.ts +7 -0
  43. package/dist/features/automations/template-grammar.d.ts +73 -0
  44. package/dist/features/automations/var-access-resolver.d.ts +6 -0
  45. package/dist/features/automations/when-evaluator.d.ts +3 -0
  46. package/dist/features/automations/when-pushdown.d.ts +2 -0
  47. package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
  48. package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
  49. package/dist/features/backrefs/index.d.ts +1 -0
  50. package/dist/features/content/constants.d.ts +12 -0
  51. package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
  52. package/dist/features/content/handlers/create.d.ts +3 -0
  53. package/dist/features/content/handlers/delete.d.ts +3 -0
  54. package/dist/features/content/handlers/facets.d.ts +3 -0
  55. package/dist/features/content/handlers/get.d.ts +4 -0
  56. package/dist/features/content/handlers/helpers.d.ts +12 -0
  57. package/dist/features/content/handlers/list.d.ts +3 -0
  58. package/dist/features/content/handlers/update.d.ts +3 -0
  59. package/dist/features/content/index.d.ts +4 -0
  60. package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
  61. package/dist/features/dashboard-layout/index.d.ts +1 -0
  62. package/dist/features/draft/draft.handler.d.ts +4 -0
  63. package/dist/features/draft/draft.middleware.d.ts +9 -0
  64. package/dist/features/draft/index.d.ts +1 -0
  65. package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +19 -24
  66. package/dist/features/email/email.service.d.ts +13 -0
  67. package/dist/features/email/email.types.d.ts +94 -0
  68. package/{src/features/email/index.ts → dist/features/email/index.d.ts} +3 -16
  69. package/dist/features/email/providers/resend.d.ts +36 -0
  70. package/dist/features/email/providers/smtp.d.ts +11 -0
  71. package/dist/features/email/templates/automation-mail.d.ts +8 -0
  72. package/dist/features/email/templates/password-changed.d.ts +15 -0
  73. package/dist/features/email/templates/password-reset.d.ts +18 -0
  74. package/dist/features/email/templates/shell.d.ts +58 -0
  75. package/dist/features/notifications/index.d.ts +1 -0
  76. package/dist/features/notifications/notifications.handler.d.ts +15 -0
  77. package/dist/features/password-reset/index.d.ts +6 -0
  78. package/dist/features/password-reset/request.d.ts +10 -0
  79. package/dist/features/password-reset/reset.d.ts +9 -0
  80. package/dist/features/rotate-field/index.d.ts +1 -0
  81. package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
  82. package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
  83. package/dist/features/schema/schema.handler.d.ts +7 -0
  84. package/dist/features/seeds/index.d.ts +1 -0
  85. package/dist/features/seeds/seeds.handler.d.ts +6 -0
  86. package/dist/features/settings/settings.handler.d.ts +7 -0
  87. package/dist/features/setup/index.d.ts +7 -0
  88. package/dist/features/stats/index.d.ts +1 -0
  89. package/dist/features/stats/stats.handler.d.ts +7 -0
  90. package/dist/features/webhooks/index.d.ts +3 -0
  91. package/dist/index.d.ts +6 -0
  92. package/dist/index.js +9666 -0
  93. package/dist/media-utils.d.ts +25 -0
  94. package/dist/middleware/auth-providers.middleware.d.ts +8 -0
  95. package/dist/middleware/observability.middleware.d.ts +20 -0
  96. package/dist/middleware/rate-limit.middleware.d.ts +9 -0
  97. package/dist/middleware/repository.middleware.d.ts +34 -0
  98. package/dist/middleware/seed-registry.middleware.d.ts +10 -0
  99. package/dist/middleware/storage.middleware.d.ts +7 -0
  100. package/dist/middleware.d.ts +11 -0
  101. package/dist/public/access-policy.d.ts +12 -0
  102. package/dist/public/api-key-middleware.d.ts +6 -0
  103. package/dist/public/cache-utils.d.ts +10 -0
  104. package/dist/public/entry-projection.d.ts +2 -0
  105. package/dist/public/idempotency.d.ts +9 -0
  106. package/dist/public/index.d.ts +11 -0
  107. package/dist/public/problem-details.d.ts +31 -0
  108. package/dist/public/public-add.d.ts +3 -0
  109. package/dist/public/public-edit.d.ts +5 -0
  110. package/dist/public/public-errors.d.ts +13 -0
  111. package/dist/public/public-read.d.ts +3 -0
  112. package/dist/public/public-routes.d.ts +3 -0
  113. package/dist/public/query-builder.d.ts +28 -0
  114. package/dist/public/rate-limit-middleware.d.ts +3 -0
  115. package/dist/public/read-list.d.ts +26 -0
  116. package/dist/public/read-single.d.ts +22 -0
  117. package/dist/public/response-builder.d.ts +22 -0
  118. package/dist/public/sanitize.d.ts +23 -0
  119. package/{src/public/slug-utils.ts → dist/public/slug-utils.d.ts} +3 -10
  120. package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
  121. package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
  122. package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
  123. package/dist/search-utils.d.ts +54 -0
  124. package/dist/search.d.ts +6 -0
  125. package/dist/shared/apply-policies.d.ts +10 -0
  126. package/dist/shared/automations.repository.d1.d.ts +12 -0
  127. package/dist/shared/background-notification-service.d.ts +18 -0
  128. package/dist/shared/base.repository.d1.d.ts +18 -0
  129. package/dist/shared/content-utils.d.ts +21 -0
  130. package/dist/shared/content.repository.d1.d.ts +68 -0
  131. package/dist/shared/d1-activity-log.repository.d.ts +13 -0
  132. package/dist/shared/d1-activity-logger.d.ts +20 -0
  133. package/dist/shared/d1-analytics.repository.d.ts +20 -0
  134. package/dist/shared/d1-content-scan.repository.d.ts +7 -0
  135. package/dist/shared/d1-notification.repository.d.ts +17 -0
  136. package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
  137. package/dist/shared/d1-search.repository.d.ts +14 -0
  138. package/dist/shared/d1-session.repository.d.ts +12 -0
  139. package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
  140. package/dist/shared/d1-user.repository.d.ts +18 -0
  141. package/dist/shared/d1-widget.repository.d.ts +31 -0
  142. package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
  143. package/dist/shared/demo-data-sql.d.ts +1 -0
  144. package/dist/shared/demo-data.repository.d1.d.ts +6 -0
  145. package/dist/shared/execution-context-scheduler.d.ts +6 -0
  146. package/dist/shared/fixed-clock.d.ts +13 -0
  147. package/dist/shared/fts-sync.d.ts +1 -0
  148. package/dist/shared/idempotency.repository.d1.d.ts +16 -0
  149. package/dist/shared/in-memory-activity-logger.d.ts +11 -0
  150. package/dist/shared/in-memory-notification-service.d.ts +11 -0
  151. package/dist/shared/in-memory-seed.repository.d.ts +18 -0
  152. package/dist/shared/media.repository.d1.d.ts +35 -0
  153. package/dist/shared/qstash-notification-service.d.ts +18 -0
  154. package/dist/shared/query-utils.d.ts +36 -0
  155. package/dist/shared/request-utils.d.ts +13 -0
  156. package/dist/shared/schema-mutator.d1.d.ts +12 -0
  157. package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
  158. package/dist/shared/seed-registry-cache.d.ts +12 -0
  159. package/dist/shared/seed.repository.d1.d.ts +13 -0
  160. package/dist/shared/sequential-id-generator.d.ts +13 -0
  161. package/dist/shared/site-settings.repository.d1.d.ts +7 -0
  162. package/dist/shared/storage/factory.d.ts +8 -0
  163. package/dist/shared/storage/s3-bucket.d.ts +43 -0
  164. package/dist/shared/storage-utils.d.ts +7 -0
  165. package/dist/shared/system-stats.repository.d1.d.ts +25 -0
  166. package/dist/types.d.ts +85 -0
  167. package/dist/upload.d.ts +16 -0
  168. package/dist/widget.d.ts +7 -0
  169. package/package.json +27 -18
  170. package/assets/dashboard/assets/chart-BrJFdsGZ.js +0 -39
  171. package/assets/dashboard/assets/index-CIaoMxuB.css +0 -1
  172. package/assets/dashboard/assets/index-wTKgygsg.js +0 -636
  173. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +0 -1
  174. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +0 -1
  175. package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
  176. package/src/auth/__fixtures__/static-token-service.ts +0 -22
  177. package/src/auth/bcrypt-hash-provider.ts +0 -24
  178. package/src/auth/constants.ts +0 -14
  179. package/src/auth/generate-refresh-token.test.ts +0 -23
  180. package/src/auth/hash-provider.test.ts +0 -55
  181. package/src/auth/jose-token-service.ts +0 -62
  182. package/src/auth/jwt-claims-passthrough.test.ts +0 -87
  183. package/src/auth/login.test.ts +0 -100
  184. package/src/auth/login.ts +0 -78
  185. package/src/auth/refresh.ts +0 -11
  186. package/src/auth/token-service.test.ts +0 -90
  187. package/src/factory.ts +0 -370
  188. package/src/features/automations/__tests__/action-executors.test.ts +0 -255
  189. package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
  190. package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
  191. package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
  192. package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
  193. package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
  194. package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
  195. package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
  196. package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
  197. package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
  198. package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
  199. package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
  200. package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
  201. package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
  202. package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
  203. package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
  204. package/src/features/automations/action-executors/index.ts +0 -37
  205. package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
  206. package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
  207. package/src/features/automations/action-executors/webhook.executor.ts +0 -67
  208. package/src/features/automations/automation-runner.ts +0 -85
  209. package/src/features/automations/automation-runner.utils.ts +0 -47
  210. package/src/features/automations/automations.handler.ts +0 -197
  211. package/src/features/automations/automations.schema.ts +0 -177
  212. package/src/features/automations/context-resolver.ts +0 -152
  213. package/src/features/automations/cron-runner.ts +0 -140
  214. package/src/features/automations/cron-runner.utils.ts +0 -44
  215. package/src/features/automations/filter-translation.ts +0 -46
  216. package/src/features/automations/index.ts +0 -16
  217. package/src/features/automations/template-grammar.ts +0 -245
  218. package/src/features/automations/var-access-resolver.ts +0 -140
  219. package/src/features/automations/when-evaluator.ts +0 -87
  220. package/src/features/automations/when-pushdown.ts +0 -57
  221. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
  222. package/src/features/backrefs/backrefs.handler.ts +0 -168
  223. package/src/features/backrefs/d1-backref.repository.ts +0 -84
  224. package/src/features/backrefs/index.ts +0 -5
  225. package/src/features/content/constants.ts +0 -16
  226. package/src/features/content/handlers/bulk.handler.ts +0 -185
  227. package/src/features/content/handlers/create.ts +0 -129
  228. package/src/features/content/handlers/delete.ts +0 -62
  229. package/src/features/content/handlers/facets.ts +0 -49
  230. package/src/features/content/handlers/get.ts +0 -120
  231. package/src/features/content/handlers/helpers.test.ts +0 -180
  232. package/src/features/content/handlers/helpers.ts +0 -93
  233. package/src/features/content/handlers/list.ts +0 -174
  234. package/src/features/content/handlers/update.ts +0 -174
  235. package/src/features/content/index.ts +0 -26
  236. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +0 -388
  237. package/src/features/dashboard-layout/dashboard-layout.handler.ts +0 -206
  238. package/src/features/dashboard-layout/index.ts +0 -5
  239. package/src/features/draft/draft.handler.ts +0 -195
  240. package/src/features/draft/draft.middleware.ts +0 -66
  241. package/src/features/draft/index.ts +0 -5
  242. package/src/features/email/email.service.ts +0 -98
  243. package/src/features/email/email.types.ts +0 -119
  244. package/src/features/email/providers/resend.ts +0 -67
  245. package/src/features/email/providers/smtp.ts +0 -55
  246. package/src/features/email/templates/automation-mail.ts +0 -19
  247. package/src/features/email/templates/password-changed.ts +0 -63
  248. package/src/features/email/templates/password-reset.ts +0 -68
  249. package/src/features/email/templates/shell.ts +0 -96
  250. package/src/features/notifications/index.ts +0 -5
  251. package/src/features/notifications/notifications.handler.ts +0 -105
  252. package/src/features/password-reset/index.ts +0 -19
  253. package/src/features/password-reset/request.ts +0 -92
  254. package/src/features/password-reset/reset.ts +0 -102
  255. package/src/features/rotate-field/index.ts +0 -5
  256. package/src/features/rotate-field/rotate-field.handler.ts +0 -132
  257. package/src/features/rotate-field/rotate-field.schema.ts +0 -17
  258. package/src/features/schema/schema.handler.ts +0 -131
  259. package/src/features/seeds/index.ts +0 -5
  260. package/src/features/seeds/seeds.handler.test.ts +0 -1199
  261. package/src/features/seeds/seeds.handler.ts +0 -558
  262. package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
  263. package/src/features/settings/settings.handler.ts +0 -392
  264. package/src/features/setup/index.ts +0 -288
  265. package/src/features/stats/index.ts +0 -5
  266. package/src/features/stats/stats.handler.ts +0 -420
  267. package/src/features/webhooks/index.ts +0 -63
  268. package/src/index.ts +0 -65
  269. package/src/media-utils.ts +0 -82
  270. package/src/middleware/auth-providers.middleware.test.ts +0 -85
  271. package/src/middleware/auth-providers.middleware.ts +0 -45
  272. package/src/middleware/observability.middleware.test.ts +0 -93
  273. package/src/middleware/observability.middleware.ts +0 -70
  274. package/src/middleware/rate-limit.middleware.ts +0 -45
  275. package/src/middleware/repository.middleware.ts +0 -117
  276. package/src/middleware/seed-registry.middleware.test.ts +0 -97
  277. package/src/middleware/seed-registry.middleware.ts +0 -21
  278. package/src/middleware/storage.middleware.ts +0 -25
  279. package/src/middleware.ts +0 -45
  280. package/src/public/access-policy.ts +0 -27
  281. package/src/public/api-key-middleware.ts +0 -57
  282. package/src/public/cache-utils.ts +0 -38
  283. package/src/public/entry-projection.ts +0 -46
  284. package/src/public/idempotency.ts +0 -23
  285. package/src/public/index.ts +0 -16
  286. package/src/public/problem-details.ts +0 -124
  287. package/src/public/public-add.ts +0 -114
  288. package/src/public/public-edit.ts +0 -163
  289. package/src/public/public-errors.ts +0 -19
  290. package/src/public/public-read.ts +0 -63
  291. package/src/public/public-routes.ts +0 -88
  292. package/src/public/query-builder.test.ts +0 -224
  293. package/src/public/query-builder.ts +0 -156
  294. package/src/public/rate-limit-middleware.ts +0 -34
  295. package/src/public/read-list.ts +0 -54
  296. package/src/public/read-single.ts +0 -48
  297. package/src/public/response-builder.ts +0 -30
  298. package/src/public/sanitize.ts +0 -69
  299. package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
  300. package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
  301. package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
  302. package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
  303. package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
  304. package/src/rate-limit/no-op-rate-limiter.ts +0 -11
  305. package/src/search-utils.test.ts +0 -211
  306. package/src/search-utils.ts +0 -213
  307. package/src/search.ts +0 -65
  308. package/src/shared/apply-policies.test.ts +0 -81
  309. package/src/shared/apply-policies.ts +0 -67
  310. package/src/shared/automations.repository.d1.ts +0 -150
  311. package/src/shared/background-notification-service.test.ts +0 -62
  312. package/src/shared/background-notification-service.ts +0 -52
  313. package/src/shared/base.repository.d1.ts +0 -32
  314. package/src/shared/content-utils.test.ts +0 -165
  315. package/src/shared/content-utils.ts +0 -86
  316. package/src/shared/content.repository.d1.test.ts +0 -507
  317. package/src/shared/content.repository.d1.ts +0 -851
  318. package/src/shared/d1-activity-log.repository.test.ts +0 -140
  319. package/src/shared/d1-activity-log.repository.ts +0 -105
  320. package/src/shared/d1-activity-logger.test.ts +0 -86
  321. package/src/shared/d1-activity-logger.ts +0 -67
  322. package/src/shared/d1-analytics.repository.test.ts +0 -78
  323. package/src/shared/d1-analytics.repository.ts +0 -85
  324. package/src/shared/d1-content-scan.repository.test.ts +0 -80
  325. package/src/shared/d1-content-scan.repository.ts +0 -37
  326. package/src/shared/d1-notification.repository.test.ts +0 -128
  327. package/src/shared/d1-notification.repository.ts +0 -118
  328. package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
  329. package/src/shared/d1-password-reset-token.repository.ts +0 -56
  330. package/src/shared/d1-search.repository.test.ts +0 -87
  331. package/src/shared/d1-search.repository.ts +0 -88
  332. package/src/shared/d1-session.repository.test.ts +0 -125
  333. package/src/shared/d1-session.repository.ts +0 -102
  334. package/src/shared/d1-setup-checklist.repository.ts +0 -36
  335. package/src/shared/d1-user.repository.test.ts +0 -151
  336. package/src/shared/d1-user.repository.ts +0 -119
  337. package/src/shared/d1-widget.repository.test.ts +0 -267
  338. package/src/shared/d1-widget.repository.ts +0 -385
  339. package/src/shared/dashboard-layout.repository.d1.test.ts +0 -135
  340. package/src/shared/dashboard-layout.repository.d1.ts +0 -58
  341. package/src/shared/demo-data-sql.ts +0 -216
  342. package/src/shared/demo-data.repository.d1.ts +0 -20
  343. package/src/shared/execution-context-scheduler.ts +0 -13
  344. package/src/shared/fixed-clock.ts +0 -25
  345. package/src/shared/fts-sync.ts +0 -8
  346. package/src/shared/idempotency.repository.d1.test.ts +0 -83
  347. package/src/shared/idempotency.repository.d1.ts +0 -60
  348. package/src/shared/in-memory-activity-logger.ts +0 -19
  349. package/src/shared/in-memory-notification-service.ts +0 -19
  350. package/src/shared/in-memory-seed.repository.ts +0 -58
  351. package/src/shared/media.repository.d1.test.ts +0 -107
  352. package/src/shared/media.repository.d1.ts +0 -68
  353. package/src/shared/qstash-notification-service.test.ts +0 -67
  354. package/src/shared/qstash-notification-service.ts +0 -55
  355. package/src/shared/query-utils.ts +0 -141
  356. package/src/shared/request-utils.ts +0 -26
  357. package/src/shared/schema-mutator.d1.test.ts +0 -142
  358. package/src/shared/schema-mutator.d1.ts +0 -64
  359. package/src/shared/seed-layout.repository.d1.test.ts +0 -114
  360. package/src/shared/seed-layout.repository.d1.ts +0 -62
  361. package/src/shared/seed-registry-cache.test.ts +0 -68
  362. package/src/shared/seed-registry-cache.ts +0 -49
  363. package/src/shared/seed.repository.d1.test.ts +0 -143
  364. package/src/shared/seed.repository.d1.ts +0 -98
  365. package/src/shared/sequential-id-generator.ts +0 -33
  366. package/src/shared/site-settings.repository.d1.ts +0 -53
  367. package/src/shared/storage/factory.ts +0 -41
  368. package/src/shared/storage/s3-bucket.ts +0 -195
  369. package/src/shared/storage-utils.ts +0 -40
  370. package/src/shared/system-stats.repository.d1.test.ts +0 -58
  371. package/src/shared/system-stats.repository.d1.ts +0 -48
  372. package/src/types.ts +0 -90
  373. package/src/upload.ts +0 -198
  374. package/src/widget.test.ts +0 -85
  375. package/src/widget.ts +0 -251
@@ -1,68 +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
- import type { EmailLocale } from '../email.types'
6
- import { buildEmailShell } from './shell'
7
-
8
- /**
9
- * Localized texts for the password reset email.
10
- *
11
- * ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
12
- * 1. Add the ISO code in `SUPPORTED_EMAIL_LOCALES` (`email.types.ts`).
13
- * 2. Add a corresponding key in this object with all fields.
14
- * TypeScript immediately reports missing keys thanks to
15
- * `Record<EmailLocale, …>`.
16
- * ─────────────────────────────────────────────────────────────────────────────
17
- */
18
- const COPY: Record<EmailLocale, {
19
- subject: string
20
- title: string
21
- body: string
22
- ctaLabel: string
23
- footer: string
24
- }> = {
25
- en: {
26
- subject: 'Reset your Beech CMS password',
27
- title: 'Reset your password',
28
- body: 'You requested a password reset for your Beech CMS account. Click the button below to set a new password. This link expires in 30 minutes.',
29
- ctaLabel: 'Reset password',
30
- footer: "If you didn't request this, you can safely ignore this email.",
31
- },
32
- it: {
33
- subject: 'Reimposta la tua password Beech CMS',
34
- title: 'Reimposta la tua password',
35
- body: 'Hai richiesto il reset della password per il tuo account Beech CMS. Clicca il pulsante qui sotto per impostare una nuova password. Questo link scade tra 30 minuti.',
36
- ctaLabel: 'Reimposta password',
37
- footer: 'Se non hai richiesto questo, puoi ignorare questa email in tutta sicurezza.',
38
- },
39
- }
40
-
41
- /**
42
- * Builds the email with the password reset link.
43
- *
44
- * Composes the localized content with the base layout (`buildEmailShell`)
45
- * injecting the CTA button pointing to the reset URL.
46
- *
47
- * @param resetUrl - Complete URL with the token in plain text, e.g.
48
- * `https://dashboard.beechcms.dev/reset-password?token=<uuid>`.
49
- * It is directly embedded in the CTA button — do not sanitize
50
- * further: the token is an internally generated UUID.
51
- * @param locale - Language for the email subject and body.
52
- * @returns Object with `subject` (string) and `html` (complete HTML document).
53
- */
54
- export function buildPasswordResetEmail(
55
- resetUrl: string,
56
- locale: EmailLocale,
57
- ): { subject: string; html: string } {
58
- const c = COPY[locale]
59
- return {
60
- subject: c.subject,
61
- html: buildEmailShell(locale, {
62
- title: c.title,
63
- body: c.body,
64
- cta: { label: c.ctaLabel, href: resetUrl },
65
- footer: c.footer,
66
- }),
67
- }
68
- }
@@ -1,96 +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
- import type { EmailLocale } from '../email.types'
6
-
7
- /**
8
- * Content slots that every template must provide to compose a complete email.
9
- * Each slot corresponds to a visual block in the email card layout.
10
- */
11
- export interface EmailShellSlots {
12
- /**
13
- * H2 heading shown at the top of the card. Keep it under ~50 characters
14
- * to ensure good readability on mobile clients.
15
- */
16
- title: string
17
-
18
- /**
19
- * Main body text. Rendered as a paragraph.
20
- * Safe inline HTML is allowed (e.g., `<strong>`, `<a href="...">`),
21
- * but avoid block elements (`<p>`, `<div>`) that could break
22
- * the layout structure in rigid email clients (Outlook, Gmail).
23
- */
24
- body: string
25
-
26
- /**
27
- * Optional call-to-action button, rendered as a dark link-button.
28
- * Omit for notification-only emails that do not require user action.
29
- */
30
- cta?: { label: string; href: string }
31
-
32
- /**
33
- * Optional warning paragraph. Rendered in red (#ef4444) to attract
34
- * attention. Use it for security alerts
35
- * ("if it wasn't you, act immediately").
36
- */
37
- warning?: string
38
-
39
- /**
40
- * Small gray text at the bottom of the card.
41
- * Used for notes like "automated notification, do not reply".
42
- */
43
- footer: string
44
- }
45
-
46
- /**
47
- * Builds the base HTML layout shared by all Beech CMS transactional emails.
48
- *
49
- * ─── SINGLE SOURCE OF TRUTH FOR BRANDING ─────────────────────────────────────
50
- * Modifying this function changes the visual appearance of ALL outgoing emails
51
- * simultaneously:
52
- * - background and card color
53
- * - border style and border-radius
54
- * - typographic scale and spacing
55
- * - CTA button style
56
- *
57
- * To change the text or structure of a specific email, instead modify
58
- * the corresponding template file (`templates/password-reset.ts`, etc.).
59
- * ─────────────────────────────────────────────────────────────────────────────
60
- *
61
- * @param locale - Used for the `lang` attribute of the `<html>` tag.
62
- * @param slots - Content blocks injected into the layout.
63
- * @returns A complete and self-contained HTML document ready to be sent.
64
- */
65
- export function buildEmailShell(locale: EmailLocale, slots: EmailShellSlots): string {
66
- const ctaBlock = slots.cta
67
- ? `<a href="${slots.cta.href}"
68
- style="display:inline-block;background:#111;color:#fff;padding:12px 24px;
69
- border-radius:6px;text-decoration:none;font-size:15px;font-weight:500;
70
- margin-bottom:24px">
71
- ${slots.cta.label}
72
- </a>`
73
- : ''
74
-
75
- const warningBlock = slots.warning
76
- ? `<p style="margin:0 0 24px;color:#ef4444;font-size:15px;line-height:1.5;font-weight:500">
77
- ${slots.warning}
78
- </p>`
79
- : ''
80
-
81
- return `<!DOCTYPE html>
82
- <html lang="${locale}">
83
- <head>
84
- <meta charset="UTF-8">
85
- <meta name="viewport" content="width=device-width,initial-scale=1">
86
- </head>
87
- <body style="font-family:sans-serif;background:#f9f9f9;margin:0;padding:32px">
88
- <div style="max-width:480px;margin:0 auto;background:#fff;border-radius:8px;
89
- padding:32px;border:1px solid #e5e5e5">
90
- <h2 style="margin:0 0 16px;font-size:20px;color:#111">${slots.title}</h2>
91
- <p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.5">${slots.body}</p>
92
- ${ctaBlock}${warningBlock}<p style="margin:0;color:#999;font-size:13px">${slots.footer}</p>
93
- </div>
94
- </body>
95
- </html>`
96
- }
@@ -1,5 +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
- export { notificationsApp } from './notifications.handler'
@@ -1,105 +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 { Env, Variables } from '../../types'
8
-
9
- /**
10
- * Notifications Feature Handler.
11
- *
12
- * Manages retrieval, mark read/unread, and deletion of system notifications.
13
- * All persistence goes through the {@link INotificationRepository} injected
14
- * by `repositoryMiddleware`. The handler owns the HTTP concerns only:
15
- * ETag negotiation, status codes, error mapping.
16
- */
17
- const notificationsApp = new Hono<{ Bindings: Env; Variables: Variables }>()
18
-
19
- const NOTIFICATION_LIST_LIMIT = 50
20
-
21
- /**
22
- * GET /notifications
23
- *
24
- * Returns the most recent notifications. The ETag is built from aggregate
25
- * stats so unchanged inboxes return 304 without serialising the full list.
26
- */
27
- notificationsApp.get('/notifications', async (context) => {
28
- try {
29
- const notificationRepository = context.get('notificationRepository')
30
- const notificationStats = await notificationRepository.stats()
31
- const etagValue = `W/"${notificationStats.totalCount}-${notificationStats.latestCreatedAt}-${notificationStats.readCount}"`
32
-
33
- const ifNoneMatch = context.req.header('If-None-Match')
34
- if (ifNoneMatch === etagValue) {
35
- return new Response(null, { status: 304 })
36
- }
37
-
38
- const notifications = await notificationRepository.list(NOTIFICATION_LIST_LIMIT)
39
-
40
- context.header('ETag', etagValue)
41
- context.header('Cache-Control', 'no-cache, must-revalidate')
42
-
43
- return context.json(notifications)
44
- } catch (error) {
45
- console.error('[Notifications] Fetch error:', error)
46
- return context.json({ error: 'Failed to fetch notifications' }, 500)
47
- }
48
- })
49
-
50
- /**
51
- * PATCH /notifications/:id/read — mark a single notification as read.
52
- */
53
- notificationsApp.patch('/notifications/:id/read', async (context) => {
54
- try {
55
- const notificationId = context.req.param('id')
56
- await context.get('notificationRepository').markRead(notificationId)
57
- return context.json({ success: true })
58
- } catch (error) {
59
- console.error('[Notifications] Mark read error:', error)
60
- return context.json({ error: 'Failed to update notification' }, 500)
61
- }
62
- })
63
-
64
- /**
65
- * PATCH /notifications/:id/unread — mark a single notification as unread.
66
- */
67
- notificationsApp.patch('/notifications/:id/unread', async (context) => {
68
- try {
69
- const notificationId = context.req.param('id')
70
- await context.get('notificationRepository').markUnread(notificationId)
71
- return context.json({ success: true })
72
- } catch (error) {
73
- console.error('[Notifications] Mark unread error:', error)
74
- return context.json({ error: 'Failed to update notification' }, 500)
75
- }
76
- })
77
-
78
- /**
79
- * DELETE /notifications/:id — permanently remove a notification.
80
- */
81
- notificationsApp.delete('/notifications/:id', async (context) => {
82
- try {
83
- const notificationId = context.req.param('id')
84
- await context.get('notificationRepository').delete(notificationId)
85
- return context.json({ success: true })
86
- } catch (error) {
87
- console.error('[Notifications] Delete error:', error)
88
- return context.json({ error: 'Failed to delete notification' }, 500)
89
- }
90
- })
91
-
92
- /**
93
- * POST /notifications/mark-all-read — mark every notification as read.
94
- */
95
- notificationsApp.post('/notifications/mark-all-read', async (context) => {
96
- try {
97
- await context.get('notificationRepository').markAllRead()
98
- return context.json({ success: true })
99
- } catch (error) {
100
- console.error('[Notifications] Mark all read error:', error)
101
- return context.json({ error: 'Failed to update notifications' }, 500)
102
- }
103
- })
104
-
105
- export { notificationsApp }
@@ -1,19 +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 { Env, Variables } from '../../types'
8
- import { requestPasswordReset } from './request'
9
- import { resetPassword } from './reset'
10
-
11
- export const passwordResetApp = new Hono<{ Bindings: Env; Variables: Variables }>()
12
-
13
- // Feature flag: lets the dashboard know whether to show the forgot-password link
14
- passwordResetApp.get('/auth/features', (context) => {
15
- return context.json({ passwordReset: context.env.EMAIL_PROVIDER === 'smtp' || Boolean(context.env.RESEND_API_KEY) })
16
- })
17
-
18
- passwordResetApp.post('/auth/forgot-password', requestPasswordReset)
19
- passwordResetApp.post('/auth/reset-password', resetPassword)
@@ -1,92 +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 type { Context } from 'hono'
7
- import type { Env, Variables } from '../../types'
8
- import { sendPasswordResetEmail, resolveEmailLocale } from '../email'
9
- import { sha256hex } from '@beechcms/core'
10
- import { getClientIp } from '../../shared/request-utils'
11
-
12
- const PASSWORD_RESET_TOKEN_EXPIRY_SECONDS = 30 * 60
13
-
14
- /**
15
- * Handles the password reset request.
16
- * Generates a reset token, stores its hash in the database, and sends an email to the user.
17
- */
18
- export async function requestPasswordReset(
19
- context: Context<{ Bindings: Env; Variables: Variables }>
20
- ): Promise<Response> {
21
- const { env, req } = context
22
-
23
- const useSmtp = env.EMAIL_PROVIDER === 'smtp'
24
- if (!useSmtp && !env.RESEND_API_KEY) {
25
- return context.json({ error: 'Service not available' }, 503)
26
- }
27
-
28
- let payload: Record<string, unknown>
29
- try {
30
- payload = await req.json()
31
- } catch {
32
- return context.json({ error: 'Invalid request body' }, 400)
33
- }
34
-
35
- const emailInput = payload.email
36
- if (typeof emailInput !== 'string' || !emailInput.trim()) {
37
- return context.json({ error: 'Invalid request' }, 400)
38
- }
39
-
40
- const normalizedEmail = emailInput.trim().toLowerCase()
41
- const emailLocale = resolveEmailLocale(payload.locale)
42
-
43
- const clientIpAddress = getClientIp(req)
44
- const forgotPasswordRateLimit = await context.get('rateLimiters').getLimiter('forgotPassword').checkLimit(clientIpAddress)
45
- if (!forgotPasswordRateLimit.isAllowed) {
46
- return context.json({ error: 'Too many requests' }, 429)
47
- }
48
-
49
- // Always return 200 even when the user is not found to prevent user enumeration.
50
- const registeredUser = await context.get('userRepository').findByEmail(normalizedEmail)
51
- if (!registeredUser) {
52
- return context.json({ success: true })
53
- }
54
-
55
- const nowTimestamp = Math.floor(Date.now() / 1000)
56
- await context.get('passwordResetTokenRepository').invalidatePending(registeredUser.id, nowTimestamp)
57
-
58
- const resetToken = crypto.randomUUID()
59
- const tokenHash = await sha256hex(resetToken)
60
- const expiresAt = nowTimestamp + PASSWORD_RESET_TOKEN_EXPIRY_SECONDS
61
-
62
- await context.get('passwordResetTokenRepository').create({
63
- userId: registeredUser.id,
64
- tokenHash,
65
- expiresAt,
66
- })
67
-
68
- const baseUrl = (env.APP_URL ?? new URL(req.url).origin).replace(/\/$/, '')
69
- const resetUrl = `${baseUrl}/admin/reset-password?token=${resetToken}`
70
-
71
- const smtpBaseUrl = env.SMTP_HOST
72
- ? `http://${env.SMTP_HOST}:${env.SMTP_PORT ?? '8025'}`
73
- : undefined
74
- try {
75
- await sendPasswordResetEmail({
76
- to: normalizedEmail,
77
- resetUrl,
78
- locale: emailLocale,
79
- apiKey: env.RESEND_API_KEY ?? '',
80
- from: env.EMAIL_FROM,
81
- isDev: env.ENV !== 'production',
82
- provider: env.EMAIL_PROVIDER as 'smtp' | 'resend' | undefined,
83
- smtpBaseUrl,
84
- })
85
- } catch (error) {
86
- if (env.ENV !== 'production') {
87
- console.error('[password-reset] Failed to send email:', error)
88
- }
89
- }
90
-
91
- return context.json({ success: true })
92
- }
@@ -1,102 +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 type { Context } from 'hono'
7
- import type { Env, Variables } from '../../types'
8
- import { sendPasswordChangedEmail, resolveEmailLocale } from '../email'
9
- import { sha256hex } from '@beechcms/core'
10
- import { getClientIp } from '../../shared/request-utils'
11
-
12
- const MIN_PASSWORD_LENGTH = 8
13
- const MAX_PASSWORD_LENGTH = 128
14
-
15
- /**
16
- * Handles the actual password reset process using a valid token.
17
- */
18
- export async function resetPassword(
19
- context: Context<{ Bindings: Env; Variables: Variables }>
20
- ): Promise<Response> {
21
- const { env, req, executionCtx } = context
22
-
23
- const useSmtp = env.EMAIL_PROVIDER === 'smtp'
24
- if (!useSmtp && !env.RESEND_API_KEY) {
25
- return context.json({ error: 'Service not available' }, 503)
26
- }
27
-
28
- const clientIpAddress = getClientIp(req)
29
- const resetPasswordRateLimit = await context.get('rateLimiters').getLimiter('resetPassword').checkLimit(clientIpAddress)
30
- if (!resetPasswordRateLimit.isAllowed) {
31
- return context.json({ error: 'Too many requests' }, 429)
32
- }
33
-
34
- let payload: Record<string, unknown>
35
- try {
36
- payload = await req.json()
37
- } catch {
38
- return context.json({ error: 'Invalid request body' }, 400)
39
- }
40
-
41
- const resetToken = payload.token
42
- if (typeof resetToken !== 'string' || !resetToken) {
43
- return context.json({ error: 'Invalid or expired token' }, 400)
44
- }
45
-
46
- const newPassword = payload.password
47
- const isPasswordInvalid =
48
- typeof newPassword !== 'string' ||
49
- newPassword.length < MIN_PASSWORD_LENGTH ||
50
- newPassword.length > MAX_PASSWORD_LENGTH
51
-
52
- if (isPasswordInvalid) {
53
- return context.json({
54
- error: `Password must be between ${MIN_PASSWORD_LENGTH} and ${MAX_PASSWORD_LENGTH} characters`,
55
- }, 400)
56
- }
57
-
58
- const emailLocale = resolveEmailLocale(payload.locale)
59
- const tokenHash = await sha256hex(resetToken as string)
60
- const nowTimestamp = Math.floor(Date.now() / 1000)
61
-
62
- const tokenRecord = await context.get('passwordResetTokenRepository').findValidByHashWithEmail(tokenHash, nowTimestamp)
63
- if (!tokenRecord) {
64
- return context.json({ error: 'Invalid or expired token' }, 400)
65
- }
66
-
67
- const hashedNewPassword = await context.get('hashProvider').hash(newPassword as string)
68
-
69
- await context.get('passwordResetTokenRepository').markUsed(tokenRecord.id, nowTimestamp)
70
- await context.get('userRepository').updatePasswordHash(tokenRecord.userId, hashedNewPassword)
71
- await context.get('sessionRepository').revokeAllForUser(tokenRecord.userId, nowTimestamp)
72
-
73
- const smtpBaseUrl = env.SMTP_HOST
74
- ? `http://${env.SMTP_HOST}:${env.SMTP_PORT ?? '8025'}`
75
- : undefined
76
- const sendNotification = async () => {
77
- try {
78
- await sendPasswordChangedEmail({
79
- to: tokenRecord.email,
80
- locale: emailLocale,
81
- apiKey: env.RESEND_API_KEY ?? '',
82
- from: env.EMAIL_FROM,
83
- isDev: env.ENV !== 'production',
84
- provider: env.EMAIL_PROVIDER as 'smtp' | 'resend' | undefined,
85
- smtpBaseUrl,
86
- })
87
- } catch (error) {
88
- if (env.ENV !== 'production') {
89
- console.error('[password-reset] Failed to send password change notification:', error)
90
- }
91
- }
92
- }
93
-
94
- try {
95
- executionCtx.waitUntil(sendNotification())
96
- } catch {
97
- // executionCtx might not be available in some testing environments
98
- void sendNotification()
99
- }
100
-
101
- return context.json({ success: true })
102
- }
@@ -1,5 +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
- export { rotateFieldApp } from './rotate-field.handler'
@@ -1,132 +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 { resolvePolicies, verifyHashField, sha256hex, validateAndSanitizeSeedPayload, EntryNotFoundError } from '@beechcms/core'
8
- import { publicProblem } from '../../public/problem-details'
9
- import { rotateFieldRequestSchema } from './rotate-field.schema'
10
- import type { Env, Variables } from '../../types'
11
-
12
- const rotateFieldApp = new Hono<{ Bindings: Env; Variables: Variables }>()
13
-
14
- rotateFieldApp.post('/:slug/:id/rotate-field', async (context) => {
15
- const seedSlug = context.req.param('slug')
16
- const entryId = context.req.param('id')
17
-
18
- const seed = context.get('getSeed')(seedSlug)
19
- if (!seed) {
20
- return publicProblem(context, {
21
- type: 'content-seed-not-found',
22
- title: 'Not Found',
23
- status: 404,
24
- detail: `Seed '${seedSlug}' not found`
25
- })
26
- }
27
-
28
- let requestBody: unknown
29
- try {
30
- requestBody = await context.req.json()
31
- } catch {
32
- return publicProblem(context, {
33
- type: 'rotate-field-invalid-json',
34
- title: 'Bad Request',
35
- status: 400,
36
- detail: 'Invalid JSON body'
37
- })
38
- }
39
-
40
- const parsedRequestBody = rotateFieldRequestSchema.safeParse(requestBody)
41
- if (!parsedRequestBody.success) {
42
- return publicProblem(context, {
43
- type: 'rotate-field-invalid-body',
44
- title: 'Bad Request',
45
- status: 400,
46
- detail: parsedRequestBody.error.issues[0]?.message ?? 'Invalid body'
47
- })
48
- }
49
-
50
- const { fieldAlias, currentValue, nextValue } = parsedRequestBody.data
51
-
52
- const targetFieldBranch = seed.branches.find((branch) => branch.alias === fieldAlias)
53
- if (!targetFieldBranch) {
54
- return publicProblem(context, {
55
- type: 'rotate-field-unknown-field',
56
- title: 'Bad Request',
57
- status: 400,
58
- detail: `Field '${fieldAlias}' does not exist in seed '${seedSlug}'`
59
- })
60
- }
61
-
62
- const { privacy } = resolvePolicies(targetFieldBranch)
63
- if (privacy !== 'hash') {
64
- return publicProblem(context, {
65
- type: 'rotate-field-not-hashable',
66
- title: 'Unprocessable Entity',
67
- status: 422,
68
- detail: `Field '${fieldAlias}' does not use hash privacy and cannot be rotated with this endpoint`
69
- })
70
- }
71
-
72
- let contentRecord: Record<string, unknown>
73
- try {
74
- contentRecord = await context.get('repository').findById(seed, entryId)
75
- } catch (error) {
76
- if (error instanceof EntryNotFoundError) {
77
- return publicProblem(context, {
78
- type: 'content-not-found',
79
- title: 'Not Found',
80
- status: 404,
81
- detail: `Entry '${entryId}' not found`
82
- })
83
- }
84
- throw error
85
- }
86
-
87
- const storedFieldValueHash = contentRecord[targetFieldBranch.alias]
88
-
89
- if (typeof storedFieldValueHash !== 'string' || storedFieldValueHash.length === 0) {
90
- return publicProblem(context, {
91
- type: 'rotate-field-not-set',
92
- title: 'Unprocessable Entity',
93
- status: 422,
94
- detail: `Field '${fieldAlias}' has no stored value to rotate`
95
- })
96
- }
97
-
98
- const isCurrentValueValid = await verifyHashField(storedFieldValueHash, currentValue)
99
- if (!isCurrentValueValid) {
100
- return publicProblem(context, {
101
- type: 'rotate-field-current-mismatch',
102
- title: 'Forbidden',
103
- status: 403,
104
- detail: 'Current value does not match stored value'
105
- })
106
- }
107
-
108
- const fieldValidationResult = validateAndSanitizeSeedPayload(
109
- seed,
110
- { [fieldAlias]: nextValue },
111
- { operation: 'update', allowNull: false, requireAtLeastOneValidField: true, enforceRequiredFields: false }
112
- )
113
-
114
- if (fieldValidationResult.details.length > 0) {
115
- return publicProblem(context, {
116
- type: 'rotate-field-invalid-next',
117
- title: 'Bad Request',
118
- status: 400,
119
- detail: `Invalid value for field '${fieldAlias}': ${fieldValidationResult.details[0]?.message ?? 'validation failed'}`
120
- })
121
- }
122
-
123
- const newFieldValueHash = await sha256hex(nextValue)
124
-
125
- await context.get('repository').update(seed, entryId, { [targetFieldBranch.alias]: newFieldValueHash })
126
-
127
- return context.json({ success: true })
128
- })
129
-
130
-
131
- export { rotateFieldApp }
132
-
@@ -1,17 +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
- import { z } from 'zod'
6
-
7
- /**
8
- * Schema for rotating a hashed field value.
9
- * Used to update fields that have 'hash' privacy (e.g., passwords, PINs).
10
- */
11
- export const rotateFieldRequestSchema = z.object({
12
- fieldAlias: z.string().min(1, "The field alias is required"),
13
- currentValue: z.string().min(1, "The current value is required"),
14
- nextValue: z.string().min(1, "The new value is required"),
15
- })
16
-
17
- export type RotateFieldRequest = z.infer<typeof rotateFieldRequestSchema>