Update site changes

This commit is contained in:
Michilis
2026-01-31 22:32:54 +00:00
parent d3c69f2936
commit 6df3baf0be
25 changed files with 764 additions and 137 deletions

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, events, tickets } from '../db/index.js';
import { db, events, tickets, payments, eventPaymentOverrides, emailLogs, invoices } from '../db/index.js';
import { eq, desc, and, gte, sql } from 'drizzle-orm';
import { requireAuth, getAuthUser } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
@@ -23,7 +23,7 @@ const validationHook = (result: any, c: any) => {
}
};
const createEventSchema = z.object({
const baseEventSchema = z.object({
title: z.string().min(1),
titleEs: z.string().optional().nullable(),
description: z.string().min(1),
@@ -38,9 +38,38 @@ const createEventSchema = z.object({
status: z.enum(['draft', 'published', 'cancelled', 'completed', 'archived']).default('draft'),
// Accept relative paths (/uploads/...) or full URLs
bannerUrl: z.string().optional().nullable().or(z.literal('')),
// External booking support
externalBookingEnabled: z.boolean().default(false),
externalBookingUrl: z.string().url().optional().nullable().or(z.literal('')),
});
const updateEventSchema = createEventSchema.partial();
const createEventSchema = baseEventSchema.refine(
(data) => {
// If external booking is enabled, URL must be provided and must start with https://
if (data.externalBookingEnabled) {
return !!(data.externalBookingUrl && data.externalBookingUrl.startsWith('https://'));
}
return true;
},
{
message: 'External booking URL is required and must be a valid HTTPS link when external booking is enabled',
path: ['externalBookingUrl'],
}
);
const updateEventSchema = baseEventSchema.partial().refine(
(data) => {
// If external booking is enabled, URL must be provided and must start with https://
if (data.externalBookingEnabled) {
return !!(data.externalBookingUrl && data.externalBookingUrl.startsWith('https://'));
}
return true;
},
{
message: 'External booking URL is required and must be a valid HTTPS link when external booking is enabled',
path: ['externalBookingUrl'],
}
);
// Get all events (public)
eventsRouter.get('/', async (c) => {
@@ -211,6 +240,44 @@ eventsRouter.delete('/:id', requireAuth(['admin']), async (c) => {
return c.json({ error: 'Event not found' }, 404);
}
// Get all tickets for this event
const eventTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).eventId, id))
.all();
// Delete invoices and payments for all tickets of this event
for (const ticket of eventTickets) {
// Get payments for this ticket
const ticketPayments = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
.all();
// Delete invoices for each payment
for (const payment of ticketPayments) {
await (db as any).delete(invoices).where(eq((invoices as any).paymentId, payment.id));
}
// Delete payments for this ticket
await (db as any).delete(payments).where(eq((payments as any).ticketId, ticket.id));
}
// Delete all tickets for this event
await (db as any).delete(tickets).where(eq((tickets as any).eventId, id));
// Delete event payment overrides
await (db as any).delete(eventPaymentOverrides).where(eq((eventPaymentOverrides as any).eventId, id));
// Set eventId to null on email logs (they reference this event but can exist without it)
await (db as any)
.update(emailLogs)
.set({ eventId: null })
.where(eq((emailLogs as any).eventId, id));
// Finally delete the event
await (db as any).delete(events).where(eq((events as any).id, id));
return c.json({ message: 'Event deleted successfully' });
@@ -257,6 +324,8 @@ eventsRouter.post('/:id/duplicate', requireAuth(['admin', 'organizer']), async (
capacity: existing.capacity,
status: 'draft',
bannerUrl: existing.bannerUrl,
externalBookingEnabled: existing.externalBookingEnabled || false,
externalBookingUrl: existing.externalBookingUrl,
createdAt: now,
updatedAt: now,
};

View File

@@ -311,7 +311,21 @@ paymentsRouter.post('/:id/reject', requireAuth(['admin', 'organizer']), zValidat
})
.where(eq((payments as any).id, id));
// Note: We don't cancel the ticket automatically - admin can do that separately if needed
// Cancel the ticket - booking is no longer valid after rejection
await (db as any)
.update(tickets)
.set({
status: 'cancelled',
updatedAt: now,
})
.where(eq((tickets as any).id, payment.ticketId));
// Send rejection email asynchronously (for manual payment methods only)
if (['bank_transfer', 'tpago'].includes(payment.provider)) {
emailService.sendPaymentRejectionEmail(id).catch(err => {
console.error('[Email] Failed to send payment rejection email:', err);
});
}
const updated = await (db as any)
.select()
@@ -319,7 +333,7 @@ paymentsRouter.post('/:id/reject', requireAuth(['admin', 'organizer']), zValidat
.where(eq((payments as any).id, id))
.get();
return c.json({ payment: updated, message: 'Payment rejected' });
return c.json({ payment: updated, message: 'Payment rejected and booking cancelled' });
});
// Update admin note