Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Sep 12, 2025

Summary by CodeRabbit

  • New Features

    • Newly approved partners are queued to receive notifications about active, relevant bounties roughly 1 hour after approval.
    • Bulk approvals schedule targeted bounty notifications to the affected partners; notifications can be limited to specific partner IDs.
  • Bug Fixes / Notes

    • When processing paginated notification jobs, a partner filter provided on the first page is not propagated to subsequent pages.

@vercel
Copy link
Contributor

vercel bot commented Sep 12, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 12, 2025 2:18pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

Warning

Rate limit exceeded

@devkiran has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0c661 and f736e07.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (4 hunks)
  • apps/web/lib/actions/partners/bulk-approve-partners.ts (3 hunks)
  • apps/web/lib/api/bounties/get-bounties-by-group.ts (1 hunks)
  • apps/web/lib/partners/approve-partner-enrollment.ts (3 hunks)

Walkthrough

Adds partner-scoped bounty notifications: cron endpoint accepts optional partnerIds to filter enrollments; approval flows enqueue delayed QStash jobs per bounty with partnerIds; new helper fetches active bounties grouped by groupId; pagination behavior unchanged.

Changes

Cohort / File(s) Summary
Cron notify route
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
Adds optional input partnerIds: string[]; conditionally filters programEnrollment by partnerId IN partnerIds; includes partnerIds in parsed body and updates log message; pagination still enqueues next page but does not propagate partnerIds.
Partner approval flows
apps/web/lib/actions/partners/bulk-approve-partners.ts, apps/web/lib/partners/approve-partner-enrollment.ts
On approvals, fetches bounties by group and enqueues QStash JSON publishes to /api/cron/bounties/notify-partners with { bountyId, partnerIds }, scheduled ~1 hour later; collects these promises into existing Promise.allSettled; adds imports getBountiesByGroup, qstash, APP_DOMAIN_WITH_NGROK.
Bounty lookup helper
apps/web/lib/api/bounties/get-bounties-by-group.ts
New getBountiesByGroup({ programId, groupIds }) which queries active bounties (time window + group association), deduplicates groupIds, and returns Record<groupId, { id, name }[]> mapping; no new error handling.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin
  participant App as Web App
  participant Helper as getBountiesByGroup
  participant Q as QStash
  participant Cron as /api/cron/bounties/notify-partners
  participant DB as Prisma

  rect rgba(200,230,255,0.25)
    note over Admin,App: Partner approval (bulk or single)
    Admin->>App: Approve partner(s)
    App->>Helper: getBountiesByGroup(programId, groupIds)
    Helper->>DB: Query active bounties by group/time
    DB-->>Helper: Bounties by group
    Helper-->>App: Map groupId -> [bounty]
    loop For each bounty/group/partner set
      App->>Q: publishJSON({ bountyId, partnerIds }, notBefore=+1h, url=APP_DOMAIN_WITH_NGROK/.../notify-partners)
    end
  end

  rect rgba(220,255,220,0.25)
    note over Q,Cron: Deferred notification processing
    Q-->>Cron: POST { bountyId, partnerIds?, page }
    Cron->>DB: Fetch bounty
    alt partnerIds provided
      Cron->>DB: programEnrollment where partnerId IN partnerIds
    else no partnerIds
      Cron->>DB: programEnrollment for bounty/group
    end
    Cron-->>Q: Enqueue next page if MAX_PAGE_SIZE reached (page+1)
    Cron->>App: Send emails to selected partners
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • steven-tey

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title succinctly and accurately reflects the primary change: scheduling bounty notification emails for newly-joined partners by queuing QStash cron jobs from approval flows and updating the cron handler; it is concise, on-topic, and clear for a teammate scanning history.

Poem

A rabbit taps the QStash queue, hop-hop—delay by one,
Groups and bounties neatly mapped, the notices soon run.
partnerIds in tidy rows, the cron will filter through,
Emails sprout like clover leaves—precise, not scattered dew.
With gentle paws and Prisma trails, we nudge the work anew. 🐇✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch schedule-bounty-email

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.

@devkiran devkiran marked this pull request as ready for review September 12, 2025 13:32
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: 3

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/bounties/notify-partners/route.ts (2)

142-149: Fix pagination filter leak: propagate partnerIds to next page

When partnerIds is provided, the next page omits it and will email all eligible partners. This is a severe privacy/notification bug.

   const res = await qstash.publishJSON({
     url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/notify-partners`,
     body: {
       bountyId,
-      page: page + 1,
+      page: page + 1,
+      ...(partnerIds?.length ? { partnerIds } : {}),
     },
   });

56-63: Start-time gating can drop notifications; re-enqueue until startsAt

If the bounty hasn’t started, the handler returns early without re-enqueuing, so notifications may never send. Requeue for startsAt (plus a small buffer).

-  let diffMinutes = differenceInMinutes(bounty.startsAt, new Date());
-
-  if (diffMinutes >= 10) {
-    return logAndRespond(
-      `Bounty ${bountyId} not started yet, it will start at ${bounty.startsAt.toISOString()}`,
-    );
-  }
+  const diffMinutes = differenceInMinutes(bounty.startsAt, new Date());
+  if (diffMinutes > 0) {
+    await qstash.publishJSON({
+      url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/notify-partners`,
+      body: { bountyId, page: 0, ...(partnerIds?.length ? { partnerIds } : {}) },
+      notBefore: Math.floor(bounty.startsAt.getTime() / 1000) + 60, // run ~1 min after start
+    });
+    return logAndRespond(
+      `Bounty ${bountyId} not started yet; re-enqueued for ${bounty.startsAt.toISOString()}.`,
+    );
+  }
🧹 Nitpick comments (5)
apps/web/lib/api/bounties/get-bounties-by-group.ts (1)

