Security recovery: hold sweep, dashboard updates, and admin fixes.

This commit is contained in:
Michilis
2026-07-01 05:51:38 +00:00
parent 38526f17b5
commit cacc52ec24
45 changed files with 1452 additions and 474 deletions
+47 -9
View File
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, dbGet, dbAll, users, tickets, events, payments, magicLinkTokens, userSessions, invoices, auditLogs, emailLogs, paymentOptions, legalPages, siteSettings } from '../db/index.js';
import { eq, desc, sql } from 'drizzle-orm';
import { eq, desc, sql, and, gte, lte } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow } from '../lib/utils.js';
@@ -27,7 +27,43 @@ const updateUserSchema = z.object({
// Get all users (admin only)
usersRouter.get('/', requireAuth(['admin']), async (c) => {
const role = c.req.query('role');
const search = c.req.query('search');
const accountStatus = c.req.query('accountStatus');
const hasBookings = c.req.query('hasBookings'); // 'yes' | 'no'
const registeredAfter = c.req.query('registeredAfter');
const registeredBefore = c.req.query('registeredBefore');
const eventId = c.req.query('eventId');
const page = Math.max(parseInt(c.req.query('page') || '1', 10) || 1, 1);
const pageSize = Math.min(Math.max(parseInt(c.req.query('pageSize') || '50', 10) || 50, 1), 200);
const conditions: any[] = [];
if (role) conditions.push(eq((users as any).role, role));
if (accountStatus) conditions.push(eq((users as any).accountStatus, accountStatus));
if (registeredAfter) conditions.push(gte((users as any).createdAt, registeredAfter));
if (registeredBefore) conditions.push(lte((users as any).createdAt, registeredBefore));
if (search) {
const like = `%${search.toLowerCase()}%`;
conditions.push(sql`(
LOWER(${(users as any).name}) LIKE ${like}
OR LOWER(${(users as any).email}) LIKE ${like}
OR LOWER(COALESCE(${(users as any).phone}, '')) LIKE ${like}
)`);
}
if (hasBookings === 'yes') {
conditions.push(sql`EXISTS (SELECT 1 FROM tickets WHERE tickets.user_id = ${(users as any).id})`);
} else if (hasBookings === 'no') {
conditions.push(sql`NOT EXISTS (SELECT 1 FROM tickets WHERE tickets.user_id = ${(users as any).id})`);
}
if (eventId) {
conditions.push(sql`EXISTS (SELECT 1 FROM tickets WHERE tickets.user_id = ${(users as any).id} AND tickets.event_id = ${eventId})`);
}
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
const totalQuery = whereClause
? (db as any).select({ count: sql<number>`count(*)` }).from(users).where(whereClause)
: (db as any).select({ count: sql<number>`count(*)` }).from(users);
const totalRow = await dbGet<any>(totalQuery);
let query = (db as any).select({
id: (users as any).id,
email: (users as any).email,
@@ -40,14 +76,16 @@ usersRouter.get('/', requireAuth(['admin']), async (c) => {
accountStatus: (users as any).accountStatus,
createdAt: (users as any).createdAt,
}).from(users);
if (role) {
query = query.where(eq((users as any).role, role));
if (whereClause) {
query = query.where(whereClause);
}
const result = await dbAll(query.orderBy(desc((users as any).createdAt)));
return c.json({ users: result });
const result = await dbAll(
query.orderBy(desc((users as any).createdAt)).limit(pageSize).offset((page - 1) * pageSize)
);
return c.json({ users: result, total: Number(totalRow?.count || 0), page, pageSize });
});
// Get user statistics (admin) — registered before /:id so "stats" is not parsed as a user id