192 lines
7.4 KiB
JavaScript
192 lines
7.4 KiB
JavaScript
import { readFile } from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, resolve } from "node:path";
|
|
const RESEND_API_KEY = process.env.RESEND_API_KEY;
|
|
const DEFAULT_FROM = process.env.EMAIL_FROM || "Stargirlnails <no-reply@stargirlnails.de>";
|
|
// Helper function to format dates for ICS files (YYYYMMDDTHHMMSS)
|
|
function formatDateForICS(date, time) {
|
|
// date is in YYYY-MM-DD format, time is in HH:MM format
|
|
const [year, month, day] = date.split('-');
|
|
const [hours, minutes] = time.split(':');
|
|
return `${year}${month}${day}T${hours}${minutes}00`;
|
|
}
|
|
// Helper function to create ICS (iCalendar) file content
|
|
function createICSFile(params) {
|
|
const { date, time, durationMinutes, customerName, treatmentName } = params;
|
|
// Calculate start and end times in Europe/Berlin timezone
|
|
const dtStart = formatDateForICS(date, time);
|
|
// Calculate end time
|
|
const [hours, minutes] = time.split(':').map(Number);
|
|
const startDate = new Date(`${date}T${time}:00`);
|
|
const endDate = new Date(startDate.getTime() + durationMinutes * 60000);
|
|
const endHours = String(endDate.getHours()).padStart(2, '0');
|
|
const endMinutes = String(endDate.getMinutes()).padStart(2, '0');
|
|
const dtEnd = formatDateForICS(date, `${endHours}:${endMinutes}`);
|
|
// Create unique ID for this event
|
|
const uid = `booking-${Date.now()}-${Math.random().toString(36).substr(2, 9)}@stargirlnails.de`;
|
|
// Current timestamp for DTSTAMP
|
|
const now = new Date();
|
|
const dtstamp = now.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
|
// ICS content
|
|
const icsContent = [
|
|
'BEGIN:VCALENDAR',
|
|
'VERSION:2.0',
|
|
'PRODID:-//Stargirlnails Kiel//Booking System//DE',
|
|
'CALSCALE:GREGORIAN',
|
|
'METHOD:REQUEST',
|
|
'BEGIN:VEVENT',
|
|
`UID:${uid}`,
|
|
`DTSTAMP:${dtstamp}`,
|
|
`DTSTART;TZID=Europe/Berlin:${dtStart}`,
|
|
`DTEND;TZID=Europe/Berlin:${dtEnd}`,
|
|
`SUMMARY:${treatmentName} - Stargirlnails Kiel`,
|
|
`DESCRIPTION:Termin für ${treatmentName} bei Stargirlnails Kiel`,
|
|
'LOCATION:Stargirlnails Kiel',
|
|
`ORGANIZER;CN=Stargirlnails Kiel:mailto:${process.env.EMAIL_FROM?.match(/<(.+)>/)?.[1] || 'no-reply@stargirlnails.de'}`,
|
|
`ATTENDEE;CN=${customerName};RSVP=TRUE:mailto:${customerName}`,
|
|
'STATUS:CONFIRMED',
|
|
'SEQUENCE:0',
|
|
'BEGIN:VALARM',
|
|
'TRIGGER:-PT24H',
|
|
'ACTION:DISPLAY',
|
|
'DESCRIPTION:Erinnerung: Termin morgen bei Stargirlnails Kiel',
|
|
'END:VALARM',
|
|
'END:VEVENT',
|
|
'BEGIN:VTIMEZONE',
|
|
'TZID:Europe/Berlin',
|
|
'BEGIN:DAYLIGHT',
|
|
'TZOFFSETFROM:+0100',
|
|
'TZOFFSETTO:+0200',
|
|
'TZNAME:CEST',
|
|
'DTSTART:19700329T020000',
|
|
'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU',
|
|
'END:DAYLIGHT',
|
|
'BEGIN:STANDARD',
|
|
'TZOFFSETFROM:+0200',
|
|
'TZOFFSETTO:+0100',
|
|
'TZNAME:CET',
|
|
'DTSTART:19701025T030000',
|
|
'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU',
|
|
'END:STANDARD',
|
|
'END:VTIMEZONE',
|
|
'END:VCALENDAR'
|
|
].join('\r\n');
|
|
return icsContent;
|
|
}
|
|
// Cache for AGB PDF to avoid reading it multiple times
|
|
let cachedAGBPDF = null;
|
|
async function getAGBPDFBase64() {
|
|
if (cachedAGBPDF)
|
|
return cachedAGBPDF;
|
|
try {
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const agbPath = resolve(__dirname, "../../../AGB.pdf");
|
|
const buf = await readFile(agbPath);
|
|
cachedAGBPDF = buf.toString('base64');
|
|
return cachedAGBPDF;
|
|
}
|
|
catch (error) {
|
|
console.warn("Could not read AGB.pdf:", error);
|
|
return null;
|
|
}
|
|
}
|
|
export async function sendEmail(params) {
|
|
if (!RESEND_API_KEY) {
|
|
// In development or if not configured, skip sending but don't fail the flow
|
|
console.warn("Resend API key not configured. Skipping email send.");
|
|
return { success: false };
|
|
}
|
|
const payload = {
|
|
from: params.from || DEFAULT_FROM,
|
|
to: Array.isArray(params.to) ? params.to : [params.to],
|
|
subject: params.subject,
|
|
text: params.text,
|
|
html: params.html,
|
|
cc: params.cc ? (Array.isArray(params.cc) ? params.cc : [params.cc]) : undefined,
|
|
bcc: params.bcc ? (Array.isArray(params.bcc) ? params.bcc : [params.bcc]) : undefined,
|
|
reply_to: params.replyTo ? (Array.isArray(params.replyTo) ? params.replyTo : [params.replyTo]) : undefined,
|
|
attachments: params.attachments,
|
|
};
|
|
console.log(`Sending email via Resend: to=${JSON.stringify(payload.to)}, subject="${params.subject}"`);
|
|
const response = await fetch("https://api.resend.com/emails", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${RESEND_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!response.ok) {
|
|
const body = await response.text().catch(() => "");
|
|
console.error("Resend send error:", response.status, body);
|
|
return { success: false };
|
|
}
|
|
const responseData = await response.json().catch(() => ({}));
|
|
console.log("Resend email sent successfully:", responseData);
|
|
return { success: true };
|
|
}
|
|
export async function sendEmailWithAGB(params) {
|
|
const agbBase64 = await getAGBPDFBase64();
|
|
if (agbBase64) {
|
|
params.attachments = [
|
|
...(params.attachments || []),
|
|
{
|
|
filename: "AGB_Stargirlnails_Kiel.pdf",
|
|
content: agbBase64,
|
|
type: "application/pdf"
|
|
}
|
|
];
|
|
}
|
|
return sendEmail(params);
|
|
}
|
|
export async function sendEmailWithAGBAndCalendar(params, calendarParams) {
|
|
const agbBase64 = await getAGBPDFBase64();
|
|
// Create ICS file content
|
|
const icsContent = createICSFile(calendarParams);
|
|
const icsBase64 = Buffer.from(icsContent, 'utf-8').toString('base64');
|
|
// Attach both AGB and ICS file
|
|
params.attachments = [...(params.attachments || [])];
|
|
if (agbBase64) {
|
|
params.attachments.push({
|
|
filename: "AGB_Stargirlnails_Kiel.pdf",
|
|
content: agbBase64,
|
|
type: "application/pdf"
|
|
});
|
|
}
|
|
params.attachments.push({
|
|
filename: "Termin_Stargirlnails.ics",
|
|
content: icsBase64,
|
|
type: "text/calendar"
|
|
});
|
|
return sendEmail(params);
|
|
}
|
|
export async function sendEmailWithInspirationPhoto(params, photoData, customerName) {
|
|
if (!photoData) {
|
|
return sendEmail(params);
|
|
}
|
|
// Extract file extension from base64 data URL
|
|
const match = photoData.match(/data:image\/([^;]+);base64,(.+)/);
|
|
if (!match) {
|
|
console.warn("Invalid photo data format");
|
|
return sendEmail(params);
|
|
}
|
|
const [, extension, base64Content] = match;
|
|
const filename = `inspiration_${customerName.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.${extension}`;
|
|
// Check if attachment is too large (max 1MB base64 content)
|
|
if (base64Content.length > 1024 * 1024) {
|
|
console.warn(`Photo attachment too large: ${base64Content.length} chars, skipping attachment`);
|
|
return sendEmail(params);
|
|
}
|
|
// console.log(`Sending email with photo attachment: ${filename}, size: ${base64Content.length} chars`);
|
|
params.attachments = [
|
|
...(params.attachments || []),
|
|
{
|
|
filename,
|
|
content: base64Content,
|
|
type: `image/${extension}`
|
|
}
|
|
];
|
|
return sendEmail(params);
|
|
}
|