Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions apps/web/lib/api/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,7 @@ export function buildPaginationQuery(filters: Filters): PaginationQuery {
}

return {
// Order by id only for better query performance on large datasets (single-column PK index).
// Trade-off: ordering is by id rather than createdAt, so order may not strictly match creation time.
orderBy: {
[sortBy]: sortOrder,
},
orderBy: [{ [sortBy]: sortOrder }, { id: sortOrder }],
take: pageSize,
skip: (page - 1) * pageSize,
};
Expand Down
16 changes: 11 additions & 5 deletions apps/web/lib/api/partners/get-partners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { prisma } from "@/lib/prisma";
import { getPartnersQuerySchemaExtended } from "@/lib/zod/schemas/partners";
import { toCentsNumber } from "@dub/utils";
import * as z from "zod/v4";
import { buildPaginationQuery } from "../pagination";
import { buildProgramEnrollmentWhereForList } from "./program-enrollment-query";

type PartnerFilters = z.infer<typeof getPartnersQuerySchemaExtended> & {
Expand All @@ -24,6 +25,13 @@ export async function getPartners(filters: PartnerFilters) {
...enrollmentRest
} = filters;

const { take, skip, orderBy } = buildPaginationQuery({
page,
pageSize,
sortBy,
sortOrder,
});

const partners = await prisma.programEnrollment.findMany({
where: buildProgramEnrollmentWhereForList({
...enrollmentRest,
Expand Down Expand Up @@ -54,11 +62,9 @@ export async function getPartners(filters: PartnerFilters) {
}
: {}),
},
take: pageSize,
skip: (page - 1) * pageSize,
orderBy: {
[sortBy]: sortOrder,
},
take,
skip,
orderBy,
});

return partners.map(
Expand Down
61 changes: 61 additions & 0 deletions apps/web/tests/partners/pagination.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { EnrolledPartnerProps } from "@/lib/types";
import { beforeAll, describe, expect, test } from "vitest";
import { expectNoOverlap } from "../utils/helpers";
import { IntegrationHarness } from "../utils/integration";

describe.concurrent("/partners/** - pagination", async () => {
const h = new IntegrationHarness();
let http: IntegrationHarness["http"];
let baseline: EnrolledPartnerProps[];
let baselineIds: string[];

const commonQuery = {
pageSize: "5",
sortBy: "totalSaleAmount",
sortOrder: "desc",
};

beforeAll(async () => {
({ http } = await h.init());

const { status, data } = await http.get<EnrolledPartnerProps[]>({
path: "/partners",
query: { ...commonQuery, pageSize: "100" },
});

expect(status).toEqual(200);

baseline = data;
baselineIds = baseline.map((p) => p.id);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

test("Offset pagination has no duplicate partners across pages", async () => {
const page1 = await http.get<EnrolledPartnerProps[]>({
path: "/partners",
query: { ...commonQuery, page: "1" },
});
const page2 = await http.get<EnrolledPartnerProps[]>({
path: "/partners",
query: { ...commonQuery, page: "2" },
});
const page3 = await http.get<EnrolledPartnerProps[]>({
path: "/partners",
query: { ...commonQuery, page: "3" },
});

expect(page1.status).toEqual(200);
expect(page2.status).toEqual(200);
expect(page3.status).toEqual(200);

expectNoOverlap(page1.data, page2.data);
expectNoOverlap(page2.data, page3.data);
expectNoOverlap(page1.data, page3.data);

const paginatedIds = [...page1.data, ...page2.data, ...page3.data].map(
(p) => p.id,
);

expect(new Set(paginatedIds).size).toBe(paginatedIds.length);
expect(paginatedIds).toEqual(baselineIds.slice(0, paginatedIds.length));
});
});
Loading