Files
Spanglish/backend/src/routes/admin.ts

303 lines
9.0 KiB
TypeScript

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<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
);
const totalEvents = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(events)
);
const totalTickets = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
);
const confirmedTickets = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(eq((tickets as any).status, 'confirmed'))
);
const pendingPayments = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(payments)
.where(eq((payments as any).status, 'pending'))
);
const paidPayments = await dbAll<any>(
(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<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(contacts)
.where(eq((contacts as any).status, 'new'))
);
const totalSubscribers = await dbGet<any>(
(db as any)
.select({ count: sql<number>`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<any>((db as any).select().from(events));
const eventStats = await Promise.all(
allEvents.map(async (event: any) => {
const ticketCount = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(eq((tickets as any).eventId, event.id))
);
const confirmedCount = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(
and(
eq((tickets as any).eventId, event.id),
eq((tickets as any).status, 'confirmed')
)
)
);
const checkedInCount = await dbGet<any>(
(db as any)
.select({ count: sql<number>`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<any>(query);
// Get user and event details for each ticket
const enrichedTickets = await Promise.all(
ticketList.map(async (ticket: any) => {
const user = await dbGet<any>(
(db as any)
.select()
.from(users)
.where(eq((users as any).id, ticket.userId))
);
const event = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
);
const payment = await dbGet<any>(
(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<any>(query);
// Enrich with event and ticket data
const enrichedPayments = await Promise.all(
allPayments.map(async (payment: any) => {
const ticket = await dbGet<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
);
if (!ticket) return null;
const event = await dbGet<any>(
(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;