first commit
This commit is contained in:
652
backend/src/routes/tickets.ts
Normal file
652
backend/src/routes/tickets.ts
Normal file
@@ -0,0 +1,652 @@
|
||||
import { Hono } from 'hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { z } from 'zod';
|
||||
import { db, tickets, events, users, payments } 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';
|
||||
import { createInvoice, isLNbitsConfigured } from '../lib/lnbits.js';
|
||||
import emailService from '../lib/email.js';
|
||||
|
||||
const ticketsRouter = new Hono();
|
||||
|
||||
const createTicketSchema = z.object({
|
||||
eventId: z.string(),
|
||||
firstName: z.string().min(2),
|
||||
lastName: z.string().min(2),
|
||||
email: z.string().email(),
|
||||
phone: z.string().min(6, 'Phone number is required'),
|
||||
preferredLanguage: z.enum(['en', 'es']).optional(),
|
||||
paymentMethod: z.enum(['bancard', 'lightning', 'cash', 'bank_transfer', 'tpago']).default('cash'),
|
||||
ruc: z.string().regex(/^[0-9]{6,8}-[0-9]{1}$/, 'Invalid RUC format').optional(),
|
||||
});
|
||||
|
||||
const updateTicketSchema = z.object({
|
||||
status: z.enum(['pending', 'confirmed', 'cancelled', 'checked_in']).optional(),
|
||||
adminNote: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateNoteSchema = z.object({
|
||||
note: z.string().max(1000),
|
||||
});
|
||||
|
||||
const adminCreateTicketSchema = z.object({
|
||||
eventId: z.string(),
|
||||
firstName: z.string().min(2),
|
||||
lastName: z.string().optional().or(z.literal('')),
|
||||
email: z.string().email().optional().or(z.literal('')),
|
||||
phone: z.string().optional().or(z.literal('')),
|
||||
preferredLanguage: z.enum(['en', 'es']).optional(),
|
||||
autoCheckin: z.boolean().optional().default(false),
|
||||
adminNote: z.string().max(1000).optional(),
|
||||
});
|
||||
|
||||
// Book a ticket (public)
|
||||
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();
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
}
|
||||
|
||||
if (event.status !== 'published') {
|
||||
return c.json({ error: 'Event is not available for booking' }, 400);
|
||||
}
|
||||
|
||||
// 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')
|
||||
)
|
||||
)
|
||||
.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();
|
||||
|
||||
const now = getNow();
|
||||
|
||||
const fullName = `${data.firstName} ${data.lastName}`.trim();
|
||||
|
||||
if (!user) {
|
||||
const userId = generateId();
|
||||
user = {
|
||||
id: userId,
|
||||
email: data.email,
|
||||
password: '', // No password for guest bookings
|
||||
name: fullName,
|
||||
phone: data.phone || null,
|
||||
role: 'user',
|
||||
languagePreference: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await (db as any).insert(users).values(user);
|
||||
}
|
||||
|
||||
// Check for duplicate booking
|
||||
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)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
|
||||
if (existingTicket && existingTicket.status !== 'cancelled') {
|
||||
return c.json({ error: 'You have already booked this event' }, 400);
|
||||
}
|
||||
|
||||
// Create ticket
|
||||
const ticketId = generateId();
|
||||
const qrCode = generateTicketCode();
|
||||
|
||||
// Cash payments start as pending, card/lightning start as pending until payment confirmed
|
||||
const ticketStatus = 'pending';
|
||||
|
||||
const newTicket = {
|
||||
id: ticketId,
|
||||
userId: user.id,
|
||||
eventId: data.eventId,
|
||||
attendeeFirstName: data.firstName,
|
||||
attendeeLastName: data.lastName,
|
||||
attendeeEmail: data.email,
|
||||
attendeePhone: data.phone,
|
||||
attendeeRuc: data.ruc || null,
|
||||
preferredLanguage: data.preferredLanguage || null,
|
||||
status: ticketStatus,
|
||||
qrCode,
|
||||
checkinAt: null,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
await (db as any).insert(tickets).values(newTicket);
|
||||
|
||||
// Create payment record
|
||||
const paymentId = generateId();
|
||||
const newPayment = {
|
||||
id: paymentId,
|
||||
ticketId,
|
||||
provider: data.paymentMethod,
|
||||
amount: event.price,
|
||||
currency: event.currency,
|
||||
status: 'pending',
|
||||
reference: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await (db as any).insert(payments).values(newPayment);
|
||||
|
||||
// If Lightning payment, create LNbits invoice
|
||||
let lnbitsInvoice = null;
|
||||
if (data.paymentMethod === 'lightning' && event.price > 0) {
|
||||
if (!isLNbitsConfigured()) {
|
||||
// Delete the ticket and payment we just created
|
||||
await (db as any).delete(payments).where(eq((payments as any).id, paymentId));
|
||||
await (db as any).delete(tickets).where(eq((tickets as any).id, ticketId));
|
||||
return c.json({
|
||||
error: 'Bitcoin Lightning payments are not available at this time'
|
||||
}, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const apiUrl = process.env.API_URL || 'http://localhost:3001';
|
||||
|
||||
// Pass the fiat currency directly to LNbits - it handles conversion automatically
|
||||
lnbitsInvoice = await createInvoice({
|
||||
amount: event.price,
|
||||
unit: event.currency, // LNbits supports fiat currencies like USD, PYG, etc.
|
||||
memo: `Spanglish: ${event.title} - ${fullName}`,
|
||||
webhookUrl: `${apiUrl}/api/lnbits/webhook`,
|
||||
expiry: 900, // 15 minutes expiry for faster UX
|
||||
extra: {
|
||||
ticketId,
|
||||
eventId: event.id,
|
||||
eventTitle: event.title,
|
||||
attendeeName: fullName,
|
||||
attendeeEmail: data.email,
|
||||
},
|
||||
});
|
||||
|
||||
// Update payment with LNbits payment hash reference
|
||||
await (db as any)
|
||||
.update(payments)
|
||||
.set({ reference: lnbitsInvoice.paymentHash })
|
||||
.where(eq((payments as any).id, paymentId));
|
||||
|
||||
(newPayment as any).reference = lnbitsInvoice.paymentHash;
|
||||
} catch (error: any) {
|
||||
console.error('Failed to create Lightning invoice:', error);
|
||||
// Delete the ticket and payment we just created since Lightning payment failed
|
||||
await (db as any).delete(payments).where(eq((payments as any).id, paymentId));
|
||||
await (db as any).delete(tickets).where(eq((tickets as any).id, ticketId));
|
||||
return c.json({
|
||||
error: `Failed to create Lightning invoice: ${error.message || 'Unknown error'}`
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return c.json({
|
||||
ticket: {
|
||||
...newTicket,
|
||||
event: {
|
||||
title: event.title,
|
||||
startDatetime: event.startDatetime,
|
||||
location: event.location,
|
||||
},
|
||||
},
|
||||
payment: newPayment,
|
||||
lightningInvoice: lnbitsInvoice ? {
|
||||
paymentHash: lnbitsInvoice.paymentHash,
|
||||
paymentRequest: lnbitsInvoice.paymentRequest,
|
||||
amount: lnbitsInvoice.amount, // Amount in satoshis
|
||||
fiatAmount: lnbitsInvoice.fiatAmount,
|
||||
fiatCurrency: lnbitsInvoice.fiatCurrency,
|
||||
expiry: lnbitsInvoice.expiry,
|
||||
} : null,
|
||||
message: 'Booking created successfully',
|
||||
}, 201);
|
||||
});
|
||||
|
||||
// Get ticket by ID
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
// Get payment
|
||||
const payment = await (db as any).select().from(payments).where(eq((payments as any).ticketId, id)).get();
|
||||
|
||||
return c.json({
|
||||
ticket: {
|
||||
...ticket,
|
||||
event,
|
||||
payment,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Update ticket status (admin/organizer)
|
||||
ticketsRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff']), zValidator('json', updateTicketSchema), async (c) => {
|
||||
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();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
const updates: any = {};
|
||||
|
||||
if (data.status) {
|
||||
updates.status = data.status;
|
||||
if (data.status === 'checked_in') {
|
||||
updates.checkinAt = getNow();
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
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();
|
||||
|
||||
return c.json({ ticket: updated });
|
||||
});
|
||||
|
||||
// Check-in ticket
|
||||
ticketsRouter.post('/:id/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();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
if (ticket.status === 'checked_in') {
|
||||
return c.json({ error: 'Ticket already checked in' }, 400);
|
||||
}
|
||||
|
||||
if (ticket.status !== 'confirmed') {
|
||||
return c.json({ error: 'Ticket must be confirmed before check-in' }, 400);
|
||||
}
|
||||
|
||||
await (db as any)
|
||||
.update(tickets)
|
||||
.set({ status: 'checked_in', checkinAt: getNow() })
|
||||
.where(eq((tickets as any).id, id));
|
||||
|
||||
const updated = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
|
||||
return c.json({ ticket: updated, message: 'Check-in successful' });
|
||||
});
|
||||
|
||||
// Mark payment as received (for cash payments - admin only)
|
||||
ticketsRouter.post('/:id/mark-paid', requireAuth(['admin', 'organizer', 'staff']), async (c) => {
|
||||
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();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
if (ticket.status === 'confirmed') {
|
||||
return c.json({ error: 'Ticket already confirmed' }, 400);
|
||||
}
|
||||
|
||||
if (ticket.status === 'cancelled') {
|
||||
return c.json({ error: 'Cannot confirm cancelled ticket' }, 400);
|
||||
}
|
||||
|
||||
const now = getNow();
|
||||
|
||||
// Update ticket status
|
||||
await (db as any)
|
||||
.update(tickets)
|
||||
.set({ status: 'confirmed' })
|
||||
.where(eq((tickets as any).id, id));
|
||||
|
||||
// Update payment status
|
||||
await (db as any)
|
||||
.update(payments)
|
||||
.set({
|
||||
status: 'paid',
|
||||
paidAt: now,
|
||||
paidByAdminId: user.id,
|
||||
updatedAt: now,
|
||||
})
|
||||
.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();
|
||||
|
||||
// Send confirmation emails asynchronously (don't block the response)
|
||||
Promise.all([
|
||||
emailService.sendBookingConfirmation(id),
|
||||
payment ? emailService.sendPaymentReceipt(payment.id) : Promise.resolve(),
|
||||
]).catch(err => {
|
||||
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();
|
||||
|
||||
return c.json({ ticket: updated, message: 'Payment marked as received' });
|
||||
});
|
||||
|
||||
// User marks payment as sent (for manual payment methods: bank_transfer, tpago)
|
||||
// This sets status to "pending_approval" and notifies admin
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
if (!payment) {
|
||||
return c.json({ error: 'Payment not found' }, 404);
|
||||
}
|
||||
|
||||
// Only allow for manual payment methods
|
||||
if (!['bank_transfer', 'tpago'].includes(payment.provider)) {
|
||||
return c.json({ error: 'This action is only available for bank transfer or TPago payments' }, 400);
|
||||
}
|
||||
|
||||
// Only allow if currently pending
|
||||
if (payment.status !== 'pending') {
|
||||
return c.json({ error: 'Payment has already been processed' }, 400);
|
||||
}
|
||||
|
||||
const now = getNow();
|
||||
|
||||
// Update payment status to pending_approval
|
||||
await (db as any)
|
||||
.update(payments)
|
||||
.set({
|
||||
status: 'pending_approval',
|
||||
userMarkedPaidAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
.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();
|
||||
|
||||
// TODO: Send notification to admin about pending payment approval
|
||||
|
||||
return c.json({
|
||||
payment: updatedPayment,
|
||||
message: 'Payment marked as sent. Waiting for admin approval.'
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel ticket
|
||||
ticketsRouter.post('/:id/cancel', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await getAuthUser(c);
|
||||
|
||||
const ticket = await (db as any).select().from(tickets).where(eq((tickets as any).id, id)).get();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
// Check authorization (admin or ticket owner)
|
||||
if (!user || (user.role !== 'admin' && user.id !== ticket.userId)) {
|
||||
return c.json({ error: 'Unauthorized' }, 403);
|
||||
}
|
||||
|
||||
if (ticket.status === 'cancelled') {
|
||||
return c.json({ error: 'Ticket already cancelled' }, 400);
|
||||
}
|
||||
|
||||
await (db as any).update(tickets).set({ status: 'cancelled' }).where(eq((tickets as any).id, id));
|
||||
|
||||
return c.json({ message: 'Ticket cancelled successfully' });
|
||||
});
|
||||
|
||||
// Remove check-in (reset to confirmed)
|
||||
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();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
if (ticket.status !== 'checked_in') {
|
||||
return c.json({ error: 'Ticket is not checked in' }, 400);
|
||||
}
|
||||
|
||||
await (db as any)
|
||||
.update(tickets)
|
||||
.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();
|
||||
|
||||
return c.json({ ticket: updated, message: 'Check-in removed successfully' });
|
||||
});
|
||||
|
||||
// Update admin note
|
||||
ticketsRouter.post('/:id/note', requireAuth(['admin', 'organizer', 'staff']), zValidator('json', updateNoteSchema), async (c) => {
|
||||
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();
|
||||
|
||||
if (!ticket) {
|
||||
return c.json({ error: 'Ticket not found' }, 404);
|
||||
}
|
||||
|
||||
await (db as any)
|
||||
.update(tickets)
|
||||
.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();
|
||||
|
||||
return c.json({ ticket: updated, message: 'Note updated successfully' });
|
||||
});
|
||||
|
||||
// Admin create ticket (at the door)
|
||||
ticketsRouter.post('/admin/create', requireAuth(['admin', 'organizer', 'staff']), zValidator('json', adminCreateTicketSchema), 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();
|
||||
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')`
|
||||
)
|
||||
)
|
||||
.get();
|
||||
|
||||
if ((ticketCount?.count || 0) >= event.capacity) {
|
||||
return c.json({ error: 'Event is at capacity' }, 400);
|
||||
}
|
||||
|
||||
const now = getNow();
|
||||
|
||||
// For door sales, email might be empty - use a generated placeholder
|
||||
const attendeeEmail = data.email && data.email.trim()
|
||||
? data.email.trim()
|
||||
: `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();
|
||||
|
||||
const adminFullName = data.lastName && data.lastName.trim()
|
||||
? `${data.firstName} ${data.lastName}`.trim()
|
||||
: data.firstName;
|
||||
|
||||
if (!user) {
|
||||
const userId = generateId();
|
||||
user = {
|
||||
id: userId,
|
||||
email: attendeeEmail,
|
||||
password: '',
|
||||
name: adminFullName,
|
||||
phone: data.phone || null,
|
||||
role: 'user',
|
||||
languagePreference: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await (db as any).insert(users).values(user);
|
||||
}
|
||||
|
||||
// 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)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
|
||||
if (existingTicket && existingTicket.status !== 'cancelled') {
|
||||
return c.json({ error: 'This person already has a ticket for this event' }, 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Create ticket
|
||||
const ticketId = generateId();
|
||||
const qrCode = generateTicketCode();
|
||||
|
||||
// For door sales, mark as confirmed (or checked_in if auto-checkin)
|
||||
const ticketStatus = data.autoCheckin ? 'checked_in' : 'confirmed';
|
||||
|
||||
const newTicket = {
|
||||
id: ticketId,
|
||||
userId: user.id,
|
||||
eventId: data.eventId,
|
||||
attendeeFirstName: data.firstName,
|
||||
attendeeLastName: data.lastName && data.lastName.trim() ? data.lastName.trim() : null,
|
||||
attendeeEmail: data.email && data.email.trim() ? data.email.trim() : null,
|
||||
attendeePhone: data.phone && data.phone.trim() ? data.phone.trim() : null,
|
||||
preferredLanguage: data.preferredLanguage || null,
|
||||
status: ticketStatus,
|
||||
qrCode,
|
||||
checkinAt: data.autoCheckin ? now : null,
|
||||
adminNote: data.adminNote || null,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
await (db as any).insert(tickets).values(newTicket);
|
||||
|
||||
// Create payment record (marked as paid for door sales)
|
||||
const paymentId = generateId();
|
||||
const adminUser = (c as any).get('user');
|
||||
const newPayment = {
|
||||
id: paymentId,
|
||||
ticketId,
|
||||
provider: 'cash',
|
||||
amount: event.price,
|
||||
currency: event.currency,
|
||||
status: 'paid',
|
||||
reference: 'Door sale',
|
||||
paidAt: now,
|
||||
paidByAdminId: adminUser?.id || null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await (db as any).insert(payments).values(newPayment);
|
||||
|
||||
return c.json({
|
||||
ticket: {
|
||||
...newTicket,
|
||||
event: {
|
||||
title: event.title,
|
||||
startDatetime: event.startDatetime,
|
||||
location: event.location,
|
||||
},
|
||||
},
|
||||
payment: newPayment,
|
||||
message: data.autoCheckin
|
||||
? 'Attendee added and checked in successfully'
|
||||
: 'Attendee added successfully',
|
||||
}, 201);
|
||||
});
|
||||
|
||||
// Get all tickets (admin)
|
||||
ticketsRouter.get('/', requireAuth(['admin', 'organizer']), async (c) => {
|
||||
const eventId = c.req.query('eventId');
|
||||
const status = c.req.query('status');
|
||||
|
||||
let query = (db as any).select().from(tickets);
|
||||
|
||||
const conditions = [];
|
||||
if (eventId) {
|
||||
conditions.push(eq((tickets as any).eventId, eventId));
|
||||
}
|
||||
if (status) {
|
||||
conditions.push(eq((tickets as any).status, status));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(and(...conditions));
|
||||
}
|
||||
|
||||
const result = await query.all();
|
||||
|
||||
return c.json({ tickets: result });
|
||||
});
|
||||
|
||||
export default ticketsRouter;
|
||||
Reference in New Issue
Block a user