Files
scan-wave/src/routes/private/api/google/gmail/+server.ts
2025-07-12 14:10:33 +02:00

155 lines
3.8 KiB
TypeScript

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { sendGmail } from '$lib/google/gmail/server.js';
import QRCode from 'qrcode';
interface Participant {
id: string;
name: string;
surname: string;
email: string;
}
interface EmailResult {
participant: Participant;
success: boolean;
error?: string;
}
async function generateQRCode(participantId: string): Promise<string> {
const qrCodeBase64 = await QRCode.toDataURL(participantId, {
type: 'image/png',
margin: 2,
scale: 8
});
// Remove the data URL prefix to get just the base64 string
return qrCodeBase64.replace(/^data:image\/png;base64,/, '');
}
async function sendEmailToParticipant(
participant: Participant,
subject: string,
text: string,
eventId: string,
refreshToken: string,
supabase: any
): Promise<EmailResult> {
try {
const qrCodeBase64Data = await generateQRCode(participant.id);
// Send email with QR code
await sendGmail(refreshToken, {
to: participant.email,
subject: subject,
text: text,
qr_code: qrCodeBase64Data
});
// Call the participant_emailed RPC function
try {
await supabase.rpc('participant_emailed', {
p_participant_id: participant.id
});
} catch (dbError) {
console.error('Failed to call participant_emailed RPC:', dbError);
// Don't fail the entire operation if the RPC call fails
}
return {
participant: participant,
success: true
};
} catch (error) {
console.error('Failed to send email to participant:', participant.email, error);
return {
participant: participant,
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
function validateRequest(participants: unknown, subject: unknown, text: unknown, eventId: unknown, refreshToken: unknown) {
if (!participants || !Array.isArray(participants)) {
return { error: 'Invalid participants array', status: 400 };
}
if (!subject || !text) {
return { error: 'Subject and text are required', status: 400 };
}
if (!eventId) {
return { error: 'Event ID is required', status: 400 };
}
if (!refreshToken || typeof refreshToken !== 'string') {
return { error: 'Refresh token is required', status: 401 };
}
return null;
}
export const POST: RequestHandler = async ({ request, locals }) => {
try {
const { participants, subject, text, eventId, refreshToken } = await request.json();
const validationError = validateRequest(participants, subject, text, eventId, refreshToken);
if (validationError) {
return json({ error: validationError.error }, { status: validationError.status });
}
const results: EmailResult[] = [];
let successCount = 0;
let errorCount = 0;
// Send emails to each participant
for (const participant of participants as Participant[]) {
const result = await sendEmailToParticipant(
participant,
subject as string,
text as string,
eventId as string,
refreshToken as string,
locals.supabase
);
results.push(result);
if (result.success) {
successCount++;
} else {
errorCount++;
}
}
return json({
success: true,
results,
summary: {
total: participants.length,
success: successCount,
errors: errorCount
}
});
} catch (error) {
console.error('Email sending error:', error);
// Handle specific Gmail API errors
if (error instanceof Error) {
if (error.message.includes('Invalid Credentials') || error.message.includes('unauthorized')) {
return json({ error: 'Invalid or expired Google credentials' }, { status: 401 });
}
if (error.message.includes('quota')) {
return json({ error: 'Gmail API quota exceeded' }, { status: 429 });
}
if (error.message.includes('rate')) {
return json({ error: 'Rate limit exceeded' }, { status: 429 });
}
}
return json({ error: 'Failed to send emails' }, { status: 500 });
}
};