Update site changes
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user