/** * GET /api/profile/me/cooldown-history?cursor=&limit=20 * * Cursor-paginated Cooldown-Historie. Frontend nutzt für Profile-Streak-Section * (project_profile_page_design.md §3 — Cooldown-Timeline). * * Status-Berechnung: * - resolvedAt set → 'resolved' * - cancelledAt set → 'cancelled' * - cooldownEndsAt > now → 'active' * - cooldownEndsAt <= now & no resolved/cancelled → 'expired' (auto-resolved * beim nächsten /cooldown/status-call, hier als 'resolved' angezeigt zur * UX-Konsistenz) * * Response: * { items: CooldownEntry[], nextCursor?: string } * * Pagination via opaque cursor (= last item's id). Frontend persistiert * cursor und schickt zurück. Limit 20 (max 50). */ import { requireUser } from "../../../utils/auth"; import { usePrisma } from "../../../utils/prisma"; const DEFAULT_LIMIT = 20; const MAX_LIMIT = 50; type CooldownEntry = { id: string; startedAt: string; cooldownEndsAt: string; durationMinutes: number; status: "active" | "resolved" | "cancelled"; resolvedAt: string | null; cancelledAt: string | null; reason: string | null; }; export default defineEventHandler(async (event) => { const user = await requireUser(event); const query = getQuery(event); const cursor = typeof query.cursor === "string" ? query.cursor : undefined; const limit = Math.min( MAX_LIMIT, Math.max(1, parseInt(query.limit as string, 10) || DEFAULT_LIMIT), ); const db = usePrisma(); const rows = await db.cooldownRequest.findMany({ where: { userId: user.id }, orderBy: { cooldownStartedAt: "desc" }, take: limit + 1, // +1 to know if there's a next page ...(cursor ? { cursor: { id: cursor }, skip: 1, } : {}), select: { id: true, reason: true, cooldownStartedAt: true, cooldownEndsAt: true, resolvedAt: true, cancelledAt: true, }, }); const hasMore = rows.length > limit; const items = (hasMore ? rows.slice(0, limit) : rows).map((r): CooldownEntry => { const startedAt = r.cooldownStartedAt; const endsAt = r.cooldownEndsAt; const durationMinutes = Math.max( 0, Math.round((endsAt.getTime() - startedAt.getTime()) / 60_000), ); let status: CooldownEntry["status"]; if (r.cancelledAt) status = "cancelled"; else if (r.resolvedAt || endsAt <= new Date()) status = "resolved"; else status = "active"; return { id: r.id, startedAt: startedAt.toISOString(), cooldownEndsAt: endsAt.toISOString(), durationMinutes, status, resolvedAt: r.resolvedAt?.toISOString() ?? null, cancelledAt: r.cancelledAt?.toISOString() ?? null, reason: r.reason ?? null, }; }); return { success: true, data: { items, nextCursor: hasMore ? items[items.length - 1]?.id : null, }, }; });