@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,392 +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 { sha256hex, type SiteSettings } from '@beechcms/core'
8
- import type { Env, Variables } from '../../types'
9
-
10
- const settingsApp = new Hono<{ Bindings: Env; Variables: Variables }>()
11
-
12
- const EMAIL_VALIDATION_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
13
- const MIN_PASSWORD_LENGTH = 8
14
- const MAX_PASSWORD_LENGTH = 128
15
- const SESSION_LIST_LIMIT = 20
16
- const ACTIVITY_LOG_LIMIT = 30
17
-
18
- /**
19
- * GET /api/settings
20
- * Retrieves the general site configuration from the database.
21
- */
22
- settingsApp.get('/', async (context) => {
23
- const s = await context.get('siteSettingsRepository').getAll()
24
- return context.json({
25
- siteTitle: s.siteTitle,
26
- siteLogo: '/beechLogoDark.svg',
27
- defaultLanguage: s.defaultLanguage,
28
- timezone: s.timezone,
29
- currency: s.currency,
30
- company: {
31
- name: s.companyName,
32
- website: s.companyWebsite,
33
- abbreviation: s.companyAbbreviation,
34
- },
35
- dateFormat: context.env.DATE_FORMAT || 'DD-MM-YYYY',
36
- features: {
37
- drafts: true,
38
- media: true,
39
- search: true,
40
- activityLog: true,
41
- email: context.env.EMAIL_PROVIDER === 'smtp' || !!(context.env.EMAIL_API_KEY || context.env.RESEND_API_KEY),
42
- },
43
- })
44
- })
45
-
46
- /**
47
- * PUT /api/settings
48
- * Updates the general site configuration in the database.
49
- */
50
- settingsApp.put('/', async (context) => {
51
- let payload: Record<string, unknown>
52
- try {
53
- payload = await context.req.json()
54
- } catch {
55
- return context.json({ error: 'Invalid JSON body' }, 400)
56
- }
57
-
58
- const siteTitle = typeof payload.siteTitle === 'string' ? payload.siteTitle.trim() : undefined
59
- const defaultLanguage = typeof payload.defaultLanguage === 'string' ? payload.defaultLanguage.trim() : undefined
60
- const timezone = typeof payload.timezone === 'string' ? payload.timezone.trim() : undefined
61
- const currency = typeof payload.currency === 'string' ? payload.currency.trim() : undefined
62
-
63
- let companyName: string | undefined | null = undefined
64
- let companyWebsite: string | undefined | null = undefined
65
- let companyAbbreviation: string | undefined | null = undefined
66
-
67
- if (payload.company !== undefined) {
68
- if (payload.company === null) {
69
- companyName = null
70
- companyWebsite = null
71
- companyAbbreviation = null
72
- } else if (typeof payload.company === 'object') {
73
- const company = payload.company as Record<string, unknown>
74
- companyName = typeof company.name === 'string' ? company.name.trim() : (company.name === null ? null : undefined)
75
- companyWebsite = typeof company.website === 'string' ? (company.website.trim() || null) : (company.website === null ? null : undefined)
76
- companyAbbreviation = typeof company.abbreviation === 'string' ? (company.abbreviation.trim() || null) : (company.abbreviation === null ? null : undefined)
77
- }
78
- }
79
-
80
- if (defaultLanguage !== undefined && !['it', 'en'].includes(defaultLanguage)) {
81
- return context.json({ type: 'bad-request', title: 'Bad Request', status: 400, detail: 'Invalid default language (must be it or en)' }, 400)
82
- }
83
-
84
- if (companyWebsite && companyWebsite !== '') {
85
- try {
86
- new URL(companyWebsite)
87
- } catch {
88
- return context.json({ type: 'bad-request', title: 'Bad Request', status: 400, detail: 'Invalid company website URL' }, 400)
89
- }
90
- }
91
-
92
- const fieldsToUpdate: Partial<SiteSettings> = {}
93
- if (siteTitle !== undefined) fieldsToUpdate.siteTitle = siteTitle
94
- if (defaultLanguage !== undefined) fieldsToUpdate.defaultLanguage = defaultLanguage
95
- if (timezone !== undefined) fieldsToUpdate.timezone = timezone
96
- if (currency !== undefined) fieldsToUpdate.currency = currency
97
- if (companyName !== undefined) fieldsToUpdate.companyName = companyName
98
- if (companyWebsite !== undefined) fieldsToUpdate.companyWebsite = companyWebsite
99
- if (companyAbbreviation !== undefined) fieldsToUpdate.companyAbbreviation = companyAbbreviation
100
-
101
- // If companyName is updated and siteTitle isn't specified, sync siteTitle
102
- if (companyName && siteTitle === undefined) {
103
- fieldsToUpdate.siteTitle = companyName
104
- }
105
-
106
- await context.get('siteSettingsRepository').setMany(fieldsToUpdate)
107
- return context.json({ success: true })
108
- })
109
-
110
- /**
111
- * GET /api/settings/me
112
- * Retrieves the currently authenticated user's profile and preferences.
113
- */
114
- settingsApp.get('/me', async (context) => {
115
- const { sub: userId } = context.get('jwtPayload')
116
-
117
- const currentUser = await context.get('userRepository').findById(userId)
118
- if (!currentUser) {
119
- return context.json({ error: 'User not found' }, 404)
120
- }
121
-
122
- let notificationPreferences: Record<string, boolean>
123
- try {
124
- notificationPreferences = JSON.parse(currentUser.notificationPreferences || '{}')
125
- } catch {
126
- notificationPreferences = {}
127
- }
128
-
129
- let avatarUrl = currentUser.avatarUrl
130
- if (!avatarUrl && currentUser.email) {
131
- const emailHash = await sha256hex(currentUser.email.trim().toLowerCase())
132
- avatarUrl = `https://gravatar.com/avatar/${emailHash}?d=mp`
133
- }
134
-
135
- return context.json({
136
- id: currentUser.id,
137
- email: currentUser.email,
138
- name: currentUser.name,
139
- surname: currentUser.surname,
140
- avatarUrl,
141
- notificationPrefs: {
142
- contentCreate: notificationPreferences.contentCreate ?? true,
143
- contentUpdate: notificationPreferences.contentUpdate ?? true,
144
- contentDelete: notificationPreferences.contentDelete ?? true,
145
- mediaUpload: notificationPreferences.mediaUpload ?? false,
146
- },
147
- })
148
- })
149
-
150
- /**
151
- * PUT /api/settings/profile
152
- * Updates the user's name and email address.
153
- */
154
- settingsApp.put('/profile', async (context) => {
155
- const { sub: userId } = context.get('jwtPayload')
156
-
157
- let payload: Record<string, unknown>
158
- try {
159
- payload = await context.req.json()
160
- } catch {
161
- return context.json({ error: 'Invalid JSON body' }, 400)
162
- }
163
-
164
- const nameInput = typeof payload.name === 'string' ? payload.name.trim() : null
165
- const surnameInput = typeof payload.surname === 'string' ? payload.surname.trim() : null
166
- const emailInput = typeof payload.email === 'string' ? payload.email.trim().toLowerCase() : null
167
-
168
- if (emailInput !== null && !EMAIL_VALIDATION_REGEX.test(emailInput)) {
169
- return context.json({ type: 'bad-request', title: 'Bad Request', status: 400, detail: 'Invalid email format' }, 400)
170
- }
171
-
172
- if (nameInput !== null && nameInput.length > 100) {
173
- return context.json({ type: 'bad-request', title: 'Bad Request', status: 400, detail: 'Name is too long (maximum 100 characters)' }, 400)
174
- }
175
-
176
- if (surnameInput !== null && surnameInput.length > 100) {
177
- return context.json({ type: 'bad-request', title: 'Bad Request', status: 400, detail: 'Surname is too long (maximum 100 characters)' }, 400)
178
- }
179
-
180
- const hasNoFields = nameInput === null && surnameInput === null && emailInput === null
181
- if (hasNoFields) {
182
- return context.json({ error: 'No fields to update' }, 400)
183
- }
184
-
185
- if (emailInput !== null) {
186
- const emailTaken = await context.get('userRepository').emailBelongsToAnotherUser(emailInput, userId)
187
- if (emailTaken) {
188
- return context.json({ type: 'conflict', title: 'Conflict', status: 409, detail: 'Email address is already in use' }, 409)
189
- }
190
- }
191
-
192
- const fieldsToUpdate: { name?: string; surname?: string; email?: string } = {}
193
- if (nameInput !== null) fieldsToUpdate.name = nameInput
194
- if (surnameInput !== null) fieldsToUpdate.surname = surnameInput
195
- if (emailInput !== null) fieldsToUpdate.email = emailInput
196
-
197
- await context.get('userRepository').updateProfile(userId, fieldsToUpdate)
198
- return context.json({ success: true })
199
- })
200
-
201
- /**
202
- * PUT /api/settings/password
203
- * Updates the user's password after verifying the current one.
204
- */
205
- settingsApp.put('/password', async (context) => {
206
- const { sub: userId } = context.get('jwtPayload')
207
-
208
- let payload: Record<string, unknown>
209
- try {
210
- payload = await context.req.json()
211
- } catch {
212
- return context.json({ error: 'Invalid JSON body' }, 400)
213
- }
214
-
215
- const currentPassword = typeof payload.currentPassword === 'string' ? payload.currentPassword : ''
216
- const newPassword = typeof payload.newPassword === 'string' ? payload.newPassword : ''
217
-
218
- if (!currentPassword || !newPassword) {
219
- return context.json({ error: 'Both currentPassword and newPassword are required' }, 400)
220
- }
221
- if (newPassword.length < MIN_PASSWORD_LENGTH) {
222
- return context.json({ error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long` }, 400)
223
- }
224
- if (newPassword.length > MAX_PASSWORD_LENGTH) {
225
- return context.json({ error: 'Password is too long' }, 400)
226
- }
227
-
228
- const userRecord = await context.get('userRepository').findById(userId)
229
- if (!userRecord) {
230
- return context.json({ error: 'User not found' }, 404)
231
- }
232
-
233
- const hashProvider = context.get('hashProvider')
234
- const isPasswordCorrect = await hashProvider.verify(currentPassword, userRecord.passwordHash)
235
- if (!isPasswordCorrect) {
236
- return context.json({ type: 'invalid-credentials', title: 'Unauthorized', status: 401, detail: 'Current password is incorrect' }, 401)
237
- }
238
-
239
- const hashedNewPassword = await hashProvider.hash(newPassword)
240
- await context.get('userRepository').updatePasswordHash(userId, hashedNewPassword)
241
- return context.json({ success: true })
242
- })
243
-
244
- /**
245
- * PUT /api/settings/avatar
246
- * Updates the user's avatar URL.
247
- */
248
- settingsApp.put('/avatar', async (context) => {
249
- const { sub: userId } = context.get('jwtPayload')
250
-
251
- let payload: Record<string, unknown>
252
- try {
253
- payload = await context.req.json()
254
- } catch {
255
- return context.json({ error: 'Invalid JSON body' }, 400)
256
- }
257
-
258
- const avatarUrl = typeof payload.avatarUrl === 'string' ? payload.avatarUrl.trim() : null
259
- await context.get('userRepository').updateAvatarUrl(userId, avatarUrl)
260
- return context.json({ success: true })
261
- })
262
-
263
- /**
264
- * GET /api/settings/sessions
265
- * Retrieves a list of active refresh tokens for the user.
266
- */
267
- settingsApp.get('/sessions', async (context) => {
268
- const { sub: userId } = context.get('jwtPayload')
269
- const nowTimestamp = Math.floor(Date.now() / 1000)
270
- const sessions = await context.get('sessionRepository').listActiveForUser(userId, nowTimestamp, SESSION_LIST_LIMIT)
271
- return context.json(sessions)
272
- })
273
-
274
- /**
275
- * DELETE /api/settings/sessions/:id
276
- * Revokes a specific refresh token (session).
277
- */
278
- settingsApp.delete('/sessions/:id', async (context) => {
279
- const { sub: userId } = context.get('jwtPayload')
280
- const sessionId = context.req.param('id')
281
- const nowTimestamp = Math.floor(Date.now() / 1000)
282
-
283
- const wasRevoked = await context.get('sessionRepository').revokeById(sessionId, userId, nowTimestamp)
284
- if (!wasRevoked) {
285
- return context.json({ error: 'Session not found or already revoked' }, 404)
286
- }
287
-
288
- return context.json({ success: true })
289
- })
290
-
291
- /**
292
- * GET /api/settings/activity
293
- * Retrieves the latest activity logs for the user.
294
- */
295
- settingsApp.get('/activity', async (context) => {
296
- const { sub: userId } = context.get('jwtPayload')
297
-
298
- const entries = await context.get('activityLogRepository').list({
299
- userId,
300
- limit: ACTIVITY_LOG_LIMIT,
301
- })
302
-
303
- // Preserve legacy snake_case shape consumed by the dashboard activity tab.
304
- const responseEntries = entries.map((entry) => ({
305
- id: entry.id,
306
- action: entry.action,
307
- entity_type: entry.entityType,
308
- entity_slug: entry.entitySlug,
309
- details: entry.details ? JSON.stringify(entry.details) : null,
310
- created_at: entry.createdAt,
311
- }))
312
-
313
- return context.json(responseEntries)
314
- })
315
-
316
- /**
317
- * GET /api/settings/storage
318
- * Calculates storage usage and identifies orphaned media files.
319
- */
320
- settingsApp.get('/storage', async (context) => {
321
- const mediaRepo = context.get('mediaRepository')
322
- const statsRepo = context.get('systemStatsRepository')
323
-
324
- const totalStorageUsedBytes = await statsRepo.getStorageUsage()
325
- const totalFileCount = await mediaRepo.count()
326
-
327
- const registeredSeeds = context.get('seedRegistry').all()
328
- const referencedMediaKeys = await context.get('contentScanRepository').getReferencedMediaKeys(registeredSeeds)
329
-
330
- const { items: allMediaRows } = await mediaRepo.list({ limit: 50, offset: 0 })
331
- const orphanedMediaFiles = allMediaRows.filter(mediaFile => !referencedMediaKeys.has(mediaFile.key))
332
-
333
- return context.json({
334
- totalBytes: totalStorageUsedBytes,
335
- fileCount: totalFileCount,
336
- orphans: orphanedMediaFiles,
337
- })
338
- })
339
-
340
- /**
341
- * GET /api/settings/notifications
342
- * Retrieves the user's notification preferences.
343
- */
344
- settingsApp.get('/notifications', async (context) => {
345
- const { sub: userId } = context.get('jwtPayload')
346
-
347
- const userRecord = await context.get('userRepository').findById(userId)
348
- if (!userRecord) {
349
- return context.json({ error: 'User not found' }, 404)
350
- }
351
-
352
- let userPreferences: Record<string, boolean>
353
- try {
354
- userPreferences = JSON.parse(userRecord.notificationPreferences || '{}')
355
- } catch {
356
- userPreferences = {}
357
- }
358
-
359
- return context.json({
360
- contentCreate: userPreferences.contentCreate ?? true,
361
- contentUpdate: userPreferences.contentUpdate ?? true,
362
- contentDelete: userPreferences.contentDelete ?? true,
363
- mediaUpload: userPreferences.mediaUpload ?? false,
364
- })
365
- })
366
-
367
- /**
368
- * PUT /api/settings/notifications
369
- * Updates the user's notification preferences.
370
- */
371
- settingsApp.put('/notifications', async (context) => {
372
- const { sub: userId } = context.get('jwtPayload')
373
-
374
- let payload: Record<string, unknown>
375
- try {
376
- payload = await context.req.json()
377
- } catch {
378
- return context.json({ error: 'Invalid JSON body' }, 400)
379
- }
380
-
381
- const newPreferences = {
382
- contentCreate: payload.contentCreate === true,
383
- contentUpdate: payload.contentUpdate === true,
384
- contentDelete: payload.contentDelete === true,
385
- mediaUpload: payload.mediaUpload === true,
386
- }
387
-
388
- await context.get('userRepository').updateNotificationPreferences(userId, JSON.stringify(newPreferences))
389
- return context.json({ success: true })
390
- })
391
-
392
- export { settingsApp }
@@ -1,288 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- /// <reference types="@cloudflare/workers-types" />
6
- import { Hono } from 'hono'
7
- import type { Env, Variables } from '../../types'
8
- import { publicProblem } from '../../public/problem-details'
9
-
10
- const setupApp = new Hono<{ Bindings: Env; Variables: Variables }>()
11
-
12
- /**
13
- * GET /auth/setup
14
- * Returns setup status + environment flags for the wizard.
15
- */
16
- setupApp.get('/auth/setup', async (context) => {
17
- const userCount = await context.get('userRepository').countAll()
18
- const isDeveloper = context.env.ENV === 'development'
19
- const mail =
20
- context.env.EMAIL_PROVIDER === 'smtp' ||
21
- !!(context.env.EMAIL_API_KEY || context.env.RESEND_API_KEY)
22
- const qstash = !!context.env.QSTASH_TOKEN
23
-
24
- return context.json({
25
- needsSetup: userCount === 0,
26
- environment: { isDeveloper, services: { mail, qstash } },
27
- })
28
- })
29
-
30
- /**
31
- * POST /auth/setup
32
- * Creates the first administrator account and persists site defaults.
33
- */
34
- setupApp.post('/auth/setup', async (context) => {
35
- const userCount = await context.get('userRepository').countAll()
36
-
37
- if (userCount > 0) {
38
- return publicProblem(context, {
39
- type: 'setup-already-done',
40
- title: 'Setup already completed',
41
- status: 403,
42
- detail: 'An administrator account already exists. Initial setup can only be performed once.',
43
- })
44
- }
45
-
46
- let payload: unknown
47
- try {
48
- payload = await context.req.json()
49
- } catch {
50
- return publicProblem(context, {
51
- type: 'bad-request',
52
- title: 'Invalid JSON body',
53
- status: 400,
54
- detail: 'The request body could not be parsed as valid JSON.',
55
- })
56
- }
57
-
58
- if (!payload || typeof payload !== 'object') {
59
- return publicProblem(context, {
60
- type: 'bad-request',
61
- title: 'Invalid request',
62
- status: 400,
63
- detail: 'The request payload is missing or invalid.',
64
- })
65
- }
66
-
67
- const p = payload as Record<string, unknown>
68
- const { email, password, name, surname, settings, track, company, loadDemoData } = p
69
-
70
- if (typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
71
- return publicProblem(context, {
72
- type: 'validation-error',
73
- title: 'Valid email required',
74
- status: 422,
75
- detail: 'A valid email address is required for the administrator account.',
76
- })
77
- }
78
-
79
- if (typeof password !== 'string' || password.length < 8 || password.length > 128) {
80
- return publicProblem(context, {
81
- type: 'validation-error',
82
- title: 'Invalid password',
83
- status: 422,
84
- detail: 'Password must be between 8 and 128 characters long.',
85
- })
86
- }
87
-
88
- if (!settings || typeof settings !== 'object') {
89
- return publicProblem(context, {
90
- type: 'validation-error',
91
- title: 'Settings required',
92
- status: 422,
93
- detail: 'settings.language, settings.timezone, and settings.currency are required.',
94
- })
95
- }
96
-
97
- const s = settings as Record<string, unknown>
98
- const language = typeof s.language === 'string' ? s.language : ''
99
- const timezone = typeof s.timezone === 'string' ? s.timezone.trim() : ''
100
- const currency = typeof s.currency === 'string' ? s.currency.trim() : ''
101
-
102
- if (!['it', 'en'].includes(language)) {
103
- return publicProblem(context, {
104
- type: 'validation-error',
105
- title: 'Invalid language',
106
- status: 422,
107
- detail: 'settings.language must be "it" or "en".',
108
- })
109
- }
110
-
111
- if (!timezone) {
112
- return publicProblem(context, {
113
- type: 'validation-error',
114
- title: 'Timezone required',
115
- status: 422,
116
- detail: 'settings.timezone must be a non-empty IANA timezone string.',
117
- })
118
- }
119
-
120
- if (!currency) {
121
- return publicProblem(context, {
122
- type: 'validation-error',
123
- title: 'Currency required',
124
- status: 422,
125
- detail: 'settings.currency must be a non-empty ISO 4217 currency code.',
126
- })
127
- }
128
-
129
- if (track === 'normal') {
130
- if (!company || typeof company !== 'object') {
131
- return publicProblem(context, {
132
- type: 'validation-error',
133
- title: 'Company info required',
134
- status: 422,
135
- detail: 'company.name and company.website are required for the normal track.',
136
- })
137
- }
138
- const c = company as Record<string, unknown>
139
- if (typeof c.name !== 'string' || !c.name.trim()) {
140
- return publicProblem(context, {
141
- type: 'validation-error',
142
- title: 'Company name required',
143
- status: 422,
144
- detail: 'company.name must be a non-empty string.',
145
- })
146
- }
147
- if (typeof c.website !== 'string' || !c.website.trim()) {
148
- return publicProblem(context, {
149
- type: 'validation-error',
150
- title: 'Company website required',
151
- status: 422,
152
- detail: 'company.website must be a valid URL.',
153
- })
154
- }
155
- try {
156
- new URL(c.website.trim())
157
- } catch {
158
- return publicProblem(context, {
159
- type: 'validation-error',
160
- title: 'Invalid company website',
161
- status: 422,
162
- detail: 'company.website must be a valid URL.',
163
- })
164
- }
165
- }
166
-
167
- if (track === 'developer' && loadDemoData === true) {
168
- await context.get('demoDataRepository').loadDemoData()
169
-
170
- // Inject custom SaaS dashboard layout
171
- const layout = {
172
- version: 1,
173
- pages: [
174
- {
175
- id: 'page-1',
176
- slug: 'overview',
177
- label: 'Overview',
178
- icon: 'LayoutDashboard',
179
- sections: [
180
- {
181
- id: 'sec-0',
182
- hideLabel: true,
183
- columns: [
184
- { id: 'c0', widgets: [{ id: 'w0', type: 'core/text', config: { content: '# Acme SaaS Analytics\nBenvenuto nella dashboard di produzione. Qui puoi monitorare le metriche in tempo reale.' } }] }
185
- ]
186
- },
187
- {
188
- id: 'sec-1',
189
- hideLabel: true,
190
- columns: [
191
- { id: 'c1', widgets: [{ id: 'w1', type: 'core/stat', config: { seedSlug: 'clienti', formula: { op: 'sum', column: 'mrr' }, window: 'all' }, title: 'Totale MRR' }] },
192
- { id: 'c2', widgets: [{ id: 'w2', type: 'core/stat', config: { seedSlug: 'clienti', formula: { op: 'count' }, window: 'all' }, title: 'Clienti Attivi' }] },
193
- { id: 'c3', widgets: [{ id: 'w3', type: 'core/stat', config: { seedSlug: 'abbonamenti', formula: { op: 'count' }, window: 'all' }, title: 'Abbonamenti' }] },
194
- { id: 'c4', widgets: [{ id: 'w4', type: 'core/stat', config: { seedSlug: 'ticket', formula: { op: 'countWhere', column: 'ticket_status', value: 'open' }, window: 'all' }, title: 'Ticket Aperti' }] }
195
- ]
196
- },
197
- {
198
- id: 'sec-2',
199
- hideLabel: true,
200
- columns: [
201
- { id: 'c5', widgets: [{ id: 'w5', type: 'core/area-chart', config: { seedSlug: 'clienti', groupColumn: 'created_at', window: 'all' }, title: 'Andamento Iscrizioni' }] },
202
- { id: 'c6', widgets: [{ id: 'w6', type: 'core/pie-chart', config: { seedSlug: 'clienti', column: 'tier', window: 'all' }, title: 'Distribuzione Piani' }] }
203
- ],
204
- columnSpans: [8, 4]
205
- },
206
- {
207
- id: 'sec-3',
208
- hideLabel: true,
209
- columns: [
210
- { id: 'c7', widgets: [{ id: 'w7', type: 'core/data-table', config: { seedSlug: 'clienti', pageSize: 5 }, title: 'Ultimi Iscritti' }] },
211
- { id: 'c8', widgets: [{ id: 'w8', type: 'core/data-table', config: { seedSlug: 'changelog', pageSize: 5 }, title: 'Ultime Release' }] }
212
- ],
213
- columnSpans: [6, 6]
214
- }
215
- ]
216
- },
217
- {
218
- id: 'page-2',
219
- slug: 'operations',
220
- label: 'Operations',
221
- icon: 'LifeBuoy',
222
- sections: [
223
- {
224
- id: 'sec-4',
225
- hideLabel: true,
226
- columns: [
227
- { id: 'c9', widgets: [{ id: 'w9', type: 'core/stat', config: { seedSlug: 'ticket', formula: { op: 'countWhere', column: 'priority', value: 'high' }, window: 'all' }, title: 'Ticket Alta Priorità' }] },
228
- { id: 'c10', widgets: [{ id: 'w10', type: 'core/stat', config: { seedSlug: 'ticket', formula: { op: 'countWhere', column: 'ticket_status', value: 'closed' }, window: 'all' }, title: 'Ticket Chiusi' }] },
229
- { id: 'c11', widgets: [{ id: 'w11', type: 'core/stat', config: { seedSlug: 'changelog', formula: { op: 'count' }, window: 'all' }, title: 'Update Rilasciati' }] }
230
- ]
231
- },
232
- {
233
- id: 'sec-5',
234
- hideLabel: true,
235
- columns: [
236
- { id: 'c12', widgets: [{ id: 'w12', type: 'core/bar-chart', config: { seedSlug: 'ticket', groupColumn: 'created_at', window: 'all' }, title: 'Ticket nel tempo' }] },
237
- { id: 'c13', widgets: [{ id: 'w13', type: 'core/line-chart', config: { seedSlug: 'abbonamenti', groupColumn: 'created_at', window: 'all' }, title: 'Nuovi Abbonamenti' }] }
238
- ],
239
- columnSpans: [6, 6]
240
- },
241
- {
242
- id: 'sec-6',
243
- hideLabel: true,
244
- columns: [
245
- { id: 'c14', widgets: [{ id: 'w14', type: 'core/data-table', config: { seedSlug: 'ticket', pageSize: 10 }, title: 'Ticket in lavorazione' }] }
246
- ]
247
- }
248
- ]
249
- }
250
- ]
251
- }
252
- await context.get('dashboardLayoutRepository').upsert('default', layout as any, 'system')
253
- }
254
-
255
- const passwordHash = await context.get('hashProvider').hash(password)
256
- const normalizedEmail = email.trim().toLowerCase()
257
- const normalizedName = typeof name === 'string' ? name.trim() : null
258
- const normalizedSurname = typeof surname === 'string' ? surname.trim() : null
259
-
260
- await context.get('userRepository').create({
261
- id: context.get('idGenerator').uuid(),
262
- email: normalizedEmail,
263
- passwordHash,
264
- role: 'admin',
265
- name: normalizedName,
266
- surname: normalizedSurname,
267
- })
268
-
269
- if (track === 'normal' && company && typeof company === 'object') {
270
- const c = company as Record<string, unknown>
271
- const companyName = (c.name as string).trim()
272
- await context.get('siteSettingsRepository').setMany({
273
- defaultLanguage: language,
274
- timezone,
275
- currency,
276
- companyName,
277
- companyWebsite: (c.website as string).trim(),
278
- companyAbbreviation: typeof c.abbreviation === 'string' ? c.abbreviation.trim() || null : null,
279
- siteTitle: companyName,
280
- })
281
- } else {
282
- await context.get('siteSettingsRepository').setMany({ defaultLanguage: language, timezone, currency })
283
- }
284
-
285
- return context.json({ success: true }, 201)
286
- })
287
-
288
- export { setupApp }
@@ -1,5 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- export { statsApp } from './stats.handler'