16-54: Tighten Prisma filter for readability and index-friendliness

The nested AND: [{ OR: [...] }] is redundant. Flattening improves readability and can help query planners. Functionality is unchanged.

-      // Only fetch bounties that are relevant to the specified groups
-      AND: [
-        {
-          OR: [
-            {
-              groups: {
-                none: {},
-              },
-            },
-            {
-              groups: {
-                some: {
-                  groupId: {
-                    in: targetGroupIds,
-                  },
-                },
-              },
-            },
-          ],
-        },
-      ],
+      // Only bounties with no groups or intersecting target groups
+      OR: [
+        { groups: { none: {} } },
+        { groups: { some: { groupId: { in: targetGroupIds } } } },
+      ],
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (2)

118-140: Strengthen idempotency key to avoid collisions across subsets

The key ignores partnerIds, so runs with different subsets can collide. Include a stable subset hash.

Minimal change (non-cryptographic, acceptable if subsets are rare):

         headers: {
-          "Idempotency-Key": `${bountyId}-page-${page}`,
+          "Idempotency-Key": `${bountyId}-page-${page}${partnerIds?.length ? `-subset` : `-all`}`,
         },

Prefer hashing the subset for uniqueness:

// Near parse line, compute once:
const subsetKey =
  partnerIds?.length ? `-${partnerIds.slice().sort().join(",")}` : "-all";

and then:

- "Idempotency-Key": `${bountyId}-page-${page}`,
+ "Idempotency-Key": `${bountyId}-page-${page}${subsetKey}`,

160-166: Safely log unknown errors

error is unknown. Narrow to Error before accessing .message.

-    await log({
-      message: "New bounties published cron failed. Error: " + error.message,
+    const msg =
+      error instanceof Error ? error.message : JSON.stringify(error);
+    await log({
+      message: "New bounties published cron failed. Error: " + msg,
       type: "errors",
     });
apps/web/lib/partners/approve-partner-enrollment.ts (1)

177-182: Optionally include startsAt in helper to schedule closer to go-live

If you want to avoid the cron handler re-queues, extend getBountiesByGroup to select startsAt and schedule notBefore = max(now+1h, startsAt+60s).

apps/web/lib/actions/partners/bulk-approve-partners.ts (1)

195-206: Chunk partnerIds to keep QStash payloads small and resilient

Large groups can produce big payloads. Chunking reduces risk of hitting request size/time limits and eases retries.

-          for (const bounty of bounties) {
-            bountyNotificationPromises.push(
-              qstash.publishJSON({
-                url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/notify-partners`,
-                body: {
-                  bountyId: bounty.id,
-                  partnerIds: partners.map((p) => p.id),
-                },
-                notBefore:
-                  Math.floor(new Date().getTime() / 1000) + 1 * 60 * 60, // 1 hour from now
-              }),
-            );
-          }
+          for (const bounty of bounties) {
+            for (const partnerIdChunk of chunk(
+              partners.map((p) => p.id),
+              500,
+            )) {
+              bountyNotificationPromises.push(
+                qstash.publishJSON({
+                  url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/notify-partners`,
+                  body: {
+                    bountyId: bounty.id,
+                    partnerIds: partnerIdChunk,
+                  },
+                  notBefore:
+                    Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour from now
+                }),
+              );
+            }
+          }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0c661 and af57801.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (4 hunks)
  • apps/web/lib/actions/partners/bulk-approve-partners.ts (3 hunks)
  • apps/web/lib/api/bounties/get-bounties-by-group.ts (1 hunks)
  • apps/web/lib/partners/approve-partner-enrollment.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-26T15:38:48.173Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/lib/api/bounties/get-bounty-or-throw.ts:53-63
Timestamp: 2025-08-26T15:38:48.173Z
Learning: In bounty-related code, getBountyOrThrow returns group objects with { id } field (transformed from BountyGroup.groupId), while other routes working directly with BountyGroup Prisma records use the actual groupId field. This is intentional - getBountyOrThrow abstracts the join table details.

Applied to files:

  • apps/web/lib/api/bounties/get-bounties-by-group.ts
  • apps/web/lib/partners/approve-partner-enrollment.ts
📚 Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
PR: dubinc/dub#2735
File: apps/web/lib/actions/partners/delete-reward.ts:33-41
Timestamp: 2025-08-14T05:17:51.825Z
Learning: In the partner groups system, a rewardId can only belong to one group, establishing a one-to-one relationship between rewards and groups. This means using Prisma's `update` method (rather than `updateMany`) is appropriate when updating groups by rewardId.

Applied to files:

  • apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
🧬 Code graph analysis (2)
apps/web/lib/partners/approve-partner-enrollment.ts (2)
apps/web/lib/api/bounties/get-bounties-by-group.ts (1)
  • getBountiesByGroup (9-90)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/lib/actions/partners/bulk-approve-partners.ts (2)
apps/web/lib/api/bounties/get-bounties-by-group.ts (1)
  • getBountiesByGroup (9-90)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vade Review
🔇 Additional comments (1)
apps/web/lib/actions/partners/bulk-approve-partners.ts (1)

164-171: LGTM on helper usage

Fetching once and reusing the map across groups is clean and efficient.

@devkiran
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

✅ Actions performed

Full review triggered.

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