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:
Michilis
2026-02-02 03:46:35 +00:00
parent 9410e83b89
commit bafd1425c4
61 changed files with 5015 additions and 881 deletions

View File

@@ -1,5 +1,5 @@
import { Hono } from 'hono';
import { db, users, events, tickets, payments, contacts, emailSubscribers } from '../db/index.js';
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';
@@ -11,74 +11,84 @@ adminRouter.get('/dashboard', requireAuth(['admin', 'organizer']), async (c) =>
const now = getNow();
// Get upcoming events
const upcomingEvents = await (db as any)
.select()
.from(events)
.where(
and(
eq((events as any).status, 'published'),
gte((events as any).startDatetime, now)
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)
.all();
.orderBy((events as any).startDatetime)
.limit(5)
);
// Get recent tickets
const recentTickets = await (db as any)
.select()
.from(tickets)
.orderBy(desc((tickets as any).createdAt))
.limit(10)
.all();
const recentTickets = await dbAll(
(db as any)
.select()
.from(tickets)
.orderBy(desc((tickets as any).createdAt))
.limit(10)
);
// Get total stats
const totalUsers = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.get();
const totalUsers = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
);
const totalEvents = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(events)
.get();
const totalEvents = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(events)
);
const totalTickets = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.get();
const totalTickets = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
);
const confirmedTickets = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(eq((tickets as any).status, 'confirmed'))
.get();
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 (db as any)
.select({ count: sql<number>`count(*)` })
.from(payments)
.where(eq((payments as any).status, 'pending'))
.get();
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 (db as any)
.select()
.from(payments)
.where(eq((payments as any).status, 'paid'))
.all();
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 + (p.amount || 0), 0);
const newContacts = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(contacts)
.where(eq((contacts as any).status, 'new'))
.get();
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 (db as any)
.select({ count: sql<number>`count(*)` })
.from(emailSubscribers)
.where(eq((emailSubscribers as any).status, 'active'))
.get();
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: {
@@ -101,37 +111,40 @@ adminRouter.get('/dashboard', requireAuth(['admin', 'organizer']), async (c) =>
// Get analytics data (admin)
adminRouter.get('/analytics', requireAuth(['admin']), async (c) => {
// Get events with ticket counts
const allEvents = await (db as any).select().from(events).all();
const allEvents = await dbAll<any>((db as any).select().from(events));
const eventStats = await Promise.all(
allEvents.map(async (event: any) => {
const ticketCount = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(eq((tickets as any).eventId, event.id))
.get();
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 (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 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')
)
)
)
.get();
);
const checkedInCount = await (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')
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')
)
)
)
.get();
);
return {
id: event.id,
@@ -163,28 +176,31 @@ adminRouter.get('/export/tickets', requireAuth(['admin']), async (c) => {
query = query.where(eq((tickets as any).eventId, eventId));
}
const ticketList = await query.all();
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 (db as any)
.select()
.from(users)
.where(eq((users as any).id, ticket.userId))
.get();
const user = await dbGet<any>(
(db as any)
.select()
.from(users)
.where(eq((users as any).id, ticket.userId))
);
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))
);
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
);
return {
ticketId: ticket.id,
@@ -215,24 +231,26 @@ adminRouter.get('/export/financial', requireAuth(['admin']), async (c) => {
// Get all payments
let query = (db as any).select().from(payments);
const allPayments = await query.all();
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 (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
.get();
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 (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))
);
// Apply filters
if (eventId && ticket.eventId !== eventId) return null;

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, users, magicLinkTokens, User } from '../db/index.js';
import { db, dbGet, users, magicLinkTokens, User } from '../db/index.js';
import { eq } from 'drizzle-orm';
import {
hashPassword,
@@ -16,7 +16,7 @@ import {
invalidateAllUserSessions,
requireAuth,
} from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
import { generateId, getNow, toDbBool } from '../lib/utils.js';
import { sendEmail } from '../lib/email.js';
// User type that includes all fields (some added in schema updates)
@@ -121,7 +121,9 @@ auth.post('/register', zValidator('json', registerSchema), async (c) => {
}
// Check if email exists
const existing = await (db as any).select().from(users).where(eq((users as any).email, data.email)).get();
const existing = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, data.email))
);
if (existing) {
// If user exists but is unclaimed, allow claiming
if (!existing.isClaimed || existing.accountStatus === 'unclaimed') {
@@ -149,7 +151,7 @@ auth.post('/register', zValidator('json', registerSchema), async (c) => {
phone: data.phone || null,
role: firstUser ? 'admin' : 'user',
languagePreference: data.languagePreference || null,
isClaimed: true,
isClaimed: toDbBool(true),
googleId: null,
rucNumber: null,
accountStatus: 'active',
@@ -189,7 +191,9 @@ auth.post('/login', zValidator('json', loginSchema), async (c) => {
}, 429);
}
const user = await (db as any).select().from(users).where(eq((users as any).email, data.email)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, data.email))
);
if (!user) {
recordFailedAttempt(data.email);
return c.json({ error: 'Invalid credentials' }, 401);
@@ -243,7 +247,9 @@ auth.post('/login', zValidator('json', loginSchema), async (c) => {
auth.post('/magic-link/request', zValidator('json', magicLinkRequestSchema), async (c) => {
const { email } = c.req.valid('json');
const user = await (db as any).select().from(users).where(eq((users as any).email, email)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, email))
);
if (!user) {
// Don't reveal if email exists
@@ -288,7 +294,9 @@ auth.post('/magic-link/verify', zValidator('json', magicLinkVerifySchema), async
return c.json({ error: verification.error }, 400);
}
const user = await (db as any).select().from(users).where(eq((users as any).id, verification.userId)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).id, verification.userId))
);
if (!user || user.accountStatus === 'suspended') {
return c.json({ error: 'Invalid token' }, 400);
@@ -317,7 +325,9 @@ auth.post('/magic-link/verify', zValidator('json', magicLinkVerifySchema), async
auth.post('/password-reset/request', zValidator('json', passwordResetRequestSchema), async (c) => {
const { email } = c.req.valid('json');
const user = await (db as any).select().from(users).where(eq((users as any).email, email)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, email))
);
if (!user) {
// Don't reveal if email exists
@@ -389,7 +399,9 @@ auth.post('/password-reset/confirm', zValidator('json', passwordResetSchema), as
auth.post('/claim-account/request', zValidator('json', magicLinkRequestSchema), async (c) => {
const { email } = c.req.valid('json');
const user = await (db as any).select().from(users).where(eq((users as any).email, email)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, email))
);
if (!user) {
return c.json({ message: 'If an unclaimed account exists with this email, a claim link has been sent.' });
@@ -439,7 +451,7 @@ auth.post('/claim-account/confirm', zValidator('json', claimAccountSchema), asyn
const now = getNow();
const updates: Record<string, any> = {
isClaimed: true,
isClaimed: toDbBool(true),
accountStatus: 'active',
updatedAt: now,
};
@@ -461,7 +473,9 @@ auth.post('/claim-account/confirm', zValidator('json', claimAccountSchema), asyn
.set(updates)
.where(eq((users as any).id, verification.userId));
const user = await (db as any).select().from(users).where(eq((users as any).id, verification.userId)).get();
const user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).id, verification.userId))
);
const authToken = await createToken(user.id, user.email, user.role);
const refreshToken = await createRefreshToken(user.id);
@@ -510,11 +524,15 @@ auth.post('/google', zValidator('json', googleAuthSchema), async (c) => {
const { sub: googleId, email, name } = googleData;
// Check if user exists by email or google_id
let user = await (db as any).select().from(users).where(eq((users as any).email, email)).get();
let user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).email, email))
);
if (!user) {
// Check by google_id
user = await (db as any).select().from(users).where(eq((users as any).googleId, googleId)).get();
user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).googleId, googleId))
);
}
const now = getNow();
@@ -530,7 +548,7 @@ auth.post('/google', zValidator('json', googleAuthSchema), async (c) => {
.update(users)
.set({
googleId,
isClaimed: true,
isClaimed: toDbBool(true),
accountStatus: 'active',
updatedAt: now,
})
@@ -538,7 +556,9 @@ auth.post('/google', zValidator('json', googleAuthSchema), async (c) => {
}
// Refresh user data
user = await (db as any).select().from(users).where(eq((users as any).id, user.id)).get();
user = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).id, user.id))
);
} else {
// Create new user
const firstUser = await isFirstUser();
@@ -552,7 +572,7 @@ auth.post('/google', zValidator('json', googleAuthSchema), async (c) => {
phone: null,
role: firstUser ? 'admin' : 'user',
languagePreference: null,
isClaimed: true,
isClaimed: toDbBool(true),
googleId,
rucNumber: null,
accountStatus: 'active',

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, contacts, emailSubscribers } from '../db/index.js';
import { db, dbGet, dbAll, contacts, emailSubscribers } from '../db/index.js';
import { eq, desc } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
@@ -48,11 +48,12 @@ contactsRouter.post('/subscribe', zValidator('json', subscribeSchema), async (c)
const data = c.req.valid('json');
// Check if already subscribed
const existing = await (db as any)
.select()
.from(emailSubscribers)
.where(eq((emailSubscribers as any).email, data.email))
.get();
const existing = await dbGet<any>(
(db as any)
.select()
.from(emailSubscribers)
.where(eq((emailSubscribers as any).email, data.email))
);
if (existing) {
if (existing.status === 'unsubscribed') {
@@ -87,11 +88,9 @@ contactsRouter.post('/subscribe', zValidator('json', subscribeSchema), async (c)
contactsRouter.post('/unsubscribe', zValidator('json', z.object({ email: z.string().email() })), async (c) => {
const { email } = c.req.valid('json');
const existing = await (db as any)
.select()
.from(emailSubscribers)
.where(eq((emailSubscribers as any).email, email))
.get();
const existing = await dbGet<any>(
(db as any).select().from(emailSubscribers).where(eq((emailSubscribers as any).email, email))
);
if (!existing) {
return c.json({ error: 'Email not found' }, 404);
@@ -115,7 +114,7 @@ contactsRouter.get('/', requireAuth(['admin', 'organizer']), async (c) => {
query = query.where(eq((contacts as any).status, status));
}
const result = await query.orderBy(desc((contacts as any).createdAt)).all();
const result = await dbAll(query.orderBy(desc((contacts as any).createdAt)));
return c.json({ contacts: result });
});
@@ -124,11 +123,12 @@ contactsRouter.get('/', requireAuth(['admin', 'organizer']), async (c) => {
contactsRouter.get('/:id', requireAuth(['admin', 'organizer']), async (c) => {
const id = c.req.param('id');
const contact = await (db as any)
.select()
.from(contacts)
.where(eq((contacts as any).id, id))
.get();
const contact = await dbGet(
(db as any)
.select()
.from(contacts)
.where(eq((contacts as any).id, id))
);
if (!contact) {
return c.json({ error: 'Contact not found' }, 404);
@@ -142,11 +142,9 @@ contactsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json
const id = c.req.param('id');
const data = c.req.valid('json');
const existing = await (db as any)
.select()
.from(contacts)
.where(eq((contacts as any).id, id))
.get();
const existing = await dbGet<any>(
(db as any).select().from(contacts).where(eq((contacts as any).id, id))
);
if (!existing) {
return c.json({ error: 'Contact not found' }, 404);
@@ -157,11 +155,9 @@ contactsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json
.set({ status: data.status })
.where(eq((contacts as any).id, id));
const updated = await (db as any)
.select()
.from(contacts)
.where(eq((contacts as any).id, id))
.get();
const updated = await dbGet<any>(
(db as any).select().from(contacts).where(eq((contacts as any).id, id))
);
return c.json({ contact: updated });
});
@@ -185,7 +181,7 @@ contactsRouter.get('/subscribers/list', requireAuth(['admin', 'marketing']), asy
query = query.where(eq((emailSubscribers as any).status, status));
}
const result = await query.orderBy(desc((emailSubscribers as any).createdAt)).all();
const result = await dbAll(query.orderBy(desc((emailSubscribers as any).createdAt)));
return c.json({ subscribers: result });
});

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, users, tickets, payments, events, invoices, User } from '../db/index.js';
import { db, dbGet, dbAll, users, tickets, payments, events, invoices, User } from '../db/index.js';
import { eq, desc, and, gt, sql } from 'drizzle-orm';
import { requireAuth, getUserSessions, invalidateSession, invalidateAllUserSessions, hashPassword, validatePassword } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
@@ -70,11 +70,12 @@ dashboard.put('/profile', zValidator('json', updateProfileSchema), async (c) =>
})
.where(eq((users as any).id, user.id));
const updatedUser = await (db as any)
.select()
.from(users)
.where(eq((users as any).id, user.id))
.get();
const updatedUser = await dbGet<any>(
(db as any)
.select()
.from(users)
.where(eq((users as any).id, user.id))
);
return c.json({
profile: {
@@ -95,36 +96,40 @@ dashboard.put('/profile', zValidator('json', updateProfileSchema), async (c) =>
dashboard.get('/tickets', async (c) => {
const user = (c as any).get('user') as AuthUser;
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
.orderBy(desc((tickets as any).createdAt))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
.orderBy(desc((tickets as any).createdAt))
);
// Get event details for each ticket
const ticketsWithEvents = await Promise.all(
userTickets.map(async (ticket: any) => {
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))
);
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
);
// Check for invoice
let invoice = null;
let invoice: any = null;
if (payment && payment.status === 'paid') {
invoice = await (db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
.get();
invoice = await dbGet<any>(
(db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
);
}
return {
@@ -168,40 +173,44 @@ dashboard.get('/tickets/:id', async (c) => {
const user = (c as any).get('user') as AuthUser;
const ticketId = c.req.param('id');
const ticket = await (db as any)
.select()
.from(tickets)
.where(
and(
eq((tickets as any).id, ticketId),
eq((tickets as any).userId, user.id)
const ticket = await dbGet<any>(
(db as any)
.select()
.from(tickets)
.where(
and(
eq((tickets as any).id, ticketId),
eq((tickets as any).userId, user.id)
)
)
)
.get();
);
if (!ticket) {
return c.json({ error: 'Ticket not found' }, 404);
}
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))
);
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
);
let invoice = null;
if (payment && payment.status === 'paid') {
invoice = await (db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
.get();
invoice = await dbGet(
(db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
);
}
return c.json({
@@ -222,11 +231,12 @@ dashboard.get('/next-event', async (c) => {
const now = getNow();
// Get user's tickets for upcoming events
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
);
if (userTickets.length === 0) {
return c.json({ nextEvent: null });
@@ -240,11 +250,12 @@ dashboard.get('/next-event', async (c) => {
for (const ticket of userTickets) {
if (ticket.status === 'cancelled') continue;
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) continue;
@@ -253,11 +264,12 @@ dashboard.get('/next-event', async (c) => {
if (!nextEvent || new Date(event.startDatetime) < new Date(nextEvent.startDatetime)) {
nextEvent = event;
nextTicket = ticket;
nextPayment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
.get();
nextPayment = await dbGet(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
);
}
}
}
@@ -282,11 +294,12 @@ dashboard.get('/payments', async (c) => {
const user = (c as any).get('user') as AuthUser;
// Get all user's tickets first
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
);
const ticketIds = userTickets.map((t: any) => t.id);
@@ -297,29 +310,32 @@ dashboard.get('/payments', async (c) => {
// Get all payments for user's tickets
const allPayments = [];
for (const ticketId of ticketIds) {
const ticketPayments = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
.all();
const ticketPayments = await dbAll<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
);
for (const payment of ticketPayments) {
const ticket = userTickets.find((t: any) => t.id === payment.ticketId);
const event = ticket
? await (db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
.get()
? await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
)
: null;
let invoice = null;
let invoice: any = null;
if (payment.status === 'paid') {
invoice = await (db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
.get();
invoice = await dbGet<any>(
(db as any)
.select()
.from(invoices)
.where(eq((invoices as any).paymentId, payment.id))
);
}
allPayments.push({
@@ -355,36 +371,40 @@ dashboard.get('/payments', async (c) => {
dashboard.get('/invoices', async (c) => {
const user = (c as any).get('user') as AuthUser;
const userInvoices = await (db as any)
.select()
.from(invoices)
.where(eq((invoices as any).userId, user.id))
.orderBy(desc((invoices as any).createdAt))
.all();
const userInvoices = await dbAll<any>(
(db as any)
.select()
.from(invoices)
.where(eq((invoices as any).userId, user.id))
.orderBy(desc((invoices as any).createdAt))
);
// Get payment and event details for each invoice
const invoicesWithDetails = await Promise.all(
userInvoices.map(async (invoice: any) => {
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, invoice.paymentId))
.get();
let event = null;
if (payment) {
const ticket = await (db as any)
const payment = await dbGet<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
.get();
.from(payments)
.where(eq((payments as any).id, invoice.paymentId))
);
let event: any = null;
if (payment) {
const ticket = await dbGet<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
);
if (ticket) {
event = await (db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
.get();
event = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
);
}
}
@@ -511,11 +531,12 @@ dashboard.get('/summary', async (c) => {
const membershipDays = Math.floor((now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
// Get ticket count
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, user.id))
);
const totalTickets = userTickets.length;
const confirmedTickets = userTickets.filter((t: any) => t.status === 'confirmed' || t.status === 'checked_in').length;
@@ -524,11 +545,12 @@ dashboard.get('/summary', async (c) => {
for (const ticket of userTickets) {
if (ticket.status === 'cancelled') continue;
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 && new Date(event.startDatetime) > now) {
upcomingTickets.push({ ticket, event });
@@ -540,16 +562,17 @@ dashboard.get('/summary', async (c) => {
let pendingPayments = 0;
for (const ticketId of ticketIds) {
const payment = await (db as any)
.select()
.from(payments)
.where(
and(
eq((payments as any).ticketId, ticketId),
eq((payments as any).status, 'pending_approval')
const payment = await dbGet(
(db as any)
.select()
.from(payments)
.where(
and(
eq((payments as any).ticketId, ticketId),
eq((payments as any).status, 'pending_approval')
)
)
)
.get();
);
if (payment) pendingPayments++;
}

View File

@@ -1,9 +1,8 @@
import { Hono } from 'hono';
import { db, emailTemplates, emailLogs, events, tickets } from '../db/index.js';
import { db, dbGet, dbAll, emailTemplates, emailLogs, events, tickets } from '../db/index.js';
import { eq, desc, and, sql } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow } from '../lib/utils.js';
import { nanoid } from 'nanoid';
import { getNow, generateId } from '../lib/utils.js';
import emailService from '../lib/email.js';
import { getTemplateVariables, defaultTemplates } from '../lib/emailTemplates.js';
@@ -13,11 +12,9 @@ const emailsRouter = new Hono();
// Get all email templates
emailsRouter.get('/templates', requireAuth(['admin', 'organizer']), async (c) => {
const templates = await (db as any)
.select()
.from(emailTemplates)
.orderBy(desc((emailTemplates as any).createdAt))
.all();
const templates = await dbAll<any>(
(db as any).select().from(emailTemplates).orderBy(desc((emailTemplates as any).createdAt))
);
// Parse variables JSON for each template
const parsedTemplates = templates.map((t: any) => ({
@@ -34,11 +31,12 @@ emailsRouter.get('/templates', requireAuth(['admin', 'organizer']), async (c) =>
emailsRouter.get('/templates/:id', requireAuth(['admin', 'organizer']), async (c) => {
const { id } = c.req.param();
const template = await (db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
.get();
const template = await dbGet<any>(
(db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
);
if (!template) {
return c.json({ error: 'Template not found' }, 404);
@@ -64,11 +62,9 @@ emailsRouter.post('/templates', requireAuth(['admin']), async (c) => {
}
// Check if slug already exists
const existing = await (db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).slug, slug))
.get();
const existing = await dbGet<any>(
(db as any).select().from(emailTemplates).where(eq((emailTemplates as any).slug, slug))
);
if (existing) {
return c.json({ error: 'Template with this slug already exists' }, 400);
@@ -76,7 +72,7 @@ emailsRouter.post('/templates', requireAuth(['admin']), async (c) => {
const now = getNow();
const template = {
id: nanoid(),
id: generateId(),
name,
slug,
subject,
@@ -111,11 +107,12 @@ emailsRouter.put('/templates/:id', requireAuth(['admin']), async (c) => {
const { id } = c.req.param();
const body = await c.req.json();
const existing = await (db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
.get();
const existing = await dbGet<any>(
(db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
);
if (!existing) {
return c.json({ error: 'Template not found' }, 404);
@@ -148,11 +145,12 @@ emailsRouter.put('/templates/:id', requireAuth(['admin']), async (c) => {
.set(updateData)
.where(eq((emailTemplates as any).id, id));
const updated = await (db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
.get();
const updated = await dbGet<any>(
(db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
);
return c.json({
template: {
@@ -169,11 +167,9 @@ emailsRouter.put('/templates/:id', requireAuth(['admin']), async (c) => {
emailsRouter.delete('/templates/:id', requireAuth(['admin']), async (c) => {
const { id } = c.req.param();
const template = await (db as any)
.select()
.from(emailTemplates)
.where(eq((emailTemplates as any).id, id))
.get();
const template = await dbGet<any>(
(db as any).select().from(emailTemplates).where(eq((emailTemplates as any).id, id))
);
if (!template) {
return c.json({ error: 'Template not found' }, 404);
@@ -306,11 +302,12 @@ emailsRouter.get('/logs', requireAuth(['admin', 'organizer']), async (c) => {
query = query.where(and(...conditions));
}
const logs = await query
.orderBy(desc((emailLogs as any).createdAt))
.limit(limit)
.offset(offset)
.all();
const logs = await dbAll(
query
.orderBy(desc((emailLogs as any).createdAt))
.limit(limit)
.offset(offset)
);
// Get total count
let countQuery = (db as any)
@@ -321,7 +318,7 @@ emailsRouter.get('/logs', requireAuth(['admin', 'organizer']), async (c) => {
countQuery = countQuery.where(and(...conditions));
}
const totalResult = await countQuery.get();
const totalResult = await dbGet<any>(countQuery);
const total = totalResult?.count || 0;
return c.json({
@@ -339,11 +336,9 @@ emailsRouter.get('/logs', requireAuth(['admin', 'organizer']), async (c) => {
emailsRouter.get('/logs/:id', requireAuth(['admin', 'organizer']), async (c) => {
const { id } = c.req.param();
const log = await (db as any)
.select()
.from(emailLogs)
.where(eq((emailLogs as any).id, id))
.get();
const log = await dbGet<any>(
(db as any).select().from(emailLogs).where(eq((emailLogs as any).id, id))
);
if (!log) {
return c.json({ error: 'Email log not found' }, 404);
@@ -362,22 +357,22 @@ emailsRouter.get('/stats', requireAuth(['admin', 'organizer']), async (c) => {
? (db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(baseCondition)
: (db as any).select({ count: sql<number>`count(*)` }).from(emailLogs);
const total = (await totalQuery.get())?.count || 0;
const total = (await dbGet<any>(totalQuery))?.count || 0;
const sentCondition = baseCondition
? and(baseCondition, eq((emailLogs as any).status, 'sent'))
: eq((emailLogs as any).status, 'sent');
const sent = (await (db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(sentCondition).get())?.count || 0;
const sent = (await dbGet<any>((db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(sentCondition)))?.count || 0;
const failedCondition = baseCondition
? and(baseCondition, eq((emailLogs as any).status, 'failed'))
: eq((emailLogs as any).status, 'failed');
const failed = (await (db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(failedCondition).get())?.count || 0;
const failed = (await dbGet<any>((db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(failedCondition)))?.count || 0;
const pendingCondition = baseCondition
? and(baseCondition, eq((emailLogs as any).status, 'pending'))
: eq((emailLogs as any).status, 'pending');
const pending = (await (db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(pendingCondition).get())?.count || 0;
const pending = (await dbGet<any>((db as any).select({ count: sql<number>`count(*)` }).from(emailLogs).where(pendingCondition)))?.count || 0;
return c.json({
stats: {

View File

@@ -1,10 +1,10 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, events, tickets, payments, eventPaymentOverrides, emailLogs, invoices } from '../db/index.js';
import { db, dbGet, dbAll, 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';
import { generateId, getNow, convertBooleansForDb, toDbDate } from '../lib/utils.js';
interface UserContext {
id: string;
@@ -15,6 +15,21 @@ interface UserContext {
const eventsRouter = new Hono<{ Variables: { user: UserContext } }>();
// Helper to normalize event data for API response
// PostgreSQL decimal returns strings, booleans are stored as integers
function normalizeEvent(event: any) {
if (!event) return event;
return {
...event,
// Convert price from string/decimal to clean number
price: typeof event.price === 'string' ? parseFloat(event.price) : Number(event.price),
// Convert capacity from string to number if needed
capacity: typeof event.capacity === 'string' ? parseInt(event.capacity, 10) : Number(event.capacity),
// Convert boolean integers to actual booleans for frontend
externalBookingEnabled: Boolean(event.externalBookingEnabled),
};
}
// Custom validation error handler
const validationHook = (result: any, c: any) => {
if (!result.success) {
@@ -23,6 +38,27 @@ const validationHook = (result: any, c: any) => {
}
};
// Helper to parse price from string (handles both "45000" and "41,44" formats)
const parsePrice = (val: unknown): number => {
if (typeof val === 'number') return val;
if (typeof val === 'string') {
// Replace comma with dot for decimal parsing (European format)
const normalized = val.replace(',', '.');
const parsed = parseFloat(normalized);
return isNaN(parsed) ? 0 : parsed;
}
return 0;
};
// Helper to normalize boolean (handles true/false and 0/1)
const normalizeBoolean = (val: unknown): boolean => {
if (typeof val === 'boolean') return val;
if (typeof val === 'number') return val !== 0;
if (val === 'true') return true;
if (val === 'false') return false;
return false;
};
const baseEventSchema = z.object({
title: z.string().min(1),
titleEs: z.string().optional().nullable(),
@@ -34,14 +70,15 @@ const baseEventSchema = z.object({
endDatetime: z.string().optional().nullable(),
location: z.string().min(1),
locationUrl: z.string().url().optional().nullable().or(z.literal('')),
price: z.number().min(0).default(0),
// Accept price as number or string (handles "45000" and "41,44" formats)
price: z.union([z.number(), z.string()]).transform(parsePrice).pipe(z.number().min(0)).default(0),
currency: z.string().default('PYG'),
capacity: z.number().min(1).default(50),
capacity: z.union([z.number(), z.string()]).transform((val) => typeof val === 'string' ? parseInt(val, 10) || 50 : val).pipe(z.number().min(1)).default(50),
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),
// External booking support - accept boolean or number (0/1 from DB)
externalBookingEnabled: z.union([z.boolean(), z.number()]).transform(normalizeBoolean).default(false),
externalBookingUrl: z.string().url().optional().nullable().or(z.literal('')),
});
@@ -94,26 +131,28 @@ eventsRouter.get('/', async (c) => {
);
}
const result = await query.orderBy(desc((events as any).startDatetime)).all();
const result = await dbAll(query.orderBy(desc((events as any).startDatetime)));
// Get ticket counts for each event
const eventsWithCounts = await Promise.all(
result.map(async (event: any) => {
const ticketCount = await (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 ticketCount = 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')
)
)
)
.get();
);
const normalized = normalizeEvent(event);
return {
...event,
...normalized,
bookedCount: ticketCount?.count || 0,
availableSeats: event.capacity - (ticketCount?.count || 0),
availableSeats: normalized.capacity - (ticketCount?.count || 0),
};
})
);
@@ -125,29 +164,33 @@ eventsRouter.get('/', async (c) => {
eventsRouter.get('/:id', async (c) => {
const id = c.req.param('id');
const event = await (db as any).select().from(events).where(eq((events as any).id, id)).get();
const event = await dbGet<any>(
(db as any).select().from(events).where(eq((events as any).id, id))
);
if (!event) {
return c.json({ error: 'Event not found' }, 404);
}
// Get ticket count
const ticketCount = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(tickets)
.where(
and(
eq((tickets as any).eventId, id),
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, id),
eq((tickets as any).status, 'confirmed')
)
)
)
.get();
);
const normalized = normalizeEvent(event);
return c.json({
event: {
...event,
...normalized,
bookedCount: ticketCount?.count || 0,
availableSeats: event.capacity - (ticketCount?.count || 0),
availableSeats: normalized.capacity - (ticketCount?.count || 0),
},
});
});
@@ -156,39 +199,42 @@ eventsRouter.get('/:id', async (c) => {
eventsRouter.get('/next/upcoming', async (c) => {
const now = getNow();
const event = await (db as any)
.select()
.from(events)
.where(
and(
eq((events as any).status, 'published'),
gte((events as any).startDatetime, now)
const event = await dbGet<any>(
(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(1)
.get();
.orderBy((events as any).startDatetime)
.limit(1)
);
if (!event) {
return c.json({ event: null });
}
const ticketCount = await (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 ticketCount = 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')
)
)
)
.get();
);
const normalized = normalizeEvent(event);
return c.json({
event: {
...event,
...normalized,
bookedCount: ticketCount?.count || 0,
availableSeats: event.capacity - (ticketCount?.count || 0),
availableSeats: normalized.capacity - (ticketCount?.count || 0),
},
});
});
@@ -200,16 +246,22 @@ eventsRouter.post('/', requireAuth(['admin', 'organizer']), zValidator('json', c
const now = getNow();
const id = generateId();
// Convert data for database compatibility
const dbData = convertBooleansForDb(data);
const newEvent = {
id,
...data,
...dbData,
startDatetime: toDbDate(data.startDatetime),
endDatetime: data.endDatetime ? toDbDate(data.endDatetime) : null,
createdAt: now,
updatedAt: now,
};
await (db as any).insert(events).values(newEvent);
return c.json({ event: newEvent }, 201);
// Return normalized event data
return c.json({ event: normalizeEvent(newEvent) }, 201);
});
// Update event (admin/organizer only)
@@ -217,46 +269,64 @@ eventsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json',
const id = c.req.param('id');
const data = c.req.valid('json');
const existing = await (db as any).select().from(events).where(eq((events as any).id, id)).get();
const existing = await dbGet(
(db as any).select().from(events).where(eq((events as any).id, id))
);
if (!existing) {
return c.json({ error: 'Event not found' }, 404);
}
const now = getNow();
// Convert data for database compatibility
const updateData: Record<string, any> = { ...convertBooleansForDb(data), updatedAt: now };
// Convert datetime fields if present
if (data.startDatetime) {
updateData.startDatetime = toDbDate(data.startDatetime);
}
if (data.endDatetime !== undefined) {
updateData.endDatetime = data.endDatetime ? toDbDate(data.endDatetime) : null;
}
await (db as any)
.update(events)
.set({ ...data, updatedAt: now })
.set(updateData)
.where(eq((events as any).id, id));
const updated = await (db as any).select().from(events).where(eq((events as any).id, id)).get();
const updated = await dbGet(
(db as any).select().from(events).where(eq((events as any).id, id))
);
return c.json({ event: updated });
return c.json({ event: normalizeEvent(updated) });
});
// Delete event (admin only)
eventsRouter.delete('/:id', requireAuth(['admin']), async (c) => {
const id = c.req.param('id');
const existing = await (db as any).select().from(events).where(eq((events as any).id, id)).get();
const existing = await dbGet(
(db as any).select().from(events).where(eq((events as any).id, id))
);
if (!existing) {
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();
const eventTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).eventId, id))
);
// 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();
const ticketPayments = await dbAll<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticket.id))
);
// Delete invoices for each payment
for (const payment of ticketPayments) {
@@ -289,11 +359,12 @@ eventsRouter.delete('/:id', requireAuth(['admin']), async (c) => {
eventsRouter.get('/:id/attendees', requireAuth(['admin', 'organizer', 'staff']), async (c) => {
const id = c.req.param('id');
const attendees = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).eventId, id))
.all();
const attendees = await dbAll(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).eventId, id))
);
return c.json({ attendees });
});
@@ -302,7 +373,9 @@ eventsRouter.get('/:id/attendees', requireAuth(['admin', 'organizer', 'staff']),
eventsRouter.post('/:id/duplicate', requireAuth(['admin', 'organizer']), async (c) => {
const id = c.req.param('id');
const existing = await (db as any).select().from(events).where(eq((events as any).id, id)).get();
const existing = await dbGet<any>(
(db as any).select().from(events).where(eq((events as any).id, id))
);
if (!existing) {
return c.json({ error: 'Event not found' }, 404);
}
@@ -319,7 +392,7 @@ eventsRouter.post('/:id/duplicate', requireAuth(['admin', 'organizer']), async (
descriptionEs: existing.descriptionEs,
shortDescription: existing.shortDescription,
shortDescriptionEs: existing.shortDescriptionEs,
startDatetime: existing.startDatetime,
startDatetime: existing.startDatetime, // Already in DB format from existing record
endDatetime: existing.endDatetime,
location: existing.location,
locationUrl: existing.locationUrl,
@@ -328,7 +401,7 @@ eventsRouter.post('/:id/duplicate', requireAuth(['admin', 'organizer']), async (
capacity: existing.capacity,
status: 'draft',
bannerUrl: existing.bannerUrl,
externalBookingEnabled: existing.externalBookingEnabled || false,
externalBookingEnabled: existing.externalBookingEnabled ?? 0, // Already in DB format (0/1)
externalBookingUrl: existing.externalBookingUrl,
createdAt: now,
updatedAt: now,
@@ -336,7 +409,7 @@ eventsRouter.post('/:id/duplicate', requireAuth(['admin', 'organizer']), async (
await (db as any).insert(events).values(duplicatedEvent);
return c.json({ event: duplicatedEvent, message: 'Event duplicated successfully' }, 201);
return c.json({ event: normalizeEvent(duplicatedEvent), message: 'Event duplicated successfully' }, 201);
});
export default eventsRouter;

View File

@@ -0,0 +1,393 @@
import { Hono } from 'hono';
import { db, dbGet, dbAll, legalPages } from '../db/index.js';
import { eq, desc } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow, generateId } from '../lib/utils.js';
import fs from 'fs';
import path from 'path';
const legalPagesRouter = new Hono();
// Helper: Convert plain text to simple markdown
// Preserves paragraphs and line breaks, nothing fancy
function textToMarkdown(text: string): string {
if (!text) return '';
// Split into paragraphs (double newlines)
const paragraphs = text.split(/\n\s*\n/);
// Process each paragraph
const processed = paragraphs.map(para => {
// Replace single newlines with double spaces + newline for markdown line breaks
return para.trim().replace(/\n/g, ' \n');
});
// Join paragraphs with double newlines
return processed.join('\n\n');
}
// Helper: Convert markdown to plain text for editing
function markdownToText(markdown: string): string {
if (!markdown) return '';
let text = markdown;
// Remove markdown heading markers (# ## ###)
text = text.replace(/^#{1,6}\s+/gm, '');
// Remove horizontal rules
text = text.replace(/^---+$/gm, '');
// Remove bold/italic markers
text = text.replace(/\*\*([^*]+)\*\*/g, '$1');
text = text.replace(/\*([^*]+)\*/g, '$1');
text = text.replace(/__([^_]+)__/g, '$1');
text = text.replace(/_([^_]+)_/g, '$1');
// Remove list markers (preserve text)
text = text.replace(/^\s*[\*\-\+]\s+/gm, '');
text = text.replace(/^\s*\d+\.\s+/gm, '');
// Remove link formatting [text](url) -> text
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// Remove double-space line breaks
text = text.replace(/ \n/g, '\n');
// Normalize multiple newlines
text = text.replace(/\n{3,}/g, '\n\n');
return text.trim();
}
// Helper: Extract title from markdown content
function extractTitleFromMarkdown(content: string): string {
if (!content) return 'Untitled';
const match = content.match(/^#\s+(.+?)(?:\s*[-]\s*.+)?$/m);
if (match) {
return match[1].trim();
}
// Fallback to first line
const firstLine = content.split('\n')[0].replace(/^#+\s*/, '').trim();
return firstLine || 'Untitled';
}
// Helper: Get legal directory path
function getLegalDir(): string {
// When running from backend, legal folder is in frontend
const possiblePaths = [
path.join(process.cwd(), '../frontend/legal'),
path.join(process.cwd(), 'frontend/legal'),
path.join(process.cwd(), 'legal'),
];
for (const p of possiblePaths) {
if (fs.existsSync(p)) {
return p;
}
}
return possiblePaths[0]; // Default
}
// Helper: Convert filename to slug
function fileNameToSlug(fileName: string): string {
return fileName.replace('.md', '').replace(/_/g, '-');
}
// Title map for localization
const titleMap: Record<string, { en: string; es: string }> = {
'privacy-policy': { en: 'Privacy Policy', es: 'Política de Privacidad' },
'terms-policy': { en: 'Terms & Conditions', es: 'Términos y Condiciones' },
'refund-cancelation-policy': { en: 'Refund & Cancellation Policy', es: 'Política de Reembolso y Cancelación' },
};
// Helper: Get localized content with fallback
// If requested locale content is missing, fallback to the other locale
function getLocalizedContent(page: any, locale: string = 'en'): { title: string; contentMarkdown: string } {
const isSpanish = locale === 'es';
// Title: prefer requested locale, fallback to other
let title: string;
if (isSpanish) {
title = page.titleEs || page.title;
} else {
title = page.title || page.titleEs;
}
// Content: prefer requested locale, fallback to other
let contentMarkdown: string;
if (isSpanish) {
contentMarkdown = page.contentMarkdownEs || page.contentMarkdown;
} else {
contentMarkdown = page.contentMarkdown || page.contentMarkdownEs;
}
return { title, contentMarkdown };
}
// ==================== Public Routes ====================
// Get all legal pages (public, for footer/navigation)
legalPagesRouter.get('/', async (c) => {
const locale = c.req.query('locale') || 'en';
const pages = await dbAll<any>(
(db as any)
.select({
id: (legalPages as any).id,
slug: (legalPages as any).slug,
title: (legalPages as any).title,
titleEs: (legalPages as any).titleEs,
updatedAt: (legalPages as any).updatedAt,
})
.from(legalPages)
.orderBy((legalPages as any).slug)
);
// Return pages with localized title
const localizedPages = pages.map((page: any) => ({
id: page.id,
slug: page.slug,
title: locale === 'es' ? (page.titleEs || page.title) : (page.title || page.titleEs),
updatedAt: page.updatedAt,
}));
return c.json({ pages: localizedPages });
});
// Get single legal page (public, for rendering)
legalPagesRouter.get('/:slug', async (c) => {
const { slug } = c.req.param();
const locale = c.req.query('locale') || 'en';
// First try to get from database
const page = await dbGet<any>(
(db as any).select().from(legalPages).where(eq((legalPages as any).slug, slug))
);
if (page) {
// Get localized content with fallback
const { title, contentMarkdown } = getLocalizedContent(page, locale);
return c.json({
page: {
id: page.id,
slug: page.slug,
title,
contentMarkdown,
updatedAt: page.updatedAt,
source: 'database',
}
});
}
// Fallback to filesystem
const legalDir = getLegalDir();
const fileName = slug.replace(/-/g, '_') + '.md';
const filePath = path.join(legalDir, fileName);
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8');
const titles = titleMap[slug];
const title = locale === 'es'
? (titles?.es || titles?.en || slug)
: (titles?.en || titles?.es || slug);
return c.json({
page: {
slug,
title,
contentMarkdown: content,
source: 'filesystem',
}
});
}
return c.json({ error: 'Legal page not found' }, 404);
});
// ==================== Admin Routes ====================
// Get all legal pages for admin (with full content)
legalPagesRouter.get('/admin/list', requireAuth(['admin']), async (c) => {
const pages = await dbAll<any>(
(db as any)
.select()
.from(legalPages)
.orderBy((legalPages as any).slug)
);
// Add flags to indicate which languages have content
const pagesWithFlags = pages.map((page: any) => ({
...page,
hasEnglish: Boolean(page.contentText),
hasSpanish: Boolean(page.contentTextEs),
}));
return c.json({ pages: pagesWithFlags });
});
// Get single legal page for editing (admin)
legalPagesRouter.get('/admin/:slug', requireAuth(['admin']), async (c) => {
const { slug } = c.req.param();
const page = await dbGet<any>(
(db as any).select().from(legalPages).where(eq((legalPages as any).slug, slug))
);
if (!page) {
return c.json({ error: 'Legal page not found' }, 404);
}
return c.json({
page: {
...page,
hasEnglish: Boolean(page.contentText),
hasSpanish: Boolean(page.contentTextEs),
}
});
});
// Update legal page (admin only)
// Note: No creation or deletion - only updates are allowed
// Accepts markdown content from rich text editor
legalPagesRouter.put('/admin/:slug', requireAuth(['admin']), async (c) => {
const { slug } = c.req.param();
const user = (c as any).get('user');
const body = await c.req.json();
// Accept both contentText (legacy plain text) and contentMarkdown (from rich text editor)
const { contentText, contentTextEs, contentMarkdown, contentMarkdownEs, title, titleEs } = body;
// Determine content - prefer markdown if provided, fall back to contentText
const enContent = contentMarkdown !== undefined ? contentMarkdown : contentText;
const esContent = contentMarkdownEs !== undefined ? contentMarkdownEs : contentTextEs;
// At least one content field is required
if (!enContent && !esContent) {
return c.json({ error: 'At least one language content is required' }, 400);
}
const existing = await dbGet(
(db as any)
.select()
.from(legalPages)
.where(eq((legalPages as any).slug, slug))
);
if (!existing) {
return c.json({ error: 'Legal page not found' }, 404);
}
const updateData: any = {
updatedAt: getNow(),
updatedBy: user?.id || null,
};
// Update English content if provided
if (enContent !== undefined) {
// Store markdown directly (from rich text editor)
updateData.contentMarkdown = enContent;
// Derive plain text from markdown
updateData.contentText = markdownToText(enContent);
}
// Update Spanish content if provided
if (esContent !== undefined) {
updateData.contentMarkdownEs = esContent || null;
updateData.contentTextEs = esContent ? markdownToText(esContent) : null;
}
// Allow updating titles
if (title !== undefined) {
updateData.title = title;
}
if (titleEs !== undefined) {
updateData.titleEs = titleEs || null;
}
await (db as any)
.update(legalPages)
.set(updateData)
.where(eq((legalPages as any).slug, slug));
const updated = await dbGet<any>(
(db as any).select().from(legalPages).where(eq((legalPages as any).slug, slug))
);
return c.json({
page: {
...updated,
hasEnglish: Boolean(updated.contentText),
hasSpanish: Boolean(updated.contentTextEs),
},
message: 'Legal page updated successfully'
});
});
// Seed legal pages from filesystem (admin only)
// This imports markdown files and converts them to plain text for editing
// Files are imported as English content - Spanish can be added manually
legalPagesRouter.post('/admin/seed', requireAuth(['admin']), async (c) => {
const user = (c as any).get('user');
// Check if table already has data
const existingPages = await dbAll(
(db as any)
.select({ id: (legalPages as any).id })
.from(legalPages)
.limit(1)
);
if (existingPages.length > 0) {
return c.json({
message: 'Legal pages already seeded. Use update to modify pages.',
seeded: 0
});
}
const legalDir = getLegalDir();
if (!fs.existsSync(legalDir)) {
return c.json({ error: `Legal directory not found: ${legalDir}` }, 400);
}
const files = fs.readdirSync(legalDir).filter(f => f.endsWith('.md'));
const seededPages: string[] = [];
const now = getNow();
for (const file of files) {
const filePath = path.join(legalDir, file);
const contentMarkdown = fs.readFileSync(filePath, 'utf-8');
const slug = fileNameToSlug(file);
const contentText = markdownToText(contentMarkdown);
const titles = titleMap[slug];
const title = titles?.en || extractTitleFromMarkdown(contentMarkdown);
const titleEs = titles?.es || null;
await (db as any).insert(legalPages).values({
id: generateId(),
slug,
title,
titleEs,
contentText, // English plain text
contentTextEs: null, // Spanish to be added manually
contentMarkdown, // English markdown
contentMarkdownEs: null, // Spanish to be generated when contentTextEs is set
updatedAt: now,
updatedBy: user?.id || null,
createdAt: now,
});
seededPages.push(slug);
}
return c.json({
message: `Successfully seeded ${seededPages.length} legal pages (English content imported, Spanish can be added via editor)`,
seeded: seededPages.length,
pages: seededPages,
});
});
export default legalPagesRouter;

View File

@@ -1,6 +1,6 @@
import { Hono } from 'hono';
import { streamSSE } from 'hono/streaming';
import { db, tickets, payments } from '../db/index.js';
import { db, dbGet, tickets, payments } from '../db/index.js';
import { eq } from 'drizzle-orm';
import { getNow } from '../lib/utils.js';
import { verifyWebhookPayment, getPaymentStatus } from '../lib/lnbits.js';
@@ -157,11 +157,9 @@ async function handlePaymentComplete(ticketId: string, paymentHash: string) {
const now = getNow();
// Check if already confirmed to avoid duplicate updates
const existingTicket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, ticketId))
.get();
const existingTicket = await dbGet<any>(
(db as any).select().from(tickets).where(eq((tickets as any).id, ticketId))
);
if (existingTicket?.status === 'confirmed') {
console.log(`Ticket ${ticketId} already confirmed, skipping update`);
@@ -188,11 +186,12 @@ async function handlePaymentComplete(ticketId: string, paymentHash: string) {
console.log(`Ticket ${ticketId} confirmed via Lightning payment (hash: ${paymentHash})`);
// Get payment for sending receipt
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
);
// Send confirmation emails asynchronously
Promise.all([
@@ -211,11 +210,9 @@ lnbitsRouter.get('/stream/:ticketId', async (c) => {
const ticketId = c.req.param('ticketId');
// Verify ticket exists
const ticket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, ticketId))
.get();
const ticket = await dbGet<any>(
(db as any).select().from(tickets).where(eq((tickets as any).id, ticketId))
);
if (!ticket) {
return c.json({ error: 'Ticket not found' }, 404);
@@ -227,11 +224,9 @@ lnbitsRouter.get('/stream/:ticketId', async (c) => {
}
// Get payment to start background checker
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
.get();
const payment = await dbGet<any>(
(db as any).select().from(payments).where(eq((payments as any).ticketId, ticketId))
);
// Start background checker if not already running
if (payment?.reference && !activeCheckers.has(ticketId)) {
@@ -290,21 +285,23 @@ lnbitsRouter.get('/stream/:ticketId', async (c) => {
lnbitsRouter.get('/status/:ticketId', async (c) => {
const ticketId = c.req.param('ticketId');
const ticket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, ticketId))
.get();
const ticket = await dbGet<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, ticketId))
);
if (!ticket) {
return c.json({ error: 'Ticket not found' }, 404);
}
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).ticketId, ticketId))
);
return c.json({
ticketStatus: ticket.status,

View File

@@ -1,5 +1,5 @@
import { Hono } from 'hono';
import { db, media } from '../db/index.js';
import { db, dbGet, dbAll, media } from '../db/index.js';
import { eq } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
@@ -85,11 +85,9 @@ mediaRouter.post('/upload', requireAuth(['admin', 'organizer']), async (c) => {
mediaRouter.get('/:id', async (c) => {
const id = c.req.param('id');
const mediaRecord = await (db as any)
.select()
.from(media)
.where(eq((media as any).id, id))
.get();
const mediaRecord = await dbGet<any>(
(db as any).select().from(media).where(eq((media as any).id, id))
);
if (!mediaRecord) {
return c.json({ error: 'Media not found' }, 404);
@@ -102,11 +100,9 @@ mediaRouter.get('/:id', async (c) => {
mediaRouter.delete('/:id', requireAuth(['admin', 'organizer']), async (c) => {
const id = c.req.param('id');
const mediaRecord = await (db as any)
.select()
.from(media)
.where(eq((media as any).id, id))
.get();
const mediaRecord = await dbGet<any>(
(db as any).select().from(media).where(eq((media as any).id, id))
);
if (!mediaRecord) {
return c.json({ error: 'Media not found' }, 404);
@@ -142,7 +138,7 @@ mediaRouter.get('/', requireAuth(['admin', 'organizer']), async (c) => {
query = query.where(eq((media as any).relatedId, relatedId));
}
const result = await query.all();
const result = await dbAll(query);
return c.json({ media: result });
});

View File

@@ -1,10 +1,10 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, paymentOptions, eventPaymentOverrides, events } from '../db/index.js';
import { db, dbGet, paymentOptions, eventPaymentOverrides, events } from '../db/index.js';
import { eq } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
import { generateId, getNow, convertBooleansForDb } from '../lib/utils.js';
const paymentOptionsRouter = new Hono();
@@ -52,10 +52,9 @@ const updateEventOverridesSchema = z.object({
// Get global payment options
paymentOptionsRouter.get('/', requireAuth(['admin']), async (c) => {
const options = await (db as any)
.select()
.from(paymentOptions)
.get();
const options = await dbGet<any>(
(db as any).select().from(paymentOptions)
);
// If no options exist yet, return defaults
if (!options) {
@@ -92,17 +91,21 @@ paymentOptionsRouter.put('/', requireAuth(['admin']), zValidator('json', updateP
const now = getNow();
// Check if options exist
const existing = await (db as any)
.select()
.from(paymentOptions)
.get();
const existing = await dbGet<any>(
(db as any)
.select()
.from(paymentOptions)
);
// Convert boolean fields for database compatibility
const dbData = convertBooleansForDb(data);
if (existing) {
// Update existing
await (db as any)
.update(paymentOptions)
.set({
...data,
...dbData,
updatedAt: now,
updatedBy: user.id,
})
@@ -112,16 +115,17 @@ paymentOptionsRouter.put('/', requireAuth(['admin']), zValidator('json', updateP
const id = generateId();
await (db as any).insert(paymentOptions).values({
id,
...data,
...dbData,
updatedAt: now,
updatedBy: user.id,
});
}
const updated = await (db as any)
.select()
.from(paymentOptions)
.get();
const updated = await dbGet(
(db as any)
.select()
.from(paymentOptions)
);
return c.json({ paymentOptions: updated, message: 'Payment options updated successfully' });
});
@@ -131,28 +135,31 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
const eventId = c.req.param('eventId');
// Get the event first to verify it exists
const event = await (db as any)
.select()
.from(events)
.where(eq((events as any).id, eventId))
.get();
const event = await dbGet(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, eventId))
);
if (!event) {
return c.json({ error: 'Event not found' }, 404);
}
// Get global options
const globalOptions = await (db as any)
.select()
.from(paymentOptions)
.get();
const globalOptions = await dbGet<any>(
(db as any)
.select()
.from(paymentOptions)
);
// Get event overrides
const overrides = await (db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
.get();
const overrides = await dbGet<any>(
(db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
);
// Merge global with overrides (override takes precedence if not null)
const defaults = {
@@ -206,11 +213,9 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
paymentOptionsRouter.get('/event/:eventId/overrides', requireAuth(['admin', 'organizer']), async (c) => {
const eventId = c.req.param('eventId');
const overrides = await (db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
.get();
const overrides = await dbGet<any>(
(db as any).select().from(eventPaymentOverrides).where(eq((eventPaymentOverrides as any).eventId, eventId))
);
return c.json({ overrides: overrides || null });
});
@@ -222,28 +227,27 @@ paymentOptionsRouter.put('/event/:eventId/overrides', requireAuth(['admin', 'org
const now = getNow();
// Verify event exists
const event = await (db as any)
.select()
.from(events)
.where(eq((events as any).id, eventId))
.get();
const event = await dbGet<any>(
(db as any).select().from(events).where(eq((events as any).id, eventId))
);
if (!event) {
return c.json({ error: 'Event not found' }, 404);
}
// Check if overrides exist
const existing = await (db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
.get();
const existing = await dbGet<any>(
(db as any).select().from(eventPaymentOverrides).where(eq((eventPaymentOverrides as any).eventId, eventId))
);
// Convert boolean fields for database compatibility
const dbData = convertBooleansForDb(data);
if (existing) {
await (db as any)
.update(eventPaymentOverrides)
.set({
...data,
...dbData,
updatedAt: now,
})
.where(eq((eventPaymentOverrides as any).id, existing.id));
@@ -252,17 +256,18 @@ paymentOptionsRouter.put('/event/:eventId/overrides', requireAuth(['admin', 'org
await (db as any).insert(eventPaymentOverrides).values({
id,
eventId,
...data,
...dbData,
createdAt: now,
updatedAt: now,
});
}
const updated = await (db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
.get();
const updated = await dbGet(
(db as any)
.select()
.from(eventPaymentOverrides)
.where(eq((eventPaymentOverrides as any).eventId, eventId))
);
return c.json({ overrides: updated, message: 'Event payment overrides updated successfully' });
});

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, payments, tickets, events } from '../db/index.js';
import { db, dbGet, dbAll, payments, tickets, events } from '../db/index.js';
import { eq, desc, and, or, sql } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow } from '../lib/utils.js';
@@ -30,11 +30,12 @@ paymentsRouter.get('/', requireAuth(['admin']), async (c) => {
const pendingApproval = c.req.query('pendingApproval');
// Get all payments with their associated tickets
let allPayments = await (db as any)
.select()
.from(payments)
.orderBy(desc((payments as any).createdAt))
.all();
let allPayments = await dbAll<any>(
(db as any)
.select()
.from(payments)
.orderBy(desc((payments as any).createdAt))
);
// Filter by status
if (status) {
@@ -54,19 +55,21 @@ paymentsRouter.get('/', requireAuth(['admin']), async (c) => {
// Enrich with ticket and event data
const enrichedPayments = await Promise.all(
allPayments.map(async (payment: any) => {
const ticket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
.get();
let event = null;
if (ticket) {
event = await (db as any)
const ticket = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
.get();
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
);
let event: any = null;
if (ticket) {
event = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
);
}
return {
@@ -93,29 +96,32 @@ paymentsRouter.get('/', requireAuth(['admin']), async (c) => {
// Get payments pending approval (admin dashboard view)
paymentsRouter.get('/pending-approval', requireAuth(['admin', 'organizer']), async (c) => {
const pendingPayments = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).status, 'pending_approval'))
.orderBy(desc((payments as any).userMarkedPaidAt))
.all();
const pendingPayments = await dbAll<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).status, 'pending_approval'))
.orderBy(desc((payments as any).userMarkedPaidAt))
);
// Enrich with ticket and event data
const enrichedPayments = await Promise.all(
pendingPayments.map(async (payment: any) => {
const ticket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
.get();
let event = null;
if (ticket) {
event = await (db as any)
const ticket = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
.get();
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
);
let event: any = null;
if (ticket) {
event = await dbGet<any>(
(db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
);
}
return {
@@ -144,22 +150,24 @@ paymentsRouter.get('/pending-approval', requireAuth(['admin', 'organizer']), asy
paymentsRouter.get('/:id', requireAuth(['admin', 'organizer']), async (c) => {
const id = c.req.param('id');
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!payment) {
return c.json({ error: 'Payment not found' }, 404);
}
// Get associated ticket
const ticket = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
.get();
const ticket = await dbGet(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).id, payment.ticketId))
);
return c.json({ payment: { ...payment, ticket } });
});
@@ -170,11 +178,12 @@ paymentsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json
const data = c.req.valid('json');
const user = (c as any).get('user');
const existing = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const existing = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!existing) {
return c.json({ error: 'Payment not found' }, 404);
@@ -211,11 +220,12 @@ paymentsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json
});
}
const updated = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const updated = await dbGet(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
return c.json({ payment: updated });
});
@@ -226,11 +236,12 @@ paymentsRouter.post('/:id/approve', requireAuth(['admin', 'organizer']), zValida
const { adminNote } = c.req.valid('json');
const user = (c as any).get('user');
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!payment) {
return c.json({ error: 'Payment not found' }, 404);
@@ -269,11 +280,12 @@ paymentsRouter.post('/:id/approve', requireAuth(['admin', 'organizer']), zValida
console.error('[Email] Failed to send confirmation emails:', err);
});
const updated = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const updated = await dbGet(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
return c.json({ payment: updated, message: 'Payment approved successfully' });
});
@@ -284,11 +296,12 @@ paymentsRouter.post('/:id/reject', requireAuth(['admin', 'organizer']), zValidat
const { adminNote } = c.req.valid('json');
const user = (c as any).get('user');
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!payment) {
return c.json({ error: 'Payment not found' }, 404);
@@ -327,11 +340,12 @@ paymentsRouter.post('/:id/reject', requireAuth(['admin', 'organizer']), zValidat
});
}
const updated = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const updated = await dbGet(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
return c.json({ payment: updated, message: 'Payment rejected and booking cancelled' });
});
@@ -342,11 +356,12 @@ paymentsRouter.post('/:id/note', requireAuth(['admin', 'organizer']), async (c)
const body = await c.req.json();
const { adminNote } = body;
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!payment) {
return c.json({ error: 'Payment not found' }, 404);
@@ -362,11 +377,12 @@ paymentsRouter.post('/:id/note', requireAuth(['admin', 'organizer']), async (c)
})
.where(eq((payments as any).id, id));
const updated = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const updated = await dbGet(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
return c.json({ payment: updated, message: 'Note updated' });
});
@@ -375,11 +391,12 @@ paymentsRouter.post('/:id/note', requireAuth(['admin', 'organizer']), async (c)
paymentsRouter.post('/:id/refund', requireAuth(['admin']), async (c) => {
const id = c.req.param('id');
const payment = await (db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
.get();
const payment = await dbGet<any>(
(db as any)
.select()
.from(payments)
.where(eq((payments as any).id, id))
);
if (!payment) {
return c.json({ error: 'Payment not found' }, 404);
@@ -426,7 +443,7 @@ paymentsRouter.post('/webhook', async (c) => {
// Get payment statistics (admin)
paymentsRouter.get('/stats/overview', requireAuth(['admin']), async (c) => {
const allPayments = await (db as any).select().from(payments).all();
const allPayments = await dbAll<any>((db as any).select().from(payments));
const stats = {
total: allPayments.length,

View File

@@ -1,10 +1,10 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, siteSettings } from '../db/index.js';
import { db, dbGet, siteSettings } from '../db/index.js';
import { eq } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { generateId, getNow } from '../lib/utils.js';
import { generateId, getNow, toDbBool } from '../lib/utils.js';
interface UserContext {
id: string;
@@ -34,7 +34,9 @@ const updateSiteSettingsSchema = z.object({
// Get site settings (public - needed for frontend timezone)
siteSettingsRouter.get('/', async (c) => {
const settings = await (db as any).select().from(siteSettings).limit(1).get();
const settings = await dbGet(
(db as any).select().from(siteSettings).limit(1)
);
if (!settings) {
// Return default settings if none exist
@@ -95,7 +97,9 @@ siteSettingsRouter.put('/', requireAuth(['admin']), zValidator('json', updateSit
const now = getNow();
// Check if settings exist
const existing = await (db as any).select().from(siteSettings).limit(1).get();
const existing = await dbGet<any>(
(db as any).select().from(siteSettings).limit(1)
);
if (!existing) {
// Create new settings record
@@ -112,7 +116,7 @@ siteSettingsRouter.put('/', requireAuth(['admin']), zValidator('json', updateSit
instagramUrl: data.instagramUrl || null,
twitterUrl: data.twitterUrl || null,
linkedinUrl: data.linkedinUrl || null,
maintenanceMode: data.maintenanceMode || false,
maintenanceMode: toDbBool(data.maintenanceMode || false),
maintenanceMessage: data.maintenanceMessage || null,
maintenanceMessageEs: data.maintenanceMessageEs || null,
updatedAt: now,
@@ -125,18 +129,24 @@ siteSettingsRouter.put('/', requireAuth(['admin']), zValidator('json', updateSit
}
// Update existing settings
const updateData = {
const updateData: Record<string, any> = {
...data,
updatedAt: now,
updatedBy: user.id,
};
// Convert maintenanceMode boolean to appropriate format for database
if (typeof data.maintenanceMode === 'boolean') {
updateData.maintenanceMode = toDbBool(data.maintenanceMode);
}
await (db as any)
.update(siteSettings)
.set(updateData)
.where(eq((siteSettings as any).id, existing.id));
const updated = await (db as any).select().from(siteSettings).where(eq((siteSettings as any).id, existing.id)).get();
const updated = await dbGet(
(db as any).select().from(siteSettings).where(eq((siteSettings as any).id, existing.id))
);
return c.json({ settings: updated, message: 'Settings updated successfully' });
});

View File

@@ -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 });
});

View File

@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, users, tickets, events, payments } from '../db/index.js';
import { db, dbGet, dbAll, users, tickets, events, payments } from '../db/index.js';
import { eq, desc, sql } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow } from '../lib/utils.js';
@@ -40,7 +40,7 @@ usersRouter.get('/', requireAuth(['admin']), async (c) => {
query = query.where(eq((users as any).role, role));
}
const result = await query.orderBy(desc((users as any).createdAt)).all();
const result = await dbAll(query.orderBy(desc((users as any).createdAt)));
return c.json({ users: result });
});
@@ -55,19 +55,20 @@ usersRouter.get('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing',
return c.json({ error: 'Forbidden' }, 403);
}
const user = await (db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
createdAt: (users as any).createdAt,
})
.from(users)
.where(eq((users as any).id, id))
.get();
const user = await dbGet(
(db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
createdAt: (users as any).createdAt,
})
.from(users)
.where(eq((users as any).id, id))
);
if (!user) {
return c.json({ error: 'User not found' }, 404);
@@ -92,7 +93,9 @@ usersRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing',
delete data.role;
}
const existing = await (db as any).select().from(users).where(eq((users as any).id, id)).get();
const existing = await dbGet(
(db as any).select().from(users).where(eq((users as any).id, id))
);
if (!existing) {
return c.json({ error: 'User not found' }, 404);
}
@@ -102,18 +105,19 @@ usersRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing',
.set({ ...data, updatedAt: getNow() })
.where(eq((users as any).id, id));
const updated = await (db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
})
.from(users)
.where(eq((users as any).id, id))
.get();
const updated = await dbGet(
(db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
})
.from(users)
.where(eq((users as any).id, id))
);
return c.json({ user: updated });
});
@@ -128,21 +132,23 @@ usersRouter.get('/:id/history', requireAuth(['admin', 'organizer', 'staff', 'mar
return c.json({ error: 'Forbidden' }, 403);
}
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
.orderBy(desc((tickets as any).createdAt))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
.orderBy(desc((tickets as any).createdAt))
);
// Get event details for each ticket
const history = await Promise.all(
userTickets.map(async (ticket: any) => {
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))
);
return {
...ticket,
@@ -164,7 +170,9 @@ usersRouter.delete('/:id', requireAuth(['admin']), async (c) => {
return c.json({ error: 'Cannot delete your own account' }, 400);
}
const existing = await (db as any).select().from(users).where(eq((users as any).id, id)).get();
const existing = await dbGet<any>(
(db as any).select().from(users).where(eq((users as any).id, id))
);
if (!existing) {
return c.json({ error: 'User not found' }, 404);
}
@@ -176,11 +184,12 @@ usersRouter.delete('/:id', requireAuth(['admin']), async (c) => {
try {
// Get all tickets for this user
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
.all();
const userTickets = await dbAll<any>(
(db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
);
// Delete payments associated with user's tickets
for (const ticket of userTickets) {
@@ -202,16 +211,18 @@ usersRouter.delete('/:id', requireAuth(['admin']), async (c) => {
// Get user statistics (admin)
usersRouter.get('/stats/overview', requireAuth(['admin']), async (c) => {
const totalUsers = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.get();
const totalUsers = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
);
const adminCount = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.where(eq((users as any).role, 'admin'))
.get();
const adminCount = await dbGet<any>(
(db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.where(eq((users as any).role, 'admin'))
);
return c.json({
stats: {