-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Refactor to @axiomhq/nextjs
#3069
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 57 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
WalkthroughReplace external next-axiom usage with an internal Axiom server wrapper and client, add centralized Axiom logger and route wrappers, extract/shared error codes and exceeded-limit messaging, change date-range validation to non-throwing results plus an asserting wrapper, and update many import paths, typings, middleware, and tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Req as Incoming Request
participant MW as Middleware
participant Logger as Axiom Logger
participant Wrapper as withAxiom
participant Route as Route Handler
Req->>MW: request arrives
MW->>Logger: logger.info(transformMiddlewareRequest(req))
MW->>MW: ev.waitUntil(logger.flush())
MW->>Route: continue
Route->>Wrapper: wrapped handler invoked
Wrapper->>Route: handler(req or clonedReq)
Route->>Route: process request
alt success
Wrapper->>Logger: onSuccess logs body/searchParams & level
Wrapper->>Logger: flush()
else error
Wrapper->>Logger: log error and flush
end
Wrapper-->>Req: response
sequenceDiagram
participant Caller as Caller
participant Assert as assertValidDateRangeForPlan
participant Validate as validDateRangeForPlan
participant Err as DubApiError
Caller->>Assert: assertValidDateRangeForPlan(params)
Assert->>Validate: validDateRangeForPlan(params)
Validate-->>Assert: { valid: false, code, message } or { valid: true }
alt invalid
Assert->>Caller: throw DubApiError(message, code)
else valid
Assert->>Caller: return true
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/track/open/route.ts (1)
35-35: Replaceconsole.logwith Axiom logger.Since the handler is wrapped with
withAxiom, usereq.log.info()instead ofconsole.logfor consistent structured logging.Apply this diff:
- console.log(`Checking cache for ${ip}:${dubDomain}:*`); + req.log.info(`Checking cache for ${ip}:${dubDomain}:*`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
apps/web/app/(ee)/api/track/click/route.ts(1 hunks)apps/web/app/(ee)/api/track/open/route.ts(1 hunks)apps/web/app/(ee)/api/track/visit/route.ts(1 hunks)apps/web/lib/auth/partner.ts(1 hunks)apps/web/lib/auth/publishable-key.ts(1 hunks)apps/web/lib/auth/session.ts(1 hunks)apps/web/lib/auth/workspace.ts(1 hunks)apps/web/lib/embed/referrals/auth.ts(1 hunks)apps/web/package.json(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-21T03:03:39.879Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2737
File: apps/web/lib/api/cors.ts:1-5
Timestamp: 2025-08-21T03:03:39.879Z
Learning: Dub publishable keys are sent via Authorization header using Bearer token format, not via custom X-Dub-Publishable-Key header. The publishable key middleware extracts keys using req.headers.get("Authorization")?.replace("Bearer ", "") and validates they start with "dub_pk_".
Applied to files:
apps/web/lib/auth/publishable-key.ts
📚 Learning: 2025-08-21T03:03:39.879Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2737
File: apps/web/lib/api/cors.ts:1-5
Timestamp: 2025-08-21T03:03:39.879Z
Learning: Dub publishable keys are sent via Authorization header using Bearer token format, not via custom X-Dub-Publishable-Key header. The publishable key middleware extracts keys using req.headers.get("authorization")?.replace("Bearer ", "").
Applied to files:
apps/web/lib/auth/publishable-key.ts
🧬 Code graph analysis (3)
apps/web/app/(ee)/api/track/visit/route.ts (7)
apps/web/app/(ee)/api/track/click/route.ts (1)
POST(50-177)apps/web/lib/api/utils.ts (1)
parseRequestBody(9-20)apps/web/lib/api/errors.ts (2)
DubApiError(75-92)handleAndReturnErrorResponse(175-178)apps/web/lib/types.ts (1)
RedisLinkProps(157-181)apps/web/lib/api/cors.ts (1)
COMMON_CORS_HEADERS(1-5)apps/web/lib/planetscale/get-workspace-via-edge.ts (1)
getWorkspaceViaEdge(5-61)apps/web/lib/tinybird/record-click.ts (1)
recordClick(33-308)
apps/web/app/(ee)/api/track/click/route.ts (7)
apps/web/lib/api/utils.ts (1)
parseRequestBody(9-20)apps/web/lib/types.ts (1)
RedisLinkProps(157-181)apps/web/lib/api/errors.ts (2)
DubApiError(75-92)handleAndReturnErrorResponse(175-178)packages/utils/src/functions/urls.ts (1)
isValidUrl(1-8)apps/web/lib/planetscale/get-workspace-via-edge.ts (1)
getWorkspaceViaEdge(5-61)apps/web/lib/tinybird/record-click.ts (1)
recordClick(33-308)apps/web/lib/api/cors.ts (1)
COMMON_CORS_HEADERS(1-5)
apps/web/app/(ee)/api/track/open/route.ts (7)
apps/web/app/(ee)/api/track/click/route.ts (1)
POST(50-177)apps/web/lib/zod/schemas/opens.ts (2)
trackOpenRequestSchema(4-26)trackOpenResponseSchema(28-54)apps/web/lib/api/utils.ts (1)
parseRequestBody(9-20)apps/web/lib/api/cors.ts (1)
COMMON_CORS_HEADERS(1-5)apps/web/lib/types.ts (1)
RedisLinkProps(157-181)apps/web/lib/api/errors.ts (2)
DubApiError(75-92)handleAndReturnErrorResponse(175-178)apps/web/lib/tinybird/record-click.ts (1)
recordClick(33-308)
⏰ 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). (2)
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: build
🔇 Additional comments (4)
apps/web/app/(ee)/api/track/open/route.ts (4)
22-27: LGTM! Body logging enabled via Axiom.The
withAxiomwrapper withlogRequestDetails: ["body"]successfully enables request body logging, aligning with the PR objectives.
80-100: LGTM! Proper cache-aside pattern implementation.The clickId assignment and link fetching logic correctly implements the cache-aside pattern with proper fallback to database queries and error handling for missing links.
102-125: LGTM! Proper workspace validation and click tracking.The workspace validation ensures the link belongs to a workspace before recording the click. The
recordClickconfiguration withskipRatelimit: true,shouldCacheClickId: true, andtrigger: "deeplink"is appropriate for tracking deep link open events.
127-140: LGTM! Proper response construction and error handling.The response is correctly validated against
trackOpenResponseSchemaand includes proper CORS headers. Error handling uses the standardhandleAndReturnErrorResponseutility.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
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
🧹 Nitpick comments (2)
apps/web/middleware.ts (1)
39-41: Consider the performance impact of per-request logging.Logging and flushing on every middleware request could introduce latency, especially under high traffic. Consider whether all middleware requests need to be logged, or if this should be sampling-based or limited to specific paths.
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
17-17: Consider adding type annotation for better type safety.While the untyped
reqparameter works correctly, explicitly typing it asreq: Requestwould improve type safety and developer ergonomics (better IDE autocomplete and compile-time checking). The Stripe webhook endpoint (seeapps/web/app/(ee)/api/stripe/integration/webhook/route.ts) uses this pattern.Apply this diff to add the type annotation:
-export const POST = withAxiom(async (req) => { +export const POST = withAxiom(async (req: Request) => {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
apps/web/app/(ee)/api/hubspot/webhook/route.ts(2 hunks)apps/web/app/(ee)/api/singular/webhook/route.ts(2 hunks)apps/web/app/(ee)/api/stripe/integration/webhook/route.ts(1 hunks)apps/web/app/(ee)/api/track/click/route.ts(2 hunks)apps/web/app/(ee)/api/track/open/route.ts(2 hunks)apps/web/app/(ee)/api/track/visit/route.ts(1 hunks)apps/web/instrumentation.ts(1 hunks)apps/web/lib/auth/partner.ts(2 hunks)apps/web/lib/auth/publishable-key.ts(2 hunks)apps/web/lib/auth/session.ts(2 hunks)apps/web/lib/auth/workspace.ts(2 hunks)apps/web/lib/axiom/index.ts(1 hunks)apps/web/lib/axiom/server.ts(1 hunks)apps/web/lib/embed/referrals/auth.ts(2 hunks)apps/web/lib/middleware/axiom.ts(0 hunks)apps/web/lib/middleware/index.ts(0 hunks)apps/web/middleware.ts(2 hunks)apps/web/next.config.js(2 hunks)apps/web/package.json(1 hunks)
💤 Files with no reviewable changes (2)
- apps/web/lib/middleware/axiom.ts
- apps/web/lib/middleware/index.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/web/package.json
- apps/web/lib/auth/publishable-key.ts
- apps/web/app/(ee)/api/track/visit/route.ts
- apps/web/app/(ee)/api/track/open/route.ts
- apps/web/app/(ee)/api/track/click/route.ts
- apps/web/lib/embed/referrals/auth.ts
- apps/web/lib/auth/session.ts
- apps/web/lib/auth/partner.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-17T06:41:45.620Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
Applied to files:
apps/web/app/(ee)/api/singular/webhook/route.ts
🧬 Code graph analysis (5)
apps/web/app/(ee)/api/singular/webhook/route.ts (1)
apps/web/lib/axiom/server.ts (1)
withAxiom(15-15)
apps/web/instrumentation.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(5-13)
apps/web/middleware.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(5-13)
apps/web/lib/axiom/server.ts (1)
apps/web/lib/axiom/index.ts (1)
axiomClient(3-5)
apps/web/app/(ee)/api/hubspot/webhook/route.ts (5)
apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (1)
POST(27-110)apps/web/app/(ee)/api/track/click/route.ts (1)
POST(50-171)apps/web/app/(ee)/api/track/open/route.ts (1)
POST(22-140)apps/web/app/(ee)/api/track/visit/route.ts (1)
POST(20-115)apps/web/lib/axiom/server.ts (1)
withAxiom(15-15)
⏰ 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). (2)
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: build
🔇 Additional comments (7)
apps/web/next.config.js (1)
21-203: LGTM!The removal of the
withAxiomwrapper from the Next.js config export aligns with the migration to a local Axiom wrapper. The configuration remains otherwise unchanged.apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (1)
1-1: LGTM!The migration from
next-axiomto the local@/lib/axiom/serverwrapper is straightforward and maintains the same functionality.apps/web/instrumentation.ts (1)
1-4: LGTM!The centralized error handling setup using
createOnRequestErrorwith the shared logger provides consistent error logging across the application.apps/web/app/(ee)/api/singular/webhook/route.ts (1)
3-3: LGTM!The migration to the local Axiom wrapper is clean and maintains existing functionality.
apps/web/lib/auth/workspace.ts (1)
65-67: Review comment is based on incorrect assumptions—request body logging is not present in the code.The concern references a PR title mentioning
logRequestDetails: 'body', but verification shows:
- No
logRequestDetailsconfiguration exists anywhere in the codebasecreateAxiomRouteHandleris called with only the logger parameter and no config options- Per @axiomhq/nextjs documentation, request body logging requires explicit
onSuccess/onErrorhandlers to read and attach the body—it is not automatic- All
withAxiomcalls across auth routes (workspace, session, partner, publishable-key) pass no configurationThe code is currently secure with respect to request body logging. No action is required.
Likely an incorrect or invalid review comment.
apps/web/lib/axiom/server.ts (1)
15-15: No issues found — request body logging is not supported by createAxiomRouteHandler at the route handler level.createAxiomRouteHandler in @axiomhq/nextjs v0.1.5 does not expose a documented logRequestDetails option. The package documents createAxiomRouteHandler accepting callbacks (onError/onSuccess) and a store and notes it adds a request_id via the server context formatter, but it does not list a logRequestDetails config for route handlers. The code as written does not introduce request body logging for auth tokens or PII.
apps/web/app/(ee)/api/hubspot/webhook/route.ts (1)
2-2: LGTM! Import change aligns with the migration objective.The import change from
next-axiomto the local Axiom wrapper is correct and consistent with the PR's objective to replace externalnext-axiomusage with a local implementation.
next-axiom + add logRequestDetails: "body"@axiomhq/nextjs
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: 2
♻️ Duplicate comments (3)
apps/web/lib/axiom/axiom.ts (1)
3-5: Validate environment variable before usage.The non-null assertion on
NEXT_PUBLIC_AXIOM_TOKENwill cause a runtime crash if the variable is undefined.Apply this diff to add runtime validation:
-export const axiomClient = new Axiom({ - token: process.env.NEXT_PUBLIC_AXIOM_TOKEN!, -}); +const token = process.env.NEXT_PUBLIC_AXIOM_TOKEN; +if (!token) { + throw new Error("NEXT_PUBLIC_AXIOM_TOKEN environment variable is required"); +} + +export const axiomClient = new Axiom({ token });apps/web/lib/axiom/server.ts (1)
22-30: Validate environment variable before usage.The non-null assertion on
NEXT_PUBLIC_AXIOM_DATASET(line 26) will cause a runtime crash if the variable is undefined.Apply this diff to add validation:
+const dataset = process.env.NEXT_PUBLIC_AXIOM_DATASET; +if (!dataset) { + throw new Error("NEXT_PUBLIC_AXIOM_DATASET environment variable is required"); +} + export const logger = new Logger({ transports: [ new AxiomJSTransport({ axiom: axiomClient, - dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET!, + dataset, }), ], formatters: nextJsFormatters, });apps/web/lib/auth/workspace.ts (1)
65-67: Workspace routes should not log request bodies.Using
withAxiomBodyLogfor workspace-scoped routes will log full request bodies (including POST/PATCH/PUT) to Axiom, potentially capturing user PII, workspace settings, API credentials, and other sensitive data. This is a privacy and compliance risk.Apply this diff to use the non-body-logging wrapper:
- return withAxiomBodyLog( + return withAxiom( async ( req, { params: initialParams }: { params: Promise<Record<string, string>> },Note: A previous review comment indicated this concern was addressed, but the current code still uses the body-logging wrapper. Please verify the intended behavior.
🧹 Nitpick comments (2)
apps/web/tests/setupTests.ts (1)
10-46: Consider adding type safety to the mock constructors.The mock constructors use
_config: anywhich bypasses TypeScript's type checking. While this is acceptable for test mocks, consider using proper types orunknownto catch potential type mismatches during testing.Example:
vi.mock("@axiomhq/js", () => ({ Axiom: class { - constructor(_config: any) {} + constructor(_config: unknown) {} ingest = vi.fn().mockResolvedValue(undefined); query = vi.fn().mockResolvedValue({ matches: [] }); }, }));apps/web/lib/api/errors.ts (1)
3-8: Move"server-only"import to the top of the file.The
"server-only"import should be placed before all other imports to ensure the module is marked as server-only before any code executes. This follows Next.js conventions and prevents potential issues with client-side bundling.Apply this diff to reorder the imports:
+import "server-only"; import z from "@/lib/zod"; import { NextResponse } from "next/server"; -import "server-only"; import { ZodError } from "zod";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (76)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(1 hunks)apps/web/app/(ee)/api/events/export/route.ts(2 hunks)apps/web/app/(ee)/api/events/route.ts(2 hunks)apps/web/app/(ee)/api/groups/route.ts(1 hunks)apps/web/app/(ee)/api/partner-profile/users/route.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx(1 hunks)apps/web/app/api/analytics/dashboard/route.ts(2 hunks)apps/web/app/api/analytics/export/route.ts(2 hunks)apps/web/app/api/analytics/route.ts(2 hunks)apps/web/app/api/domains/route.ts(1 hunks)apps/web/app/api/folders/route.ts(1 hunks)apps/web/app/api/links/bulk/route.ts(2 hunks)apps/web/app/api/links/route.ts(0 hunks)apps/web/app/api/links/sync/route.ts(1 hunks)apps/web/app/api/tags/route.ts(1 hunks)apps/web/app/api/workspaces/[idOrSlug]/invites/accept/route.ts(1 hunks)apps/web/app/api/workspaces/[idOrSlug]/invites/route.ts(1 hunks)apps/web/app/api/workspaces/[idOrSlug]/users/route.ts(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/conversion-tracking-toggle.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/hostname-section.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsx(1 hunks)apps/web/lib/actions/partners/confirm-payouts.ts(1 hunks)apps/web/lib/actions/partners/force-withdrawal.ts(1 hunks)apps/web/lib/actions/partners/generate-paypal-oauth-url.ts(1 hunks)apps/web/lib/actions/partners/generate-stripe-account-link.ts(1 hunks)apps/web/lib/actions/partners/retry-failed-paypal-payouts.ts(1 hunks)apps/web/lib/actions/partners/update-partner-payout-settings.ts(1 hunks)apps/web/lib/actions/partners/update-partner-profile.ts(1 hunks)apps/web/lib/actions/safe-action.ts(1 hunks)apps/web/lib/analytics/utils/valid-date-range-for-plan.ts(3 hunks)apps/web/lib/api/error-codes.ts(1 hunks)apps/web/lib/api/errors.ts(3 hunks)apps/web/lib/api/links/usage-checks.ts(1 hunks)apps/web/lib/api/tokens/throw-if-no-access.ts(1 hunks)apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts(1 hunks)apps/web/lib/auth/partner-users/partner-user-permissions.ts(0 hunks)apps/web/lib/auth/partner-users/throw-if-no-permission.ts(1 hunks)apps/web/lib/auth/partner.ts(2 hunks)apps/web/lib/auth/workspace.ts(4 hunks)apps/web/lib/axiom/axiom.ts(1 hunks)apps/web/lib/axiom/server.ts(1 hunks)apps/web/lib/client-access-check.ts(1 hunks)apps/web/lib/exceeded-limit-error.ts(1 hunks)apps/web/lib/integrations/common/ui/configure-webhook.tsx(1 hunks)apps/web/lib/zod/schemas/links.ts(1 hunks)apps/web/package.json(2 hunks)apps/web/tests/setupTests.ts(1 hunks)apps/web/tests/utils/integration-old.ts(2 hunks)apps/web/tests/utils/integration.ts(2 hunks)apps/web/ui/analytics/toggle.tsx(1 hunks)apps/web/ui/domains/domain-card.tsx(1 hunks)apps/web/ui/modals/add-edit-domain-modal.tsx(1 hunks)apps/web/ui/modals/add-edit-token-modal.tsx(1 hunks)apps/web/ui/oauth-apps/add-edit-app-form.tsx(1 hunks)apps/web/ui/oauth-apps/add-edit-integration-form.tsx(1 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(1 hunks)apps/web/ui/partners/connect-payout-button.tsx(1 hunks)apps/web/ui/partners/online-presence-form.tsx(1 hunks)apps/web/ui/webhooks/add-edit-webhook-form.tsx(1 hunks)apps/web/ui/webhooks/webhook-header.tsx(1 hunks)apps/web/ui/workspaces/delete-workspace.tsx(1 hunks)
💤 Files with no reviewable changes (2)
- apps/web/app/api/links/route.ts
- apps/web/lib/auth/partner-users/partner-user-permissions.ts
✅ Files skipped from review due to trivial changes (5)
- apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/page-client.tsx
- apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/hostname-section.tsx
- apps/web/ui/partners/confirm-payouts-sheet.tsx
- apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
- apps/web/app/api/workspaces/[idOrSlug]/users/route.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/auth/partner.ts
🧰 Additional context used
🧠 Learnings (22)
📓 Common learnings
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/conversion-tracking-toggle.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions in settings components
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Do not use React components from hubspot/ui-extensions/crm in settings components
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/lib/integrations/common/ui/configure-webhook.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsxapps/web/ui/webhooks/webhook-header.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/conversion-tracking-toggle.tsxapps/web/ui/webhooks/add-edit-webhook-form.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsxapps/web/ui/workspaces/delete-workspace.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.tsx
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/lib/integrations/common/ui/configure-webhook.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsxapps/web/ui/webhooks/webhook-header.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsxapps/web/ui/webhooks/add-edit-webhook-form.tsx
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsxapps/web/ui/workspaces/delete-workspace.tsxapps/web/ui/domains/domain-card.tsx
📚 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/error-codes.tsapps/web/lib/zod/schemas/links.tsapps/web/lib/api/errors.ts
📚 Learning: 2025-05-29T09:47:33.583Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2433
File: apps/web/app/api/workspaces/[idOrSlug]/billing/payment-methods/route.ts:74-76
Timestamp: 2025-05-29T09:47:33.583Z
Learning: When Zod enum validation is used (e.g., `z.enum(PAYMENT_METHOD_TYPES)`), it ensures the validated value is one of the valid enum values, making subsequent type casts to the corresponding TypeScript types safe without additional validation.
Applied to files:
apps/web/lib/api/error-codes.ts
📚 Learning: 2025-06-25T21:20:59.837Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.
Applied to files:
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.tsapps/web/lib/actions/partners/confirm-payouts.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/app/api/links/sync/route.tsapps/web/app/api/links/bulk/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/lib/actions/partners/update-partner-payout-settings.tsapps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsxapps/web/tests/utils/integration.tsapps/web/tests/utils/integration-old.tsapps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/conversion-tracking-toggle.tsx
📚 Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsxapps/web/lib/api/tokens/throw-if-no-access.ts
📚 Learning: 2025-07-17T06:41:45.620Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.788Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.788Z
Learning: Applies to packages/hubspot-app/app/webhooks/** : Webhooks components are only allowed when config.distribution is private and config.auth.type is static
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Do not use window.fetch in settings components; use hubspot.fetch from hubspot/ui-extensions
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsx
📚 Learning: 2025-10-31T19:45:25.702Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3044
File: apps/web/lib/analytics/utils/get-interval-data.ts:31-0
Timestamp: 2025-10-31T19:45:25.702Z
Learning: In apps/web/lib/analytics/utils/get-interval-data.ts, the `mtd` and `qtd` interval functions are designed to work in both server and client contexts. When no timezone is provided, they intentionally fall back to `new Date()` (local timezone) because front-end code calls these methods without passing a timezone and should use the browser's local timezone for the user's context.
Applied to files:
apps/web/app/api/analytics/route.tsapps/web/lib/analytics/utils/valid-date-range-for-plan.ts
📚 Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.
Applied to files:
apps/web/lib/auth/workspace.ts
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions within card components
Applied to files:
apps/web/ui/domains/domain-card.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/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx
📚 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/app/api/workspaces/[idOrSlug]/invites/accept/route.tsapps/web/app/api/workspaces/[idOrSlug]/invites/route.ts
🧬 Code graph analysis (14)
apps/web/app/api/analytics/route.ts (1)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)
apps/web/lib/client-access-check.ts (1)
apps/web/lib/api/rbac/permissions.ts (2)
PermissionAction(30-30)ROLE_PERMISSIONS(32-157)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (2)
apps/web/lib/analytics/utils/valid-date-range-for-plan.ts (1)
validDateRangeForPlan(11-62)apps/web/lib/api/errors.ts (1)
DubApiError(58-75)
apps/web/lib/axiom/server.ts (2)
apps/web/lib/axiom/axiom.ts (1)
axiomClient(3-5)packages/utils/src/functions/urls.ts (1)
getSearchParams(40-49)
apps/web/lib/exceeded-limit-error.ts (1)
apps/web/lib/types.ts (1)
PlanProps(197-197)
apps/web/app/(ee)/api/events/route.ts (1)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)
apps/web/app/(ee)/api/events/export/route.ts (1)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)
apps/web/lib/auth/partner-users/throw-if-no-permission.ts (3)
packages/prisma/client.ts (1)
PartnerRole(24-24)apps/web/lib/auth/partner-users/partner-user-permissions.ts (2)
Permission(3-3)hasPermission(28-32)apps/web/lib/api/errors.ts (1)
DubApiError(58-75)
apps/web/lib/actions/safe-action.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(22-30)
apps/web/lib/auth/workspace.ts (1)
apps/web/lib/axiom/server.ts (1)
withAxiomBodyLog(32-47)
apps/web/lib/api/errors.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(22-30)
apps/web/tests/setupTests.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(22-30)
apps/web/app/api/analytics/export/route.ts (1)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)
apps/web/app/api/analytics/dashboard/route.ts (1)
apps/web/lib/api/utils/assert-valid-date-range-for-plan.ts (1)
assertValidDateRangeForPlan(4-21)
⏰ 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 (63)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx (1)
2-2: LGTM – Import path refactor looks good.The relocation of
hasPermissionto thepartner-userssubdirectory is a straightforward organizational change with no impact on functionality.apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)
4-4: LGTM – Import path refactor looks good.The import path update is consistent with the broader refactoring effort to reorganize partner permissions under the
partner-usersnamespace.apps/web/ui/partners/connect-payout-button.tsx (1)
5-5: LGTM – Import path refactor looks good.The relocation maintains the same functionality while improving the module organization.
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsx (1)
3-3: LGTM – Import path refactor looks good.The updated import path aligns with the new module structure for partner permissions.
apps/web/ui/partners/online-presence-form.tsx (1)
5-5: LGTM – Import path refactor looks good.The import path change is part of the consistent refactoring to organize partner permissions under the
partner-usersdirectory structure.apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx (1)
2-2: Import refactor verified as complete and correct.The import path
@/lib/auth/partner-users/partner-user-permissionscorrectly resolves toapps/web/lib/auth/partner-users/partner-user-permissions.tswherehasPermissionis properly exported. No stale imports from alternative paths remain in the codebase. The refactor is consistent and complete.apps/web/lib/api/links/usage-checks.ts (1)
1-1: LGTM: Import refactor aligns with modularization.The import path change to the dedicated
@/lib/exceeded-limit-errormodule is consistent with the PR's broader refactoring effort to centralize error messaging utilities.apps/web/app/api/workspaces/[idOrSlug]/invites/route.ts (1)
4-4: LGTM: Consistent with modularization effort.The import path update to
@/lib/exceeded-limit-erroraligns with the project-wide refactoring to centralize exceeded limit messaging.apps/web/lib/actions/safe-action.ts (1)
12-14: LGTM: Clean Axiom logger integration.The addition of
logger.error()andlogger.flush()properly integrates the new Axiom logging system while maintaining the existingconsole.errorfor redundancy.apps/web/package.json (2)
20-22: LGTM: Axiom dependencies support the migration.The new
@axiomhq/*packages align with the PR's migration fromnext-axiomto a custom Axiom integration.
153-155: Verify compatibility with major version updates before merging.The identified major version jumps (vite 5→7, vite-tsconfig-paths 4→5, vitest 2→4) are confirmed as real versions in the npm registry. These significant version increments commonly introduce breaking changes. While a vitest configuration exists at
apps/web/vitest.config.tsand the test script is properly configured, I cannot execute the test suite in this environment to verify actual compatibility.Before merging these dependency updates:
- Run
pnpm testlocally to confirm the full test suite passes- Review the changelogs for vite, vite-tsconfig-paths, and vitest for any migration requirements
- Check for any deprecation warnings during test execution
apps/web/lib/api/tokens/throw-if-no-access.ts (1)
1-1: LGTM: Server-side enforcement added.The
"server-only"import is good practice to prevent accidental client-side usage of this module, ensuring token validation and permission checks remain server-side only.apps/web/lib/auth/workspace.ts (1)
70-72: LGTM: Request cloning pattern is correct.The early cloning strategy is well-documented and correctly implemented. The clone is passed to handlers for body consumption, while the original is preserved for
withAxiomBodyLogto read in itsonSuccesscallback.apps/web/lib/exceeded-limit-error.ts (1)
1-29: LGTM! Clean utility extraction.The
exceededLimitErrorfunction is well-implemented with proper handling for:
- Monthly limits for specific types (links, AI, payouts)
- Currency formatting for payouts
- Correct pluralization logic
apps/web/app/(ee)/api/groups/route.ts (1)
3-8: LGTM! Import refactor is consistent.The import path update for
exceededLimitErroris clean and maintains the existing functionality.apps/web/lib/actions/partners/confirm-payouts.ts (1)
8-8: LGTM! Import path updated correctly.The import refactor maintains the existing two-stage payout validation pattern.
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1)
5-5: LGTM! Import path consistently updated.The refactor maintains the authoritative validation logic in the cron job.
apps/web/ui/oauth-apps/add-edit-integration-form.tsx (1)
5-5: LGTM! Import refactor is clean.The import path update for
clientAccessCheckmaintains existing permission validation logic.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/(basic-layout)/page-client.tsx (1)
3-3: LGTM! Import path updated correctly.The refactor maintains the existing workspace permission validation.
apps/web/app/api/workspaces/[idOrSlug]/invites/accept/route.ts (1)
1-4: LGTM! Import refactor is well-structured.The separation of
exceededLimitErrorinto its own module while keepingDubApiErrorin the errors module is a clean architectural improvement.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/conversion-tracking-toggle.tsx (1)
3-3: LGTM! Import path consistently updated.The refactor maintains the existing conversion tracking permission validation.
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/publishable-key-form.tsx (1)
3-3: Clean refactor consolidatingclientAccessCheckimport.The import path change maintains existing behavior while organizing the utility into a dedicated module.
apps/web/lib/zod/schemas/links.ts (1)
1-1: Clean refactor consolidatingErrorCodeimport.The import path change maintains existing behavior while organizing the error code enum into a dedicated module.
apps/web/ui/modals/add-edit-domain-modal.tsx (1)
1-1: Clean refactor consolidatingclientAccessCheckimport.The import path change maintains existing behavior while organizing the utility into a dedicated module.
apps/web/app/api/tags/route.ts (1)
2-4: Clean refactor consolidatingexceededLimitErrorimport.The import path change for
exceededLimitErrormaintains existing behavior while organizing the utility into a dedicated module.DubApiErrorappropriately remains in the errors module.apps/web/app/api/links/sync/route.ts (1)
4-4: Clean refactor consolidatingexceededLimitErrorimport.The import path change maintains existing behavior while organizing the utility into a dedicated module.
apps/web/ui/workspaces/delete-workspace.tsx (1)
3-3: Clean refactor consolidatingclientAccessCheckimport.The import path change maintains existing behavior while organizing the utility into a dedicated module.
apps/web/lib/integrations/common/ui/configure-webhook.tsx (1)
3-3: Clean refactor consolidatingclientAccessCheckimport.The import path change maintains existing behavior while organizing the utility into a dedicated module.
apps/web/app/api/domains/route.ts (1)
5-9: Clean refactor consolidatingexceededLimitErrorimport.The import path change for
exceededLimitErrormaintains existing behavior while organizing the utility into a dedicated module.DubApiErrorappropriately remains in the errors module.apps/web/ui/oauth-apps/add-edit-app-form.tsx (1)
3-3: LGTM! Clean import refactor.The
clientAccessCheckimport has been correctly updated to use the new centralized module path. The usage remains unchanged and consistent with the broader refactoring effort.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/[appId]/page-client.tsx (1)
4-4: LGTM! Consistent import refactor.The import path update for
clientAccessCheckis correct and aligns with the module consolidation effort across the codebase.apps/web/lib/actions/partners/update-partner-profile.ts (1)
4-4: LGTM! Import path correctly updated.The
throwIfNoPermissionimport has been properly relocated to the new dedicated module underpartner-users. Usage remains unchanged.apps/web/app/api/folders/route.ts (1)
2-5: LGTM! Proper module separation.The
exceededLimitErrorhas been correctly moved to its dedicated module while keepingDubApiErrorin the errors module. This separation is appropriate and maintains the existing functionality.apps/web/lib/actions/partners/force-withdrawal.ts (1)
3-3: LGTM! Consistent import update.The
throwIfNoPermissionimport path has been correctly updated to match the new module structure.apps/web/ui/modals/add-edit-token-modal.tsx (1)
7-7: LGTM! Import refactor correctly applied.The
clientAccessCheckimport has been properly updated to the new centralized module path. The usage at line 340-344 remains unchanged.apps/web/lib/actions/partners/retry-failed-paypal-payouts.ts (1)
3-3: LGTM! Import path updated correctly.The
throwIfNoPermissionimport has been properly relocated to the new module path, consistent with other partner action files.apps/web/ui/domains/domain-card.tsx (1)
1-1: LGTM! Import refactor complete.The
clientAccessCheckimport has been correctly updated to use the new centralized module. The usage at lines 358-361 remains unchanged.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/create-webhook-button.tsx (1)
3-3: LGTM! Clean import path refactor.The
clientAccessCheckimport has been successfully centralized to the new module location. Usage remains unchanged.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/domains/default/page-client.tsx (1)
3-3: LGTM! Import path successfully updated.The refactor correctly updates the import path to the centralized
client-access-checkmodule without affecting functionality.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx (1)
3-3: LGTM! Centralized import applied correctly.The import path update is consistent with the project-wide refactor. All call sites at lines 244 and 257 continue to work as expected.
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/new/page-client.tsx (1)
3-3: LGTM! Import refactor applied correctly.The centralized import path is properly used without affecting the permission check logic.
apps/web/app/(ee)/api/partner-profile/users/route.ts (1)
4-4: LGTM! Permission utility successfully relocated.The
throwIfNoPermissionimport has been moved to a dedicated module, improving code organization. Usage at line 175 remains unchanged.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/tokens/page.tsx (1)
4-4: LGTM! Import path refactor completed successfully.The centralized import is correctly applied. Both call sites (lines 67 and 261) continue to function properly.
apps/web/ui/webhooks/webhook-header.tsx (1)
4-4: LGTM! Consistent refactor applied.The import path update aligns with the broader consolidation effort. Functionality at line 47 remains intact.
apps/web/lib/actions/partners/update-partner-payout-settings.ts (1)
3-3: LGTM! Permission check utility properly relocated.The
throwIfNoPermissionfunction has been moved to a dedicated module under thepartner-usersnamespace, improving organization. Usage at line 16 is unaffected.apps/web/app/api/links/bulk/route.ts (1)
1-1: LGTM! Clean refactor to dedicated module.The
exceededLimitErrorutility has been properly moved to a dedicated module while maintaining the same usage pattern. This improves code organization and aligns with the PR's objective to consolidate utilities.Also applies to: 17-17
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/oauth-apps/create-oauth-app-button.tsx (1)
3-3: LGTM! Centralized permission check module.The import path update consolidates client access checks into a dedicated module, improving code organization without altering functionality.
apps/web/ui/webhooks/add-edit-webhook-form.tsx (1)
3-3: LGTM! Consistent with centralized permission checks.The import path update aligns with the broader refactor to consolidate access checks into a dedicated module.
apps/web/tests/utils/integration.ts (1)
2-2: LGTM! Proper vitest context type update.The migration from
TaskContexttoTestContextis correct for vitest test execution context. The addition ofreadonlymodifier on the field is a good practice that prevents unintended mutations.Also applies to: 15-15, 21-21
apps/web/app/app.dub.co/(dashboard)/[slug]/settings/webhooks/[webhookId]/page-client.tsx (1)
3-3: LGTM! Centralized permission check module.The import path update is consistent with the PR-wide consolidation of client access checks.
apps/web/lib/actions/partners/generate-stripe-account-link.ts (1)
3-3: LGTM! Improved module organization for partner permissions.The import path update moves partner-specific permission utilities into a dedicated
partner-usersnamespace, improving code organization and modularity.apps/web/app/app.dub.co/(dashboard)/[slug]/settings/analytics/add-hostname-modal.tsx (1)
3-3: LGTM! Centralized permission check module.The import path update aligns with the systematic refactor to consolidate client access checks across the codebase.
apps/web/lib/actions/partners/generate-paypal-oauth-url.ts (1)
3-3: LGTM! Consistent partner permission module organization.The import path update is consistent with the reorganization of partner-specific utilities into the
partner-usersnamespace.apps/web/lib/api/error-codes.ts (1)
1-15: LGTM! Clean separation of error codes.The extraction of
ErrorCodeinto a dedicated module improves modularity. The use of standardzodimport is correct for non-OpenAPI schema usage.apps/web/app/api/analytics/dashboard/route.ts (1)
71-77: LGTM! Consistent with validation refactor pattern.The replacement of
validDateRangeForPlanwithassertValidDateRangeForPlanmaintains the same throwing behavior while providing a cleaner API without thethrowErrorflag. This aligns with the broader refactor to separate validation logic from error handling.apps/web/lib/auth/partner-users/throw-if-no-permission.ts (1)
5-22: LGTM! Clean permission check abstraction.The function provides a clear, reusable pattern for partner permission checks with appropriate error handling. The early return pattern and default message improve code readability and user experience.
apps/web/app/(ee)/api/events/export/route.ts (1)
83-89: LGTM! Consistent validation refactor.The migration to
assertValidDateRangeForPlanis consistent with the broader refactor pattern across analytics routes. The behavior remains equivalent while providing a cleaner API.apps/web/app/api/analytics/route.ts (1)
91-97: LGTM! Validation refactor applied consistently.The change aligns with the project-wide validation refactor pattern, maintaining equivalent behavior with a cleaner API.
apps/web/lib/analytics/utils/valid-date-range-for-plan.ts (2)
3-9: LGTM! Well-designed result type.The
DateRangeValidationResultdiscriminated union provides a clean, type-safe pattern for validation results. The explicit error codes enable better error handling and testing.
11-62: LGTM! Excellent refactor for separation of concerns.Converting this function from throwing to returning result objects is a significant improvement:
- Pure validation logic: No side effects make the function more predictable and testable
- Flexible usage: Can be used in both throwing (
assertValidDateRangeForPlan) and non-throwing contexts- Type-safe results: The discriminated union enables exhaustive pattern matching
This refactor aligns with functional programming best practices and improves overall code quality.
apps/web/lib/client-access-check.ts (1)
14-14: No issues found—code is type-safe.The non-null assertion on line 14 is safe.
PermissionActionis derived fromPERMISSION_ACTIONSvia the type system(typeof PERMISSION_ACTIONS)[number], and verification confirms all 24 possiblePermissionActionvalues are present inROLE_PERMISSIONS. TypeScript enforces that only valid action values can reach the.find()call, guaranteeing the assertion will always succeed. No defensive checks are needed.apps/web/lib/api/errors.ts (2)
55-55: LGTM!Good refactoring to make
ErrorResponseinternal since it's only used within this module. This reduces the public API surface.
107-107: LGTM!Good refactoring to make
handleApiErrorprivate since it's only used internally viahandleAndReturnErrorResponse. This improves encapsulation and reduces the public API surface.
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
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/lib/actions/safe-action.ts(1 hunks)apps/web/lib/axiom/server.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/axiom/server.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
🧬 Code graph analysis (1)
apps/web/lib/actions/safe-action.ts (1)
apps/web/lib/axiom/server.ts (1)
logger(22-30)
⏰ 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 (1)
apps/web/lib/actions/safe-action.ts (1)
9-9: next-safe-action'shandleServerErrorcan be an async function, so the conversion to async is supported in v7.10.1. The implementation change itself is compatible with the library.However, additional concerns remain from the original review:
- Accessing
e.messagebefore theinstanceof Errorcheck: Ifeis not an Error object,e.messagewill be undefined, causing potential issues during logging.- No error handling for logging operations: If
logger.error()orlogger.flush()throw exceptions, the error handler will fail without gracefully handling the logging failure.
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
♻️ Duplicate comments (2)
apps/web/lib/axiom/server.ts (2)
30-40: Validate environment variable before usage.The non-null assertion on
NEXT_PUBLIC_AXIOM_DATASETat line 35 will cause a runtime crash if the variable is undefined despiteisAxiomEnabledbeing true (which checks for both env vars).Apply this diff to add validation:
+const dataset = process.env.NEXT_PUBLIC_AXIOM_DATASET; +if (isAxiomEnabled && !dataset) { + throw new Error("NEXT_PUBLIC_AXIOM_DATASET environment variable is required"); +} + export const logger = new Logger({ transports: isAxiomEnabled ? [ new AxiomJSTransport({ axiom: axiomClient, - dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET!, + dataset: dataset!, }), ] : [new ConsoleTransport()], formatters: nextJsFormatters, });
42-57: UsewithAxiomBodyLogselectively to avoid PII exposure.
withAxiomBodyLoglogs the full request body for POST/PATCH/PUT requests (line 48). Request bodies often contain user PII, credentials, or sensitive metadata.Consider:
- Using the plain
withAxiomwrapper for auth-protected routes that handle user data- Reserving
withAxiomBodyLogfor public or non-sensitive endpoints- Documenting which routes use body logging and why
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx(1 hunks)apps/web/lib/actions/partners/confirm-payouts.ts(1 hunks)apps/web/lib/actions/partners/update-partner-profile.ts(1 hunks)apps/web/lib/axiom/server.ts(1 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx
- apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx
- apps/web/lib/actions/partners/confirm-payouts.ts
- apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
📚 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-partner-profile.ts
🧬 Code graph analysis (1)
apps/web/lib/axiom/server.ts (2)
apps/web/lib/axiom/axiom.ts (1)
axiomClient(3-5)packages/utils/src/functions/urls.ts (1)
getSearchParams(40-49)
⏰ 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-partner-profile.ts (1)
4-4: LGTM! Clean import path refactor.The updated import path for
throwIfNoPermissionaligns with the centralized authorization utilities reorganization mentioned in the PR objectives. The more specific import path improves clarity.apps/web/ui/partners/confirm-payouts-sheet.tsx (1)
2-3: LGTM! Clean import path refactor.The import paths have been correctly updated to use the new centralized modules for
clientAccessCheckandexceededLimitError. The usage throughout the file remains unchanged and consistent.apps/web/lib/axiom/server.ts (2)
18-28: LGTM! Status code mapping is appropriate.The log level mapping correctly categorizes HTTP status codes: successful responses (1xx-3xx) as info, client errors (4xx) as warnings, and server errors (5xx) as errors.
59-59: LGTM! Clean route handler wrapper.The
withAxiomexport provides a safe, generic wrapper for route handlers without the body logging concerns ofwithAxiomBodyLog.
| if (["POST", "PATCH", "PUT"].includes(data.req.method)) { | ||
| report.body = await data.req.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.
Add error handling for JSON parsing.
The await data.req.json() call at line 48 will throw if the request body is not valid JSON, has already been consumed, or if the stream is locked. Since this is in the onSuccess callback, it could cause the logging to fail silently or propagate an unhandled error.
Apply this diff to add error handling:
// Add body to report if the method is POST, PATCH, or PUT
if (["POST", "PATCH", "PUT"].includes(data.req.method)) {
- report.body = await data.req.json();
+ try {
+ report.body = await data.req.json();
+ } catch (error) {
+ report.body = { error: "Failed to parse request body" };
+ }
}📝 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.
| if (["POST", "PATCH", "PUT"].includes(data.req.method)) { | |
| report.body = await data.req.json(); | |
| } | |
| if (["POST", "PATCH", "PUT"].includes(data.req.method)) { | |
| try { | |
| report.body = await data.req.json(); | |
| } catch (error) { | |
| report.body = { error: "Failed to parse request body" }; | |
| } | |
| } |
🤖 Prompt for AI Agents
In apps/web/lib/axiom/server.ts around lines 47 to 49, the call to await
data.req.json() can throw if the body isn't valid JSON or the stream is
unavailable; wrap the JSON parse in a try/catch, on success assign report.body
the parsed object, on failure attempt a safe fallback (e.g., try to read raw
text or set report.body = undefined) and record the parsing error in your
logger/report so the onSuccess path does not throw; ensure you do not rethrow
the error so logging continues normally.
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Chores