Improve delete-group feature to be more robust#3454
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughBatch-driven group deletion now iteratively moves program enrollments to the default group (via movePartnersToGroup with isGroupDeleted), removes inline discount/code handling from the delete flow, adds isGroupDeleted to remap cron payloads, and reduces post-deletion side effects to a single audit log entry. Changes
Sequence DiagramsequenceDiagram
participant Client
participant API as DELETE API Handler
participant DB as Database
participant Migrate as movePartnersToGroup
participant Cron as Remap Cron
participant Audit as Audit Service
Client->>API: DELETE /api/groups/[groupIdOrSlug]
loop Batch fetch & migrate (<=100)
API->>DB: Query program enrollments by groupId (limit 100)
DB-->>API: Batch of enrollments
API->>Migrate: POST movePartnersToGroup (isGroupDeleted=true)
Migrate->>Cron: Enqueue remap-discount-codes job (payload includes isGroupDeleted)
Cron->>DB: Remap discount codes for moved enrollments
DB-->>Cron: Updated counts
alt isGroupDeleted && no codes remain
Cron->>DB: Delete old discount
DB-->>Cron: Discount deleted
end
end
API->>DB: Delete group rewards & workflow (transaction)
API->>DB: Delete group (transaction)
DB-->>API: Confirm deletion
API->>Audit: Record group deletion audit log
Audit-->>API: Recorded
API-->>Client: 200 OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/web/app/`(ee)/api/groups/[groupIdOrSlug]/route.ts:
- Around line 345-353: Add a deferred cleanup job to remove orphaned discounts
after movePartnersToGroup/remap-discount-codes async work completes: when
handling group deletion in route.ts, keep the current guard that skips immediate
deletion for group.discountId but enqueue a delayed task (via qstash or your
existing job runner) that runs after a safe delay to call the discount deletion
logic for the captured group.discountId; reference the same discount deletion
code path used in the commented-out block and ensure the job payload includes
groupId/group.discountId and idempotency checks so the background handler (the
qstash consumer or a new handler function) safely deletes the discount if it’s
still orphaned.
- Around line 309-327: The current while(true) loop in the route handler that
repeatedly calls prisma.programEnrollment.findMany and movePartnersToGroup can
run indefinitely and block the HTTP request; add a max-iteration safeguard
(e.g., maxBatches or maxIterations) and break when reached, and also detect
repeated/unmoved partnerIds to avoid spinning if movePartnersToGroup doesn't
clear groupId; additionally, for large workloads refactor to enqueue a
background job (use the existing qstash/job dispatching pattern) from the route
instead of processing all batches synchronously — update the loop around
prisma.programEnrollment.findMany and movePartnersToGroup to honor the iteration
cap and enqueue remaining work when cap is hit.
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (1)
310-326: Minor: narrow the query and use structured logging.The
findManyfetches all columns but onlypartnerIdis used. Adding aselectclause reduces data transfer. Also,console.logis fine for debugging but consider using structured logging for production observability.Suggested select clause
const programEnrollments = await prisma.programEnrollment.findMany({ where: { groupId: group.id, }, take: 100, + select: { partnerId: true }, });
|
@coderabbitai full review, are there any breaking changes? |
🧩 Analysis chainI'll analyze the PR for breaking changes and trigger a full review. Let me first examine the actual code changes to provide you with a thorough analysis. 🏁 Script executed: Length of output: 10650 🏁 Script executed: Length of output: 11104 🏁 Script executed: Length of output: 43 🏁 Script executed: Length of output: 62 🏁 Script executed: Length of output: 764 🏁 Script executed: Length of output: 7888 🏁 Script executed: Length of output: 4812 🏁 Script executed: Length of output: 373 🏁 Script executed: Length of output: 1595
Breaking Changes AnalysisAfter examining the PR diff, I've identified several behavioral changes that could be breaking depending on how systems interact with this endpoint: ✅ Not Breaking - API Contract Unchanged
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/cron/groups/remap-default-links/route.ts (1)
80-95:⚠️ Potential issue | 🟠 MajorDeletion path can miss default links due to the
takelimit.
WhenisGroupDeletedis true, you include all links but stilltake: MAX_DEFAULT_LINKS_PER_GROUP(Line 94). If a partner has many links, the default links might not fall within the first N and won’t be remapped. Consider removing the cap for deletion runs or adding pagination.✅ Suggested fix (limit only for non-deletion path)
links: { // if this was invoked from the DELETE /groups/[groupId] route, the partnerGroupDefaultLinkId will be null // due to Prisma cascade SetNull on delete – therefore we should take all links and remap them instead. ...(isGroupDeleted ? {} : { where: { partnerGroupDefaultLinkId: { not: null, }, }, + take: MAX_DEFAULT_LINKS_PER_GROUP, }), orderBy: { createdAt: "asc", }, - take: MAX_DEFAULT_LINKS_PER_GROUP, // there can only be up to MAX_DEFAULT_LINKS_PER_GROUP default links per group },apps/web/app/(ee)/api/cron/groups/remap-discount-codes/route.ts (1)
45-183:⚠️ Potential issue | 🟡 MinorCleanup can be skipped when the first enrollment has no discount codes.
Line 45 takesoldDiscountfromprogramEnrollments[0], so if the first enrollment has no codes (even when later ones do), the deletion cleanup won’t run. Consider derivingoldDiscountfrom any discount code in the batch. Also, if there are no codes but a deleted group discount still exists, you may want to pass the old discount id in the payload to guarantee cleanup.✅ Suggested fix (derive from any code)
- const oldDiscount = programEnrollments[0]?.discountCodes[0]?.discount; + const oldDiscount = programEnrollments + .flatMap(({ discountCodes }) => discountCodes) + .find((code) => code.discount)?.discount;
🤖 Fix all issues with AI agents
In `@apps/web/app/`(ee)/api/groups/[groupIdOrSlug]/route.ts:
- Around line 309-325: When moving partners out of a deleting group, include
isGroupDeleted: true in the payload to ensure downstream remap jobs run deletion
logic; update the call to movePartnersToGroup (inside the loop that queries
prisma.programEnrollment.findMany and constructs partnerIds) to pass
isGroupDeleted: true, and if the movePartnersToGroup signature or type
definitions don't accept that flag, add it to the function/method signature and
propagate it to any remap/cleanup logic that relies on partnerGroupDefaultLinkId
or discount cleanup.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/web/app/`(ee)/api/cron/groups/remap-discount-codes/route.ts:
- Around line 166-183: The cleanup block that runs when isGroupDeleted &&
oldDiscount currently calls prisma.discount.delete and can raise a
RecordNotFound error under concurrent runs; change that to
prisma.discount.deleteMany({ where: { id: oldDiscount.id } }) so the operation
is idempotent (no-op if already deleted), and adjust any surrounding
logic/logging in the remap-discount-codes job to check the deleteMany count if
you need to log whether a row was removed; this touches the conditional that
references isGroupDeleted, oldDiscount and the prisma.discount.delete call
(triggered by movePartnersToGroup batches).
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/cron/groups/remap-discount-codes/route.ts (1)
45-45:oldDiscountderivation assumes uniform discount across enrollments.
programEnrollments[0]?.discountCodes[0]?.discounttakes the discount from the first enrollment's first code. This works correctly when all partners in the batch come from the same deleted group (same discount), but is fragile if this code path is ever reused for mixed-origin batches. A brief inline comment would help future readers understand the assumption.
Summary by CodeRabbit