@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,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
- }