Files
NexStudio/src/lib/newsletter.ts
Javaxman c4adc116d6 Corregge l'accesso all'env Worker per Astro 6.
Sostituisce locals.runtime.env con import da cloudflare:workers, ripristinando contatti e newsletter.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 14:06:16 +02:00

110 lines
3.1 KiB
TypeScript

/**
* Shared helpers for newsletter API routes (Cloudflare Worker / Astro).
*/
import { env as workerEnv } from 'cloudflare:workers';
export type RuntimeEnv = Record<string, unknown>;
export type SubscriberStatus = 'pending' | 'active' | 'unsubscribed';
export type NewsletterSubscriber = {
id: number;
email: string;
status: SubscriberStatus;
locale: string;
confirm_token: string | null;
unsubscribe_token: string;
source: string;
created_at: string;
confirmed_at: string | null;
unsubscribed_at: string | null;
};
/** Astro v6+: use `cloudflare:workers` env (locals.runtime.env was removed). */
export function getRuntimeEnv(_locals?: unknown): RuntimeEnv {
return workerEnv as unknown as RuntimeEnv;
}
export function readEnv(name: string, runtimeEnv: RuntimeEnv): string {
const runtimeValue = runtimeEnv[name];
if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
return runtimeValue.trim();
}
const staticValue = import.meta.env[name];
return typeof staticValue === 'string' ? staticValue.trim() : '';
}
export function getNewsletterDb(runtimeEnv: RuntimeEnv): D1Database | null {
const db = runtimeEnv.NEWSLETTER_DB;
if (db && typeof db === 'object' && 'prepare' in db) {
return db as D1Database;
}
return null;
}
export function sanitizeRedirect(candidate: string, requestUrl: URL): string {
if (!candidate) return '';
try {
const u = new URL(candidate, requestUrl);
if (u.origin !== requestUrl.origin) return '';
return u.pathname + u.search + u.hash;
} catch {
return '';
}
}
export function required(value: FormDataEntryValue | null): string {
return typeof value === 'string' ? value.trim() : '';
}
export function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 254;
}
export function normalizeEmail(email: string): string {
return email.trim().toLowerCase();
}
export function createToken(): string {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
}
export async function verifyTurnstile(
token: string,
secret: string,
ip?: string | null,
): Promise<boolean> {
const body = new URLSearchParams();
body.set('secret', secret);
body.set('response', token);
if (ip) body.set('remoteip', ip);
const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body,
});
if (!response.ok) return false;
const payload = (await response.json()) as { success?: boolean };
return payload.success === true;
}
export { getEmailBinding, sendCloudflareEmail } from './email';
export function siteOrigin(requestUrl: URL, runtimeEnv: RuntimeEnv): string {
const configured = readEnv('PUBLIC_SITE_URL', runtimeEnv) || readEnv('SITE', runtimeEnv);
if (configured) {
try {
return new URL(configured).origin;
} catch {
/* fall through */
}
}
return requestUrl.origin;
}
export function absoluteUrl(origin: string, path: string): string {
return new URL(path, origin).toString();
}