@beechcms/api 0.5.0 → 0.6.0-preview.2

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 (225) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/Searching.svg +1 -0
  4. package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
  5. package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
  6. package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
  7. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
  8. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
  9. package/assets/dashboard/beechLogoDark.svg +48 -48
  10. package/assets/dashboard/index.html +18 -18
  11. package/assets/dashboard/noResult.svg +43 -0
  12. package/assets/dashboard/sol.svg +3 -3
  13. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  14. package/assets/dashboard/working.svg +1 -0
  15. package/migrations/0000_v040_base.sql +265 -238
  16. package/migrations/0029_automations.sql +14 -14
  17. package/migrations/0030_test_seeds.sql +125 -125
  18. package/package.json +13 -9
  19. package/src/auth/{in-memory-hash-provider.ts → __fixtures__/in-memory-hash-provider.ts} +17 -13
  20. package/src/auth/{static-token-service.ts → __fixtures__/static-token-service.ts} +22 -18
  21. package/src/auth/bcrypt-hash-provider.ts +24 -20
  22. package/src/auth/constants.ts +14 -10
  23. package/src/auth/generate-refresh-token.test.ts +23 -19
  24. package/src/auth/hash-provider.test.ts +55 -46
  25. package/src/auth/jose-token-service.ts +62 -55
  26. package/src/auth/jwt-claims-passthrough.test.ts +87 -0
  27. package/src/auth/login.test.ts +100 -92
  28. package/src/auth/login.ts +78 -74
  29. package/src/auth/refresh.ts +11 -5
  30. package/src/auth/token-service.test.ts +90 -82
  31. package/src/factory.ts +370 -347
  32. package/src/features/automations/__tests__/action-executors.test.ts +255 -268
  33. package/src/features/automations/__tests__/automation-runner.test.ts +196 -192
  34. package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -56
  35. package/src/features/automations/__tests__/automations.handler.test.ts +264 -260
  36. package/src/features/automations/__tests__/automations.repository.test.ts +163 -159
  37. package/src/features/automations/__tests__/automations.schema.test.ts +224 -134
  38. package/src/features/automations/__tests__/context-resolver.test.ts +126 -122
  39. package/src/features/automations/__tests__/cron-runner.test.ts +267 -263
  40. package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -140
  41. package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -306
  42. package/src/features/automations/__tests__/template-grammar.test.ts +308 -304
  43. package/src/features/automations/__tests__/webhook.executor.test.ts +142 -0
  44. package/src/features/automations/__tests__/when-evaluator.test.ts +274 -270
  45. package/src/features/automations/__tests__/when-pushdown.test.ts +281 -277
  46. package/src/features/automations/action-executors/create-entry.executor.ts +27 -23
  47. package/src/features/automations/action-executors/edit-field.executor.ts +26 -22
  48. package/src/features/automations/action-executors/index.ts +37 -33
  49. package/src/features/automations/action-executors/send-mail.executor.ts +53 -42
  50. package/src/features/automations/action-executors/set-variable.executor.ts +159 -146
  51. package/src/features/automations/action-executors/webhook.executor.ts +67 -25
  52. package/src/features/automations/automation-runner.ts +85 -81
  53. package/src/features/automations/automation-runner.utils.ts +47 -43
  54. package/src/features/automations/automations.handler.ts +197 -193
  55. package/src/features/automations/automations.schema.ts +177 -160
  56. package/src/features/automations/context-resolver.ts +152 -148
  57. package/src/features/automations/cron-runner.ts +140 -136
  58. package/src/features/automations/cron-runner.utils.ts +44 -40
  59. package/src/features/automations/filter-translation.ts +46 -42
  60. package/src/features/automations/index.ts +16 -12
  61. package/src/features/automations/template-grammar.ts +245 -241
  62. package/src/features/automations/var-access-resolver.ts +140 -136
  63. package/src/features/automations/when-evaluator.ts +87 -83
  64. package/src/features/automations/when-pushdown.ts +57 -53
  65. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -0
  66. package/src/features/backrefs/backrefs.handler.ts +168 -0
  67. package/src/features/backrefs/d1-backref.repository.ts +84 -0
  68. package/src/features/backrefs/index.ts +5 -0
  69. package/src/features/content/constants.ts +16 -10
  70. package/src/features/content/handlers/bulk.handler.ts +185 -0
  71. package/src/features/content/handlers/create.ts +129 -165
  72. package/src/features/content/handlers/delete.ts +62 -87
  73. package/src/features/content/handlers/facets.ts +49 -45
  74. package/src/features/content/handlers/get.ts +120 -116
  75. package/src/features/content/handlers/helpers.test.ts +180 -0
  76. package/src/features/content/handlers/helpers.ts +93 -0
  77. package/src/features/content/handlers/list.ts +174 -88
  78. package/src/features/content/handlers/update.ts +174 -219
  79. package/src/features/content/index.ts +26 -20
  80. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
  81. package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
  82. package/src/features/dashboard-layout/index.ts +5 -0
  83. package/src/features/draft/draft.handler.ts +195 -170
  84. package/src/features/draft/draft.middleware.ts +66 -62
  85. package/src/features/draft/index.ts +5 -1
  86. package/src/features/email/email.provider.ts +42 -38
  87. package/src/features/email/email.service.ts +98 -93
  88. package/src/features/email/email.types.ts +119 -108
  89. package/src/features/email/index.ts +33 -29
  90. package/src/features/email/providers/resend.ts +67 -63
  91. package/src/features/email/providers/smtp.ts +55 -0
  92. package/src/features/email/templates/automation-mail.ts +19 -15
  93. package/src/features/email/templates/password-changed.ts +63 -59
  94. package/src/features/email/templates/password-reset.ts +68 -64
  95. package/src/features/email/templates/shell.ts +96 -92
  96. package/src/features/notifications/index.ts +5 -1
  97. package/src/features/notifications/notifications.handler.ts +105 -101
  98. package/src/features/password-reset/index.ts +19 -15
  99. package/src/features/password-reset/request.ts +92 -82
  100. package/src/features/password-reset/reset.ts +102 -92
  101. package/src/features/rotate-field/index.ts +5 -1
  102. package/src/features/rotate-field/rotate-field.handler.ts +132 -128
  103. package/src/features/rotate-field/rotate-field.schema.ts +17 -13
  104. package/src/features/schema/schema.handler.ts +131 -16
  105. package/src/features/seeds/index.ts +5 -0
  106. package/src/features/seeds/seeds.handler.test.ts +1199 -0
  107. package/src/features/seeds/seeds.handler.ts +558 -0
  108. package/src/features/settings/__tests__/settings.handler.test.ts +555 -0
  109. package/src/features/settings/settings.handler.ts +392 -302
  110. package/src/features/setup/index.ts +288 -91
  111. package/src/features/stats/index.ts +5 -1
  112. package/src/features/stats/stats.handler.ts +420 -430
  113. package/src/features/webhooks/index.ts +63 -0
  114. package/src/index.ts +65 -56
  115. package/src/media-utils.ts +82 -78
  116. package/src/middleware/auth-providers.middleware.test.ts +85 -0
  117. package/src/middleware/auth-providers.middleware.ts +45 -32
  118. package/src/middleware/observability.middleware.test.ts +93 -0
  119. package/src/middleware/observability.middleware.ts +70 -52
  120. package/src/middleware/rate-limit.middleware.ts +45 -41
  121. package/src/middleware/repository.middleware.ts +117 -91
  122. package/src/middleware/seed-registry.middleware.test.ts +97 -0
  123. package/src/middleware/seed-registry.middleware.ts +21 -0
  124. package/src/middleware/storage.middleware.ts +25 -21
  125. package/src/middleware.ts +45 -47
  126. package/src/public/access-policy.ts +27 -23
  127. package/src/public/api-key-middleware.ts +57 -53
  128. package/src/public/cache-utils.ts +38 -34
  129. package/src/public/entry-projection.ts +46 -42
  130. package/src/public/idempotency.ts +23 -19
  131. package/src/public/index.ts +16 -12
  132. package/src/public/problem-details.ts +124 -53
  133. package/src/public/public-add.ts +114 -110
  134. package/src/public/public-edit.ts +163 -159
  135. package/src/public/public-errors.ts +19 -15
  136. package/src/public/public-read.ts +63 -59
  137. package/src/public/public-routes.ts +88 -84
  138. package/src/public/query-builder.test.ts +224 -220
  139. package/src/public/query-builder.ts +156 -152
  140. package/src/public/rate-limit-middleware.ts +34 -30
  141. package/src/public/read-list.ts +54 -50
  142. package/src/public/read-single.ts +48 -44
  143. package/src/public/response-builder.ts +30 -26
  144. package/src/public/sanitize.ts +69 -65
  145. package/src/public/slug-utils.ts +18 -14
  146. package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -26
  147. package/src/rate-limit/cloudflare-rate-limiter.ts +15 -11
  148. package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -33
  149. package/src/rate-limit/in-memory-rate-limiter.ts +17 -13
  150. package/src/rate-limit/no-op-rate-limiter.test.ts +22 -18
  151. package/src/rate-limit/no-op-rate-limiter.ts +11 -7
  152. package/src/search-utils.test.ts +211 -207
  153. package/src/search-utils.ts +213 -209
  154. package/src/search.ts +65 -61
  155. package/src/shared/apply-policies.test.ts +81 -77
  156. package/src/shared/apply-policies.ts +67 -63
  157. package/src/shared/automations.repository.d1.ts +150 -146
  158. package/src/shared/background-notification-service.test.ts +62 -58
  159. package/src/shared/background-notification-service.ts +52 -48
  160. package/src/shared/base.repository.d1.ts +32 -28
  161. package/src/shared/content-utils.test.ts +165 -161
  162. package/src/shared/content-utils.ts +86 -82
  163. package/src/shared/content.repository.d1.test.ts +507 -312
  164. package/src/shared/content.repository.d1.ts +851 -382
  165. package/src/shared/d1-activity-log.repository.test.ts +140 -136
  166. package/src/shared/d1-activity-log.repository.ts +105 -101
  167. package/src/shared/d1-activity-logger.test.ts +86 -82
  168. package/src/shared/d1-activity-logger.ts +67 -63
  169. package/src/shared/d1-analytics.repository.test.ts +78 -74
  170. package/src/shared/d1-analytics.repository.ts +85 -81
  171. package/src/shared/d1-content-scan.repository.test.ts +80 -76
  172. package/src/shared/d1-content-scan.repository.ts +37 -29
  173. package/src/shared/d1-notification.repository.test.ts +128 -124
  174. package/src/shared/d1-notification.repository.ts +118 -114
  175. package/src/shared/d1-password-reset-token.repository.test.ts +81 -77
  176. package/src/shared/d1-password-reset-token.repository.ts +56 -52
  177. package/src/shared/d1-search.repository.test.ts +87 -83
  178. package/src/shared/d1-search.repository.ts +88 -84
  179. package/src/shared/d1-session.repository.test.ts +125 -121
  180. package/src/shared/d1-session.repository.ts +102 -98
  181. package/src/shared/d1-setup-checklist.repository.ts +36 -0
  182. package/src/shared/d1-user.repository.test.ts +151 -147
  183. package/src/shared/d1-user.repository.ts +119 -109
  184. package/src/shared/d1-widget.repository.test.ts +267 -217
  185. package/src/shared/d1-widget.repository.ts +385 -337
  186. package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
  187. package/src/shared/dashboard-layout.repository.d1.ts +58 -0
  188. package/src/shared/demo-data-sql.ts +216 -0
  189. package/src/shared/demo-data.repository.d1.ts +20 -0
  190. package/src/shared/execution-context-scheduler.ts +13 -9
  191. package/src/shared/fixed-clock.ts +25 -21
  192. package/src/shared/fts-sync.ts +8 -4
  193. package/src/shared/idempotency.repository.d1.test.ts +83 -79
  194. package/src/shared/idempotency.repository.d1.ts +60 -56
  195. package/src/shared/in-memory-activity-logger.ts +19 -15
  196. package/src/shared/in-memory-notification-service.ts +19 -15
  197. package/src/shared/in-memory-seed.repository.ts +58 -0
  198. package/src/shared/media.repository.d1.test.ts +107 -103
  199. package/src/shared/media.repository.d1.ts +68 -64
  200. package/src/shared/qstash-notification-service.test.ts +67 -0
  201. package/src/shared/qstash-notification-service.ts +55 -0
  202. package/src/shared/query-utils.ts +141 -137
  203. package/src/shared/request-utils.ts +26 -22
  204. package/src/shared/schema-mutator.d1.test.ts +142 -0
  205. package/src/shared/schema-mutator.d1.ts +64 -0
  206. package/src/shared/seed-layout.repository.d1.test.ts +114 -0
  207. package/src/shared/seed-layout.repository.d1.ts +62 -0
  208. package/src/shared/seed-registry-cache.test.ts +68 -0
  209. package/src/shared/seed-registry-cache.ts +49 -0
  210. package/src/shared/seed.repository.d1.test.ts +143 -0
  211. package/src/shared/seed.repository.d1.ts +98 -0
  212. package/src/shared/sequential-id-generator.ts +33 -22
  213. package/src/shared/site-settings.repository.d1.ts +53 -0
  214. package/src/shared/storage/factory.ts +41 -40
  215. package/src/shared/storage/s3-bucket.ts +195 -163
  216. package/src/shared/storage-utils.ts +40 -36
  217. package/src/shared/system-stats.repository.d1.test.ts +58 -54
  218. package/src/shared/system-stats.repository.d1.ts +48 -44
  219. package/src/types.ts +90 -67
  220. package/src/upload.ts +198 -187
  221. package/src/widget.test.ts +85 -0
  222. package/src/widget.ts +251 -208
  223. package/assets/dashboard/assets/index-B1mUfgiK.css +0 -1
  224. package/assets/dashboard/assets/index-CHxxc_id.js +0 -629
  225. package/src/shared/storage/r2-binding-bucket.ts +0 -81
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ /// <reference types="@cloudflare/workers-types" />
6
+ import type { EmailProvider } from '../email.provider'
7
+ import type { OutboundEmail } from '../email.types'
8
+
9
+ export interface SmtpProviderConfig {
10
+ /** Base URL of the Mailpit HTTP API, e.g. http://localhost:8025 */
11
+ baseUrl: string
12
+ }
13
+
14
+ interface MailpitAddress { Name?: string; Email: string }
15
+ interface MailpitSendPayload {
16
+ From: MailpitAddress
17
+ To: MailpitAddress[]
18
+ Subject: string
19
+ HTML?: string
20
+ Text?: string
21
+ }
22
+
23
+ function parseAddress(raw: string): MailpitAddress {
24
+ const match = raw.match(/^\s*(.+?)\s*<([^>]+)>\s*$/)
25
+ if (match) return { Name: match[1], Email: match[2] }
26
+ return { Email: raw.trim() }
27
+ }
28
+
29
+ export class SmtpEmailProvider implements EmailProvider {
30
+ private readonly endpoint: string
31
+
32
+ constructor(config: SmtpProviderConfig) {
33
+ this.endpoint = `${config.baseUrl.replace(/\/$/, '')}/api/v1/send`
34
+ }
35
+
36
+ async send(email: OutboundEmail): Promise<void> {
37
+ const payload: MailpitSendPayload = {
38
+ From: parseAddress(email.from),
39
+ To: email.to.map(parseAddress),
40
+ Subject: email.subject,
41
+ HTML: email.html,
42
+ }
43
+
44
+ const response = await fetch(this.endpoint, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify(payload),
48
+ })
49
+
50
+ if (!response.ok) {
51
+ const body = await response.text().catch(() => `HTTP ${response.status}`)
52
+ throw new Error(`[SmtpEmailProvider] send failed — ${body}`)
53
+ }
54
+ }
55
+ }
@@ -1,15 +1,19 @@
1
- import type { AutomationMailParams } from '../email.types'
2
-
3
- /** Identity builder: automation payloads are already user-authored. */
4
- export function buildAutomationEmail(params: AutomationMailParams) {
5
- return {
6
- to: params.to,
7
- subject: params.subject,
8
- html: params.body,
9
- text: stripHtml(params.body),
10
- }
11
- }
12
-
13
- function stripHtml(input: string): string {
14
- return input.replace(/<[^>]+>/g, '').trim()
15
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import type { AutomationMailParams } from '../email.types'
6
+
7
+ /** Identity builder: automation payloads are already user-authored. */
8
+ export function buildAutomationEmail(params: AutomationMailParams) {
9
+ return {
10
+ to: params.to,
11
+ subject: params.subject,
12
+ html: params.body,
13
+ text: stripHtml(params.body),
14
+ }
15
+ }
16
+
17
+ function stripHtml(input: string): string {
18
+ return input.replace(/<[^>]+>/g, '').trim()
19
+ }
@@ -1,59 +1,63 @@
1
- import type { EmailLocale } from '../email.types'
2
- import { buildEmailShell } from './shell'
3
-
4
- /**
5
- * Localized texts for the "password changed" security notification.
6
- *
7
- * ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
8
- * See instructions in `templates/password-reset.ts`.
9
- * ─────────────────────────────────────────────────────────────────────────────
10
- */
11
- const COPY: Record<EmailLocale, {
12
- subject: string
13
- title: string
14
- body: string
15
- warning: string
16
- footer: string
17
- }> = {
18
- en: {
19
- subject: 'Your Beech CMS password has been changed',
20
- title: 'Your password has been changed',
21
- body: 'Your Beech CMS account password was successfully changed. If you made this change, no action is needed.',
22
- warning:
23
- 'If you did not make this change, your account may be compromised. Contact your administrator immediately.',
24
- footer: 'This is an automated security notification. Do not reply to this email.',
25
- },
26
- it: {
27
- subject: 'La tua password Beech CMS è stata modificata',
28
- title: 'La tua password è stata modificata',
29
- body: 'La password del tuo account Beech CMS è stata modificata con successo. Se hai effettuato tu questa modifica, non devi fare nulla.',
30
- warning:
31
- 'Se non hai effettuato tu questa modifica, il tuo account potrebbe essere compromesso. Contatta immediatamente il tuo amministratore.',
32
- footer: 'Questa è una notifica di sicurezza automatica. Non rispondere a questa email.',
33
- },
34
- }
35
-
36
- /**
37
- * Builds the "password changed" notification email.
38
- *
39
- * This email does not have a CTA button — it is a pure security notification.
40
- * The `warning` block (red text) warns the user to take action if they
41
- * were not the one who changed the password.
42
- *
43
- * @param locale - Language for the email subject and body.
44
- * @returns Object with `subject` (string) and `html` (complete HTML document).
45
- */
46
- export function buildPasswordChangedEmail(
47
- locale: EmailLocale,
48
- ): { subject: string; html: string } {
49
- const c = COPY[locale]
50
- return {
51
- subject: c.subject,
52
- html: buildEmailShell(locale, {
53
- title: c.title,
54
- body: c.body,
55
- warning: c.warning,
56
- footer: c.footer,
57
- }),
58
- }
59
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import type { EmailLocale } from '../email.types'
6
+ import { buildEmailShell } from './shell'
7
+
8
+ /**
9
+ * Localized texts for the "password changed" security notification.
10
+ *
11
+ * ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
12
+ * See instructions in `templates/password-reset.ts`.
13
+ * ─────────────────────────────────────────────────────────────────────────────
14
+ */
15
+ const COPY: Record<EmailLocale, {
16
+ subject: string
17
+ title: string
18
+ body: string
19
+ warning: string
20
+ footer: string
21
+ }> = {
22
+ en: {
23
+ subject: 'Your Beech CMS password has been changed',
24
+ title: 'Your password has been changed',
25
+ body: 'Your Beech CMS account password was successfully changed. If you made this change, no action is needed.',
26
+ warning:
27
+ 'If you did not make this change, your account may be compromised. Contact your administrator immediately.',
28
+ footer: 'This is an automated security notification. Do not reply to this email.',
29
+ },
30
+ it: {
31
+ subject: 'La tua password Beech CMS è stata modificata',
32
+ title: 'La tua password è stata modificata',
33
+ body: 'La password del tuo account Beech CMS è stata modificata con successo. Se hai effettuato tu questa modifica, non devi fare nulla.',
34
+ warning:
35
+ 'Se non hai effettuato tu questa modifica, il tuo account potrebbe essere compromesso. Contatta immediatamente il tuo amministratore.',
36
+ footer: 'Questa è una notifica di sicurezza automatica. Non rispondere a questa email.',
37
+ },
38
+ }
39
+
40
+ /**
41
+ * Builds the "password changed" notification email.
42
+ *
43
+ * This email does not have a CTA button it is a pure security notification.
44
+ * The `warning` block (red text) warns the user to take action if they
45
+ * were not the one who changed the password.
46
+ *
47
+ * @param locale - Language for the email subject and body.
48
+ * @returns Object with `subject` (string) and `html` (complete HTML document).
49
+ */
50
+ export function buildPasswordChangedEmail(
51
+ locale: EmailLocale,
52
+ ): { subject: string; html: string } {
53
+ const c = COPY[locale]
54
+ return {
55
+ subject: c.subject,
56
+ html: buildEmailShell(locale, {
57
+ title: c.title,
58
+ body: c.body,
59
+ warning: c.warning,
60
+ footer: c.footer,
61
+ }),
62
+ }
63
+ }
@@ -1,64 +1,68 @@
1
- import type { EmailLocale } from '../email.types'
2
- import { buildEmailShell } from './shell'
3
-
4
- /**
5
- * Localized texts for the password reset email.
6
- *
7
- * ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
8
- * 1. Add the ISO code in `SUPPORTED_EMAIL_LOCALES` (`email.types.ts`).
9
- * 2. Add a corresponding key in this object with all fields.
10
- * TypeScript immediately reports missing keys thanks to
11
- * `Record<EmailLocale, …>`.
12
- * ─────────────────────────────────────────────────────────────────────────────
13
- */
14
- const COPY: Record<EmailLocale, {
15
- subject: string
16
- title: string
17
- body: string
18
- ctaLabel: string
19
- footer: string
20
- }> = {
21
- en: {
22
- subject: 'Reset your Beech CMS password',
23
- title: 'Reset your password',
24
- body: 'You requested a password reset for your Beech CMS account. Click the button below to set a new password. This link expires in 30 minutes.',
25
- ctaLabel: 'Reset password',
26
- footer: "If you didn't request this, you can safely ignore this email.",
27
- },
28
- it: {
29
- subject: 'Reimposta la tua password Beech CMS',
30
- title: 'Reimposta la tua password',
31
- body: 'Hai richiesto il reset della password per il tuo account Beech CMS. Clicca il pulsante qui sotto per impostare una nuova password. Questo link scade tra 30 minuti.',
32
- ctaLabel: 'Reimposta password',
33
- footer: 'Se non hai richiesto questo, puoi ignorare questa email in tutta sicurezza.',
34
- },
35
- }
36
-
37
- /**
38
- * Builds the email with the password reset link.
39
- *
40
- * Composes the localized content with the base layout (`buildEmailShell`)
41
- * injecting the CTA button pointing to the reset URL.
42
- *
43
- * @param resetUrl - Complete URL with the token in plain text, e.g.
44
- * `https://dashboard.beechcms.dev/reset-password?token=<uuid>`.
45
- * It is directly embedded in the CTA button do not sanitize
46
- * further: the token is an internally generated UUID.
47
- * @param locale - Language for the email subject and body.
48
- * @returns Object with `subject` (string) and `html` (complete HTML document).
49
- */
50
- export function buildPasswordResetEmail(
51
- resetUrl: string,
52
- locale: EmailLocale,
53
- ): { subject: string; html: string } {
54
- const c = COPY[locale]
55
- return {
56
- subject: c.subject,
57
- html: buildEmailShell(locale, {
58
- title: c.title,
59
- body: c.body,
60
- cta: { label: c.ctaLabel, href: resetUrl },
61
- footer: c.footer,
62
- }),
63
- }
64
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import type { EmailLocale } from '../email.types'
6
+ import { buildEmailShell } from './shell'
7
+
8
+ /**
9
+ * Localized texts for the password reset email.
10
+ *
11
+ * ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
12
+ * 1. Add the ISO code in `SUPPORTED_EMAIL_LOCALES` (`email.types.ts`).
13
+ * 2. Add a corresponding key in this object with all fields.
14
+ * TypeScript immediately reports missing keys thanks to
15
+ * `Record<EmailLocale, …>`.
16
+ * ─────────────────────────────────────────────────────────────────────────────
17
+ */
18
+ const COPY: Record<EmailLocale, {
19
+ subject: string
20
+ title: string
21
+ body: string
22
+ ctaLabel: string
23
+ footer: string
24
+ }> = {
25
+ en: {
26
+ subject: 'Reset your Beech CMS password',
27
+ title: 'Reset your password',
28
+ body: 'You requested a password reset for your Beech CMS account. Click the button below to set a new password. This link expires in 30 minutes.',
29
+ ctaLabel: 'Reset password',
30
+ footer: "If you didn't request this, you can safely ignore this email.",
31
+ },
32
+ it: {
33
+ subject: 'Reimposta la tua password Beech CMS',
34
+ title: 'Reimposta la tua password',
35
+ body: 'Hai richiesto il reset della password per il tuo account Beech CMS. Clicca il pulsante qui sotto per impostare una nuova password. Questo link scade tra 30 minuti.',
36
+ ctaLabel: 'Reimposta password',
37
+ footer: 'Se non hai richiesto questo, puoi ignorare questa email in tutta sicurezza.',
38
+ },
39
+ }
40
+
41
+ /**
42
+ * Builds the email with the password reset link.
43
+ *
44
+ * Composes the localized content with the base layout (`buildEmailShell`)
45
+ * injecting the CTA button pointing to the reset URL.
46
+ *
47
+ * @param resetUrl - Complete URL with the token in plain text, e.g.
48
+ * `https://dashboard.beechcms.dev/reset-password?token=<uuid>`.
49
+ * It is directly embedded in the CTA button — do not sanitize
50
+ * further: the token is an internally generated UUID.
51
+ * @param locale - Language for the email subject and body.
52
+ * @returns Object with `subject` (string) and `html` (complete HTML document).
53
+ */
54
+ export function buildPasswordResetEmail(
55
+ resetUrl: string,
56
+ locale: EmailLocale,
57
+ ): { subject: string; html: string } {
58
+ const c = COPY[locale]
59
+ return {
60
+ subject: c.subject,
61
+ html: buildEmailShell(locale, {
62
+ title: c.title,
63
+ body: c.body,
64
+ cta: { label: c.ctaLabel, href: resetUrl },
65
+ footer: c.footer,
66
+ }),
67
+ }
68
+ }
@@ -1,92 +1,96 @@
1
- import type { EmailLocale } from '../email.types'
2
-
3
- /**
4
- * Content slots that every template must provide to compose a complete email.
5
- * Each slot corresponds to a visual block in the email card layout.
6
- */
7
- export interface EmailShellSlots {
8
- /**
9
- * H2 heading shown at the top of the card. Keep it under ~50 characters
10
- * to ensure good readability on mobile clients.
11
- */
12
- title: string
13
-
14
- /**
15
- * Main body text. Rendered as a paragraph.
16
- * Safe inline HTML is allowed (e.g., `<strong>`, `<a href="...">`),
17
- * but avoid block elements (`<p>`, `<div>`) that could break
18
- * the layout structure in rigid email clients (Outlook, Gmail).
19
- */
20
- body: string
21
-
22
- /**
23
- * Optional call-to-action button, rendered as a dark link-button.
24
- * Omit for notification-only emails that do not require user action.
25
- */
26
- cta?: { label: string; href: string }
27
-
28
- /**
29
- * Optional warning paragraph. Rendered in red (#ef4444) to attract
30
- * attention. Use it for security alerts
31
- * ("if it wasn't you, act immediately").
32
- */
33
- warning?: string
34
-
35
- /**
36
- * Small gray text at the bottom of the card.
37
- * Used for notes like "automated notification, do not reply".
38
- */
39
- footer: string
40
- }
41
-
42
- /**
43
- * Builds the base HTML layout shared by all Beech CMS transactional emails.
44
- *
45
- * ─── SINGLE SOURCE OF TRUTH FOR BRANDING ─────────────────────────────────────
46
- * Modifying this function changes the visual appearance of ALL outgoing emails
47
- * simultaneously:
48
- * - background and card color
49
- * - border style and border-radius
50
- * - typographic scale and spacing
51
- * - CTA button style
52
- *
53
- * To change the text or structure of a specific email, instead modify
54
- * the corresponding template file (`templates/password-reset.ts`, etc.).
55
- * ─────────────────────────────────────────────────────────────────────────────
56
- *
57
- * @param locale - Used for the `lang` attribute of the `<html>` tag.
58
- * @param slots - Content blocks injected into the layout.
59
- * @returns A complete and self-contained HTML document ready to be sent.
60
- */
61
- export function buildEmailShell(locale: EmailLocale, slots: EmailShellSlots): string {
62
- const ctaBlock = slots.cta
63
- ? `<a href="${slots.cta.href}"
64
- style="display:inline-block;background:#111;color:#fff;padding:12px 24px;
65
- border-radius:6px;text-decoration:none;font-size:15px;font-weight:500;
66
- margin-bottom:24px">
67
- ${slots.cta.label}
68
- </a>`
69
- : ''
70
-
71
- const warningBlock = slots.warning
72
- ? `<p style="margin:0 0 24px;color:#ef4444;font-size:15px;line-height:1.5;font-weight:500">
73
- ${slots.warning}
74
- </p>`
75
- : ''
76
-
77
- return `<!DOCTYPE html>
78
- <html lang="${locale}">
79
- <head>
80
- <meta charset="UTF-8">
81
- <meta name="viewport" content="width=device-width,initial-scale=1">
82
- </head>
83
- <body style="font-family:sans-serif;background:#f9f9f9;margin:0;padding:32px">
84
- <div style="max-width:480px;margin:0 auto;background:#fff;border-radius:8px;
85
- padding:32px;border:1px solid #e5e5e5">
86
- <h2 style="margin:0 0 16px;font-size:20px;color:#111">${slots.title}</h2>
87
- <p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.5">${slots.body}</p>
88
- ${ctaBlock}${warningBlock}<p style="margin:0;color:#999;font-size:13px">${slots.footer}</p>
89
- </div>
90
- </body>
91
- </html>`
92
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import type { EmailLocale } from '../email.types'
6
+
7
+ /**
8
+ * Content slots that every template must provide to compose a complete email.
9
+ * Each slot corresponds to a visual block in the email card layout.
10
+ */
11
+ export interface EmailShellSlots {
12
+ /**
13
+ * H2 heading shown at the top of the card. Keep it under ~50 characters
14
+ * to ensure good readability on mobile clients.
15
+ */
16
+ title: string
17
+
18
+ /**
19
+ * Main body text. Rendered as a paragraph.
20
+ * Safe inline HTML is allowed (e.g., `<strong>`, `<a href="...">`),
21
+ * but avoid block elements (`<p>`, `<div>`) that could break
22
+ * the layout structure in rigid email clients (Outlook, Gmail).
23
+ */
24
+ body: string
25
+
26
+ /**
27
+ * Optional call-to-action button, rendered as a dark link-button.
28
+ * Omit for notification-only emails that do not require user action.
29
+ */
30
+ cta?: { label: string; href: string }
31
+
32
+ /**
33
+ * Optional warning paragraph. Rendered in red (#ef4444) to attract
34
+ * attention. Use it for security alerts
35
+ * ("if it wasn't you, act immediately").
36
+ */
37
+ warning?: string
38
+
39
+ /**
40
+ * Small gray text at the bottom of the card.
41
+ * Used for notes like "automated notification, do not reply".
42
+ */
43
+ footer: string
44
+ }
45
+
46
+ /**
47
+ * Builds the base HTML layout shared by all Beech CMS transactional emails.
48
+ *
49
+ * ─── SINGLE SOURCE OF TRUTH FOR BRANDING ─────────────────────────────────────
50
+ * Modifying this function changes the visual appearance of ALL outgoing emails
51
+ * simultaneously:
52
+ * - background and card color
53
+ * - border style and border-radius
54
+ * - typographic scale and spacing
55
+ * - CTA button style
56
+ *
57
+ * To change the text or structure of a specific email, instead modify
58
+ * the corresponding template file (`templates/password-reset.ts`, etc.).
59
+ * ─────────────────────────────────────────────────────────────────────────────
60
+ *
61
+ * @param locale - Used for the `lang` attribute of the `<html>` tag.
62
+ * @param slots - Content blocks injected into the layout.
63
+ * @returns A complete and self-contained HTML document ready to be sent.
64
+ */
65
+ export function buildEmailShell(locale: EmailLocale, slots: EmailShellSlots): string {
66
+ const ctaBlock = slots.cta
67
+ ? `<a href="${slots.cta.href}"
68
+ style="display:inline-block;background:#111;color:#fff;padding:12px 24px;
69
+ border-radius:6px;text-decoration:none;font-size:15px;font-weight:500;
70
+ margin-bottom:24px">
71
+ ${slots.cta.label}
72
+ </a>`
73
+ : ''
74
+
75
+ const warningBlock = slots.warning
76
+ ? `<p style="margin:0 0 24px;color:#ef4444;font-size:15px;line-height:1.5;font-weight:500">
77
+ ${slots.warning}
78
+ </p>`
79
+ : ''
80
+
81
+ return `<!DOCTYPE html>
82
+ <html lang="${locale}">
83
+ <head>
84
+ <meta charset="UTF-8">
85
+ <meta name="viewport" content="width=device-width,initial-scale=1">
86
+ </head>
87
+ <body style="font-family:sans-serif;background:#f9f9f9;margin:0;padding:32px">
88
+ <div style="max-width:480px;margin:0 auto;background:#fff;border-radius:8px;
89
+ padding:32px;border:1px solid #e5e5e5">
90
+ <h2 style="margin:0 0 16px;font-size:20px;color:#111">${slots.title}</h2>
91
+ <p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.5">${slots.body}</p>
92
+ ${ctaBlock}${warningBlock}<p style="margin:0;color:#999;font-size:13px">${slots.footer}</p>
93
+ </div>
94
+ </body>
95
+ </html>`
96
+ }
@@ -1 +1,5 @@
1
- export { notificationsApp } from './notifications.handler'
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ export { notificationsApp } from './notifications.handler'