Add PostgreSQL support with SQLite/Postgres database compatibility layer
- Add dbGet/dbAll helper functions for database-agnostic queries - Add toDbBool/convertBooleansForDb for boolean type conversion - Add toDbDate/getNow for timestamp type handling - Add generateId that returns UUID for Postgres, nanoid for SQLite - Update all routes to use compatibility helpers - Add normalizeEvent to return clean number types from Postgres decimal - Add formatPrice utility for consistent price display - Add legal pages admin interface with RichTextEditor - Update carousel images - Add drizzle migration files for PostgreSQL
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, tickets, events, users, payments, paymentOptions } from '../db/index.js';
|
||||
import { db, dbGet, dbAll, tickets, events, users, payments, paymentOptions } from '../db/index.js';
|
||||
import { eq, and, sql } from 'drizzle-orm';
|
||||
import { requireAuth, getAuthUser } from '../lib/auth.js';
|
||||
import { generateId, generateTicketCode, getNow } from '../lib/utils.js';
|
||||
@@ -47,7 +47,9 @@ ticketsRouter.post('/', zValidator('json', createTicketSchema), async (c) => {
|
||||
const data = c.req.valid('json');
|
||||
|
||||
// Get event
|
||||
const event = await (db as any).select().from(events).where(eq((events as any).id, data.eventId)).get();
|
||||
const event = await dbGet<any>(
|
||||
(db as any).select().from(events).where(eq((events as any).id, data.eventId))
|
||||
);
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
}
|
||||
@@ -57,23 +59,26 @@ ticketsRouter.post('/', zValidator('json', createTicketSchema), async (c) => {
|
||||
}
|
||||
|
||||
// Check capacity
|
||||
const ticketCount = await (db as any)
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).eventId, data.eventId),
|
||||
eq((tickets as any).status, 'confirmed')
|
||||
const ticketCount = await dbGet<any>(
|
||||
(db as any)
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).eventId, data.eventId),
|
||||
eq((tickets as any).status, 'confirmed')
|
||||
)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
);
|
||||
|
||||
if ((ticketCount?.count || 0) >= event.capacity) {
|
||||
return c.json({ error: 'Event is sold out' }, 400);
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
let user = await (db as any).select().from(users).where(eq((users as any).email, data.email)).get();
|
||||
let user = await dbGet<any>(
|
||||
(db as any).select().from(users).where(eq((users as any).email, data.email))
|
||||
);
|
||||
|
||||
const now = getNow();
|
||||
|
||||
@@ -98,24 +103,26 @@ ticketsRouter.post('/', zValidator('json', createTicketSchema), async (c) => {
|
||||
}
|
||||
|
||||
// Check for duplicate booking (unless allowDuplicateBookings is enabled)
|
||||
const globalOptions = await (db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
.get();
|
||||
const globalOptions = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
);
|
||||
|
||||
const allowDuplicateBookings = globalOptions?.allowDuplicateBookings ?? false;
|
||||
|
||||
if (!allowDuplicateBookings) {
|
||||
const existingTicket = await (db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).userId, user.id),
|
||||
eq((tickets as any).eventId, data.eventId)
|
||||
const existingTicket = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).userId, user.id),
|
||||
eq((tickets as any).eventId, data.eventId)
|
||||
)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
);
|
||||
|
||||
if (existingTicket && existingTicket.status !== 'cancelled') {
|
||||
return c.json({ error: 'You have already booked this event' }, 400);
|
||||
@@ -251,9 +258,11 @@ ticketsRouter.post('/', zValidator('json', createTicketSchema), async (c) => {
|
||||
// Download ticket as PDF
|
||||
ticketsRouter.get('/:id/pdf', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await getAuthUser(c);
|
||||
const user: any = await getAuthUser(c);
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -278,7 +287,9 @@ ticketsRouter.get('/:id/pdf', async (c) => {
|
||||
}
|
||||
|
||||
// Get event
|
||||
const event = await (db as any).select().from(events).where(eq((events as any).id, ticket.eventId)).get();
|
||||
const event = await dbGet<any>(
|
||||
(db as any).select().from(events).where(eq((events as any).id, ticket.eventId))
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
@@ -316,17 +327,23 @@ ticketsRouter.get('/:id/pdf', async (c) => {
|
||||
ticketsRouter.get('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
// Get associated event
|
||||
const event = await (db as any).select().from(events).where(eq((events as any).id, ticket.eventId)).get();
|
||||
const event = await dbGet(
|
||||
(db as any).select().from(events).where(eq((events as any).id, ticket.eventId))
|
||||
);
|
||||
|
||||
// Get payment
|
||||
const payment = await (db as any).select().from(payments).where(eq((payments as any).ticketId, id)).get();
|
||||
const payment = await dbGet(
|
||||
(db as any).select().from(payments).where(eq((payments as any).ticketId, id))
|
||||
);
|
||||
|
||||
return c.json({
|
||||
ticket: {
|
||||
@@ -342,7 +359,9 @@ ticketsRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff']), zValidat
|
||||
const id = c.req.param('id');
|
||||
const data = c.req.valid('json');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -361,7 +380,9 @@ ticketsRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff']), zValidat
|
||||
await (db as any).update(tickets).set(updates).where(eq((tickets as any).id, id));
|
||||
}
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const updated = await dbGet(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
return c.json({ ticket: updated });
|
||||
});
|
||||
@@ -376,19 +397,21 @@ ticketsRouter.post('/validate', requireAuth(['admin', 'organizer', 'staff']), as
|
||||
}
|
||||
|
||||
// Try to find ticket by QR code or ID
|
||||
let ticket = await (db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(eq((tickets as any).qrCode, code))
|
||||
.get();
|
||||
let ticket = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(eq((tickets as any).qrCode, code))
|
||||
);
|
||||
|
||||
// If not found by QR, try by ID
|
||||
if (!ticket) {
|
||||
ticket = await (db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(eq((tickets as any).id, code))
|
||||
.get();
|
||||
ticket = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(eq((tickets as any).id, code))
|
||||
);
|
||||
}
|
||||
|
||||
if (!ticket) {
|
||||
@@ -409,11 +432,12 @@ ticketsRouter.post('/validate', requireAuth(['admin', 'organizer', 'staff']), as
|
||||
}
|
||||
|
||||
// Get event details
|
||||
const event = await (db as any)
|
||||
.select()
|
||||
.from(events)
|
||||
.where(eq((events as any).id, ticket.eventId))
|
||||
.get();
|
||||
const event = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(events)
|
||||
.where(eq((events as any).id, ticket.eventId))
|
||||
);
|
||||
|
||||
// Determine validity status
|
||||
let validityStatus = 'invalid';
|
||||
@@ -433,11 +457,12 @@ ticketsRouter.post('/validate', requireAuth(['admin', 'organizer', 'staff']), as
|
||||
// Get admin who checked in (if applicable)
|
||||
let checkedInBy = null;
|
||||
if (ticket.checkedInByAdminId) {
|
||||
const admin = await (db as any)
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq((users as any).id, ticket.checkedInByAdminId))
|
||||
.get();
|
||||
const admin = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq((users as any).id, ticket.checkedInByAdminId))
|
||||
);
|
||||
checkedInBy = admin ? admin.name : null;
|
||||
}
|
||||
|
||||
@@ -469,7 +494,9 @@ ticketsRouter.post('/:id/checkin', requireAuth(['admin', 'organizer', 'staff']),
|
||||
const id = c.req.param('id');
|
||||
const adminUser = (c as any).get('user');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -494,10 +521,14 @@ ticketsRouter.post('/:id/checkin', requireAuth(['admin', 'organizer', 'staff']),
|
||||
})
|
||||
.where(eq((tickets as any).id, id));
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const updated = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
// Get event for response
|
||||
const event = await (db as any).select().from(events).where(eq((events as any).id, ticket.eventId)).get();
|
||||
const event = await dbGet<any>(
|
||||
(db as any).select().from(events).where(eq((events as any).id, ticket.eventId))
|
||||
);
|
||||
|
||||
return c.json({
|
||||
ticket: {
|
||||
@@ -517,7 +548,9 @@ ticketsRouter.post('/:id/mark-paid', requireAuth(['admin', 'organizer', 'staff']
|
||||
const id = c.req.param('id');
|
||||
const user = (c as any).get('user');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -551,11 +584,12 @@ ticketsRouter.post('/:id/mark-paid', requireAuth(['admin', 'organizer', 'staff']
|
||||
.where(eq((payments as any).ticketId, id));
|
||||
|
||||
// Get payment for sending receipt
|
||||
const payment = await (db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).ticketId, id))
|
||||
.get();
|
||||
const payment = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).ticketId, id))
|
||||
);
|
||||
|
||||
// Send confirmation emails asynchronously (don't block the response)
|
||||
Promise.all([
|
||||
@@ -565,7 +599,9 @@ ticketsRouter.post('/:id/mark-paid', requireAuth(['admin', 'organizer', 'staff']
|
||||
console.error('[Email] Failed to send confirmation emails:', err);
|
||||
});
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const updated = await dbGet(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
return c.json({ ticket: updated, message: 'Payment marked as received' });
|
||||
});
|
||||
@@ -575,18 +611,21 @@ ticketsRouter.post('/:id/mark-paid', requireAuth(['admin', 'organizer', 'staff']
|
||||
ticketsRouter.post('/:id/mark-payment-sent', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
// Get the payment
|
||||
const payment = await (db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).ticketId, id))
|
||||
.get();
|
||||
const payment = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).ticketId, id))
|
||||
);
|
||||
|
||||
if (!payment) {
|
||||
return c.json({ error: 'Payment not found' }, 404);
|
||||
@@ -632,11 +671,12 @@ ticketsRouter.post('/:id/mark-payment-sent', async (c) => {
|
||||
.where(eq((payments as any).id, payment.id));
|
||||
|
||||
// Get updated payment
|
||||
const updatedPayment = await (db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).id, payment.id))
|
||||
.get();
|
||||
const updatedPayment = await dbGet(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq((payments as any).id, payment.id))
|
||||
);
|
||||
|
||||
// TODO: Send notification to admin about pending payment approval
|
||||
|
||||
@@ -649,9 +689,11 @@ ticketsRouter.post('/:id/mark-payment-sent', async (c) => {
|
||||
// Cancel ticket
|
||||
ticketsRouter.post('/:id/cancel', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await getAuthUser(c);
|
||||
const user: any = await getAuthUser(c);
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -675,7 +717,9 @@ ticketsRouter.post('/:id/cancel', async (c) => {
|
||||
ticketsRouter.post('/:id/remove-checkin', requireAuth(['admin', 'organizer', 'staff']), async (c) => {
|
||||
const id = c.req.param('id');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -690,7 +734,9 @@ ticketsRouter.post('/:id/remove-checkin', requireAuth(['admin', 'organizer', 'st
|
||||
.set({ status: 'confirmed', checkinAt: null })
|
||||
.where(eq((tickets as any).id, id));
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const updated = await dbGet(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
return c.json({ ticket: updated, message: 'Check-in removed successfully' });
|
||||
});
|
||||
@@ -700,7 +746,9 @@ ticketsRouter.post('/:id/note', requireAuth(['admin', 'organizer', 'staff']), zV
|
||||
const id = c.req.param('id');
|
||||
const { note } = c.req.valid('json');
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const ticket = await dbGet<any>(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
@@ -711,7 +759,9 @@ ticketsRouter.post('/:id/note', requireAuth(['admin', 'organizer', 'staff']), zV
|
||||
.set({ adminNote: note || null })
|
||||
.where(eq((tickets as any).id, id));
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
const updated = await dbGet(
|
||||
(db as any).select().from(tickets).where(eq((tickets as any).id, id))
|
||||
);
|
||||
|
||||
return c.json({ ticket: updated, message: 'Note updated successfully' });
|
||||
});
|
||||
@@ -721,22 +771,25 @@ ticketsRouter.post('/admin/create', requireAuth(['admin', 'organizer', 'staff'])
|
||||
const data = c.req.valid('json');
|
||||
|
||||
// Get event
|
||||
const event = await (db as any).select().from(events).where(eq((events as any).id, data.eventId)).get();
|
||||
const event = await dbGet<any>(
|
||||
(db as any).select().from(events).where(eq((events as any).id, data.eventId))
|
||||
);
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
}
|
||||
|
||||
// Check capacity
|
||||
const ticketCount = await (db as any)
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).eventId, data.eventId),
|
||||
sql`${(tickets as any).status} IN ('confirmed', 'checked_in')`
|
||||
const ticketCount = await dbGet<any>(
|
||||
(db as any)
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).eventId, data.eventId),
|
||||
sql`${(tickets as any).status} IN ('confirmed', 'checked_in')`
|
||||
)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
);
|
||||
|
||||
if ((ticketCount?.count || 0) >= event.capacity) {
|
||||
return c.json({ error: 'Event is at capacity' }, 400);
|
||||
@@ -750,7 +803,9 @@ ticketsRouter.post('/admin/create', requireAuth(['admin', 'organizer', 'staff'])
|
||||
: `door-${generateId()}@doorentry.local`;
|
||||
|
||||
// Find or create user
|
||||
let user = await (db as any).select().from(users).where(eq((users as any).email, attendeeEmail)).get();
|
||||
let user = await dbGet<any>(
|
||||
(db as any).select().from(users).where(eq((users as any).email, attendeeEmail))
|
||||
);
|
||||
|
||||
const adminFullName = data.lastName && data.lastName.trim()
|
||||
? `${data.firstName} ${data.lastName}`.trim()
|
||||
@@ -774,16 +829,17 @@ ticketsRouter.post('/admin/create', requireAuth(['admin', 'organizer', 'staff'])
|
||||
|
||||
// Check for existing active ticket for this user and event (only if real email provided)
|
||||
if (data.email && data.email.trim() && !data.email.includes('@doorentry.local')) {
|
||||
const existingTicket = await (db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).userId, user.id),
|
||||
eq((tickets as any).eventId, data.eventId)
|
||||
const existingTicket = await dbGet<any>(
|
||||
(db as any)
|
||||
.select()
|
||||
.from(tickets)
|
||||
.where(
|
||||
and(
|
||||
eq((tickets as any).userId, user.id),
|
||||
eq((tickets as any).eventId, data.eventId)
|
||||
)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
);
|
||||
|
||||
if (existingTicket && existingTicket.status !== 'cancelled') {
|
||||
return c.json({ error: 'This person already has a ticket for this event' }, 400);
|
||||
@@ -869,7 +925,7 @@ ticketsRouter.get('/', requireAuth(['admin', 'organizer']), async (c) => {
|
||||
query = query.where(and(...conditions));
|
||||
}
|
||||
|
||||
const result = await query.all();
|
||||
const result = await dbAll(query);
|
||||
|
||||
return c.json({ tickets: result });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user