@beechcms/api 0.4.0-preview.11 → 0.4.0-preview.13

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 (86) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/assets/index-CewtCjom.css +1 -0
  4. package/assets/dashboard/assets/index-FQ6JhvRH.js +554 -0
  5. package/assets/dashboard/beechLogoDark.svg +48 -48
  6. package/assets/dashboard/index.html +18 -18
  7. package/assets/dashboard/sol.svg +3 -3
  8. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  9. package/migrations/0000_v040_base.sql +213 -213
  10. package/package.json +2 -2
  11. package/src/auth/constants.ts +10 -10
  12. package/src/auth/login.ts +91 -91
  13. package/src/auth/refresh.ts +127 -127
  14. package/src/factory.ts +347 -325
  15. package/src/features/content/constants.ts +10 -10
  16. package/src/features/content/handlers/create.ts +153 -163
  17. package/src/features/content/handlers/delete.ts +76 -85
  18. package/src/features/content/handlers/facets.ts +45 -45
  19. package/src/features/content/handlers/get.ts +116 -116
  20. package/src/features/content/handlers/list.ts +88 -88
  21. package/src/features/content/handlers/update.ts +207 -216
  22. package/src/features/content/index.ts +20 -20
  23. package/src/features/draft/draft.handler.ts +272 -272
  24. package/src/features/draft/index.ts +1 -1
  25. package/src/features/email/email.provider.ts +38 -38
  26. package/src/features/email/email.service.ts +80 -80
  27. package/src/features/email/email.types.ts +98 -98
  28. package/src/features/email/index.ts +28 -28
  29. package/src/features/email/providers/resend.ts +63 -63
  30. package/src/features/email/templates/password-changed.ts +59 -59
  31. package/src/features/email/templates/password-reset.ts +64 -64
  32. package/src/features/email/templates/shell.ts +92 -93
  33. package/src/features/notifications/index.ts +1 -1
  34. package/src/features/notifications/notifications.handler.ts +130 -88
  35. package/src/features/password-reset/index.ts +15 -15
  36. package/src/features/password-reset/request.ts +106 -88
  37. package/src/features/password-reset/reset.ts +128 -110
  38. package/src/features/rotate-field/index.ts +1 -1
  39. package/src/features/rotate-field/rotate-field.handler.ts +132 -82
  40. package/src/features/rotate-field/rotate-field.schema.ts +13 -9
  41. package/src/features/schema/schema.handler.ts +16 -16
  42. package/src/features/settings/settings.handler.ts +414 -267
  43. package/src/features/setup/index.ts +96 -59
  44. package/src/features/stats/index.ts +1 -1
  45. package/src/features/stats/stats.handler.ts +458 -395
  46. package/src/index.ts +24 -24
  47. package/src/media-utils.ts +78 -78
  48. package/src/middleware/repository.middleware.ts +24 -18
  49. package/src/middleware/storage.middleware.ts +21 -0
  50. package/src/middleware.ts +67 -67
  51. package/src/public/access-policy.ts +23 -23
  52. package/src/public/api-key-middleware.ts +53 -53
  53. package/src/public/index.ts +12 -12
  54. package/src/public/problem-details.ts +48 -42
  55. package/src/public/public-add.ts +166 -166
  56. package/src/public/public-edit.ts +158 -158
  57. package/src/public/public-errors.ts +15 -15
  58. package/src/public/public-read.ts +216 -216
  59. package/src/public/public-routes.ts +84 -31
  60. package/src/public/query-builder.ts +152 -152
  61. package/src/public/rate-limit-middleware.ts +42 -42
  62. package/src/public/response-builder.ts +26 -26
  63. package/src/public/sanitize.ts +65 -65
  64. package/src/public/slug-utils.ts +14 -14
  65. package/src/search-utils.ts +192 -192
  66. package/src/search.ts +72 -72
  67. package/src/shared/activity-logger.ts +79 -79
  68. package/src/shared/apply-policies.ts +63 -63
  69. package/src/shared/base.repository.d1.ts +28 -28
  70. package/src/shared/content-utils.ts +82 -108
  71. package/src/shared/content.repository.d1.ts +382 -382
  72. package/src/shared/fts-sync.ts +4 -4
  73. package/src/shared/idempotency.repository.d1.ts +56 -45
  74. package/src/shared/media.repository.d1.ts +64 -0
  75. package/src/shared/notification-service.ts +56 -56
  76. package/src/shared/query-utils.ts +137 -137
  77. package/src/shared/storage/factory.ts +40 -0
  78. package/src/shared/storage/r2-binding-bucket.ts +81 -0
  79. package/src/shared/storage/s3-bucket.ts +163 -0
  80. package/src/shared/storage-utils.ts +36 -36
  81. package/src/shared/system-stats.repository.d1.ts +44 -0
  82. package/src/types.ts +46 -41
  83. package/src/upload.ts +179 -331
  84. package/src/widget.ts +349 -349
  85. package/assets/dashboard/assets/index-9Ch2xWJr.js +0 -554
  86. package/assets/dashboard/assets/index-ye3325L9.css +0 -1
