all repos — stealth-developers @ 5b737e9cb31de3ccd0e38cbef41bf9b3d1ba8a83

feat(api): impl pagination
vi did:web:vt3e.cat
Wed, 20 May 2026 01:12:24 +0100
commit

5b737e9cb31de3ccd0e38cbef41bf9b3d1ba8a83

parent

a78eed8b66665f02ed3de6a3c6749c475061b59e

2 files changed, 33 insertions(+), 6 deletions(-)

jump to
M pkgs/api/src/responses.tspkgs/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.tspkgs/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); }, },