Refactor monolithic modules and harden booking, email, and auth infrastructure.

Split oversized frontend API client, email service, and admin/booking pages into focused modules while preserving import surfaces, and add Redis-backed queues, stale booking cleanup, stronger auth, and scale deployment configs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Michilis
2026-06-25 07:12:59 +00:00
co-authored by Cursor
parent f0e2de2834
commit 613bd7be1d
75 changed files with 7702 additions and 5580 deletions
+142
View File
@@ -0,0 +1,142 @@
import { fetchApi, API_BASE } from './client';
import type {
Ticket,
Payment,
BookingData,
TicketValidationResult,
TicketSearchResult,
LiveSearchResult,
} from './types';
export const ticketsApi = {
book: (data: BookingData) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets', {
method: 'POST',
body: JSON.stringify(data),
}),
getById: (id: string) => fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`),
getAll: (params?: { eventId?: string; status?: string }) => {
const query = new URLSearchParams();
if (params?.eventId) query.set('eventId', params.eventId);
if (params?.status) query.set('status', params.status);
return fetchApi<{ tickets: Ticket[] }>(`/api/tickets?${query}`);
},
// Validate ticket by QR code (for scanner)
validate: (code: string, eventId?: string) =>
fetchApi<TicketValidationResult>('/api/tickets/validate', {
method: 'POST',
body: JSON.stringify({ code, eventId }),
}),
// Search tickets by name/email (for scanner manual search)
search: (query: string, eventId?: string) =>
fetchApi<{ tickets: TicketSearchResult[] }>('/api/tickets/search', {
method: 'POST',
body: JSON.stringify({ query, eventId }),
}),
// Get event check-in stats (for scanner header counter)
getCheckinStats: (eventId: string) =>
fetchApi<{ eventId: string; capacity: number; checkedIn: number; totalActive: number }>(
`/api/tickets/stats/checkin?eventId=${eventId}`
),
// Live search tickets (GET - for scanner live search with debounce)
searchLive: (q: string, eventId?: string) => {
const params = new URLSearchParams();
params.set('q', q);
if (eventId) params.set('eventId', eventId);
return fetchApi<{ tickets: LiveSearchResult[] }>(`/api/tickets/search?${params}`);
},
checkin: (id: string) =>
fetchApi<{ ticket: Ticket & { attendeeName?: string }; event?: { id: string; title: string }; message: string }>(`/api/tickets/${id}/checkin`, {
method: 'POST',
}),
removeCheckin: (id: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/remove-checkin`, {
method: 'POST',
}),
cancel: (id: string) =>
fetchApi<{ message: string }>(`/api/tickets/${id}/cancel`, { method: 'POST' }),
updateStatus: (id: string, status: string) =>
fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`, {
method: 'PUT',
body: JSON.stringify({ status }),
}),
updateNote: (id: string, note: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/note`, {
method: 'POST',
body: JSON.stringify({ note }),
}),
markPaid: (id: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/mark-paid`, {
method: 'POST',
}),
// For manual payment methods (bank_transfer, tpago) - user marks payment as sent
markPaymentSent: (id: string, payerName?: string) =>
fetchApi<{ payment: Payment; message: string }>(`/api/tickets/${id}/mark-payment-sent`, {
method: 'POST',
body: JSON.stringify({ payerName }),
}),
adminCreate: (data: {
eventId: string;
firstName: string;
lastName?: string;
email?: string;
phone?: string;
preferredLanguage?: 'en' | 'es';
autoCheckin?: boolean;
adminNote?: string;
}) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/create', {
method: 'POST',
body: JSON.stringify(data),
}),
manualCreate: (data: {
eventId: string;
firstName: string;
lastName?: string;
email: string;
phone?: string;
preferredLanguage?: 'en' | 'es';
adminNote?: string;
}) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/manual', {
method: 'POST',
body: JSON.stringify(data),
}),
guestCreate: (data: {
eventId: string;
firstName: string;
lastName?: string;
email?: string;
phone?: string;
preferredLanguage?: 'en' | 'es';
adminNote?: string;
}) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/guest', {
method: 'POST',
body: JSON.stringify(data),
}),
checkPaymentStatus: (ticketId: string) =>
fetchApi<{ ticketStatus: string; paymentStatus: string; lnbitsStatus?: string; isPaid: boolean }>(
`/api/lnbits/status/${ticketId}`
),
// Get PDF download URL (returns the URL, not the PDF itself)
getPdfUrl: (id: string) => `${API_BASE}/api/tickets/${id}/pdf`,
};