Skip to content

Add API endpoint for partner deactivation#3414

Merged
steven-tey merged 47 commits intomainfrom
add-deactivate-partner-api
Feb 4, 2026
Merged

Add API endpoint for partner deactivation#3414
steven-tey merged 47 commits intomainfrom
add-deactivate-partner-api

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Feb 4, 2026

Summary by CodeRabbit

  • New Features

    • Add partner deactivation endpoint (accepts partnerId or tenantId); deactivated partners show updated enrollment status and expired links.
    • Add API schema and OpenAPI metadata for the new endpoint.
  • Updates

    • Frontend now calls the single-partner deactivate API (partnerId payload).
    • Centralized deactivation processing to ensure consistent status updates and downstream notifications.
  • Tests

    • Add end-to-end tests for deactivation via partnerId and tenantId; add test utility to fetch partners and verify statuses.

marcusljf and others added 30 commits January 16, 2026 08:23
Updated the partner deactivation email template to handle cases where the entire partner program is deactivated, not just individual partnerships. The email content now conditionally renders different messages and instructions based on whether the deactivation is for the whole program or a single partner.
Introduces logic to detect and warn users when changing or canceling plans would result in losing partner program access. Adds a confirmation modal for such cases in subscription and upgrade flows, and centralizes partner access checks in a new utility.
Added logic to display a warning in the failed payment email if the workspace has an active partner program and a qualifying plan. Also updated the email copy for clarity and improved user guidance. The webhook handler now selects additional workspace fields needed for this logic.
Removed the specific reference to 'Dub' in the failed payment attempt heading to make the message more generic.
Introduces an isSubmitting state to disable and show loading on the 'Continue' button during async confirmation. Updates the onConfirm prop to support async functions and ensures the modal closes before confirmation logic executes.
Removed an extra space after '3rd' in the payment attempt heading of the failed payment email template.
The modal is no longer closed before the onConfirm action completes, ensuring the confirmation process is not interrupted and improving user experience.
Updated the plan change confirmation modal to present partner program impact details as a concise bulleted list. Clarified messaging and integrated the pending payouts link directly into the list for improved readability.
- Add deactivatedAt column to Program schema to track deactivation timestamp
- Hide deactivated programs from partner profile endpoint
- Update deactivation email subject to differentiate between program-wide and individual partnership deactivations
Base automatically changed from deactivate-program-email to main February 4, 2026 06:25
@devkiran devkiran marked this pull request as draft February 4, 2026 08:31
@devkiran devkiran marked this pull request as ready for review February 4, 2026 11:40
@devkiran devkiran requested a review from steven-tey February 4, 2026 11:41
@devkiran
Copy link
Collaborator Author

devkiran commented Feb 4, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

✅ Actions performed

Full review triggered.

Copy link
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

🤖 Fix all issues with AI agents
In `@apps/web/lib/api/partners/deactivate-partner.ts`:
- Around line 101-116: The qstash.publishJSON call in deactivatePartner can fail
silently and leave a partner deactivated without any cleanup job queued; wrap
the qstash.publishJSON invocation in a try-catch inside deactivatePartner and on
error log the failure and persist a retry entry (outbox) or set a DB flag to
trigger a background retry, so the /api/cron/partners/deactivate job can be
retried later; specifically update the block that calls qstash.publishJSON (and
its surrounding success/error branches) to catch exceptions, record the failed
job (e.g., insert a retry record referencing programId and partner.id) and
ensure the scheduled job is retriable from that persisted state (mirror the
try-catch pattern used in schedule-campaigns.ts).
🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/partners.ts (1)

824-825: Adding a refinement here would duplicate runtime validation without improving OpenAPI docs.

The route already validates this requirement via throwIfNoPartnerIdOrTenantId() on line 19. Additionally, the schema documents the constraint in the tenantId field description. A refinement alone won't enhance the OpenAPI schema—zod-openapi cannot auto-translate refine() into OpenAPI constraints and requires manual .meta({ override: ... }) configuration to express the constraint in the generated schema. If strict schema-level validation is desired, add both the refinement and a corresponding .meta() override; otherwise, the current setup is sufficient.

