129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
// Route handler server-side para envío de correos vía banco único mail.*.
|
|
// El navegador NUNCA lleva la X-Api-Key: llama aquí (/api/correos) y este
|
|
// route llama a SAM.API (POST /api/email/send) con X-Module + X-Api-Key.
|
|
// Docs: sam/docs/email-modulos.md punto 7.
|
|
//
|
|
// Env (servidor, SIN prefijo NEXT_PUBLIC_):
|
|
// MAIL_API_KEY=clave entregada por POST /api/email/modules (solo servidor)
|
|
// MAIL_MODULE=helpdesk (opcional, default helpdesk)
|
|
// SAM_API_URL=https://api.devops.norteamericano.cl (default prod;
|
|
// en dev cae a NEXT_PUBLIC_API_URL)
|
|
|
|
const API_BASE =
|
|
process.env.SAM_API_URL ??
|
|
process.env.NEXT_PUBLIC_API_URL ??
|
|
"https://api.devops.norteamericano.cl";
|
|
const MODULE = process.env.MAIL_MODULE ?? "helpdesk";
|
|
|
|
type SendBody = {
|
|
to: string | string[];
|
|
templateKey?: string;
|
|
subject?: string;
|
|
htmlBody?: string;
|
|
vars?: Record<string, string | null>;
|
|
};
|
|
|
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
|
async function requireAdmin(req: Request): Promise<Response | null> {
|
|
const auth = req.headers.get("authorization") ?? "";
|
|
if (!auth.toLowerCase().startsWith("bearer ")) {
|
|
return Response.json(
|
|
{ success: false, message: "Requiere sesión de admin" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
try {
|
|
const me = await fetch(`${API_BASE}/api/helpdesk/me`, {
|
|
headers: { Authorization: auth },
|
|
});
|
|
if (!me.ok) {
|
|
return Response.json(
|
|
{ success: false, message: "Sesión inválida" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
const user = (await me.json()) as { role?: string };
|
|
if (user.role !== "admin") {
|
|
return Response.json(
|
|
{ success: false, message: "Requiere rol admin" },
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
return null;
|
|
} catch (err) {
|
|
return Response.json(
|
|
{ success: false, message: `No se pudo verificar admin: ${String(err)}` },
|
|
{ status: 502 }
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function POST(req: Request): Promise<Response> {
|
|
const denied = await requireAdmin(req);
|
|
if (denied) return denied;
|
|
|
|
const apiKey = process.env.MAIL_API_KEY ?? "";
|
|
if (!apiKey) {
|
|
return Response.json(
|
|
{ success: false, message: "MAIL_API_KEY no configurada en el servidor del módulo" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
let body: SendBody;
|
|
try {
|
|
body = (await req.json()) as SendBody;
|
|
} catch {
|
|
return Response.json({ success: false, message: "JSON inválido" }, { status: 400 });
|
|
}
|
|
|
|
const raw = Array.isArray(body.to) ? body.to : [body.to];
|
|
const clean = [
|
|
...new Set(
|
|
raw.map((e) => String(e ?? "").trim()).filter((e) => EMAIL_RE.test(e))
|
|
),
|
|
].slice(0, 50);
|
|
if (clean.length === 0) {
|
|
return Response.json({ success: false, message: "Sin destinatarios válidos" }, { status: 400 });
|
|
}
|
|
if (!body.templateKey && (!body.subject || !body.htmlBody)) {
|
|
return Response.json(
|
|
{ success: false, message: "templateKey o subject+htmlBody requeridos" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
let upstream: Response;
|
|
try {
|
|
upstream = await fetch(`${API_BASE}/api/email/send`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Module": MODULE,
|
|
"X-Api-Key": apiKey,
|
|
},
|
|
body: JSON.stringify({
|
|
to: clean.length === 1 ? clean[0] : clean,
|
|
templateKey: body.templateKey,
|
|
subject: body.subject,
|
|
htmlBody: body.htmlBody,
|
|
vars: body.vars ?? {},
|
|
}),
|
|
});
|
|
} catch (err) {
|
|
return Response.json(
|
|
{ success: false, message: `No se pudo contactar a SAM.API: ${String(err)}` },
|
|
{ status: 502 }
|
|
);
|
|
}
|
|
|
|
let data: unknown = null;
|
|
try {
|
|
data = await upstream.json();
|
|
} catch {
|
|
data = { success: false, message: `SAM.API respondió ${upstream.status} sin JSON` };
|
|
}
|
|
return Response.json(data, { status: upstream.ok ? 200 : upstream.status });
|
|
}
|