@beechcms/api 0.4.0-preview.8 → 0.4.0
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.
- package/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/index-CewtCjom.css +1 -0
- package/assets/dashboard/assets/index-FQ6JhvRH.js +554 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +213 -213
- package/package.json +2 -2
- package/src/auth/constants.ts +10 -10
- package/src/auth/login.ts +91 -91
- package/src/auth/refresh.ts +127 -127
- package/src/factory.ts +347 -303
- package/src/features/content/constants.ts +10 -0
- package/src/features/content/handlers/create.ts +153 -0
- package/src/features/content/handlers/delete.ts +76 -0
- package/src/features/content/handlers/facets.ts +45 -0
- package/src/features/content/handlers/get.ts +116 -0
- package/src/features/content/handlers/list.ts +88 -0
- package/src/features/content/handlers/update.ts +207 -0
- package/src/features/content/index.ts +20 -0
- package/src/features/draft/draft.handler.ts +272 -198
- package/src/features/draft/index.ts +1 -1
- package/src/features/email/email.provider.ts +38 -38
- package/src/features/email/email.service.ts +80 -80
- package/src/features/email/email.types.ts +98 -98
- package/src/features/email/index.ts +28 -28
- package/src/features/email/providers/resend.ts +63 -63
- package/src/features/email/templates/password-changed.ts +59 -59
- package/src/features/email/templates/password-reset.ts +64 -64
- package/src/features/email/templates/shell.ts +92 -93
- package/src/features/notifications/index.ts +1 -1
- package/src/features/notifications/notifications.handler.ts +130 -88
- package/src/features/password-reset/index.ts +15 -15
- package/src/features/password-reset/request.ts +106 -88
- package/src/features/password-reset/reset.ts +128 -110
- package/src/features/rotate-field/index.ts +1 -1
- package/src/features/rotate-field/rotate-field.handler.ts +132 -82
- package/src/features/rotate-field/rotate-field.schema.ts +13 -9
- package/src/features/schema/schema.handler.ts +16 -16
- package/src/features/settings/settings.handler.ts +414 -249
- package/src/features/setup/index.ts +96 -59
- package/src/features/stats/index.ts +1 -1
- package/src/features/stats/stats.handler.ts +458 -395
- package/src/index.ts +24 -11
- package/src/media-utils.ts +78 -78
- package/src/middleware/repository.middleware.ts +24 -0
- package/src/middleware/storage.middleware.ts +21 -0
- package/src/middleware.ts +67 -67
- package/src/public/access-policy.ts +23 -23
- package/src/public/api-key-middleware.ts +53 -53
- package/src/public/index.ts +12 -12
- package/src/public/problem-details.ts +48 -42
- package/src/public/public-add.ts +166 -183
- package/src/public/public-edit.ts +158 -183
- package/src/public/public-errors.ts +15 -15
- package/src/public/public-read.ts +216 -217
- package/src/public/public-routes.ts +84 -31
- package/src/public/query-builder.ts +152 -241
- package/src/public/rate-limit-middleware.ts +42 -42
- package/src/public/response-builder.ts +26 -26
- package/src/public/sanitize.ts +65 -65
- package/src/public/slug-utils.ts +14 -14
- package/src/search-utils.ts +192 -192
- package/src/search.ts +72 -72
- package/src/shared/activity-logger.ts +79 -79
- package/src/shared/apply-policies.ts +63 -63
- package/src/shared/base.repository.d1.ts +28 -0
- package/src/shared/content-utils.ts +82 -108
- package/src/shared/content.repository.d1.ts +382 -0
- package/src/shared/fts-sync.ts +4 -4
- package/src/shared/idempotency.repository.d1.ts +56 -0
- package/src/shared/media.repository.d1.ts +64 -0
- package/src/shared/notification-service.ts +56 -56
- package/src/shared/query-utils.ts +137 -137
- package/src/shared/storage/factory.ts +40 -0
- package/src/shared/storage/r2-binding-bucket.ts +81 -0
- package/src/shared/storage/s3-bucket.ts +163 -0
- package/src/shared/storage-utils.ts +36 -36
- package/src/shared/system-stats.repository.d1.ts +44 -0
- package/src/types.ts +46 -36
- package/src/upload.ts +179 -335
- package/src/widget.ts +349 -349
- package/assets/dashboard/assets/index-CC-jbp6g.js +0 -554
- package/assets/dashboard/assets/index-CQODXprH.css +0 -1
- package/src/content.ts +0 -502
- package/src/features/draft/draft.test.ts +0 -315
- package/src/features/rotate-field/rotate-field.test.ts +0 -297
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email Service —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* ───
|
|
11
|
-
*
|
|
12
|
-
* `createProvider()`
|
|
13
|
-
*
|
|
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
|
-
/**
|
|
26
|
-
const DEFAULT_FROM = 'Beech CMS <onboarding@resend.dev>'
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `new ResendEmailProvider(
|
|
33
|
-
*/
|
|
34
|
-
function createProvider(apiKey: string, isDev: boolean): EmailProvider {
|
|
35
|
-
return new ResendEmailProvider(apiKey, isDev)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `templates/password-reset.ts`
|
|
43
|
-
* `templates/shell.ts`.
|
|
44
|
-
*
|
|
45
|
-
* @throws
|
|
46
|
-
*
|
|
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
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* @throws
|
|
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 project — needs 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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// ── Locale ────────────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 1.
|
|
15
|
-
* 2.
|
|
16
|
-
* file in `templates/`. TypeScript
|
|
17
|
-
*/
|
|
18
|
-
export const SUPPORTED_EMAIL_LOCALES = ['en', 'it'] as const
|
|
19
|
-
export type EmailLocale = (typeof SUPPORTED_EMAIL_LOCALES)[number]
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
* a
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @param raw -
|
|
27
|
-
* @returns
|
|
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
|
-
// ──
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
export interface OutboundEmail {
|
|
47
|
-
/**
|
|
48
|
-
from: string
|
|
49
|
-
/**
|
|
50
|
-
to: string[]
|
|
51
|
-
subject: string
|
|
52
|
-
/**
|
|
53
|
-
html: string
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ──
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*/
|
|
63
|
-
export interface BaseEmailParams {
|
|
64
|
-
/**
|
|
65
|
-
to: string
|
|
66
|
-
/**
|
|
67
|
-
locale: EmailLocale
|
|
68
|
-
/**
|
|
69
|
-
* API key
|
|
70
|
-
*
|
|
71
|
-
*/
|
|
72
|
-
apiKey: string
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
* Default: "Beech CMS <onboarding@resend.dev>" (
|
|
76
|
-
* In
|
|
77
|
-
*
|
|
78
|
-
*/
|
|
79
|
-
from?: string
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*/
|
|
84
|
-
isDev?: boolean
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
export interface PasswordResetEmailParams extends BaseEmailParams {
|
|
89
|
-
/**
|
|
90
|
-
* URL
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*/
|
|
94
|
-
resetUrl: string
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
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 email — adds 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
|
-
/**
|
|
6
|
-
const RESEND_API_URL = 'https://api.resend.com/emails'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* ───
|
|
15
|
-
* 1.
|
|
16
|
-
* 2.
|
|
17
|
-
* 3. In `email.service.ts
|
|
18
|
-
*
|
|
19
|
-
* 4.
|
|
20
|
-
* 5.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
-
*/
|
|
25
|
-
export class ResendEmailProvider implements EmailProvider {
|
|
26
|
-
private readonly apiKey: string
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
private readonly isDev: boolean
|
|
30
|
-
|
|
31
|
-
constructor(apiKey: string, isDev = false) {
|
|
32
|
-
this.apiKey = apiKey
|
|
33
|
-
this.isDev = isDev
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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]
|
|
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
|
+
}
|