@beechcms/api 0.6.0-preview.2 → 0.6.0-preview.4

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 (388) hide show
  1. package/README.md +4 -4
  2. package/assets/dashboard/BeechLogo.svg +18 -18
  3. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  4. package/assets/dashboard/assets/chart-BwlCt-IA.js +39 -0
  5. package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  6. package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  7. package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  8. package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  9. package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  10. package/assets/dashboard/assets/index-BcVN-DNy.js +642 -0
  11. package/assets/dashboard/assets/index-Cf1yRCqG.css +1 -0
  12. package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
  13. package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
  14. package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
  15. package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
  16. package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
  17. package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
  18. package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
  19. package/assets/dashboard/assets/pie-chart-recharts-BMWpjFzf.js +1 -0
  20. package/assets/dashboard/assets/timeseries-chart-recharts-8_GwAFR0.js +1 -0
  21. package/assets/dashboard/beechLogoDark.svg +48 -48
  22. package/assets/dashboard/index.html +18 -18
  23. package/assets/dashboard/noResult.svg +42 -42
  24. package/assets/dashboard/sol.svg +3 -3
  25. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  26. package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
  27. package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
  28. package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
  29. package/dist/auth/constants.d.ts +10 -0
  30. package/dist/auth/jose-token-service.d.ts +14 -0
  31. package/dist/auth/login.d.ts +32 -0
  32. package/dist/auth/refresh.d.ts +1 -0
  33. package/dist/factory.d.ts +45 -0
  34. package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
  35. package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
  36. package/dist/features/automations/action-executors/index.d.ts +13 -0
  37. package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
  38. package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
  39. package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
  40. package/dist/features/automations/automation-runner.d.ts +13 -0
  41. package/dist/features/automations/automation-runner.utils.d.ts +3 -0
  42. package/dist/features/automations/automations.handler.d.ts +7 -0
  43. package/dist/features/automations/automations.schema.d.ts +191 -0
  44. package/dist/features/automations/context-resolver.d.ts +8 -0
  45. package/dist/features/automations/cron-runner.d.ts +9 -0
  46. package/dist/features/automations/cron-runner.utils.d.ts +1 -0
  47. package/dist/features/automations/filter-translation.d.ts +9 -0
  48. package/dist/features/automations/index.d.ts +7 -0
  49. package/dist/features/automations/template-grammar.d.ts +73 -0
  50. package/dist/features/automations/var-access-resolver.d.ts +6 -0
  51. package/dist/features/automations/when-evaluator.d.ts +3 -0
  52. package/dist/features/automations/when-pushdown.d.ts +2 -0
  53. package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
  54. package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
  55. package/dist/features/backrefs/index.d.ts +1 -0
  56. package/dist/features/content/constants.d.ts +12 -0
  57. package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
  58. package/dist/features/content/handlers/create.d.ts +3 -0
  59. package/dist/features/content/handlers/delete.d.ts +3 -0
  60. package/dist/features/content/handlers/facets.d.ts +3 -0
  61. package/dist/features/content/handlers/get.d.ts +4 -0
  62. package/dist/features/content/handlers/helpers.d.ts +12 -0
  63. package/dist/features/content/handlers/list.d.ts +3 -0
  64. package/dist/features/content/handlers/update.d.ts +3 -0
  65. package/dist/features/content/index.d.ts +4 -0
  66. package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
  67. package/dist/features/dashboard-layout/index.d.ts +1 -0
  68. package/dist/features/draft/draft.handler.d.ts +4 -0
  69. package/dist/features/draft/draft.middleware.d.ts +9 -0
  70. package/dist/features/draft/index.d.ts +1 -0
  71. package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +19 -24
  72. package/dist/features/email/email.service.d.ts +13 -0
  73. package/dist/features/email/email.types.d.ts +94 -0
  74. package/{src/features/email/index.ts → dist/features/email/index.d.ts} +3 -16
  75. package/dist/features/email/providers/resend.d.ts +36 -0
  76. package/dist/features/email/providers/smtp.d.ts +11 -0
  77. package/dist/features/email/templates/automation-mail.d.ts +8 -0
  78. package/dist/features/email/templates/password-changed.d.ts +15 -0
  79. package/dist/features/email/templates/password-reset.d.ts +18 -0
  80. package/dist/features/email/templates/shell.d.ts +58 -0
  81. package/dist/features/notifications/index.d.ts +1 -0
  82. package/dist/features/notifications/notifications.handler.d.ts +15 -0
  83. package/dist/features/password-reset/index.d.ts +6 -0
  84. package/dist/features/password-reset/request.d.ts +10 -0
  85. package/dist/features/password-reset/reset.d.ts +9 -0
  86. package/dist/features/rotate-field/index.d.ts +1 -0
  87. package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
  88. package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
  89. package/dist/features/schema/schema.handler.d.ts +7 -0
  90. package/dist/features/seeds/index.d.ts +1 -0
  91. package/dist/features/seeds/seeds.handler.d.ts +6 -0
  92. package/dist/features/settings/settings.handler.d.ts +7 -0
  93. package/dist/features/setup/index.d.ts +7 -0
  94. package/dist/features/stats/index.d.ts +1 -0
  95. package/dist/features/stats/stats.handler.d.ts +7 -0
  96. package/dist/features/webhooks/index.d.ts +3 -0
  97. package/dist/index.d.ts +7 -0
  98. package/dist/index.js +9732 -0
  99. package/dist/media-utils.d.ts +25 -0
  100. package/dist/middleware/auth-providers.middleware.d.ts +8 -0
  101. package/dist/middleware/observability.middleware.d.ts +20 -0
  102. package/dist/middleware/queue.middleware.d.ts +11 -0
  103. package/dist/middleware/rate-limit.middleware.d.ts +9 -0
  104. package/dist/middleware/repository.middleware.d.ts +34 -0
  105. package/dist/middleware/seed-registry.middleware.d.ts +10 -0
  106. package/dist/middleware/storage.middleware.d.ts +7 -0
  107. package/dist/middleware.d.ts +11 -0
  108. package/dist/public/access-policy.d.ts +12 -0
  109. package/dist/public/api-key-middleware.d.ts +6 -0
  110. package/dist/public/cache-utils.d.ts +10 -0
  111. package/dist/public/entry-projection.d.ts +2 -0
  112. package/dist/public/idempotency.d.ts +9 -0
  113. package/dist/public/index.d.ts +11 -0
  114. package/dist/public/problem-details.d.ts +31 -0
  115. package/dist/public/public-add.d.ts +3 -0
  116. package/dist/public/public-edit.d.ts +5 -0
  117. package/dist/public/public-errors.d.ts +13 -0
  118. package/dist/public/public-read.d.ts +3 -0
  119. package/dist/public/public-routes.d.ts +3 -0
  120. package/dist/public/query-builder.d.ts +28 -0
  121. package/dist/public/rate-limit-middleware.d.ts +3 -0
  122. package/dist/public/read-list.d.ts +26 -0
  123. package/dist/public/read-single.d.ts +22 -0
  124. package/dist/public/response-builder.d.ts +22 -0
  125. package/dist/public/sanitize.d.ts +23 -0
  126. package/{src/public/slug-utils.ts → dist/public/slug-utils.d.ts} +3 -10
  127. package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
  128. package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
  129. package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
  130. package/dist/search-utils.d.ts +54 -0
  131. package/dist/search.d.ts +6 -0
  132. package/dist/shared/apply-policies.d.ts +10 -0
  133. package/dist/shared/automations.repository.d1.d.ts +12 -0
  134. package/dist/shared/background-notification-service.d.ts +18 -0
  135. package/dist/shared/base.repository.d1.d.ts +18 -0
  136. package/dist/shared/cloudflare-queue-service.d.ts +11 -0
  137. package/dist/shared/content-utils.d.ts +21 -0
  138. package/dist/shared/content.repository.d1.d.ts +68 -0
  139. package/dist/shared/d1-activity-log.repository.d.ts +13 -0
  140. package/dist/shared/d1-activity-logger.d.ts +20 -0
  141. package/dist/shared/d1-analytics.repository.d.ts +20 -0
  142. package/dist/shared/d1-content-scan.repository.d.ts +7 -0
  143. package/dist/shared/d1-notification.repository.d.ts +17 -0
  144. package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
  145. package/dist/shared/d1-search.repository.d.ts +14 -0
  146. package/dist/shared/d1-session.repository.d.ts +12 -0
  147. package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
  148. package/dist/shared/d1-user.repository.d.ts +18 -0
  149. package/dist/shared/d1-widget.repository.d.ts +31 -0
  150. package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
  151. package/dist/shared/demo-data-sql.d.ts +1 -0
  152. package/dist/shared/demo-data.repository.d1.d.ts +6 -0
  153. package/dist/shared/execution-context-scheduler.d.ts +6 -0
  154. package/dist/shared/fixed-clock.d.ts +13 -0
  155. package/dist/shared/fts-sync.d.ts +1 -0
  156. package/dist/shared/idempotency.repository.d1.d.ts +16 -0
  157. package/dist/shared/in-memory-activity-logger.d.ts +11 -0
  158. package/dist/shared/in-memory-notification-service.d.ts +11 -0
  159. package/dist/shared/in-memory-queue-service.d.ts +16 -0
  160. package/dist/shared/in-memory-seed.repository.d.ts +18 -0
  161. package/dist/shared/media.repository.d1.d.ts +35 -0
  162. package/dist/shared/qstash-notification-service.d.ts +18 -0
  163. package/dist/shared/query-utils.d.ts +36 -0
  164. package/dist/shared/queue-consumer.d.ts +9 -0
  165. package/dist/shared/request-utils.d.ts +13 -0
  166. package/dist/shared/schema-mutator.d1.d.ts +12 -0
  167. package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
  168. package/dist/shared/seed-registry-cache.d.ts +12 -0
  169. package/dist/shared/seed.repository.d1.d.ts +13 -0
  170. package/dist/shared/sequential-id-generator.d.ts +13 -0
  171. package/dist/shared/site-settings.repository.d1.d.ts +7 -0
  172. package/dist/shared/storage/factory.d.ts +8 -0
  173. package/dist/shared/storage/s3-bucket.d.ts +43 -0
  174. package/dist/shared/storage-utils.d.ts +7 -0
  175. package/dist/shared/system-stats.repository.d1.d.ts +25 -0
  176. package/dist/types.d.ts +87 -0
  177. package/dist/upload.d.ts +16 -0
  178. package/dist/widget.d.ts +7 -0
  179. package/migrations/0000_v040_base.sql +265 -265
  180. package/migrations/0029_automations.sql +14 -14
  181. package/migrations/0030_test_seeds.sql +125 -125
  182. package/package.json +28 -18
  183. package/assets/dashboard/assets/chart-BrJFdsGZ.js +0 -39
  184. package/assets/dashboard/assets/index-CIaoMxuB.css +0 -1
  185. package/assets/dashboard/assets/index-wTKgygsg.js +0 -636
  186. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +0 -1
  187. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +0 -1
  188. package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
  189. package/src/auth/__fixtures__/static-token-service.ts +0 -22
  190. package/src/auth/bcrypt-hash-provider.ts +0 -24
  191. package/src/auth/constants.ts +0 -14
  192. package/src/auth/generate-refresh-token.test.ts +0 -23
  193. package/src/auth/hash-provider.test.ts +0 -55
  194. package/src/auth/jose-token-service.ts +0 -62
  195. package/src/auth/jwt-claims-passthrough.test.ts +0 -87
  196. package/src/auth/login.test.ts +0 -100
  197. package/src/auth/login.ts +0 -78
  198. package/src/auth/refresh.ts +0 -11
  199. package/src/auth/token-service.test.ts +0 -90
  200. package/src/factory.ts +0 -370
  201. package/src/features/automations/__tests__/action-executors.test.ts +0 -255
  202. package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
  203. package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
  204. package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
  205. package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
  206. package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
  207. package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
  208. package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
  209. package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
  210. package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
  211. package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
  212. package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
  213. package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
  214. package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
  215. package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
  216. package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
  217. package/src/features/automations/action-executors/index.ts +0 -37
  218. package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
  219. package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
  220. package/src/features/automations/action-executors/webhook.executor.ts +0 -67
  221. package/src/features/automations/automation-runner.ts +0 -85
  222. package/src/features/automations/automation-runner.utils.ts +0 -47
  223. package/src/features/automations/automations.handler.ts +0 -197
  224. package/src/features/automations/automations.schema.ts +0 -177
  225. package/src/features/automations/context-resolver.ts +0 -152
  226. package/src/features/automations/cron-runner.ts +0 -140
  227. package/src/features/automations/cron-runner.utils.ts +0 -44
  228. package/src/features/automations/filter-translation.ts +0 -46
  229. package/src/features/automations/index.ts +0 -16
  230. package/src/features/automations/template-grammar.ts +0 -245
  231. package/src/features/automations/var-access-resolver.ts +0 -140
  232. package/src/features/automations/when-evaluator.ts +0 -87
  233. package/src/features/automations/when-pushdown.ts +0 -57
  234. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
  235. package/src/features/backrefs/backrefs.handler.ts +0 -168
  236. package/src/features/backrefs/d1-backref.repository.ts +0 -84
  237. package/src/features/backrefs/index.ts +0 -5
  238. package/src/features/content/constants.ts +0 -16
  239. package/src/features/content/handlers/bulk.handler.ts +0 -185
  240. package/src/features/content/handlers/create.ts +0 -129
  241. package/src/features/content/handlers/delete.ts +0 -62
  242. package/src/features/content/handlers/facets.ts +0 -49
  243. package/src/features/content/handlers/get.ts +0 -120
  244. package/src/features/content/handlers/helpers.test.ts +0 -180
  245. package/src/features/content/handlers/helpers.ts +0 -93
  246. package/src/features/content/handlers/list.ts +0 -174
  247. package/src/features/content/handlers/update.ts +0 -174
  248. package/src/features/content/index.ts +0 -26
  249. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +0 -388
  250. package/src/features/dashboard-layout/dashboard-layout.handler.ts +0 -206
  251. package/src/features/dashboard-layout/index.ts +0 -5
  252. package/src/features/draft/draft.handler.ts +0 -195
  253. package/src/features/draft/draft.middleware.ts +0 -66
  254. package/src/features/draft/index.ts +0 -5
  255. package/src/features/email/email.service.ts +0 -98
  256. package/src/features/email/email.types.ts +0 -119
  257. package/src/features/email/providers/resend.ts +0 -67
  258. package/src/features/email/providers/smtp.ts +0 -55
  259. package/src/features/email/templates/automation-mail.ts +0 -19
  260. package/src/features/email/templates/password-changed.ts +0 -63
  261. package/src/features/email/templates/password-reset.ts +0 -68
  262. package/src/features/email/templates/shell.ts +0 -96
  263. package/src/features/notifications/index.ts +0 -5
  264. package/src/features/notifications/notifications.handler.ts +0 -105
  265. package/src/features/password-reset/index.ts +0 -19
  266. package/src/features/password-reset/request.ts +0 -92
  267. package/src/features/password-reset/reset.ts +0 -102
  268. package/src/features/rotate-field/index.ts +0 -5
  269. package/src/features/rotate-field/rotate-field.handler.ts +0 -132
  270. package/src/features/rotate-field/rotate-field.schema.ts +0 -17
  271. package/src/features/schema/schema.handler.ts +0 -131
  272. package/src/features/seeds/index.ts +0 -5
  273. package/src/features/seeds/seeds.handler.test.ts +0 -1199
  274. package/src/features/seeds/seeds.handler.ts +0 -558
  275. package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
  276. package/src/features/settings/settings.handler.ts +0 -392
  277. package/src/features/setup/index.ts +0 -288
  278. package/src/features/stats/index.ts +0 -5
  279. package/src/features/stats/stats.handler.ts +0 -420
  280. package/src/features/webhooks/index.ts +0 -63
  281. package/src/index.ts +0 -65
  282. package/src/media-utils.ts +0 -82
  283. package/src/middleware/auth-providers.middleware.test.ts +0 -85
  284. package/src/middleware/auth-providers.middleware.ts +0 -45
  285. package/src/middleware/observability.middleware.test.ts +0 -93
  286. package/src/middleware/observability.middleware.ts +0 -70
  287. package/src/middleware/rate-limit.middleware.ts +0 -45
  288. package/src/middleware/repository.middleware.ts +0 -117
  289. package/src/middleware/seed-registry.middleware.test.ts +0 -97
  290. package/src/middleware/seed-registry.middleware.ts +0 -21
  291. package/src/middleware/storage.middleware.ts +0 -25
  292. package/src/middleware.ts +0 -45
  293. package/src/public/access-policy.ts +0 -27
  294. package/src/public/api-key-middleware.ts +0 -57
  295. package/src/public/cache-utils.ts +0 -38
  296. package/src/public/entry-projection.ts +0 -46
  297. package/src/public/idempotency.ts +0 -23
  298. package/src/public/index.ts +0 -16
  299. package/src/public/problem-details.ts +0 -124
  300. package/src/public/public-add.ts +0 -114
  301. package/src/public/public-edit.ts +0 -163
  302. package/src/public/public-errors.ts +0 -19
  303. package/src/public/public-read.ts +0 -63
  304. package/src/public/public-routes.ts +0 -88
  305. package/src/public/query-builder.test.ts +0 -224
  306. package/src/public/query-builder.ts +0 -156
  307. package/src/public/rate-limit-middleware.ts +0 -34
  308. package/src/public/read-list.ts +0 -54
  309. package/src/public/read-single.ts +0 -48
  310. package/src/public/response-builder.ts +0 -30
  311. package/src/public/sanitize.ts +0 -69
  312. package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
  313. package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
  314. package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
  315. package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
  316. package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
  317. package/src/rate-limit/no-op-rate-limiter.ts +0 -11
  318. package/src/search-utils.test.ts +0 -211
  319. package/src/search-utils.ts +0 -213
  320. package/src/search.ts +0 -65
  321. package/src/shared/apply-policies.test.ts +0 -81
  322. package/src/shared/apply-policies.ts +0 -67
  323. package/src/shared/automations.repository.d1.ts +0 -150
  324. package/src/shared/background-notification-service.test.ts +0 -62
  325. package/src/shared/background-notification-service.ts +0 -52
  326. package/src/shared/base.repository.d1.ts +0 -32
  327. package/src/shared/content-utils.test.ts +0 -165
  328. package/src/shared/content-utils.ts +0 -86
  329. package/src/shared/content.repository.d1.test.ts +0 -507
  330. package/src/shared/content.repository.d1.ts +0 -851
  331. package/src/shared/d1-activity-log.repository.test.ts +0 -140
  332. package/src/shared/d1-activity-log.repository.ts +0 -105
  333. package/src/shared/d1-activity-logger.test.ts +0 -86
  334. package/src/shared/d1-activity-logger.ts +0 -67
  335. package/src/shared/d1-analytics.repository.test.ts +0 -78
  336. package/src/shared/d1-analytics.repository.ts +0 -85
  337. package/src/shared/d1-content-scan.repository.test.ts +0 -80
  338. package/src/shared/d1-content-scan.repository.ts +0 -37
  339. package/src/shared/d1-notification.repository.test.ts +0 -128
  340. package/src/shared/d1-notification.repository.ts +0 -118
  341. package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
  342. package/src/shared/d1-password-reset-token.repository.ts +0 -56
  343. package/src/shared/d1-search.repository.test.ts +0 -87
  344. package/src/shared/d1-search.repository.ts +0 -88
  345. package/src/shared/d1-session.repository.test.ts +0 -125
  346. package/src/shared/d1-session.repository.ts +0 -102
  347. package/src/shared/d1-setup-checklist.repository.ts +0 -36
  348. package/src/shared/d1-user.repository.test.ts +0 -151
  349. package/src/shared/d1-user.repository.ts +0 -119
  350. package/src/shared/d1-widget.repository.test.ts +0 -267
  351. package/src/shared/d1-widget.repository.ts +0 -385
  352. package/src/shared/dashboard-layout.repository.d1.test.ts +0 -135
  353. package/src/shared/dashboard-layout.repository.d1.ts +0 -58
  354. package/src/shared/demo-data-sql.ts +0 -216
  355. package/src/shared/demo-data.repository.d1.ts +0 -20
  356. package/src/shared/execution-context-scheduler.ts +0 -13
  357. package/src/shared/fixed-clock.ts +0 -25
  358. package/src/shared/fts-sync.ts +0 -8
  359. package/src/shared/idempotency.repository.d1.test.ts +0 -83
  360. package/src/shared/idempotency.repository.d1.ts +0 -60
  361. package/src/shared/in-memory-activity-logger.ts +0 -19
  362. package/src/shared/in-memory-notification-service.ts +0 -19
  363. package/src/shared/in-memory-seed.repository.ts +0 -58
  364. package/src/shared/media.repository.d1.test.ts +0 -107
  365. package/src/shared/media.repository.d1.ts +0 -68
  366. package/src/shared/qstash-notification-service.test.ts +0 -67
  367. package/src/shared/qstash-notification-service.ts +0 -55
  368. package/src/shared/query-utils.ts +0 -141
  369. package/src/shared/request-utils.ts +0 -26
  370. package/src/shared/schema-mutator.d1.test.ts +0 -142
  371. package/src/shared/schema-mutator.d1.ts +0 -64
  372. package/src/shared/seed-layout.repository.d1.test.ts +0 -114
  373. package/src/shared/seed-layout.repository.d1.ts +0 -62
  374. package/src/shared/seed-registry-cache.test.ts +0 -68
  375. package/src/shared/seed-registry-cache.ts +0 -49
  376. package/src/shared/seed.repository.d1.test.ts +0 -143
  377. package/src/shared/seed.repository.d1.ts +0 -98
  378. package/src/shared/sequential-id-generator.ts +0 -33
  379. package/src/shared/site-settings.repository.d1.ts +0 -53
  380. package/src/shared/storage/factory.ts +0 -41
  381. package/src/shared/storage/s3-bucket.ts +0 -195
  382. package/src/shared/storage-utils.ts +0 -40
  383. package/src/shared/system-stats.repository.d1.test.ts +0 -58
  384. package/src/shared/system-stats.repository.d1.ts +0 -48
  385. package/src/types.ts +0 -90
  386. package/src/upload.ts +0 -198
  387. package/src/widget.test.ts +0 -85
  388. package/src/widget.ts +0 -251
