feat(api): impl pagination
vi did:web:vt3e.cat
Wed, 20 May 2026 01:12:24 +0100
2 files changed,
33 insertions(+),
6 deletions(-)
M
pkgs/api/src/responses.ts
→
pkgs/api/src/responses.ts
@@ -7,7 +7,7 @@ };
export type TicketsResponse = { tickets: Ticket[]; - // todo)) type pagination + cursor?: string; }; export type MeResponse = User;
M
pkgs/bot/src/server/index.ts
→
pkgs/bot/src/server/index.ts
@@ -1,4 +1,4 @@
-import { desc, eq, asc, and, isNotNull, ne, gte, inArray, or } from "drizzle-orm"; +import { desc, eq, asc, lt, and, isNotNull, ne, gte, inArray, or } from "drizzle-orm"; import type { BunRequest } from "bun"; import _logger from "@/utils/logging";@@ -129,7 +129,6 @@
const profile = await getUser(user.id); if (!profile) return new Response("Profile not found", { status: 404 }); - // Ensures the return strictly conforms to `MeResponse` (alias for User) const response: MeResponse = { ...profile, isModerator: isMod }; return Response.json(response); },@@ -142,13 +141,41 @@ if (!user) return Response.redirect("/login", 302);
const url = new URL(req.url); const limit = Number.parseInt(url.searchParams.get("limit") || "20", 10); + const cursor = url.searchParams.get("cursor"); - const query = db.select().from(tickets).limit(limit).orderBy(desc(tickets.createdAt)); - const allTickets = isMod ? await query : await query.where(eq(tickets.authorId, user.id)); + const filters = []; + if (!isMod) filters.push(eq(tickets.authorId, user.id)); + + if (cursor) { + const cursorValue = new Date(cursor); + filters.push(lt(tickets.createdAt, cursorValue)); + } + + const allTickets = await db + .select() + .from(tickets) + .where(filters.length > 0 ? and(...filters) : undefined) + .orderBy(desc(tickets.createdAt)) + .limit(limit + 1); + + let nextCursor: string | undefined; + if (allTickets.length > limit) { + allTickets.pop(); + + const lastTicket = allTickets[allTickets.length - 1]; + nextCursor = + lastTicket.createdAt instanceof Date + ? lastTicket.createdAt.toISOString() + : String(lastTicket.createdAt); + } const hydratedTickets = await hydrateTickets(allTickets); - const response: TicketsResponse = { tickets: hydratedTickets }; + const response: TicketsResponse = { + tickets: hydratedTickets, + cursor: nextCursor, + }; + return Response.json(response); }, },