Skip to content

Discount codes api#3794

Draft
devkiran wants to merge 2 commits intoapprove-reject-endpointsfrom
discount-codes-api
Draft

Discount codes api#3794
devkiran wants to merge 2 commits intoapprove-reject-endpointsfrom
discount-codes-api

Conversation

@devkiran
Copy link
Copy Markdown
Collaborator

@devkiran devkiran commented Apr 21, 2026

Summary by CodeRabbit

  • New Features
    • Discount codes API now supports filtering by tenantId, discountId, and linkId in addition to partnerId.
    • Partner links now include associated discount code details.
    • Added API documentation for discount codes endpoint.

- Updated the GET endpoint for partner links to include discount codes in the response.
- Modified the partner-approved workflow to omit discount codes when parsing existing partner links.
- Adjusted the getPartners function to include discount codes in the links response.
- Extended the ProgramPartnerLinkSchema to include discount code details.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Apr 21, 2026 3:54pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f7e5073-5372-4dd5-88b7-c51fc37f42af

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch discount-codes-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/app/(ee)/api/partners/links/route.ts (1)

48-52: Optional: narrow the nested discountCode selection to what's exposed.

The include fetches all DiscountCode columns (including partnerId, linkId, discountId), but ProgramPartnerLinkSchema.discountCode only exposes { id, code } and Zod's default strip mode silently drops the rest. You could make the Prisma selection match the public shape to avoid overfetching.

