import { Hono } from 'hono'; import { db, dbGet, dbAll, users, events, tickets, payments, contacts, emailSubscribers } from '../db/index.js'; import { eq, and, gte, sql, desc } from 'drizzle-orm'; import { requireAuth } from '../lib/auth.js'; import { getNow } from '../lib/utils.js'; const adminRouter = new Hono(); // Dashboard overview stats (admin) adminRouter.get('/dashboard', requireAuth(['admin', 'organizer']), async (c) => { const now = getNow(); // Get upcoming events const upcomingEvents = await dbAll( (db as any) .select() .from(events) .where( and( eq((events as any).status, 'published'), gte((events as any).startDatetime, now) ) ) .orderBy((events as any).startDatetime) .limit(5) ); // Get recent tickets const recentTickets = await dbAll( (db as any) .select() .from(tickets) .orderBy(desc((tickets as any).createdAt)) .limit(10) ); // Get total stats const totalUsers = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(users) ); const totalEvents = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(events) ); const totalTickets = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(tickets) ); const confirmedTickets = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(tickets) .where(eq((tickets as any).status, 'confirmed')) ); const pendingPayments = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(payments) .where(eq((payments as any).status, 'pending')) ); const paidPayments = await dbAll( (db as any) .select() .from(payments) .where(eq((payments as any).status, 'paid')) ); const totalRevenue = paidPayments.reduce((sum: number, p: any) => sum + Number(p.amount || 0), 0); const newContacts = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(contacts) .where(eq((contacts as any).status, 'new')) ); const totalSubscribers = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(emailSubscribers) .where(eq((emailSubscribers as any).status, 'active')) ); return c.json({ dashboard: { stats: { totalUsers: totalUsers?.count || 0, totalEvents: totalEvents?.count || 0, totalTickets: totalTickets?.count || 0, confirmedTickets: confirmedTickets?.count || 0, pendingPayments: pendingPayments?.count || 0, totalRevenue, newContacts: newContacts?.count || 0, totalSubscribers: totalSubscribers?.count || 0, }, upcomingEvents, recentTickets, }, }); }); // Get analytics data (admin) adminRouter.get('/analytics', requireAuth(['admin']), async (c) => { // Get events with ticket counts const allEvents = await dbAll((db as any).select().from(events)); const eventStats = await Promise.all( allEvents.map(async (event: any) => { const ticketCount = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(tickets) .where(eq((tickets as any).eventId, event.id)) ); const confirmedCount = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(tickets) .where( and( eq((tickets as any).eventId, event.id), eq((tickets as any).status, 'confirmed') ) ) ); const checkedInCount = await dbGet( (db as any) .select({ count: sql`count(*)` }) .from(tickets) .where( and( eq((tickets as any).eventId, event.id), eq((tickets as any).status, 'checked_in') ) ) ); return { id: event.id, title: event.title, date: event.startDatetime, capacity: event.capacity, totalBookings: ticketCount?.count || 0, confirmedBookings: confirmedCount?.count || 0, checkedIn: checkedInCount?.count || 0, revenue: (confirmedCount?.count || 0) * event.price, }; }) ); return c.json({ analytics: { events: eventStats, }, }); }); // Export data (admin) adminRouter.get('/export/tickets', requireAuth(['admin']), async (c) => { const eventId = c.req.query('eventId'); let query = (db as any).select().from(tickets); if (eventId) { query = query.where(eq((tickets as any).eventId, eventId)); } const ticketList = await dbAll(query); // Get user and event details for each ticket const enrichedTickets = await Promise.all( ticketList.map(async (ticket: any) => { const user = await dbGet( (db as any) .select() .from(users) .where(eq((users as any).id, ticket.userId)) ); const event = await dbGet( (db as any) .select() .from(events) .where(eq((events as any).id, ticket.eventId)) ); const payment = await dbGet( (db as any) .select() .from(payments) .where(eq((payments as any).ticketId, ticket.id)) ); return { ticketId: ticket.id, ticketStatus: ticket.status, qrCode: ticket.qrCode, checkinAt: ticket.checkinAt, userName: user?.name, userEmail: user?.email, userPhone: user?.phone, eventTitle: event?.title, eventDate: event?.startDatetime, paymentStatus: payment?.status, paymentAmount: payment?.amount, createdAt: ticket.createdAt, }; }) ); return c.json({ tickets: enrichedTickets }); }); // Export financial data (admin) adminRouter.get('/export/financial', requireAuth(['admin']), async (c) => { const startDate = c.req.query('startDate'); const endDate = c.req.query('endDate'); const eventId = c.req.query('eventId'); // Get all payments let query = (db as any).select().from(payments); const allPayments = await dbAll(query); // Enrich with event and ticket data const enrichedPayments = await Promise.all( allPayments.map(async (payment: any) => { const ticket = await dbGet( (db as any) .select() .from(tickets) .where(eq((tickets as any).id, payment.ticketId)) ); if (!ticket) return null; const event = await dbGet( (db as any) .select() .from(events) .where(eq((events as any).id, ticket.eventId)) ); // Apply filters if (eventId && ticket.eventId !== eventId) return null; if (startDate && payment.createdAt < startDate) return null; if (endDate && payment.createdAt > endDate) return null; return { paymentId: payment.id, amount: payment.amount, currency: payment.currency, provider: payment.provider, status: payment.status, reference: payment.reference, paidAt: payment.paidAt, createdAt: payment.createdAt, ticketId: ticket.id, attendeeFirstName: ticket.attendeeFirstName, attendeeLastName: ticket.attendeeLastName, attendeeEmail: ticket.attendeeEmail, eventId: event?.id, eventTitle: event?.title, eventDate: event?.startDatetime, }; }) ); const filteredPayments = enrichedPayments.filter(p => p !== null); // Calculate summary const summary = { totalPayments: filteredPayments.length, totalPaid: filteredPayments.filter((p: any) => p.status === 'paid').reduce((sum: number, p: any) => sum + p.amount, 0), totalPending: filteredPayments.filter((p: any) => p.status === 'pending').reduce((sum: number, p: any) => sum + p.amount, 0), totalRefunded: filteredPayments.filter((p: any) => p.status === 'refunded').reduce((sum: number, p: any) => sum + p.amount, 0), byProvider: { bancard: filteredPayments.filter((p: any) => p.provider === 'bancard' && p.status === 'paid').reduce((sum: number, p: any) => sum + p.amount, 0), lightning: filteredPayments.filter((p: any) => p.provider === 'lightning' && p.status === 'paid').reduce((sum: number, p: any) => sum + p.amount, 0), cash: filteredPayments.filter((p: any) => p.provider === 'cash' && p.status === 'paid').reduce((sum: number, p: any) => sum + p.amount, 0), }, paidCount: filteredPayments.filter((p: any) => p.status === 'paid').length, pendingCount: filteredPayments.filter((p: any) => p.status === 'pending').length, refundedCount: filteredPayments.filter((p: any) => p.status === 'refunded').length, failedCount: filteredPayments.filter((p: any) => p.status === 'failed').length, }; return c.json({ payments: filteredPayments, summary }); }); export default adminRouter;