@@ -1,420 +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 { SystemClock } from '@beechcms/core'
8
- import type { Env, Variables } from '../../types'
9
- import { publicProblem } from '../../public/problem-details'
10
- import { cleanStr } from '../../shared/query-utils'
11
-
12
- const DATABASE_ERROR = 'Database error'
13
- const INTERNAL_SERVER_ERROR = 'Internal Server Error'
14
-
15
- const SECONDS_PER_MINUTE = 60
16
- const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE
17
- const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR
18
- const HOURS_24_IN_SECONDS = 24 * SECONDS_PER_HOUR
19
- const DAYS_7_IN_SECONDS = 7 * SECONDS_PER_DAY
20
- const DAYS_30_IN_SECONDS = 30 * SECONDS_PER_DAY
21
- const MAX_MEDIA_SCAN = 1000
22
- const DEFAULT_LIMIT = 12
23
- const MAX_LIMIT = 100
24
- const R2_STORAGE_LIMIT = 10 * 1024 * 1024 * 1024 // 10GB
25
- const D1_MONTHLY_REQUESTS_LIMIT = 1000000 // 1M requests/month
26
-
27
- function getMimeType(extension: string): string {
28
- if (extension === 'pdf') {
29
- return 'application/pdf'
30
- }
31
- const type = extension === 'jpg' ? 'jpeg' : (extension || 'jpeg')
32
- return `image/${type}`
33
- }
34
-
35
- const statsApp = new Hono<{ Bindings: Env; Variables: Variables }>()
36
-
37
- /**
38
- * GET /stats/media-library - Lists all files present in R2:
39
- * combines media_objects (tracked uploads) + /api/media/ URLs found in file columns of each seed
40
- */
41
- statsApp.get('/stats/media-library', async (context) => {
42
- try {
43
- const mediaRepository = context.get('mediaRepository')
44
- const limit = Math.min(Number.parseInt(context.req.query('limit') ?? String(DEFAULT_LIMIT), 10), MAX_LIMIT)
45
- const offset = Number.parseInt(context.req.query('offset') ?? '0', 10)
46
- const mediaBaseUrl = (context.env.MEDIA_BASE_URL?.trim().replace(/\/$/, '')) ?? new URL(context.req.url).origin
47
-
48
- // 1. Files tracked in the media library
49
- // Take the first MAX_MEDIA_SCAN for cross-scanning
50
- const { items: mediaRows } = await mediaRepository.list({ limit: MAX_MEDIA_SCAN, offset: 0 })
51
-
52
- const trackedKeys = new Set<string>()
53
- const allItems: Array<{ key: string; filename: string; mime_type: string; size_bytes: number; created_at: number; url: string }> = []
54
-
55
- for (const mediaRow of mediaRows) {
56
- trackedKeys.add(mediaRow.key)
57
- allItems.push({ ...mediaRow, url: `${mediaBaseUrl}/api/media/${encodeURIComponent(mediaRow.key)}` })
58
- }
59
-
60
- // 2. /api/media/ URLs in the file columns of each seed (v0.4.0 — real columns)
61
- const seeds = context.get('seedRegistry').all()
62
- const contentScanRepository = context.get('contentScanRepository')
63
- const referencedKeysFromContent = await contentScanRepository.getReferencedMediaKeys(seeds)
64
-
65
- for (const key of referencedKeysFromContent) {
66
- if (trackedKeys.has(key)) continue
67
- trackedKeys.add(key)
68
- const filename = key.replace(/^\d+-/, '')
69
- const extension = filename.split('.').pop()?.toLowerCase() ?? ''
70
- const mimeType = getMimeType(extension)
71
- const createdAt = Number.parseInt(key.split('-')[0], 10) || 0
72
- allItems.push({
73
- key,
74
- filename,
75
- mime_type: mimeType,
76
- size_bytes: 0,
77
- created_at: createdAt,
78
- url: `${mediaBaseUrl}/api/media/${encodeURIComponent(key)}`
79
- })
80
- }
81
-
82
- allItems.sort((a, b) => b.created_at - a.created_at)
83
- const total = allItems.length
84
- const paginatedItems = allItems.slice(offset, offset + limit)
85
-
86
- return context.json({ items: paginatedItems, total })
87
- } catch (error) {
88
- console.error('Media library error:', error)
89
- return context.json({ error: INTERNAL_SERVER_ERROR }, 500)
90
- }
91
- })
92
-
93
- /**
94
- * GET /stats/unused-media - Finds media in media_objects not referenced in any file column
95
- */
96
- statsApp.get('/stats/unused-media', async (context) => {
97
- try {
98
- const mediaRepository = context.get('mediaRepository')
99
- const seeds = context.get('seedRegistry').all()
100
-
101
- // All tracked media keys
102
- const { items: mediaRows } = await mediaRepository.list({ limit: MAX_MEDIA_SCAN, offset: 0 })
103
-
104
- if (mediaRows.length === 0) {
105
- return context.json({ items: [] })
106
- }
107
-
108
- // Collect all referenced keys in the file columns of each seed
109
- const contentScanRepository = context.get('contentScanRepository')
110
- const referencedKeys = await contentScanRepository.getReferencedMediaKeys(seeds)
111
-
112
- const unusedMedia = mediaRows.filter(mediaItem => !referencedKeys.has(mediaItem.key))
113
- return context.json({ items: unusedMedia })
114
- } catch (error) {
115
- console.error('Unused media error:', error)
116
- return context.json({ error: INTERNAL_SERVER_ERROR }, 500)
117
- }
118
- })
119
-
120
- /**
121
- * GET /stats/setup-checklist - Project setup status for "Project Health" widget
122
- */
123
- statsApp.get('/stats/setup-checklist', async (context) => {
124
- try {
125
- const setupChecklistRepository = context.get('setupChecklistRepository')
126
- const seeds = context.get('seedRegistry').all()
127
-
128
- const existingTables = await setupChecklistRepository.getExistingTableNames()
129
-
130
- const systemTableNames = [
131
- 'users', 'refresh_tokens', 'media_objects',
132
- 'analytics', 'system_stats', 'activity_logs',
133
- ]
134
- const systemTablesOk = systemTableNames.every(name => existingTables.has(name))
135
-
136
- const seedsCount = seeds.length
137
- const contentTablesOk = seedsCount > 0 && seeds.every(seed => existingTables.has(`content_${seed.slug}`))
138
-
139
- let adminExists = false
140
- try {
141
- adminExists = (await setupChecklistRepository.countAdmins()) > 0
142
- } catch {
143
- // users table may not exist yet
144
- }
145
-
146
- let hasContent = false
147
- const firstSeedSlug = seeds[0]?.slug ?? null
148
- if (firstSeedSlug && existingTables.has(`content_${firstSeedSlug}`)) {
149
- try {
150
- hasContent = (await setupChecklistRepository.countEntriesInSeed(firstSeedSlug)) > 0
151
- } catch {
152
- // ignore
153
- }
154
- }
155
-
156
- return context.json({
157
- systemTablesOk,
158
- seedsCount,
159
- contentTablesOk,
160
- adminExists,
161
- hasContent,
162
- firstSeedSlug,
163
- })
164
- } catch (error) {
165
- console.error('Setup checklist error:', error)
166
- return context.json({ error: 'Failed to compute setup checklist' }, 500)
167
- }
168
- })
169
-
170
- /**
171
- * GET /stats/total - Global content statistics for dashboard
172
- */
173
- statsApp.get('/stats/total', async (context) => {
174
- try {
175
- const now = SystemClock.nowSeconds()
176
- const twentyFourHoursAgo = now - HOURS_24_IN_SECONDS
177
- const sevenDaysAgo = now - DAYS_7_IN_SECONDS
178
- const thirtyDaysAgo = now - DAYS_30_IN_SECONDS
179
-
180
- // Total: SUM of per-seed counts (current live entries)
181
- const seeds = context.get('seedRegistry').all()
182
- const widgetRepository = context.get('widgetRepository')
183
- const countResults = await Promise.all(
184
- seeds.map(seed => widgetRepository.aggregate(seed, { op: 'count' }, 'all'))
185
- )
186
- const totalEntriesCount = countResults.reduce((accumulator, count) => accumulator + count, 0)
187
-
188
- // today/week/month: create events in activity_logs (entity_type = 'content')
189
- const activityLogRepository = context.get('activityLogRepository')
190
- const [todayCount, weekCount, monthCount] = await Promise.all([
191
- activityLogRepository.countSince({ action: 'create', entityType: 'content', sinceTimestamp: twentyFourHoursAgo }),
192
- activityLogRepository.countSince({ action: 'create', entityType: 'content', sinceTimestamp: sevenDaysAgo }),
193
- activityLogRepository.countSince({ action: 'create', entityType: 'content', sinceTimestamp: thirtyDaysAgo }),
194
- ])
195
-
196
- return context.json({
197
- total: totalEntriesCount,
198
- today: todayCount,
199
- week: weekCount,
200
- month: monthCount,
201
- })
202
- } catch (error) {
203
- console.error('Content stats error:', error)
204
- return publicProblem(context, {
205
- type: 'content-database-error',
206
- title: INTERNAL_SERVER_ERROR,
207
- status: 500,
208
- detail: DATABASE_ERROR,
209
- })
210
- }
211
- })
212
-
213
- /**
214
- * GET /stats/recent-activity - Latest activities registered in the system
215
- * No ETag on this endpoint: the activity feed must always be fresh after
216
- * every mutation. Cache-Control: no-store prevents the browser from keeping a
217
- * response that could be returned as a 304 stale.
218
- */
219
- statsApp.get('/stats/recent-activity', async (context) => {
220
- try {
221
- const slug = cleanStr(context.req.query('slug'))
222
-
223
- const entries = await context.get('activityLogRepository').list({
224
- entitySlug: slug ?? undefined,
225
- limit: 15,
226
- })
227
-
228
- // Preserve the legacy snake_case wire format consumed by the dashboard
229
- // recent-activity widget. The repository returns camelCase records,
230
- // so we reshape only on the way out.
231
- const activities = entries.map((entry) => ({
232
- id: entry.id,
233
- user_id: entry.userId,
234
- user_email: entry.userEmail,
235
- user_name: entry.userName,
236
- action: entry.action,
237
- entity_type: entry.entityType,
238
- entity_id: entry.entityId,
239
- entity_slug: entry.entitySlug,
240
- details: entry.details,
241
- created_at: entry.createdAt,
242
- }))
243
-
244
- context.header('Cache-Control', 'no-store')
245
-
246
- return context.json(activities)
247
- } catch (error) {
248
- console.error('Recent activity error:', error)
249
- return publicProblem(context, {
250
- type: 'content-database-error',
251
- title: INTERNAL_SERVER_ERROR,
252
- status: 500,
253
- detail: DATABASE_ERROR,
254
- })
255
- }
256
- })
257
-
258
- /**
259
- * GET /stats/health - System health status and Cloudflare quotas
260
- */
261
- statsApp.get('/stats/health', async (context) => {
262
- try {
263
- const systemStatsRepository = context.get('systemStatsRepository')
264
-
265
- // 1. Retrieve storage usage from repository (system_stats)
266
- let storageUsedBytes = 0
267
- try {
268
- storageUsedBytes = await systemStatsRepository.getStorageUsage()
269
- } catch (error) {
270
- console.warn('Health: Could not fetch storage stats from repo:', error)
271
- }
272
-
273
- // 2. Aggregate D1 requests (proxy for database health) - last 30 days
274
- const thirtyDaysAgo = SystemClock.nowSeconds() - DAYS_30_IN_SECONDS
275
- const totalRequests = await context
276
- .get('analyticsRepository')
277
- .sumByMetric('requests', '', thirtyDaysAgo)
278
-
279
- const storagePercentage = Math.min(Math.round((storageUsedBytes / R2_STORAGE_LIMIT) * 1000) / 10, 100)
280
- const d1Percentage = Math.min(Math.round((totalRequests / D1_MONTHLY_REQUESTS_LIMIT) * 1000) / 10, 100)
281
-
282
- return context.json({
283
- storage: {
284
- used: storageUsedBytes,
285
- limit: R2_STORAGE_LIMIT,
286
- percentage: storagePercentage
287
- },
288
- database: {
289
- requests30d: totalRequests,
290
- limit: D1_MONTHLY_REQUESTS_LIMIT,
291
- percentage: d1Percentage
292
- },
293
- status: (storagePercentage < 90 && d1Percentage < 90) ? 'healthy' : 'warning',
294
- lastUpdate: SystemClock.nowSeconds()
295
- })
296
- } catch (error) {
297
- console.error('System health stats error:', error)
298
- return context.json({ error: 'Failed to calculate system health' }, 500)
299
- }
300
- })
301
-
302
- /**
303
- * GET /stats/cloudflare - Cloudflare-like metrics (Visitors, Requests, Bandwidth)
304
- */
305
- statsApp.get('/stats/cloudflare', async (context) => {
306
- try {
307
- const thirtyDaysAgo = SystemClock.nowSeconds() - DAYS_30_IN_SECONDS
308
- const analyticsRepository = context.get('analyticsRepository')
309
-
310
- const [recordedRequests, recordedVisitors] = await Promise.all([
311
- analyticsRepository.sumByMetric('requests', '', thirtyDaysAgo),
312
- analyticsRepository.sumByMetric('visitors', '', thirtyDaysAgo),
313
- ])
314
-
315
- // Simulate some Cloudflare metrics not directly tracked for a premium feel
316
- const requestsCount = recordedRequests > 0 ? recordedRequests : Math.floor(Math.random() * 5000) + 1000
317
- const visitorsCount = recordedVisitors > 0 ? recordedVisitors : Math.floor(requestsCount / 12) + 1
318
- const bandwidthMB = Math.round((requestsCount * 0.15) * 10) / 10
319
-
320
- const systemStatsRepository = context.get('systemStatsRepository')
321
- let storageUsedBytes = 0
322
- try {
323
- storageUsedBytes = await systemStatsRepository.getStorageUsage()
324
- } catch (error) {
325
- console.warn('Could not fetch storage stats from repo:', error)
326
- }
327
-
328
- const storageUsedMB = Math.round((storageUsedBytes / (1024 * 1024)) * 10) / 10
329
- const storageLimitMB = 10 * 1024 // 10 GB Free Tier
330
-
331
- return context.json({
332
- visitors: {
333
- value: visitorsCount,
334
- trend: 12, // simulated growth %
335
- isPositive: true
336
- },
337
- requests: {
338
- value: requestsCount,
339
- trend: 8,
340
- isPositive: true
341
- },
342
- bandwidth: {
343
- value: bandwidthMB,
344
- unit: 'MB',
345
- trend: 5,
346
- isPositive: false
347
- },
348
- cacheRate: {
349
- value: 94.2,
350
- unit: '%',
351
- trend: 0.5,
352
- isPositive: true
353
- },
354
- storage: {
355
- used: storageUsedMB,
356
- limit: storageLimitMB,
357
- unit: 'MB',
358
- percentage: Math.round((storageUsedMB / storageLimitMB) * 1000) / 10
359
- }
360
- })
361
- } catch (error) {
362
- console.error('Cloudflare stats error:', error)
363
- return publicProblem(context, {
364
- type: 'content-database-error',
365
- title: INTERNAL_SERVER_ERROR,
366
- status: 500,
367
- detail: DATABASE_ERROR,
368
- })
369
- }
370
- })
371
-
372
- /**
373
- * GET /stats/breakdown - Content distribution for "Content Pulse" widget
374
- */
375
- statsApp.get('/stats/breakdown', async (context) => {
376
- try {
377
- const seeds = context.get('seedRegistry').all()
378
-
379
- const widgetRepository = context.get('widgetRepository')
380
- const counts = await Promise.all(
381
- seeds.map(seed => widgetRepository.aggregate(seed, { op: 'count' }, 'all'))
382
- )
383
-
384
- const breakdown = seeds.map((seed, index) => ({
385
- slug: seed.slug,
386
- label: seed.labelPlural || seed.label,
387
- count: counts[index] ?? 0,
388
- }))
389
-
390
- return context.json(breakdown)
391
- } catch (error) {
392
- console.error('Breakdown stats error:', error)
393
- return publicProblem(context, {
394
- type: 'content-database-error',
395
- title: INTERNAL_SERVER_ERROR,
396
- status: 500,
397
- detail: DATABASE_ERROR,
398
- })
399
- }
400
- })
401
-
402
- /**
403
- * POST /stats/storage/sync - Recalculates space occupied on R2 (expensive operation, use with caution)
404
- */
405
- statsApp.post('/stats/storage/sync', async (context) => {
406
- try {
407
- const bucket = context.get('bucket')
408
- const systemStatsRepository = context.get('systemStatsRepository')
409
-
410
- const realSize = await bucket.getTotalSize()
411
- await systemStatsRepository.setStorage(realSize)
412
-
413
- return context.json({ success: true, size: realSize })
414
- } catch (error) {
415
- console.error('Storage sync error:', error)
416
- return context.json({ error: INTERNAL_SERVER_ERROR }, 500)
417
- }
418
- })
419
-
420
- export { statsApp }
@@ -1,63 +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 { Hono } from 'hono'
6
- import { Receiver } from '@upstash/qstash'
7
- import type { AppEnv } from '../../types'
8
- import type { CreateNotificationInput } from '@beechcms/core'
9
-
10
- export const webhooksApp = new Hono<AppEnv>()
11
-
12
- webhooksApp.post('/qstash', async (context) => {
13
- const currentSigningKey = context.env.QSTASH_CURRENT_SIGNING_KEY
14
- const nextSigningKey = context.env.QSTASH_NEXT_SIGNING_KEY
15
-
16
- if (!currentSigningKey || !nextSigningKey) {
17
- console.error('QStash webhook called but signing keys are not configured')
18
- return context.text('Configuration error', 500)
19
- }
20
-
21
- const receiver = new Receiver({
22
- currentSigningKey,
23
- nextSigningKey,
24
- })
25
-
26
- const body = await context.req.text()
27
- const signature = context.req.header('Upstash-Signature')
28
-
29
- if (!signature) {
30
- return context.text('Missing signature', 401)
31
- }
32
-
33
- try {
34
- const isValid = await receiver.verify({
35
- signature,
36
- body,
37
- })
38
-
39
- if (!isValid) {
40
- return context.text('Invalid signature', 401)
41
- }
42
- } catch (err) {
43
- console.error('QStash signature verification failed', err)
44
- return context.text('Invalid signature', 401)
45
- }
46
-
47
- try {
48
- const input = JSON.parse(body) as CreateNotificationInput
49
-
50
- const notificationRepository = context.get('notificationRepository')
51
-
52
- await notificationRepository.create({
53
- title: input.title,
54
- message: input.message,
55
- type: input.type ?? 'info',
56
- })
57
-
58
- return context.text('OK', 200)
59
- } catch (error) {
60
- console.error('Failed to process QStash webhook payload', error)
61
- return context.text('Internal server error', 500)
62
- }
63
- })
package/src/index.ts DELETED
@@ -1,65 +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 { createBeechApp } from './factory'
6
- import { SeedRegistry, SystemIdGenerator } from '@beechcms/core'
7
- import { runCronAutomations } from './features/automations'
8
- import { D1AutomationRepository } from './shared/automations.repository.d1'
9
- import { D1ContentRepository } from './shared/content.repository.d1'
10
- import { D1SeedRepository } from './shared/seed.repository.d1'
11
- import type { Env } from './types'
12
-
13
- const app = createBeechApp({ seeds: [] })
14
-
15
- app.get('/', (c) => c.text('Beech API is running'))
16
-
17
- export default {
18
- fetch(request: Request, env: Env, ctx: ExecutionContext) {
19
- if (env.ENV === 'development') {
20
- const checks: Array<{ name: string; url: string }> = [
21
- { name: 'MinIO', url: (env.R2_ENDPOINT ?? 'http://localhost:9000') + '/minio/health/live' },
22
- { name: 'Mailpit', url: `http://${env.SMTP_HOST ?? 'localhost'}:${env.SMTP_PORT ?? '8025'}/livez` },
23
- ]
24
- for (const c of checks) {
25
- fetch(c.url).catch(() => {
26
- console.warn(
27
- `\n⚠️ ${c.name} non raggiungibile su ${c.url}\n` +
28
- ` Beech in dev richiede lo stack Docker completo.\n` +
29
- ` Avvialo con: npm run dev:full\n`
30
- )
31
- })
32
- }
33
- }
34
- return app.fetch(request, env, ctx)
35
- },
36
-
37
- async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext) {
38
- const scheduledTime = controller?.scheduledTime ?? Date.now()
39
-
40
- if (!env.DB) {
41
- console.warn('[cron] D1 binding missing. Skipping cron automations.')
42
- return
43
- }
44
-
45
- const automationRepository = new D1AutomationRepository(env.DB)
46
- const contentRepository = new D1ContentRepository(env.DB)
47
- const seedRepository = new D1SeedRepository(env.DB)
48
- const seeds = await seedRepository.listActive()
49
- const registry = new SeedRegistry(seeds)
50
- const getSeed = (slug: string) => registry.get(slug) ?? null
51
-
52
- ctx.waitUntil(
53
- runCronAutomations(
54
- {
55
- automationRepository,
56
- contentRepository,
57
- getSeed,
58
- env: env as unknown as Record<string, string | undefined>,
59
- idGenerator: SystemIdGenerator,
60
- },
61
- scheduledTime,
62
- ),
63
- )
64
- },
65
- } satisfies ExportedHandler<Env>
@@ -1,82 +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
- /**
6
- * Media utils: estrazione chiavi R2 dal data di un'entry.
7
- * Usato alla cancellazione entry per eliminare i file associati da R2.
8
- *
9
- * @see docs/media-engine.md
10
- */
11
- import type { Seed } from '@beechcms/core'
12
-
13
- /** Pattern per estrarre la chiave R2 da URL in formato /api/media/KEY */
14
- const MEDIA_URL_PATTERN = /\/api\/media\/([^/?#]+)/
15
-
16
- /**
17
- * Estrae la chiave R2 da un URL di media.
18
- * Es: "https://x.com/api/media/1739123456-avatar.png" → "1739123456-avatar.png"
19
- *
20
- * @param mediaUrl - URL completo o path (es. /api/media/123-foto.png)
21
- * @returns Chiave R2 o null se l'URL non è valido
22
- */
23
- export function extractMediaKey(mediaUrl: string): string | null {
24
- const match = MEDIA_URL_PATTERN.exec(String(mediaUrl))
25
- return match ? decodeURIComponent(match[1]) : null
26
- }
27
-
28
- /**
29
- * Attraversa ricorsivamente un valore (stringa, array, oggetto) e raccoglie
30
- * tutte le chiavi R2 trovate in stringhe che matchano /api/media/KEY.
31
- */
32
- function collectMediaKeysRecursive(value: unknown, collectedKeys: Set<string>): void {
33
- if (typeof value === 'string') {
34
- const r2Key = extractMediaKey(value)
35
- if (r2Key) {
36
- collectedKeys.add(r2Key)
37
- return
38
- }
39
- // Legacy compat: campi json/file possono contenere JSON serializzato.
40
- try {
41
- const parsed = JSON.parse(value) as unknown
42
- if (parsed !== value) {
43
- collectMediaKeysRecursive(parsed, collectedKeys)
44
- }
45
- } catch {
46
- // ignore
47
- }
48
- return
49
- }
50
- if (Array.isArray(value)) {
51
- for (const item of value) collectMediaKeysRecursive(item, collectedKeys)
52
- return
53
- }
54
- if (value != null && typeof value === 'object') {
55
- for (const nestedValue of Object.values(value)) {
56
- collectMediaKeysRecursive(nestedValue, collectedKeys)
57
- }
58
- }
59
- }
60
-
61
- /**
62
- * Estrae tutte le chiavi R2 dal data di un'entry.
63
- * Cerca nei campi `file` (stringa URL) e `json` (array/oggetto con URL).
64
- * Il data è in formato DB: chiavi = branch.id (es. art_03, prd_05).
65
- *
66
- * @param seed - Schema del tipo di contenuto
67
- * @param entryData - Payload in formato DB (chiavi = branch ID)
68
- * @returns Array di chiavi R2 uniche da eliminare
69
- */
70
- export function extractMediaKeysFromData(
71
- seed: Seed,
72
- entryData: Record<string, unknown>
73
- ): string[] {
74
- const r2Keys = new Set<string>()
75
- for (const branch of seed.branches) {
76
- if (branch.type !== 'file' && branch.type !== 'json') continue
77
- const fieldValue = entryData[branch.alias]
78
- if (fieldValue == null) continue
79
- collectMediaKeysRecursive(fieldValue, r2Keys)
80
- }
81
- return [...r2Keys]
82
- }