Paginate the admin events list.
The admin events page fetched every event on each load and rendered them all, which grows without bound as the archive fills up. GET /api/events now takes optional page and pageSize parameters and returns total alongside the rows; pagination is opt-in, so the public pages and the admin filter dropdowns that pass neither still get the full list and the untouched response shape. Page size is selectable (10/25/50/100) and deleting the last event on a page falls back to the new last page instead of showing an empty table. The ?edit=<id> deep link no longer depends on the target being in the current page: when it is missing from the loaded rows the event is fetched directly, guarded by a ref so the modal opens once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e296e80e48
commit
5970d707af
@@ -172,6 +172,13 @@ const updateEventSchema = baseEventSchema.partial().refine(
|
||||
eventsRouter.get('/', async (c) => {
|
||||
const status = c.req.query('status');
|
||||
const upcoming = c.req.query('upcoming');
|
||||
// Pagination is opt-in: callers that pass neither page nor pageSize (public
|
||||
// pages, admin filter dropdowns) still get the full list.
|
||||
const pageParam = c.req.query('page');
|
||||
const pageSizeParam = c.req.query('pageSize');
|
||||
const paginated = pageParam !== undefined || pageSizeParam !== undefined;
|
||||
const page = Math.max(parseInt(pageParam || '1', 10) || 1, 1);
|
||||
const pageSize = Math.min(Math.max(parseInt(pageSizeParam || '25', 10) || 25, 1), 200);
|
||||
|
||||
// Only privileged users may see non-public events (drafts, archived, etc.).
|
||||
// Anonymous/regular callers are restricted to published events regardless of
|
||||
@@ -195,12 +202,24 @@ eventsRouter.get('/', async (c) => {
|
||||
conditions.push(eq((events as any).status, 'published'));
|
||||
}
|
||||
|
||||
const whereClause = conditions.length === 0
|
||||
? undefined
|
||||
: conditions.length === 1 ? conditions[0] : and(...conditions);
|
||||
|
||||
let query = (db as any).select().from(events);
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
|
||||
if (whereClause) query = query.where(whereClause);
|
||||
query = query.orderBy(desc((events as any).startDatetime));
|
||||
|
||||
let total: number | undefined;
|
||||
if (paginated) {
|
||||
let countQuery = (db as any).select({ count: sql`count(*)` }).from(events);
|
||||
if (whereClause) countQuery = countQuery.where(whereClause);
|
||||
const totalRow = await dbGet<any>(countQuery);
|
||||
total = Number(totalRow?.count || 0);
|
||||
query = query.limit(pageSize).offset((page - 1) * pageSize);
|
||||
}
|
||||
|
||||
const result = await dbAll<any>(query.orderBy(desc((events as any).startDatetime)));
|
||||
|
||||
const result = await dbAll<any>(query);
|
||||
|
||||
// Single grouped query for seat counts across all events (avoids N+1: previously
|
||||
// this ran one COUNT query per event). bookedCount = paid (confirmed/checked_in);
|
||||
@@ -227,7 +246,9 @@ eventsRouter.get('/', async (c) => {
|
||||
};
|
||||
});
|
||||
|
||||
return c.json({ events: eventsWithCounts });
|
||||
return paginated
|
||||
? c.json({ events: eventsWithCounts, total, page, pageSize })
|
||||
: c.json({ events: eventsWithCounts });
|
||||
});
|
||||
|
||||
// Get single event (public) - resolves by id, canonical slug, or historical alias
|
||||
|
||||
Reference in New Issue
Block a user