@@ -1,80 +1,80 @@
1
- /**
2
- * Email Service — orchestratore del modulo email di Beech CMS.
3
- *
4
- * Pipeline di invio:
5
- * chiamantefunzione service → template builder → provider → Resend (o altro)
6
- *
7
- * Questo è l'unico file che importa sia dai template che dal provider.
8
- * Nessun altro layer conosce l'intera pipeline.
9
- *
10
- * ─── CAMBIO PROVIDER ─────────────────────────────────────────────────────────
11
- * Per sostituire Resend con un altro servizio, modifica SOLO la funzione
12
- * `createProvider()` qui sotto: cambia l'import e l'istanziazione.
13
- * Nessun altro file del modulo nel resto del progetto va toccato.
14
- * ─────────────────────────────────────────────────────────────────────────────
15
- */
16
- import { ResendEmailProvider } from './providers/resend'
17
- import { buildPasswordResetEmail } from './templates/password-reset'
18
- import { buildPasswordChangedEmail } from './templates/password-changed'
19
- import type { EmailProvider } from './email.provider'
20
- import type {
21
- PasswordResetEmailParams,
22
- PasswordChangedEmailParams,
23
- } from './email.types'
24
-
25
- /** Indirizzo mittente di default (mittente di test Resend, funziona senza dominio verificato). */
26
- const DEFAULT_FROM = 'Beech CMS <onboarding@resend.dev>'
27
-
28
- /**
29
- * Istanzia il provider email attivo.
30
- *
31
- * Questo è il punto singolo di cambio provider: sostituisci la riga
32
- * `new ResendEmailProvider()` con qualsiasi classe che implementi `EmailProvider`.
33
- */
34
- function createProvider(apiKey: string, isDev: boolean): EmailProvider {
35
- return new ResendEmailProvider(apiKey, isDev)
36
- }
37
-
38
- /**
39
- * Invia l'email con il link di reset password al destinatario specificato.
40
- *
41
- * Il corpo dell'email è costruito dal template localizzato in
42
- * `templates/password-reset.ts` e composto con il layout base in
43
- * `templates/shell.ts`.
44
- *
45
- * @throws Se il provider rifiuta la richiesta. Il chiamante decide se propagare
46
- * l'errore (fail della request) o gestirlo silenziosamente (fire-and-forget).
47
- */
48
- export async function sendPasswordResetEmail(
49
- params: PasswordResetEmailParams,
50
- ): Promise<void> {
51
- const provider = createProvider(params.apiKey, params.isDev ?? false)
52
- const { subject, html } = buildPasswordResetEmail(params.resetUrl, params.locale)
53
- await provider.send({
54
- from: params.from ?? DEFAULT_FROM,
55
- to: [params.to],
56
- subject,
57
- html,
58
- })
59
- }
60
-
61
- /**
62
- * Invia la notifica di sicurezza "password modificata" al proprietario dell'account.
63
- *
64
- * Chiamata dopo un reset password riuscito per avvisare l'utente. Non ha un
65
- * pulsante CTA — è una pura notifica, nessuna azione richiesta all'utente.
66
- *
67
- * @throws Se il provider rifiuta la richiesta.
68
- */
69
- export async function sendPasswordChangedEmail(
70
- params: PasswordChangedEmailParams,
71
- ): Promise<void> {
72
- const provider = createProvider(params.apiKey, params.isDev ?? false)
73
- const { subject, html } = buildPasswordChangedEmail(params.locale)
74
- await provider.send({
75
- from: params.from ?? DEFAULT_FROM,
76
- to: [params.to],
77
- subject,
78
- html,
79
- })
80
- }
1
+ /**
2
+ * Email Service — orchestrator of the Beech CMS email module.
3
+ *
4
+ * Sending pipeline:
5
+ * caller → service function → template builder → provider → Resend (or other)
6
+ *
7
+ * This is the only file that imports from both templates and the provider.
8
+ * No other layer knows the entire pipeline.
9
+ *
10
+ * ─── CHANGING PROVIDER ───────────────────────────────────────────────────────
11
+ * To replace Resend with another service, ONLY modify the
12
+ * `createProvider()` function below: change the import and instantiation.
13
+ * No other module file nor anywhere else in the projectneeds to be touched.
14
+ * ─────────────────────────────────────────────────────────────────────────────
15
+ */
16
+ import { ResendEmailProvider } from './providers/resend'
17
+ import { buildPasswordResetEmail } from './templates/password-reset'
18
+ import { buildPasswordChangedEmail } from './templates/password-changed'
19
+ import type { EmailProvider } from './email.provider'
20
+ import type {
21
+ PasswordResetEmailParams,
22
+ PasswordChangedEmailParams,
23
+ } from './email.types'
24
+
25
+ /** Default sender address (Resend test sender, works without a verified domain). */
26
+ const DEFAULT_FROM = 'Beech CMS <onboarding@resend.dev>'
27
+
28
+ /**
29
+ * Instantiates the active email provider.
30
+ *
31
+ * This is the single point for changing the provider: replace the
32
+ * `new ResendEmailProvider(...)` line with any class that implements `EmailProvider`.
33
+ */
34
+ function createProvider(apiKey: string, isDev: boolean): EmailProvider {
35
+ return new ResendEmailProvider(apiKey, isDev)
36
+ }
37
+
38
+ /**
39
+ * Sends the password reset link email to the specified recipient.
40
+ *
41
+ * The email body is built from the localized template in
42
+ * `templates/password-reset.ts` and composed with the base layout in
43
+ * `templates/shell.ts`.
44
+ *
45
+ * @throws If the provider rejects the request. The caller decides whether to propagate
46
+ * the error (request fail) or handle it silently (fire-and-forget).
47
+ */
48
+ export async function sendPasswordResetEmail(
49
+ params: PasswordResetEmailParams,
50
+ ): Promise<void> {
51
+ const provider = createProvider(params.apiKey, params.isDev ?? false)
52
+ const { subject, html } = buildPasswordResetEmail(params.resetUrl, params.locale)
53
+ await provider.send({
54
+ from: params.from ?? DEFAULT_FROM,
55
+ to: [params.to],
56
+ subject,
57
+ html,
58
+ })
59
+ }
60
+
61
+ /**
62
+ * Sends the "password changed" security notification to the account owner.
63
+ *
64
+ * Called after a successful password reset to notify the user. It does not have a
65
+ * CTA button it is a pure notification, no action required from the user.
66
+ *
67
+ * @throws If the provider rejects the request.
68
+ */
69
+ export async function sendPasswordChangedEmail(
70
+ params: PasswordChangedEmailParams,
71
+ ): Promise<void> {
72
+ const provider = createProvider(params.apiKey, params.isDev ?? false)
73
+ const { subject, html } = buildPasswordChangedEmail(params.locale)
74
+ await provider.send({
75
+ from: params.from ?? DEFAULT_FROM,
76
+ to: [params.to],
77
+ subject,
78
+ html,
79
+ })
80
+ }
@@ -1,98 +1,98 @@
1
- /**
2
- * Tipi condivisi del modulo email di Beech CMS.
3
- *
4
- * Tutti i tipi usati tra provider, service e template sono definiti qui
5
- * in modo che ogni layer rimanga disaccoppiato dagli altri.
6
- */
7
-
8
- // ── Locale ────────────────────────────────────────────────────────────────────
9
-
10
- /**
11
- * Lingue supportate dal sistema di template email.
12
- *
13
- * Per aggiungere una nuova lingua:
14
- * 1. Aggiungi il codice ISO qui (es. `'fr'`).
15
- * 2. Aggiungi la traduzione corrispondente nell'oggetto `COPY` di ogni
16
- * file in `templates/`. TypeScript segnalerà le chiavi mancanti.
17
- */
18
- export const SUPPORTED_EMAIL_LOCALES = ['en', 'it'] as const
19
- export type EmailLocale = (typeof SUPPORTED_EMAIL_LOCALES)[number]
20
-
21
- /**
22
- * Risolve una stringa locale non verificata (es. dal body di una request)
23
- * a un valore `EmailLocale` supportato. Qualsiasi valore sconosciuto
24
- * ricade su `'en'` in modo sicuro.
25
- *
26
- * @param raw - Valore grezzo dal client (può essere qualsiasi cosa).
27
- * @returns Un `EmailLocale` valido, sempre.
28
- */
29
- export function resolveEmailLocale(raw: unknown): EmailLocale {
30
- if (
31
- typeof raw === 'string' &&
32
- (SUPPORTED_EMAIL_LOCALES as readonly string[]).includes(raw)
33
- ) {
34
- return raw as EmailLocale
35
- }
36
- return 'en'
37
- }
38
-
39
- // ── Messaggio outbound ────────────────────────────────────────────────────────
40
-
41
- /**
42
- * Il messaggio email risolto che il provider riceve e invia.
43
- * Viene costruito dal service combinando i parametri della chiamata
44
- * con l'output del template builder.
45
- */
46
- export interface OutboundEmail {
47
- /** Indirizzo mittente in formato RFC 5321 (es. "Beech CMS <noreply@beechcms.dev>"). */
48
- from: string
49
- /** Lista degli indirizzi destinatari. Deve contenere almeno un elemento. */
50
- to: string[]
51
- subject: string
52
- /** Corpo HTML completo. Deve essere un documento HTML valido (vedi `templates/shell.ts`). */
53
- html: string
54
- }
55
-
56
- // ── Parametri delle funzioni del service ──────────────────────────────────────
57
-
58
- /**
59
- * Parametri condivisi da ogni funzione di invio email in `email.service.ts`.
60
- * Le funzioni specifiche estendono questo tipo con i campi aggiuntivi
61
- * necessari al proprio template.
62
- */
63
- export interface BaseEmailParams {
64
- /** Indirizzo del destinatario principale. */
65
- to: string
66
- /** Lingua del corpo email. Usa `resolveEmailLocale()` prima di passarlo qui. */
67
- locale: EmailLocale
68
- /**
69
- * API key Resend (o del provider attivo). Deve essere non vuota
70
- * il chiamante è responsabile di validarla prima di invocare il service.
71
- */
72
- apiKey: string
73
- /**
74
- * Indirizzo mittente in formato RFC 5321.
75
- * Default: "Beech CMS <onboarding@resend.dev>" (mittente di test Resend).
76
- * In produzione, impostare un indirizzo verificato tramite la variabile
77
- * d'ambiente `EMAIL_FROM`.
78
- */
79
- from?: string
80
- /**
81
- * Quando `true`, gli errori del provider vengono loggati in console.
82
- * Impostare `false` in produzione per non esporre dettagli interni.
83
- */
84
- isDev?: boolean
85
- }
86
-
87
- /** Parametri per l'email di reset passwordaggiunge l'URL di reset. */
88
- export interface PasswordResetEmailParams extends BaseEmailParams {
89
- /**
90
- * URL completo che l'utente clicca per impostare la nuova password.
91
- * Contiene il token in chiaro come query param `?token=<uuid>`.
92
- * Costruito dal chiamante come `${APP_URL}/reset-password?token=${token}`.
93
- */
94
- resetUrl: string
95
- }
96
-
97
- /** Parametri per la notifica "password modificata". Nessun campo aggiuntivo. */
98
- export type PasswordChangedEmailParams = BaseEmailParams
1
+ /**
2
+ * Shared types for the Beech CMS email module.
3
+ *
4
+ * All types used across provider, service, and templates are defined here
5
+ * so that each layer remains decoupled from the others.
6
+ */
7
+
8
+ // ── Locale ────────────────────────────────────────────────────────────────────
9
+
10
+ /**
11
+ * Supported languages for the email template system.
12
+ *
13
+ * To add a new language:
14
+ * 1. Add the ISO code here (e.g., `'fr'`).
15
+ * 2. Add the corresponding translation in the `COPY` object of every
16
+ * file in `templates/`. TypeScript will flag missing keys.
17
+ */
18
+ export const SUPPORTED_EMAIL_LOCALES = ['en', 'it'] as const
19
+ export type EmailLocale = (typeof SUPPORTED_EMAIL_LOCALES)[number]
20
+
21
+ /**
22
+ * Resolves an unverified locale string (e.g., from a request body)
23
+ * to a supported `EmailLocale` value. Any unknown value
24
+ * safely falls back to `'en'`.
25
+ *
26
+ * @param raw - Raw value from the client (can be anything).
27
+ * @returns A valid `EmailLocale`, always.
28
+ */
29
+ export function resolveEmailLocale(raw: unknown): EmailLocale {
30
+ if (
31
+ typeof raw === 'string' &&
32
+ (SUPPORTED_EMAIL_LOCALES as readonly string[]).includes(raw)
33
+ ) {
34
+ return raw as EmailLocale
35
+ }
36
+ return 'en'
37
+ }
38
+
39
+ // ── Outbound message ──────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * The resolved email message that the provider receives and sends.
43
+ * It is constructed by the service by combining the call parameters
44
+ * with the template builder output.
45
+ */
46
+ export interface OutboundEmail {
47
+ /** Sender address in RFC 5321 format (e.g., "Beech CMS <noreply@beechcms.dev>"). */
48
+ from: string
49
+ /** List of recipient addresses. Must contain at least one element. */
50
+ to: string[]
51
+ subject: string
52
+ /** Complete HTML body. Must be a valid HTML document (see `templates/shell.ts`). */
53
+ html: string
54
+ }
55
+
56
+ // ── Service function parameters ───────────────────────────────────────────────
57
+
58
+ /**
59
+ * Shared parameters for every email sending function in `email.service.ts`.
60
+ * Specific functions extend this type with the additional fields
61
+ * required for their respective templates.
62
+ */
63
+ export interface BaseEmailParams {
64
+ /** Main recipient address. */
65
+ to: string
66
+ /** Email body language. Use `resolveEmailLocale()` before passing it here. */
67
+ locale: EmailLocale
68
+ /**
69
+ * Resend API key (or the active provider's key). Must be non-empty
70
+ * the caller is responsible for validating it before invoking the service.
71
+ */
72
+ apiKey: string
73
+ /**
74
+ * Sender address in RFC 5321 format.
75
+ * Default: "Beech CMS <onboarding@resend.dev>" (Resend test sender).
76
+ * In production, set a verified address via the
77
+ * `EMAIL_FROM` environment variable.
78
+ */
79
+ from?: string
80
+ /**
81
+ * When `true`, provider errors are logged to the console.
82
+ * Set to `false` in production to avoid exposing internal details.
83
+ */
84
+ isDev?: boolean
85
+ }
86
+
87
+ /** Parameters for the password reset emailadds the reset URL. */
88
+ export interface PasswordResetEmailParams extends BaseEmailParams {
89
+ /**
90
+ * Complete URL that the user clicks to set the new password.
91
+ * Contains the token in plain text as a query param `?token=<uuid>`.
92
+ * Constructed by the caller as `${APP_URL}/reset-password?token=${token}`.
93
+ */
94
+ resetUrl: string
95
+ }
96
+
97
+ /** Parameters for the "password changed" notification. No additional fields. */
98
+ export type PasswordChangedEmailParams = BaseEmailParams
@@ -1,28 +1,28 @@
1
- /**
2
- * Public API — modulo email di Beech CMS
3
- *
4
- * Questo è l'UNICO file da importare da codice esterno a questa feature.
5
- * I dettagli implementativi interni (provider, template, shell) sono privati
6
- * alla slice e non devono mai essere importati direttamente dall'esterno.
7
- *
8
- * ─── FUNZIONI ESPORTATE ───────────────────────────────────────────────────────
9
- * sendPasswordResetEmail — invia l'email con il link di reset
10
- * sendPasswordChangedEmail — invia la notifica "password modificata"
11
- *
12
- * ─── TIPI E UTILITY ESPORTATI ────────────────────────────────────────────────
13
- * EmailLocale — 'en' | 'it' (aggiungere lingue in email.types.ts)
14
- * resolveEmailLocale — resolver sicuro per locale da input non verificato
15
- * PasswordResetEmailParams — shape dei parametri per sendPasswordResetEmail
16
- * PasswordChangedEmailParams — shape dei parametri per sendPasswordChangedEmail
17
- */
18
-
19
- export { sendPasswordResetEmail, sendPasswordChangedEmail } from './email.service'
20
- export {
21
- resolveEmailLocale,
22
- SUPPORTED_EMAIL_LOCALES,
23
- } from './email.types'
24
- export type {
25
- EmailLocale,
26
- PasswordResetEmailParams,
27
- PasswordChangedEmailParams,
28
- } from './email.types'
1
+ /**
2
+ * Public API — modulo email di Beech CMS
3
+ *
4
+ * Questo è l'UNICO file da importare da codice esterno a questa feature.
5
+ * I dettagli implementativi interni (provider, template, shell) sono privati
6
+ * alla slice e non devono mai essere importati direttamente dall'esterno.
7
+ *
8
+ * ─── FUNZIONI ESPORTATE ───────────────────────────────────────────────────────
9
+ * sendPasswordResetEmail — invia l'email con il link di reset
10
+ * sendPasswordChangedEmail — invia la notifica "password modificata"
11
+ *
12
+ * ─── TIPI E UTILITY ESPORTATI ────────────────────────────────────────────────
13
+ * EmailLocale — 'en' | 'it' (aggiungere lingue in email.types.ts)
14
+ * resolveEmailLocale — resolver sicuro per locale da input non verificato
15
+ * PasswordResetEmailParams — shape dei parametri per sendPasswordResetEmail
16
+ * PasswordChangedEmailParams — shape dei parametri per sendPasswordChangedEmail
17
+ */
18
+
19
+ export { sendPasswordResetEmail, sendPasswordChangedEmail } from './email.service'
20
+ export {
21
+ resolveEmailLocale,
22
+ SUPPORTED_EMAIL_LOCALES,
23
+ } from './email.types'
24
+ export type {
25
+ EmailLocale,
26
+ PasswordResetEmailParams,
27
+ PasswordChangedEmailParams,
28
+ } from './email.types'
@@ -1,63 +1,63 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
- import type { EmailProvider } from '../email.provider'
3
- import type { OutboundEmail } from '../email.types'
4
-
5
- /** Endpoint REST di Resend per l'invio email. */
6
- const RESEND_API_URL = 'https://api.resend.com/emails'
7
-
8
- /**
9
- * Implementazione Resend di EmailProvider.
10
- *
11
- * Questo è l'UNICO file del modulo email che conosce Resend.
12
- * Ogni altro file è completamente ignaro di quale provider sia attivo.
13
- *
14
- * ─── COME SOSTITUIRE QUESTO PROVIDER ─────────────────────────────────────────
15
- * 1. Crea un nuovo file in `providers/` (es. `providers/sendgrid.ts`).
16
- * 2. Esporta una classe che implementa `EmailProvider` (un solo metodo: `send`).
17
- * 3. In `email.service.ts` sostituisci `new ResendEmailProvider()` con la
18
- * tua nuova classe nella funzione `createProvider()`.
19
- * 4. Aggiorna le variabili d'ambiente in `types.ts` e `wrangler.jsonc`.
20
- * 5. Nessun altro file nel progetto va modificato.
21
- *
22
- * Documentazione API Resend: https://resend.com/docs/api-reference/emails/send-email
23
- * ─────────────────────────────────────────────────────────────────────────────
24
- */
25
- export class ResendEmailProvider implements EmailProvider {
26
- private readonly apiKey: string
27
-
28
- /** Quando `true`, gli errori vengono loggati in console (solo in sviluppo). */
29
- private readonly isDev: boolean
30
-
31
- constructor(apiKey: string, isDev = false) {
32
- this.apiKey = apiKey
33
- this.isDev = isDev
34
- }
35
-
36
- /**
37
- * Invia l'email tramite la REST API di Resend (`POST /emails`).
38
- *
39
- * Lancia un'eccezione se Resend risponde con uno status non-2xx, in modo
40
- * che il chiamante (`email.service.ts`) possa decidere se propagare l'errore
41
- * o gestirlo silenziosamente (fire-and-forget).
42
- *
43
- * Il corpo della response viene letto per il log solo in ambiente di sviluppo,
44
- * per evitare di consumare il body stream in produzione inutilmente.
45
- */
46
- async send(email: OutboundEmail): Promise<void> {
47
- const response = await fetch(RESEND_API_URL, {
48
- method: 'POST',
49
- headers: {
50
- Authorization: `Bearer ${this.apiKey}`,
51
- 'Content-Type': 'application/json',
52
- },
53
- body: JSON.stringify(email),
54
- })
55
-
56
- if (!response.ok) {
57
- const detail = this.isDev
58
- ? await response.text()
59
- : `HTTP ${response.status}`
60
- throw new Error(`[ResendEmailProvider] invio fallito — ${detail}`)
61
- }
62
- }
63
- }
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import type { EmailProvider } from '../email.provider'
3
+ import type { OutboundEmail } from '../email.types'
4
+
5
+ /** Resend REST endpoint for sending emails. */
6
+ const RESEND_API_URL = 'https://api.resend.com/emails'
7
+
8
+ /**
9
+ * Resend implementation of EmailProvider.
10
+ *
11
+ * This is the ONLY file in the email module that knows about Resend.
12
+ * Every other file is completely unaware of which provider is active.
13
+ *
14
+ * ─── HOW TO REPLACE THIS PROVIDER ──────────────────────────────────────────
15
+ * 1. Create a new file in `providers/` (e.g., `providers/sendgrid.ts`).
16
+ * 2. Export a class that implements `EmailProvider` (a single method: `send`).
17
+ * 3. In `email.service.ts`, replace `new ResendEmailProvider(...)` with
18
+ * your new class in the `createProvider()` function.
19
+ * 4. Update the environment variables in `types.ts` and `wrangler.jsonc`.
20
+ * 5. No other file in the project needs to be modified.
21
+ *
22
+ * Resend API Documentation: https://resend.com/docs/api-reference/emails/send-email
23
+ * ─────────────────────────────────────────────────────────────────────────────
24
+ */
25
+ export class ResendEmailProvider implements EmailProvider {
26
+ private readonly apiKey: string
27
+
28
+ /** When `true`, errors are logged to the console (development only). */
29
+ private readonly isDev: boolean
30
+
31
+ constructor(apiKey: string, isDev = false) {
32
+ this.apiKey = apiKey
33
+ this.isDev = isDev
34
+ }
35
+
36
+ /**
37
+ * Sends the email via the Resend REST API (`POST /emails`).
38
+ *
39
+ * Throws an exception if Resend responds with a non-2xx status, so that
40
+ * the caller (`email.service.ts`) can decide whether to propagate the error
41
+ * or handle it silently (fire-and-forget).
42
+ *
43
+ * The response body is read for logging only in the development environment,
44
+ * to avoid unnecessarily consuming the body stream in production.
45
+ */
46
+ async send(email: OutboundEmail): Promise<void> {
47
+ const response = await fetch(RESEND_API_URL, {
48
+ method: 'POST',
49
+ headers: {
50
+ Authorization: `Bearer ${this.apiKey}`,
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify(email),
54
+ })
55
+
56
+ if (!response.ok) {
57
+ const detail = this.isDev
58
+ ? await response.text()
59
+ : `HTTP ${response.status}`
60
+ throw new Error(`[ResendEmailProvider] send failed — ${detail}`)
61
+ }
62
+ }
63
+ }