♻️ Suggested narrowing
       links: {
-        include: {
-          discountCode: true,
+        include: {
+          discountCode: {
+            select: { id: true, code: true },
+          },
         },
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/`(ee)/api/partners/links/route.ts around lines 48 - 52, The
nested Prisma include is overfetching DiscountCode fields; update the include
under links (where `discountCode: true` is used) to explicitly select only the
public fields that `ProgramPartnerLinkSchema.discountCode` exposes (e.g., `id`
and `code`) so the DB query matches the public Zod shape and avoids pulling
`partnerId`, `linkId`, `discountId`, etc.; locate the include inside the `links`
selection in apps/web/app/(ee)/api/partners/links/route.ts and replace the
boolean include with a selective `select` for `id` and `code`.
apps/web/app/(ee)/api/discount-codes/route.ts (1)

27-89: Align the discount and link lookups for consistency.

The two guarded lookups use different patterns for the same goal (ensure the record belongs to programId):

  • discount uses findUnique({ where: { id: discountId, programId } }) — scoped in the where clause
  • link uses findUnique({ where: { id: linkId } }) — unscoped, relies on the post-check

Since your codebase already uses the extended where pattern for discount, align link by adding programId to its where clause:

♻️ Suggested alignment
       linkId
         ? prisma.link.findUnique({
             where: {
               id: linkId,
+              programId,
             },
             select: {
               programId: true,
             },
           })
         : null,

Optionally drop the now-redundant discount.programId !== programId and link.programId !== programId comparisons once both are scoped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/`(ee)/api/discount-codes/route.ts around lines 27 - 89, Align
the link lookup with the discount lookup by scoping the prisma.link.findUnique
call to include programId in its where clause (i.e., use where: { id: linkId,
programId } when linkId is present) so it directly enforces record ownership;
update the linkId branch that calls prisma.link.findUnique and then you can
optionally remove the redundant post-check comparing link.programId !==
programId, similar to how discount is handled in the same Promise.all block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/app/`(ee)/api/discount-codes/route.ts:
- Around line 19-102: The GET handler currently returns an unbounded array of
DiscountCode rows; add pagination to the GET exported handler by extending
getDiscountCodesQuerySchema to accept page and pageSize (same convention as
getPartnersQuerySchema), validate/normalize them in the GET handler, and change
the prisma.discountCode.findMany call in the GET handler to use take and skip
(or cursor) based on page/pageSize; also fetch total via
prisma.discountCode.count with the same where filter and return a paginated
response object (e.g. { items: DiscountCodeSchema.array().parse(items), total,
page, pageSize }) and update the OpenAPI/response schema accordingly. Ensure you
still apply existing filters (partnerId, tenantId, discountId, linkId,
programId) to both count and findMany and keep existing not_found checks
(programEnrollment, discount, link) unchanged.

---

Nitpick comments:
In `@apps/web/app/`(ee)/api/discount-codes/route.ts:
- Around line 27-89: Align the link lookup with the discount lookup by scoping
the prisma.link.findUnique call to include programId in its where clause (i.e.,
use where: { id: linkId, programId } when linkId is present) so it directly
enforces record ownership; update the linkId branch that calls
prisma.link.findUnique and then you can optionally remove the redundant
post-check comparing link.programId !== programId, similar to how discount is
handled in the same Promise.all block.

In `@apps/web/app/`(ee)/api/partners/links/route.ts:
- Around line 48-52: The nested Prisma include is overfetching DiscountCode
fields; update the include under links (where `discountCode: true` is used) to
explicitly select only the public fields that
`ProgramPartnerLinkSchema.discountCode` exposes (e.g., `id` and `code`) so the
DB query matches the public Zod shape and avoids pulling `partnerId`, `linkId`,
`discountId`, etc.; locate the include inside the `links` selection in
apps/web/app/(ee)/api/partners/links/route.ts and replace the boolean include
with a selective `select` for `id` and `code`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1737b41e-04c6-4398-ad68-a6d17d446f99

📥 Commits

Reviewing files that changed from the base of the PR and between bed1f5e and 7abee8f.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/discount-codes/route.ts
  • apps/web/app/(ee)/api/partners/links/route.ts
  • apps/web/app/(ee)/api/workflows/partner-approved/route.ts
  • apps/web/lib/api/partners/get-partners.ts
  • apps/web/lib/openapi/discount-codes/index.ts
  • apps/web/lib/openapi/discount-codes/list-discount-codes.ts
  • apps/web/lib/openapi/index.ts
  • apps/web/lib/zod/schemas/discount.ts
  • apps/web/lib/zod/schemas/programs.ts

Comment on lines +19 to 102
// GET /api/discount-codes - get all discount codes for a program
export const GET = withWorkspace(
async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const { partnerId } = getDiscountCodesQuerySchema.parse(searchParams);
let { partnerId, tenantId, discountId, linkId } =
getDiscountCodesQuerySchema.parse(searchParams);

const programEnrollment = await getProgramEnrollmentOrThrow({
partnerId,
programId,
include: {
discountCodes: true,
const [programEnrollment, discount, link] = await Promise.all([
partnerId || tenantId
? prisma.programEnrollment.findUnique({
where: partnerId
? { partnerId_programId: { partnerId, programId } }
: { tenantId_programId: { tenantId: tenantId!, programId } },
select: {
partnerId: true,
},
})
: null,

discountId
? prisma.discount.findUnique({
where: {
id: discountId,
programId,
},
select: {
programId: true,
},
})
: null,

linkId
? prisma.link.findUnique({
where: {
id: linkId,
},
select: {
programId: true,
},
})
: null,
]);

// Filter by partner or tenant
if ((partnerId || tenantId) && !programEnrollment) {
throw new DubApiError({
code: "not_found",
message: `The partner with ${partnerId ? "partnerId" : "tenantId"} ${partnerId ?? tenantId} is not enrolled in your program.`,
});
}

if (programEnrollment) {
partnerId = programEnrollment.partnerId;
}

// Filter by discount
if (discountId && (!discount || discount.programId !== programId)) {
throw new DubApiError({
code: "not_found",
message: "Discount not found.",
});
}

// Filter by link
if (linkId && (!link || link.programId !== programId)) {
throw new DubApiError({
code: "not_found",
message: "Link not found.",
});
}

const discountCodes = await prisma.discountCode.findMany({
where: {
programId,
...(partnerId && { partnerId }),
...(linkId && { linkId }),
...(discountId && { discountId }),
},
});

const response = DiscountCodeSchema.array().parse(
programEnrollment.discountCodes,
);
const response = DiscountCodeSchema.array().parse(discountCodes);

return NextResponse.json(response);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add pagination before shipping this as a public API.

GET /discount-codes returns every DiscountCode row for the program in a single response — no take/skip/cursor, and the OpenAPI schema (z.array(DiscountCodeSchema)) bakes the unbounded-array contract into the SDK. For programs with many partner links (each link can have its own discount code), this will grow linearly with partners × links and cause slow/oversized responses on the request path. Once published, adding pagination later is a breaking change for SDK consumers.

Recommend introducing page/pageSize (matching the getPartnersQuerySchema convention elsewhere in this repo) now, and updating the OpenAPI response accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/`(ee)/api/discount-codes/route.ts around lines 19 - 102, The GET
handler currently returns an unbounded array of DiscountCode rows; add
pagination to the GET exported handler by extending getDiscountCodesQuerySchema
to accept page and pageSize (same convention as getPartnersQuerySchema),
validate/normalize them in the GET handler, and change the
prisma.discountCode.findMany call in the GET handler to use take and skip (or
cursor) based on page/pageSize; also fetch total via prisma.discountCode.count
with the same where filter and return a paginated response object (e.g. { items:
DiscountCodeSchema.array().parse(items), total, page, pageSize }) and update the
OpenAPI/response schema accordingly. Ensure you still apply existing filters
(partnerId, tenantId, discountId, linkId, programId) to both count and findMany
and keep existing not_found checks (programEnrollment, discount, link)
unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant