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