-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add social media verification functionality #3315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Introduced new actions for starting and verifying social media accounts. - Implemented ScrapeCreators API client for fetching and verifying account profiles. - Updated social platform configurations to include names for better user experience. - Added a modal for social verification in the UI, allowing users to verify their accounts on YouTube, Instagram, TikTok, and LinkedIn. - Enhanced the online presence form to support social media verification initiation. This update improves the partner experience by streamlining the verification process for social media accounts.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughNormalize partner social data into a PartnerPlatform model and SocialPlatform enum; add utilities, upsert helpers, ScrapeCreators client, and multi-modal verification (TXT/OAuth/OTP) with Redis/PKCE; migrate APIs, crons, UI, schemas, scripts, and tests to a platforms-based model; update env vars. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Frontend
participant Server as "Server Action"
participant Cache as Redis
participant DB as PrismaDB
participant Provider
Note right of Frontend: startSocialVerificationAction
User->>Frontend: request verification (platform, handle)
Frontend->>Server: startSocialVerificationAction(platform, handle, source)
alt TXT flow (website)
Server->>DB: upsert PartnerPlatform with websiteTxtRecord
Server-->>Frontend: return TXT token
User->>Provider: add DNS TXT
Frontend->>Server: verifyDomainAction(platform, handle)
Server->>Provider: resolve DNS
Server->>DB: update PartnerPlatform (verifiedAt)
else OAuth flow (Twitter/LinkedIn/TikTok)
Server->>Server: build OAuth URL (store PKCE/code_verifier in Cookie/Redis)
Server-->>Frontend: return auth URL
User->>Provider: authorize -> callback -> Server
Server->>Cache: retrieve code_verifier
Server->>Provider: exchange code for token
Server->>DB: upsert PartnerPlatform (verifiedAt, metadata)
else Code flow (YouTube/Instagram)
Server->>Server: generate OTP
Server->>Cache: store OTP (24h)
Server->>DB: upsert PartnerPlatform(handle)
Server-->>Frontend: return OTP
User->>Provider: post OTP in bio/description
Frontend->>Server: verifySocialAccountByCodeAction(platform, handle)
Server->>Cache: get OTP
Server->>Provider: fetch profile (scrapeCreators)
Server->>DB: update PartnerPlatform (verifiedAt, platformId)
end
Server-->>Frontend: success
Frontend->>DB: refresh profile/UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 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. 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 |
- Updated social platform handling to utilize the `SocialPlatform` type from Prisma. - Enhanced the `start-social-verification` action to support website verification. - Improved the `verify-domain` action to fetch verification data from the database. - Streamlined the `verify-social-account` action to check for existing verifications before proceeding. - Adjusted UI components to accommodate changes in social platform handling and verification logic. These changes enhance the overall verification workflow for partners, ensuring a more robust and user-friendly experience.
- Integrated OAuth verification for social platforms like Twitter and TikTok. - Updated the `start-social-verification` action to handle different verification types, including website verification and verification codes. - Refactored the online presence form to streamline the user experience for social media verification. - Improved handling of partner platform data, ensuring accurate retrieval of handles and verification statuses. These changes improve the overall functionality and user experience of the online presence verification workflow.
…tions - Updated import paths for `ONLINE_PRESENCE_PROVIDERS` to reflect new structure. - Introduced a new file for online presence providers, defining verification logic for Twitter and TikTok. - Enhanced the `start-social-verification` action to utilize the updated provider definitions. These changes improve the organization and functionality of the online presence verification process.
- Updated the partner API to replace online presence verification with social media platforms. - Introduced polyfill for social media fields to maintain backward compatibility. - Adjusted related components and schemas to reflect the new structure for handling social media data.
- Integrated a mapping system for social media platforms by partner ID to streamline data handling. - Updated the partner types to include platforms, ensuring compatibility with the new structure. - Refactored online presence components to leverage the new platform data format, improving clarity and maintainability.
…dling - Simplified the rendering of social media columns by utilizing a unified PartnerSocialColumn component. - Enhanced the platform mapping logic to improve data retrieval for partner applications. - Updated type definitions to ensure consistency across partner-related data structures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In @apps/web/app/(ee)/api/partners/online-presence/callback/route.ts:
- Line 31: Rename the misspelled variable parsedSeachParams to
parsedSearchParams where it's declared (the result of
requestSchema.safeParse(getSearchParams(req.url))) and update all downstream
references (e.g., any checks like parsedSeachParams.success and uses of
parsedSeachParams.data) to parsedSearchParams so the code compiles and
references the correctly named variable.
- Around line 86-93: The code constructs URLSearchParams using non-null
assertions (clientId!, clientSecret!) without validating provider credentials,
which can throw at runtime; update the callback handler (where URLSearchParams
is created) to first check that clientId and clientSecret from the provider
config (and any clientIdParam) are not null/undefined, return an appropriate
error response (or throw) if they are missing, and only then build the
URLSearchParams with the validated values (replacing clientId! and clientSecret!
with the validated variables). Ensure the validation references the same
variables used in the diff (clientId, clientSecret, clientIdParam, codeVerifier,
URLSearchParams) so the params construction is never invoked with null
credentials.
- Around line 106-109: The current OAuth callback error path logs the full
tokenResponse (variable tokenResponse) which may contain sensitive tokens;
remove the tokenResponse from the console.warn and instead log only
non-sensitive context such as response.status/response.statusText or a generic
failure message, or omit logging entirely; ensure the code path that returns
NextResponse.redirect(redirectUrl) no longer prints the tokenResponse and that
any other logging in this handler avoids including the tokenResponse or raw
response body.
- Around line 95-102: The token request always sends HTTP Basic auth; change the
fetch request to choose provider-specific auth: detect the provider (e.g., the
variable used to identify the provider in this route) and if it's Twitter/X keep
the Authorization: Basic header, otherwise remove the header and add credentials
into urlParams (use TikTok names client_key and client_secret, use LinkedIn
names client_id and client_secret) before calling fetch(tokenUrl, ...). Ensure
you no longer base64-encode credentials into Authorization for TikTok/LinkedIn
and that the body includes the correct credential field names
(client_key/client_secret or client_id/client_secret) when those providers are
used, while leaving the existing code path for Twitter/X intact.
In @apps/web/lib/actions/partners/start-social-verification.ts:
- Line 130: The code reads SOCIAL_PLATFORM_CONFIGS[platform] into platformConfig
without guarding for a missing key; add a runtime check after that assignment in
startSocialVerification (or the enclosing function) to ensure platformConfig is
defined and handle the error (e.g., throw a descriptive Error or return a
400/422 response) when it’s undefined, using the platform value in the message;
this prevents runtime crashes if the enum has values not present in
SOCIAL_PLATFORM_CONFIGS.
🧹 Nitpick comments (7)
apps/web/lib/actions/partners/start-social-verification.ts (2)
87-114: Consider validating website handle format.Unlike other platforms, the website handle is not sanitized or validated beyond the 1-50 character length constraint. Consider validating that the handle is a valid domain/URL format before storing it.
🔍 Validation example
async function startWebsiteVerification({ partnerId, handle, }: Pick<VerificationParams, "partnerId" | "handle">): Promise< Extract<VerificationResult, { type: "txt_record" }> > { + // Validate URL format + try { + new URL(handle.startsWith('http') ? handle : `https://${handle}`); + } catch { + throw new Error("Please enter a valid website URL or domain."); + } + const websiteTxtRecord = `dub-domain-verification=${uuid()}`;
152-152: Consider consistent Redis key naming convention.Redis keys use different naming conventions:
- OAuth state:
partnerSocialVerification:${state}(camelCase)- Code verification:
social-verification:${partnerId}:${platform}:${handle}(kebab-case)Consider standardizing to one convention for consistency and maintainability.
Also applies to: 212-212
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts (5)
49-56: Consider cleaning up Redis state after retrieval.The Redis state is retrieved but never deleted after use. This means the state remains in Redis until expiry, which could allow replay attacks within the expiry window. Consider deleting the state immediately after successful retrieval to prevent reuse.
♻️ Proposed enhancement
// Find the state from Redis - const stateFromRedis = await redis.get<State>( - `partnerSocialVerification:${state}`, - ); + const redisKey = `partnerSocialVerification:${state}`; + const stateFromRedis = await redis.get<State>(redisKey); if (!stateFromRedis) { console.warn("State is invalid or expired."); return NextResponse.redirect(PARTNERS_DOMAIN); } + // Delete state immediately to prevent replay attacks + await redis.del(redisKey);
79-83: Document the development mode workaround.This redirect workaround for missing PKCE verifier cookies in ngrok development mode should be documented. Consider adding a comment explaining why this is necessary and under what conditions it applies.
140-151: Clean up PKCE code verifier cookie after successful verification.After successful verification, the
online_presence_code_verifiercookie should be deleted to prevent it from being reused or lingering in the browser.♻️ Proposed cleanup
await prisma.partnerPlatform.update({ where: { partnerId_platform: { partnerId, platform, }, }, data: { verifiedAt: new Date(), metadata: metadata || undefined, }, }); + // Clean up PKCE cookie + if (pkce && codeVerifier) { + (await cookies()).delete("online_presence_code_verifier"); + } return NextResponse.redirect(redirectUrl);
149-149: Simplify metadata assignment.The expression
metadata || undefinedis redundant. Ifmetadatais falsy, Prisma will handle it correctly without explicitly passingundefined.♻️ Simplification
data: { verifiedAt: new Date(), - metadata: metadata || undefined, + metadata, },
27-27: Consider adding rate limiting to prevent abuse.This OAuth callback endpoint doesn't have rate limiting. While the state validation provides some protection, an attacker could potentially abuse this endpoint by attempting multiple token exchanges with different codes or states. Consider adding rate limiting based on IP address or session ID.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/app/(ee)/api/partners/online-presence/callback/route.tsapps/web/lib/actions/partners/start-social-verification.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts (4)
packages/prisma/client.ts (1)
SocialPlatform(35-35)packages/utils/src/functions/urls.ts (1)
getSearchParams(40-49)apps/web/lib/api/partner-profile/online-presence-providers.ts (1)
ONLINE_PRESENCE_PROVIDERS(15-158)packages/prisma/index.ts (1)
prisma(3-9)
⏰ 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: build
🔇 Additional comments (5)
apps/web/lib/actions/partners/start-social-verification.ts (3)
23-39: LGTM! Well-structured schema and types.The Zod schema provides appropriate validation, and the discriminated union types clearly model the three verification flows.
48-85: LGTM! Clear routing logic with appropriate rate limiting.The action correctly authenticates via
authPartnerActionClientand routes to the appropriate verification flow. The rate limit of 5 attempts per hour per partner per platform provides reasonable protection against abuse.
172-186: LGTM! PKCE implementation is secure.The code verifier is properly stored in an httpOnly, secure (in production), sameSite: lax cookie with appropriate 5-minute expiry. The code challenge is correctly computed and added to the OAuth request.
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts (2)
60-63: Verify authorization logic for partner ownership.The authorization check at line 60 compares
session.user.defaultPartnerIdwithpartnerIdfrom the OAuth callback state. Confirm thatdefaultPartnerIdis the appropriate field to verify the authenticated user owns or has permission to manage this partner's social account. If users can be associated with multiple partners, this check alone may not be sufficient for proper authorization.
125-128: [Rewritten review comment]
[Exactly ONE classification tag]
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts
Outdated
Show resolved
Hide resolved
|
@coderabbitai final review please |
|
@devkiran, I'll provide a comprehensive final review of this social media verification PR. 🎯 Overall AssessmentThis is a well-architected feature that successfully migrates from flat partner fields to a normalized ✅ Security HighlightsYour implementation aligns well with current OAuth 2.0 best practices:
🔍 Key ObservationsArchitecture
Code Quality
💡 Recommendations1. LinkedIn Verification TODOIn // LinkedIn verification cannot verify handle yet
// TODO: LinkedIn API doesn't provide username in responseSuggestion: Consider documenting this limitation in the UI or deferring LinkedIn support until the API provides handle verification capability. 2. Error Message ConsistencyThe verification error messages could be more user-friendly: // In verify-social-account-by-code.ts
throw new Error("Verification code not found in your profile description.");Suggestion: Consider providing guidance on where exactly to place the code (e.g., "Add the verification code to the first line of your profile description"). 3. Token Logging RiskEnsure access tokens are never logged. I noticed fetch calls to provider APIs but didn't see explicit token redaction in error logs. Consider adding: // Add to fetch error handlers
console.error("Verification failed:", {
platform,
handle,
// Never log accessToken
});4. Redis Key ExpiryThe verification codes use 24-hour expiry, which is reasonable, but consider:
5. Migration VerificationThe backfill script
6. YouTube API Rate LimitsThe cron job fetches channels in batches of 50. Verify that Google's YouTube Data API quota won't be exceeded with your expected partner volume. 🎨 Code Style Observations
📊 Testing ConsiderationsConsider adding tests for:
🚀 Performance Notes
📝 Documentation GapsConsider documenting:
SummaryThis is production-ready code with solid security foundations. The migration strategy is sound, and the OAuth implementation follows current best practices. Address the LinkedIn TODO and consider the recommendations above, but the core implementation is excellent work! 🎉 📚 For reference: OAuth 2.0 security best practices confirmed current as of January 2026, with PKCE being the recommended standard for all client types. ✅ Actions performedReview triggered.
|
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In @apps/web/app/(ee)/api/partners/online-presence/callback/route.ts:
- Around line 117-120: The current OAuth callback code logs the full
tokenResponse on failure (in the if (!response.ok) branch), which may expose
sensitive fields like refresh_token; change this to avoid printing the entire
tokenResponse—either remove the tokenResponse from the log or log only
non-sensitive context (e.g., response.status, response.statusText, or a
sanitized subset such as error code/message). Update the warning in the route
handler's OAuth callback (the if (!response.ok) block that references
tokenResponse) to redact or omit sensitive fields before logging and ensure you
still redirect using redirectUrl as before.
- Around line 96-104: The code builds OAuth URL params using non-null assertions
clientId! and clientSecret! which can crash if credentials are missing; add
null-safety by validating clientId and clientSecret (and optional clientIdParam)
before constructing urlParams in the callback handler (the function that creates
urlParams), and return or throw a clear error/HTTP 400 response when they are
absent; then use the validated values (no ! assertions) when creating
URLSearchParams and keep the optional code_verifier logic with codeVerifier as
before.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(ee)/api/partners/online-presence/callback/route.tsapps/web/lib/actions/partners/start-social-verification.tsapps/web/lib/actions/partners/verify-social-account-by-code.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/actions/partners/verify-social-account-by-code.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Applied to files:
apps/web/lib/actions/partners/start-social-verification.ts
🧬 Code graph analysis (2)
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts (4)
packages/prisma/client.ts (1)
SocialPlatform(35-35)packages/utils/src/functions/urls.ts (1)
getSearchParams(40-49)apps/web/lib/api/partner-profile/online-presence-providers.ts (1)
ONLINE_PRESENCE_PROVIDERS(15-158)packages/prisma/index.ts (1)
prisma(3-9)
apps/web/lib/actions/partners/start-social-verification.ts (5)
packages/prisma/client.ts (1)
SocialPlatform(35-35)apps/web/lib/actions/safe-action.ts (1)
authPartnerActionClient(90-130)apps/web/lib/api/partner-profile/online-presence-providers.ts (1)
ONLINE_PRESENCE_PROVIDERS(15-158)apps/web/lib/api/partner-profile/upsert-partner-platform.ts (1)
upsertPartnerPlatform(15-41)apps/web/lib/social-utils.ts (1)
SOCIAL_PLATFORM_CONFIGS(10-50)
⏰ 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: build
🔇 Additional comments (9)
apps/web/lib/actions/partners/start-social-verification.ts (6)
1-27: LGTM - imports and schema definition.The imports are well-organized and the Zod schema provides appropriate validation with sensible defaults.
48-85: LGTM - main action with rate limiting and routing.The routing logic correctly handles the three verification pathways, and rate limiting is appropriately scoped to prevent abuse per partner per platform.
87-114: LGTM - website TXT record verification.The implementation correctly generates a unique verification token and persists it for later validation.
116-166: LGTM - OAuth verification with improved state security.The refactored implementation addresses previous security concerns:
- State is now a random token (nanoid) stored in Redis, preventing state tampering
- The callback validates that the authenticated user owns the partnerId from Redis
- Platform config validation is now present (lines 132-134)
- Cookie settings for PKCE are appropriately secure
168-199: LGTM - PKCE implementation and OAuth URL construction.The PKCE flow follows the specification correctly with S256 challenge method, and the cookie settings are secure.
201-242: LGTM - code-based verification.The implementation correctly generates and stores OTP with appropriate expiry. The platform config validation (lines 211-213) addresses the previous review concern.
apps/web/app/(ee)/api/partners/online-presence/callback/route.ts (3)
15-24: LGTM - schema and type definitions.The Zod schema validation and State interface are well-defined.
40-63: LGTM - session validation and authorization checks.The implementation now properly validates:
- User is authenticated (lines 43-46)
- State is valid and not expired via Redis (lines 53-56)
- Authenticated user owns the partnerId (line 60)
This addresses the critical security concern from previous reviews about state tampering.
151-169: LGTM - verification persistence and cleanup.The implementation correctly:
- Updates the
verifiedAttimestamp on successful verification- Persists any metadata returned by the provider
- Cleans up the PKCE code verifier cookie after use
| // Get access token | ||
| const urlParams = new URLSearchParams({ | ||
| [clientIdParam ?? "client_id"]: clientId!, | ||
| client_secret: clientSecret!, | ||
| code, | ||
| redirect_uri: `${PARTNERS_DOMAIN_WITH_NGROK}/api/partners/online-presence/callback`, | ||
| grant_type: "authorization_code", | ||
| ...(codeVerifier && { code_verifier: codeVerifier }), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add null safety checks for OAuth credentials.
The code uses non-null assertions (clientId!, clientSecret!) without prior validation. If credentials are missing, this will cause a runtime error.
🛡️ Proposed fix
+ if (!clientId || !clientSecret) {
+ console.error(`Missing OAuth credentials for platform: ${platform}`);
+ return NextResponse.redirect(redirectUrl);
+ }
+
// Get access token
const urlParams = new URLSearchParams({
- [clientIdParam ?? "client_id"]: clientId!,
- client_secret: clientSecret!,
+ [clientIdParam ?? "client_id"]: clientId,
+ client_secret: clientSecret,
code,
redirect_uri: `${PARTNERS_DOMAIN_WITH_NGROK}/api/partners/online-presence/callback`,
grant_type: "authorization_code",
...(codeVerifier && { code_verifier: codeVerifier }),
});Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @apps/web/app/(ee)/api/partners/online-presence/callback/route.ts around
lines 96 - 104, The code builds OAuth URL params using non-null assertions
clientId! and clientSecret! which can crash if credentials are missing; add
null-safety by validating clientId and clientSecret (and optional clientIdParam)
before constructing urlParams in the callback handler (the function that creates
urlParams), and return or throw a clear error/HTTP 400 response when they are
absent; then use the validated values (no ! assertions) when creating
URLSearchParams and keep the optional code_verifier logic with codeVerifier as
before.
| const response = await fetch(tokenUrl, { | ||
| headers: { | ||
| "Content-Type": "application/x-www-form-urlencoded", | ||
| Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`, | ||
| }, | ||
| method: "POST", | ||
| body: urlParams.toString(), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OAuth token authentication breaks for TikTok and LinkedIn.
The current implementation unconditionally uses HTTP Basic Authentication for all providers, but this is incompatible with TikTok and LinkedIn:
- Twitter/X: Requires Basic Authorization header ✓
- TikTok: Expects
client_keyandclient_secretonly in POST body (no Basic auth) - LinkedIn: Expects
client_idandclient_secretonly in POST body (no Basic auth)
🔧 Proposed fix - provider-specific authentication
+ // Build headers - Twitter requires Basic auth, TikTok/LinkedIn use body-only credentials
+ const headers: Record<string, string> = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ };
+
+ if (platform === "twitter") {
+ headers["Authorization"] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
+ }
+
const response = await fetch(tokenUrl, {
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
- },
+ headers,
method: "POST",
body: urlParams.toString(),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch(tokenUrl, { | |
| headers: { | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`, | |
| }, | |
| method: "POST", | |
| body: urlParams.toString(), | |
| }); | |
| // Build headers - Twitter requires Basic auth, TikTok/LinkedIn use body-only credentials | |
| const headers: Record<string, string> = { | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| }; | |
| if (platform === "twitter") { | |
| headers["Authorization"] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`; | |
| } | |
| const response = await fetch(tokenUrl, { | |
| headers, | |
| method: "POST", | |
| body: urlParams.toString(), | |
| }); |
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In @apps/web/ui/partners/online-presence-form.tsx:
- Around line 565-567: PartnerProps is missing the platforms field which led to
unsafe type assertions like partnerWithPlatforms in online-presence-form.tsx;
update the PartnerProps interface to include platforms: PartnerSocialPlatform[]
(or platforms?: PartnerSocialPlatform[] if it can be absent) in the types module
where PartnerProps is declared, import or reference the existing
PartnerSocialPlatform type there, then remove the ad-hoc type assertions (e.g.,
the partnerWithPlatforms cast) in online-presence-form.tsx and anywhere else
asserting platforms so the compiler uses the corrected PartnerProps directly.
- Around line 162-170: Wrap the URL parsing logic (the creation of websiteUrl
from input.handle and the new URL(...) call used to populate
setDomainVerificationData) in a try-catch; if new URL throws, catch the error,
log it (or set a UI-visible validation error) and clear or avoid calling
setDomainVerificationData (e.g., setDomainVerificationData(undefined) or a
dedicated error state) so the component fails gracefully instead of throwing.
Ensure you still build websiteUrl from input.handle before parsing and keep the
logic inside the data.type === "txt_record" branch.
- Around line 262-273: The verify click handlers currently return
isStartingSocialVerification (a pending flag) which doesn't distinguish OAuth
redirects vs code flows; modify startSocialVerification's onSuccess handler to
set a new boolean flag (e.g., isRedirecting) when the success payload indicates
an OAuth redirect flow, and clear it for code-based flows, then update all
onVerifyClick handlers (the ones calling startSocialVerification and returning
isStartingSocialVerification) to return that new isRedirecting flag instead of
isStartingSocialVerification so VerifyButton receives true for redirecting OAuth
flows and false for verification-code flows; keep using startSocialVerification,
isStartingSocialVerification and VerifyButton names to locate changes.
🧹 Nitpick comments (4)
apps/web/lib/actions/partners/update-online-presence.ts (3)
117-139: Consider normalizing hostname for domain comparison.The current implementation treats
www.example.comandexample.comas different domains (line 126-127), which will resetverifiedAtwhen users simply add or remove thewwwprefix. Most verification systems treat these as equivalent since they typically share DNS control and TXT records.♻️ Suggested normalization approach
const oldDomain = currentHandle ? new URL(currentHandle).hostname : null; const newDomain = inputValue ? new URL(inputValue).hostname : null; + // Normalize domains by removing www prefix for comparison + const normalizeHostname = (hostname: string | null) => + hostname?.replace(/^www\./i, "").toLowerCase() ?? null; + domainChanged = - oldDomain?.toLowerCase() !== newDomain?.toLowerCase(); + normalizeHostname(oldDomain) !== normalizeHostname(newDomain);
84-96: Manual platform configuration requires maintenance.The
platformConfigsarray is manually constructed, which creates a maintenance burden if new social platforms are added to the schema. There's a risk of forgetting to update this array when adding a new platform field.Consider deriving the platform list programmatically from the schema or using a shared constant that both the schema and this logic reference. This would ensure consistency and reduce maintenance overhead.
186-191: Inconsistent return value handling.Line 187 explicitly returns
undefinedwhen no operations are needed, but line 190 implicitly returns thePromise.allresult. While the AI summary indicates no composite response is needed, making the return behavior explicit would improve code clarity.♻️ Suggested explicit return
if (operations.length === 0) { return; } - await Promise.all(operations); + await Promise.all(operations); + return;Or define an explicit return type in the action chain for better type safety.
apps/web/ui/partners/online-presence-form.tsx (1)
554-561: Move platform map outside the hook for better performance.The
platformMapis a constant object that doesn't depend on any hook state or props, yet it's recreated on every render. Define it outside the component or memoize it.♻️ Proposed fix
+// Map form property to SocialPlatform enum +const PLATFORM_MAP: Record<keyof OnlinePresenceFormData, SocialPlatform> = { + website: "website", + youtube: "youtube", + twitter: "twitter", + linkedin: "linkedin", + instagram: "instagram", + tiktok: "tiktok", +}; + function useVerifiedState({ property, }: { property: keyof OnlinePresenceFormData; }) { const { partner: partnerProfile } = usePartnerProfile(); const { watch, getFieldState } = useFormContext<OnlinePresenceFormData>(); const value = watch(property); const isValid = !!value && !getFieldState(property).invalid; const loading = !partnerProfile && isValid; - // Map form property to SocialPlatform enum - const platformMap: Record<keyof OnlinePresenceFormData, SocialPlatform> = { - website: "website", - youtube: "youtube", - twitter: "twitter", - linkedin: "linkedin", - instagram: "instagram", - tiktok: "tiktok", - }; - - const platform = platformMap[property]; + const platform = PLATFORM_MAP[property];
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/lib/actions/partners/update-online-presence.tsapps/web/ui/partners/online-presence-form.tsx
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` prop—setting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
⏰ 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: build
🔇 Additional comments (4)
apps/web/lib/actions/partners/update-online-presence.ts (4)
12-25: LGTM! Clean schema helper with proper null/undefined semantics.The three-way distinction (undefined = ignore, null = remove, string = sanitize) is well-implemented and clearly documented.
27-47: LGTM! Consistent validation pattern.The website schema follows the same null/undefined semantics as social platforms, ensuring consistent behavior across all platform types.
49-57: LGTM! Comprehensive schema definition.The schema covers all social platforms and includes useful tracking metadata via the
sourcefield.
190-190: Potential data integrity issue with parallel upserts.Using
Promise.allfor parallel database operations means if any operation fails, some may have already succeeded, leading to partial updates. This creates inconsistent state that could confuse users.Consider using a Prisma transaction to ensure atomicity (all operations succeed or all rollback), or use
Promise.allSettledto handle partial failures explicitly. See the previous review comment for detailed implementation suggestions.⛔ Skipped due to learnings
Learnt from: TWilson023 Repo: dubinc/dub PR: 2872 File: apps/web/lib/actions/partners/update-partner-profile.ts:97-104 Timestamp: 2025-09-24T15:57:55.285Z Learning: In the Dub codebase, the team prefers to maintain consistency with existing patterns for database operations, even if there are theoretical improvements available. The deleteMany: {} pattern for relation updates is an accepted approach that the team considers low-risk.Learnt from: devkiran Repo: dubinc/dub PR: 2177 File: apps/web/lib/api/links/bulk-create-links.ts:66-84 Timestamp: 2025-06-06T07:59:03.120Z Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In @apps/web/ui/partners/online-presence-form.tsx:
- Around line 162-170: When handling data.type === "txt_record" in the
online-presence form, new URL(websiteUrl).hostname can throw for malformed
input.handle; wrap the URL creation and hostname extraction in a try-catch
around the block that computes websiteUrl and calls setDomainVerificationData,
and on error gracefully handle it (e.g., setDomainVerificationData to
null/undefined or set an error state/message and log the error) so the
verification flow doesn’t crash; keep using input.handle and
data.websiteTxtRecord as before when parsing succeeds.
- Around line 664-676: The partner object is being force-cast to include
platforms (partner as typeof partner & { platforms?: PartnerSocialPlatform[]
})—remove that type assertion and instead update the type returned by
usePartnerProfile() (or the underlying PartnerProps/partner type) to include
platforms: PartnerSocialPlatform[] so consumers like the component using
getPlatformData(partner.platforms, "youtube") can access platforms safely;
ensure the hook's return type (usePartnerProfile) and any relevant
PartnerProps/partner interface are updated throughout the codebase so the
platforms field is typed and optionality matches runtime behavior.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/ui/partners/online-presence-form.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` prop—setting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
📚 Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/ui/partners/online-presence-form.tsx
🧬 Code graph analysis (1)
apps/web/ui/partners/online-presence-form.tsx (7)
apps/web/lib/types.ts (2)
PartnerProps(458-462)PartnerSocialPlatform(755-755)packages/prisma/client.ts (1)
SocialPlatform(35-35)packages/utils/src/functions/urls.ts (1)
getPrettyUrl(130-138)apps/web/lib/actions/partners/start-social-verification.ts (1)
startSocialVerificationAction(48-85)apps/web/lib/actions/parse-action-errors.ts (1)
parseActionError(3-29)apps/web/ui/modals/domain-verification-modal.tsx (1)
DomainVerificationModal(22-31)apps/web/ui/modals/social-verification-by-code-modal.tsx (1)
SocialVerificationByCodeModal(43-54)
⏰ 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: build
🔇 Additional comments (3)
apps/web/ui/partners/online-presence-form.tsx (3)
58-72: LGTM: Clean helper functions for platform data access.The helper functions provide a clean abstraction over the platforms array, using proper optional chaining for safety.
220-241: LGTM: Proper modal state management.The conditional modal rendering with state cleanup on close follows React best practices and correctly handles the verification flow state lifecycle.
78-93: LGTM: Well-structured form management and data synchronization.The form initialization, submission flow, and SWR cache mutation strategy are well-implemented:
- Form defaults properly derived from platforms data via helper functions
- Cache mutation after successful updates keeps UI in sync
- Batch update approach (line 708 sets dirty state, submission persists) provides good UX
The refactoring to the platforms-based model is clean and maintains backward compatibility.
Also applies to: 128-139, 246-249
There was a problem hiding this 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/format-partners-for-export.ts:
- Around line 56-59: The code calls polyfillSocialMediaFields(partner.platforms)
without guarding against partner.platforms being undefined, which can cause
buildSocialPlatformLookup to iterate over an undefined and throw; update the
call site in format-partners-for-export (where partner is merged) to defensively
pass an empty array when platforms is falsy (e.g.,
polyfillSocialMediaFields(partner.platforms ?? [])), or add an early check
inside polyfillSocialMediaFields/buildSocialPlatformLookup to treat undefined as
an empty array before the for...of loop, ensuring partner.platforms being
undefined during migration does not throw.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/api/partners/format-partners-for-export.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.
Applied to files:
apps/web/lib/api/partners/format-partners-for-export.ts
🧬 Code graph analysis (1)
apps/web/lib/api/partners/format-partners-for-export.ts (1)
apps/web/lib/social-utils.ts (1)
polyfillSocialMediaFields(131-144)
⏰ 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: build
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
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/auto-approve-partner/route.ts (1)
66-104: Guard against missing workspace owner before accessingusers[0].If the workspace has no user with
role: "owner",users[0]will beundefinedand line 102 will throw aTypeError. While workspaces should always have an owner, adding a defensive check prevents unexpected cron failures.🛡️ Proposed fix
const { canManageFraudEvents } = getPlanCapabilities(program.workspace.plan); + const ownerUser = program.workspace.users[0]; + + if (!ownerUser) { + return logAndRespond( + `No owner found for workspace ${program.workspace.id}. Skipping auto-approval.`, + ); + } + if (canManageFraudEvents) { const { riskSeverity } = await getPartnerApplicationRisks({ program, partner: programEnrollment.partner, }); if (riskSeverity === "high") { return logAndRespond( `Partner ${partnerId} has high risk. Skipping auto-approval.`, ); } } await approvePartnerEnrollment({ programId, partnerId, - userId: program.workspace.users[0].userId, + userId: ownerUser.userId, groupId: programEnrollment.groupId, });apps/web/app/(ee)/api/partners/route.ts (1)
69-87: Critical: BigInt serialization will fail in JSON response.When
includeSocialPlatformsistrue, the response includes theplatformsarray containingpartnerSocialPlatformSchemaobjects withBigIntfields (followers,posts,views). JavaScript's nativeJSON.stringify()(used byNextResponse.json()) cannot serializeBigIntvalues and will throw aTypeError: Do not know how to serialize a BigInt.This breaks the API whenever clients request
includeSocialPlatforms=true.🔧 Proposed fix: Convert BigInt to Number before serialization
Transform the platforms data before parsing:
return NextResponse.json( z.array(responseSchema).parse( partners.map((partner) => ({ ...partner, clicks: partner.totalClicks, leads: partner.totalLeads, conversions: partner.totalConversions, sales: partner.totalSales, saleAmount: partner.totalSaleAmount, ...polyfillSocialMediaFields(partner.platforms), + ...(includeSocialPlatforms && { + platforms: partner.platforms.map((p) => ({ + ...p, + followers: Number(p.followers), + posts: Number(p.posts), + views: Number(p.views), + })), + }), })), ), );Alternatively, update
partnerSocialPlatformSchemato usez.number()instead ofz.bigint()if the values fit within JavaScript's safe integer range, or usez.string()and store BigInt values as strings for precision.
🧹 Nitpick comments (2)
apps/web/app/(ee)/api/workflows/partner-approved/route.ts (1)
255-259: Migration query correctly fetches normalized social platform data.The query properly retrieves partnerPlatform records for polyfilling the webhook payload during the migration to the normalized model.
♻️ Optional: Use select clause for efficiency
Consider selecting only the required fields to reduce data transfer:
const partnerPlatforms = await prisma.partnerPlatform.findMany({ where: { partnerId, }, + select: { + platform: true, + handle: true, + }, });apps/web/lib/actions/partners/update-online-presence.ts (1)
129-129: Consider structured logging instead of console.error.For production environments, replace
console.errorwith a structured logging framework to ensure proper log aggregation, filtering, and observability.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.tsapps/web/app/(ee)/api/partners/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/lib/actions/partners/update-online-presence.tsapps/web/lib/actions/partners/update-partner-profile.tsapps/web/lib/api/partners/format-partners-for-export.tsapps/web/lib/api/partners/get-partners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/actions/partners/update-partner-profile.ts
- apps/web/lib/api/partners/format-partners-for-export.ts
🧰 Additional context used
🧠 Learnings (16)
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.
Applied to files:
apps/web/app/(ee)/api/partners/route.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.
Applied to files:
apps/web/app/(ee)/api/partners/route.tsapps/web/lib/api/partners/get-partners.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/app/(ee)/api/partners/route.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.tsapps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-12-09T12:54:41.818Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3207
File: apps/web/lib/cron/with-cron.ts:27-56
Timestamp: 2025-12-09T12:54:41.818Z
Learning: In `apps/web/lib/cron/with-cron.ts`, the `withCron` wrapper extracts the request body once and provides it to handlers via the `rawBody` parameter. Handlers should use this `rawBody` string parameter (e.g., `JSON.parse(rawBody)`) rather than reading from the Request object via `req.json()` or `req.text()`.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-12-15T16:45:51.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3213
File: apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts:122-122
Timestamp: 2025-12-15T16:45:51.667Z
Learning: In cron endpoints under apps/web/app/(ee)/api/cron, continue using handleCronErrorResponse for error handling. Do not detect QStash callbacks or set Upstash-NonRetryable-Error headers in these cron routes, so QStash can retry cron errors via its native retry mechanism. The existing queueFailedRequestForRetry logic should remain limited to specific user-facing API endpoints (e.g., /api/track/lead, /api/track/sale, /api/links) to retry only transient Prisma/database errors. This pattern should apply to all cron endpoints under the cron directory in this codebase.
Applied to files:
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-10-28T19:17:44.390Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:32-37
Timestamp: 2025-10-28T19:17:44.390Z
Learning: In Prisma queries, the `include` clause is only used for relationships (one-to-one, one-to-many, many-to-many). Regular scalar fields, JSON fields, and other non-relational columns are automatically included in the query result and do not need to be specified in the `include` object.
Applied to files:
apps/web/lib/api/partners/get-partners.ts
📚 Learning: 2025-12-18T16:55:57.098Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3244
File: apps/web/app/(ee)/api/cron/bounties/create-draft-submissions/route.ts:148-174
Timestamp: 2025-12-18T16:55:57.098Z
Learning: In the bounty system (e.g., apps/web/app/(ee)/api/cron/bounties/create-draft-submissions/route.ts), bounties cannot be created with workflow attributes `partnerEnrolledDays` and `partnerJoined`. Bounty workflows only support attributes available from partner link stats (clicks, sales, leads, conversions, saleAmount) and totalCommissions, which is a subset of the general WORKFLOW_ATTRIBUTES schema.
Applied to files:
apps/web/app/(ee)/api/workflows/partner-approved/route.ts
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/app/(ee)/api/workflows/partner-approved/route.ts
📚 Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
Repo: dubinc/dub PR: 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/workflows/partner-approved/route.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
Applied to files:
apps/web/lib/actions/partners/update-online-presence.ts
🧬 Code graph analysis (4)
apps/web/app/(ee)/api/partners/route.ts (2)
apps/web/lib/zod/schemas/partners.ts (1)
partnerSocialPlatformSchema(209-217)apps/web/lib/social-utils.ts (1)
polyfillSocialMediaFields(131-144)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
apps/web/app/(ee)/api/workflows/partner-approved/route.ts (3)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/partners.ts (1)
EnrolledPartnerSchema(366-467)apps/web/lib/social-utils.ts (1)
polyfillSocialMediaFields(131-144)
apps/web/lib/actions/partners/update-online-presence.ts (5)
packages/prisma/client.ts (1)
SocialPlatform(35-35)apps/web/lib/social-utils.ts (2)
sanitizeSocialHandle(70-107)sanitizeWebsite(52-68)packages/utils/src/functions/urls.ts (1)
isValidUrl(1-8)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/partner-profile/upsert-partner-platform.ts (1)
upsertPartnerPlatform(15-41)
⏰ 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: build
🔇 Additional comments (12)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (4)
1-14: LGTM!Imports and schema definition are appropriate. Using Zod v4 import path and validating required parameters.
21-36: LGTM!The composite key lookup and includes are well-structured. Including
platformsaligns with the new data model and supports thegetPartnerApplicationRiskscall.
38-62: LGTM!Validation checks are comprehensive and handle all edge cases with clear log messages.
106-108: LGTM!Success response is clear and informative.
apps/web/app/(ee)/api/workflows/partner-approved/route.ts (2)
7-8: LGTM: Import changes align with the migration.The updated imports correctly support the migration to the normalized PartnerPlatform model and the removal of group/rewards logic from the email step.
264-264: Correct implementation of backward-compatible migration.The polyfill correctly transforms normalized PartnerPlatform records into the flat social media structure expected by the webhook payload, maintaining backward compatibility during the migration.
apps/web/lib/api/partners/get-partners.ts (1)
55-59: LGTM! Nested include correctly expands partner data.The change from
partner: trueto a nested include withplatforms: trueproperly fetches the related PartnerPlatform records. The platforms data flows through to consumers via the spread operator on line 70 (...partner), enabling downstream access to the new platforms-based social data model.apps/web/app/(ee)/api/partners/route.ts (3)
7-7: LGTM! Imports support the platforms-based data model.The imports for
polyfillSocialMediaFieldsandpartnerSocialPlatformSchemaare necessary and correctly used to transform and validate the new platforms data structure.Also applies to: 12-12
23-23: Good backward compatibility design.The
includeSocialPlatformsflag enables a gradual migration: when false, clients receive only the polyfilled flat social fields (website, youtube, etc.) via line 84; when true, they additionally receive the structuredplatformsarray. This approach maintains compatibility while enabling new consumers to adopt the richer data model.Also applies to: 69-73
84-84: LGTM! Polyfill ensures backward compatibility.Unconditionally spreading
polyfillSocialMediaFields(partner.platforms)maintains backward compatibility by always including the legacy flat social fields (website, youtube, twitter, linkedin, instagram, tiktok) in the response, regardless of theincludeSocialPlatformsflag.apps/web/lib/actions/partners/update-online-presence.ts (2)
12-47: Excellent schema design with proper semantics.The schema helpers correctly leverage Zod v4's
.nullish()and transforms to preserve distinct semantics:
undefined= ignore (no change)null= explicit deletionstring= sanitize and updateThis provides a clean API for the caller and centralizes validation logic.
151-190: Acknowledge existing concern: parallel operations without transaction.The parallel execution of deletions and upserts using
Promise.allcan lead to partial updates if any operation fails, leaving data in an inconsistent state. This concern has already been flagged in a previous review.Consider using a Prisma transaction (e.g.,
prisma.$transaction([...])) to ensure atomicity, where all operations succeed or all roll back together.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/partners.ts (1)
210-218: Consider adding validation constraints to thehandlefield.The
handlefield currently accepts any string without length or format validation. Social media handles typically have platform-specific constraints (e.g., length limits, allowed characters, no spaces).♻️ Suggested enhancement with validation
export const partnerSocialPlatformSchema = z.object({ platform: z.nativeEnum(SocialPlatform), - handle: z.string(), + handle: z.string().min(1).max(255).trim(), verifiedAt: z.date().nullable(), platformId: z.string().nullable(), followers: z.bigint().default(BigInt(0)), posts: z.bigint().default(BigInt(0)), views: z.bigint().default(BigInt(0)), });Alternatively, implement platform-specific validation using
.refine()or.superRefine()if different platforms have different handle requirements.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/zod/schemas/partners.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.
Applied to files:
apps/web/lib/zod/schemas/partners.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.
Applied to files:
apps/web/lib/zod/schemas/partners.ts
🧬 Code graph analysis (1)
apps/web/lib/zod/schemas/partners.ts (2)
apps/web/lib/zod/schemas/misc.ts (1)
booleanQuerySchema(23-30)packages/prisma/client.ts (1)
SocialPlatform(35-35)
⏰ 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: build
🔇 Additional comments (3)
apps/web/lib/zod/schemas/partners.ts (3)
9-9: LGTM!The import of
SocialPlatformis correctly added to support the newpartnerSocialPlatformSchemadefined below.
470-484: LGTM!The schema extension correctly adds the
platformsarray while maintaining the existing structure. The use of.nullable()for theplatformsfield is appropriate, indicating the field is always present in responses but may be null. The chained.extend()calls are valid in Zod 4 and properly merge the profile and online presence fields.
187-187: No action needed. The parameterincludeSocialPlatformsis new and does not represent a rename of an existing parameter—includeOnlinePresenceVerificationdoes not exist in the codebase. The extended schema is internal to the dashboard and not exposed in the public API documentation (which uses the basegetPartnersQuerySchema). There is no backward compatibility concern.Likely an incorrect or invalid review comment.
There was a problem hiding this 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 @packages/prisma/schema/platform.prisma:
- Line 20: Add a Zod schema for the PartnerPlatform.metadata JSON field (e.g.,
export const PartnerPlatformMetadataSchema = z.object({ websiteTxtRecord:
z.string().optional() })) and derive a TypeScript type (e.g., type
PartnerPlatformMetadata = z.infer<typeof PartnerPlatformMetadataSchema>); then
update the code in start-social-verification.ts to validate the object before
insertion by parsing with PartnerPlatformMetadataSchema.parse({ websiteTxtRecord
}), and pass the validated object to Prisma instead of the raw literal; ensure
any other places reading/writing PartnerPlatform.metadata use the same
schema/type for consistency.
🧹 Nitpick comments (4)
packages/prisma/schema/platform.prisma (4)
1-8: Consider adding documentation for enum values.The enum is well-structured and covers major platforms. However, inline comments explaining each platform's purpose (e.g., verification method, API requirements) would improve maintainability.
Additionally, note that the enum uses
x- confirm this aligns with your branding strategy, given the platform's rebranding.
14-14: Add length constraint to the handle field.The
handlefield accepts any string length, which could lead to issues with extremely long URLs or handles. Consider adding a@db.VarChar(500)or similar constraint to prevent potential storage or performance issues.📝 Suggested enhancement
- handle String // The username/handle/URL for the platform + handle String @db.VarChar(500) // The username/handle/URL for the platform
24-26: Consider additional indexes for common query patterns.The unique constraint on
[partnerId, platform]creates an implicit index, which is excellent for partner-specific lookups. However, consider whether additional indexes are needed for other query patterns:
- Queries filtering by
platformalone (e.g., "all YouTube accounts")- Queries filtering by
verifiedAt(e.g., "recently verified accounts")- Queries combining
platformandverifiedAt(e.g., "verified Instagram accounts")If these patterns are common, add explicit indexes:
@@index([platform]) @@index([verifiedAt]) @@index([platform, verifiedAt])The automated reviewer recommended verifying the index on
PartnerPlatform(partnerId, platform)is sufficient - this suggestion extends that to cover additional access patterns.Based on the coderabbitai comments mentioning verifying index sufficiency.
16-16: Consider adding a verification status enum.The current design uses
verifiedAttimestamp to indicate verification state (null = unverified, timestamp = verified). For more nuanced verification flows, consider adding a status enum:enum VerificationStatus { pending verified failed expired }This would allow tracking intermediate states like pending verifications or failed attempts. However, for the current scope, the timestamp-only approach is acceptable and simpler.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/prisma/schema/partner.prismapackages/prisma/schema/platform.prisma
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/prisma/schema/partner.prisma
⏰ 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: build
🔇 Additional comments (2)
packages/prisma/schema/platform.prisma (2)
1-27: Overall schema design looks solid.The normalized
PartnerPlatformmodel is a clean architectural improvement over flat fields. The design supports:✅ Multi-platform support per partner via the unique constraint
✅ Cascade deletion for data integrity
✅ Extensibility through the metadata JSON field
✅ Proper handling of large metric values with BigIntThe schema aligns well with the PR objectives of streamlining partner social verification. Combined with the other architectural improvements mentioned in the automated review (PKCE, rate limiting, typed patterns), this provides a solid foundation.
17-19: The BigInt serialization is already properly handled in the application layer. The code explicitly converts BigInt values to numbers usingNumber()before display and comparison (seeapps/web/lib/partners/online-presence.tsandapps/web/lib/partners/evaluate-reward-conditions.ts), which prevents JSON serialization issues. No additional handling is needed.Likely an incorrect or invalid review comment.
| followers BigInt @default(0) | ||
| posts BigInt @default(0) | ||
| views BigInt @default(0) | ||
| metadata Json? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Search for Zod schemas or validation related to metadata
rg -n --type=ts 'metadata.*z\.|z\..*metadata' . --max-count=20
# Look for TypeScript type definitions for metadata
rg -n --type=ts 'interface.*metadata|type.*metadata' . --max-count=20
# Check for validation functions related to metadata
rg -n --type=ts 'validate.*metadata|metadata.*validate' . --max-count=20Repository: dubinc/dub
Length of output: 1765
🏁 Script executed:
# Search for PartnerPlatform related schemas
rg -n --type=ts 'PartnerPlatform.*metadata|platform.*metadata' . --max-count=20
# Check if there's a schema for PartnerPlatform
rg -n --type=ts 'PartnerPlatform.*Schema|partnerPlatform.*z\.' . --max-count=20
# Look at the platform model context in prisma
rg -n 'model.*Platform' packages/prisma/schema/ -A 20 --max-count=30Repository: dubinc/dub
Length of output: 1435
🏁 Script executed:
# Look for PartnerPlatform specific validation schemas
rg -n --type=ts 'PartnerPlatform|partnerPlatform' apps/web/lib/zod/ -C 3 --max-count=30
# Search for API endpoints that handle PartnerPlatform
fd -type f -name "*.ts" apps/web/app/api | head -20 | xargs -I {} rg -l 'PartnerPlatform' {}
# Look for any input/output validation for PartnerPlatform
rg -n --type=ts 'export.*schema.*partner|PartnerPlatform.*Input|PartnerPlatform.*Output' apps/web/lib/ -C 2 --max-count=20Repository: dubinc/dub
Length of output: 1054
🏁 Script executed:
# Look at the upsert-partner-platform file to see how metadata is handled
cat -n apps/web/lib/api/partner-profile/upsert-partner-platform.ts
# Search for where metadata validation happens in partner-related operations
rg -n --type=ts 'metadata' apps/web/lib/api/partner-profile/ -B 2 -A 2
# Check if there's input validation for the upsert operation
rg -n --type=ts 'CreatePartnerPlatform|UpsertPartnerPlatform|UpdatePartnerPlatform' apps/web/lib/ --max-count=20Repository: dubinc/dub
Length of output: 3133
🏁 Script executed:
# Find where upsertPartnerPlatform is called
rg -n --type=ts 'upsertPartnerPlatform' apps/web/ -B 5 -A 5 --max-count=30
# Check if there's a Zod schema for the API input that calls this
rg -n --type=ts 'POST.*partner.*platform|partnerPlatform.*schema' apps/web/lib/zod/ --max-count=20Repository: dubinc/dub
Length of output: 8640
🏁 Script executed:
# Look at update-online-presence to see full context of how metadata is validated
cat -n apps/web/lib/actions/partners/update-online-presence.ts | head -200
# Check start-social-verification for metadata handling
cat -n apps/web/lib/actions/partners/start-social-verification.ts | head -250Repository: dubinc/dub
Length of output: 15227
🏁 Script executed:
# Check the import-partners usage and see what metadata structure is passed
cat -n apps/web/lib/firstpromoter/import-partners.ts | sed -n '150,170p'
# Look for any middleware or hooks that might validate metadata
rg -n --type=ts 'partnerPlatform.*validate|metadata.*validate' apps/web/lib/actions/ apps/web/lib/api/ --max-count=20Repository: dubinc/dub
Length of output: 691
Add Zod schema validation for PartnerPlatform metadata field.
The metadata field is currently unvalidated. In start-social-verification.ts, metadata is inserted directly as { websiteTxtRecord } without schema validation. Define a Zod schema for the metadata structure to ensure type safety and consistency, similar to the schemas used elsewhere in the codebase (e.g., webhook and fraud event schemas).
🤖 Prompt for AI Agents
In @packages/prisma/schema/platform.prisma at line 20, Add a Zod schema for the
PartnerPlatform.metadata JSON field (e.g., export const
PartnerPlatformMetadataSchema = z.object({ websiteTxtRecord:
z.string().optional() })) and derive a TypeScript type (e.g., type
PartnerPlatformMetadata = z.infer<typeof PartnerPlatformMetadataSchema>); then
update the code in start-social-verification.ts to validate the object before
insertion by parsing with PartnerPlatformMetadataSchema.parse({ websiteTxtRecord
}), and pass the validated object to Prisma instead of the raw literal; ensure
any other places reading/writing PartnerPlatform.metadata use the same
schema/type for consistency.
This update improves the partner experience by streamlining the verification process for social media accounts.
Summary by CodeRabbit
New Features
Bug Fixes & Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.