@devkiran
Copy link
Collaborator Author

devkiran commented Feb 4, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

✅ Actions performed

Full review triggered.

Copy link
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

🤖 Fix all issues with AI agents
In `@apps/web/app/`(ee)/api/partners/deactivate/route.ts:
- Around line 26-47: The current handler resolves partnerId from tenantId only
when partnerId is missing, which allows a mismatch when both tenantId and
partnerId are provided; update the logic around tenantId/partnerId in the route
handler so that if tenantId is present you always load the programEnrollment via
prisma.programEnrollment.findUnique({ where: { tenantId_programId: { tenantId,
programId } } }) and then (1) if no enrollment throw a DubApiError not_found,
and (2) if partnerId was provided and enrollment.partnerId !== partnerId throw a
DubApiError (bad_request or conflict) indicating the tenantId/partnerId
mismatch; otherwise set partnerId = enrollment.partnerId and continue. Ensure
references to tenantId, partnerId, prisma.programEnrollment, and DubApiError are
used so the change is made in the correct place.
🧹 Nitpick comments (1)
apps/web/tests/partners/deactivate-partner.test.ts (1)

14-14: Consider adding negative test cases.

The current tests cover the happy path well. Consider adding tests for edge cases such as:

  • Attempting to deactivate an already deactivated partner
  • Attempting to deactivate a non-existent partner
  • Attempting to deactivate a banned partner (should fail per the API logic)

@steven-tey
Copy link
Collaborator

@coderabbitai full review, any breaking changes?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for usages of bulkDeactivatePartners to check if it's used elsewhere
echo "=== Searching for bulkDeactivatePartners usage ==="
rg -n 'bulkDeactivatePartners' --type ts -C 3

echo -e "\n=== Searching for existing deactivate API endpoints ==="
rg -n 'api/partners.*deactivate' --type ts -C 2

echo -e "\n=== Check if bulkDeactivatePartners is still exported ==="
cat apps/web/lib/api/partners/bulk-deactivate-partners.ts | head -50

Length of output: 12544


@steven-tey: I've analyzed the PR for breaking changes - no breaking changes detected. Here's my full review:

✅ No Breaking Changes

All changes are backward compatible:

The bulkDeactivatePartners function maintains its original signature and is still used in multiple places:

  • UI modal (bulk-deactivate-partners-modal.tsx)
  • Server action (bulk-deactivate-partners.ts)
  • Cron job (api/cron/programs/deactivate/route.ts)

Summary of Changes

Additions (non-breaking):

  1. New API endpoint: POST /api/partners/deactivate - allows deactivating a single partner by partnerId or tenantId
  2. New functions: deactivatePartner() and processPartnerDeactivation()
  3. New schema: deactivatePartnerApiSchema
  4. Comprehensive tests: End-to-end tests for both partnerId and tenantId flows

Internal Refactoring:

  • Extracted common deactivation logic to processPartnerDeactivation() to eliminate code duplication
  • Both bulkDeactivatePartners and new deactivatePartner now delegate to this shared processor
  • The refactoring maintains all existing functionality (DB updates, audit logs, qstash jobs, etc.)

Architecture Improvements

DRY principle: Deactivation logic centralized in one place
Consistency: Both bulk and single deactivation follow same workflow
Test coverage: New tests verify status changes post-deactivation
API design: Supports both partnerId and tenantId lookups

Minor Observations

The test utility fetchPartner was also added and used to enhance the ban-partner tests to verify status changes, which is a good practice for validation.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@steven-tey steven-tey merged commit 2d2cdb9 into main Feb 4, 2026
8 of 10 checks passed
@steven-tey steven-tey deleted the add-deactivate-partner-api branch February 4, 2026 19:43
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.

3 participants