Review a paginated activity endpoint

src/activity-endpoint.tsTypeScript
@@ -0,0 +1,61 @@
1+export type ActivityStatus = "queued" | "completed";
2+
3+export interface Activity { id: string; status: ActivityStatus }
4+
5+export interface ActivityRepository {
6+ list(query: {
7+ status?: ActivityStatus;
8+ limit: number;
9+ offset: number;
10+ }): Promise<Activity[]>;
11+ count(query: { status?: ActivityStatus }): Promise<number>;
12+}
13+
14+export interface ActivityRequest {
15+ query: {
16+ page?: string;
17+ pageSize?: string;
18+ status?: ActivityStatus;
19+ };
20+}
21+
22+export interface ActivityPage {
23+ items: Activity[];
24+ page: number;
25+ pageSize: number;
26+ total: number;
27+ totalPages: number;
28+}
29+
30+export type ActivityResponse =
31+ | { status: 400; body: { error: "invalid-pagination" } }
32+ | { status: 200; body: ActivityPage };
33+
34+export async function listActivity(
35+ request: ActivityRequest,
36+ repository: ActivityRepository,
37+): Promise<ActivityResponse> {
38+ const page = Number(request.query.page ?? "1");
39+ const pageSize = Number(request.query.pageSize ?? "25");
40+
41+ if (
42+ !Number.isFinite(page) ||
43+ page === 0 ||
44+ !Number.isInteger(pageSize) ||
45+ pageSize < 1 ||
46+ pageSize > 100
47+ ) {
48+ return { status: 400, body: { error: "invalid-pagination" } };
49+ }
50+
51+ const status = request.query.status;
52+ const [items, total] = await Promise.all([
53+ repository.list({ status, limit: pageSize, offset: page * pageSize }),
54+ repository.count({}),
55+ ]);
56+
57+ return {
58+ status: 200,
59+ body: { items, page, pageSize, total, totalPages: Math.ceil(total / pageSize) },
60+ };
61+}