feat(api): admin all-users filtered CSV export

This commit is contained in:
Bas van Rossem
2026-06-24 16:30:17 +02:00
parent 4b213e25a8
commit d33fcb7cce
2 changed files with 91 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ import { db } from '../db/client';
import { activities, user, workSessions } from '../db/schema';
import { getSessionUser, isAdmin } from '../lib/require-user';
import { toWorkSession } from '../lib/work-session';
import { buildSessionsCsv } from '../lib/csv';
function computeDuration(startMs: number, endMs: number, paused: number): number {
return Math.max(0, Math.round((endMs - startMs) / 1000) - paused);
@@ -230,6 +231,41 @@ adminRoutes.get('/api/admin/report', async (c) => {
});
});
adminRoutes.get('/api/admin/export', async (c) => {
const q = parseReportQuery(c);
if (!q) return c.json({ error: 'Invalid query' }, 400);
const rows = await db
.select(baseSelect)
.from(workSessions)
.leftJoin(activities, eq(workSessions.activityId, activities.id))
.leftJoin(user, eq(workSessions.userId, user.id))
.where(and(...buildSessionFilters(q)))
.orderBy(asc(workSessions.startTime));
const csv = buildSessionsCsv(
rows.map((r) => ({
id: r.session.id,
activityName: r.activityName,
userName: r.userName,
insoleType: r.session.insoleType,
pairCount: r.session.pairCount,
startTime: r.session.startTime,
endTime: r.session.endTime,
durationSeconds: r.session.durationSeconds,
pausedSeconds: r.session.pausedSeconds,
})),
{ includeWorker: true },
);
const fromDate = q.from.toISOString().slice(0, 10);
const toDate = q.to.toISOString().slice(0, 10);
return c.body(csv, 200, {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': `attachment; filename="solelog-report_${fromDate}_${toDate}.csv"`,
});
});
// Manual create → always completed, source='manual', duration derived server-side.
adminRoutes.post('/api/admin/sessions', async (c) => {
const parsed = CreateManualSessionInput.safeParse(await c.req.json().catch(() => null));