-
Notifications
You must be signed in to change notification settings - Fork 2.7k
fix(apps/web): removed deprecated zod params. #2814
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
@zhyd1997 is attempting to deploy a commit to the Dub Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughUpdated multiple Zod schemas across analytics, partner-profile, links, partners, payouts and analytics types: several fields were removed from omit/pick lists or reintroduced, altering public shapes for analytics filters/queries, partner-profile queries, link bulk updates, partner creation linkProps, payouts query/response, and partner events types. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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: 0
🧹 Nitpick comments (3)
apps/web/lib/zod/schemas/analytics.ts (3)
349-353
: Drop optional() when using default() for sortBydefault() already makes the key optional-with-default in Zod. Small cleanup for clarity.
- sortBy: z - .enum(["timestamp"]) - .optional() - .default("timestamp") + sortBy: z.enum(["timestamp"]).default("timestamp")
331-335
: Same cleanup for sortOrder: default() is enoughRemoves redundant optional() for consistency with the sortBy change.
-const sortOrder = z - .enum(["asc", "desc"]) - .default("desc") - .optional() +const sortOrder = z.enum(["asc", "desc"]).default("desc")
337-357
: Optional: enforce precedence between deprecated order and sortOrderToday both exist; ambiguity can leak downstream. Consider normalizing in schema so sortOrder always wins, then fallback to order, then default.
export const eventsQuerySchema = analyticsQuerySchema .omit({ groupBy: true }) .extend({ @@ order: sortOrder .describe("DEPRECATED. Use `sortOrder` instead.") .openapi({ deprecated: true }), - }); + }) + .transform((v) => ({ + ...v, + sortOrder: v.sortOrder ?? v.order ?? "desc", + }));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/web/lib/zod/schemas/analytics.ts
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vade Review
🔇 Additional comments (2)
apps/web/lib/zod/schemas/analytics.ts (2)
320-329
: eventsFilterTB omit change LGTMOmitting only granularity/timezone (and not page/sortBy) is consistent with the current analyticsFilterTB shape and the Tinybird events usage.
337-357
: Keep domain in eventsQuerySchema — omit only groupByanalyticsQuerySchema.omit({ groupBy: true }) preserves domain and other analytics filters used by the export/events routes; parsedParams still includes domain so the TS destructuring error is resolved.
Location: apps/web/lib/zod/schemas/analytics.ts (eventsQuerySchema).
…cs and events query schemas
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/api/resend/webhook/route.ts (1)
28-30
: Use a constant‑time signature compare.Plain string inequality is timing-attack prone. Compare HMACs with
timingSafeEqual
.- if (signature !== svix_signature) { + const a = Buffer.from(signature ?? "", "base64"); + const b = Buffer.from(svix_signature ?? "", "base64"); + if (a.length !== b.length || !timingSafeEqual(a, b)) { return NextResponse.json({ message: "Invalid signature" }, { status: 400 }); }
🧹 Nitpick comments (7)
apps/web/app/api/resend/webhook/route.ts (4)
10-11
: Parse svix-signature robustly (support “v1,” and “v1=”).The current split is brittle and fails if multiple versions are sent. Extract the first v1 signature via regex.
- const svix_signature = req.headers.get("svix-signature")?.split("v1,")[1]; + const rawSig = req.headers.get("svix-signature") || ""; + const svix_signature = rawSig.match(/(?:^|,)\s*v1[=,]([^,]+)/)?.[1];
34-35
: Guard JSON parsing to avoid 500s on bad payloads.
JSON.parse
will throw on malformed JSON.- const { - data: { email, unsubscribed }, - } = JSON.parse(rawBody) || {}; + let email: string | undefined, unsubscribed: boolean | undefined; + try { + const parsed = JSON.parse(rawBody) || {}; + ({ data: { email, unsubscribed } = {} } = parsed); + } catch { + return NextResponse.json({ message: "Invalid JSON" }, { status: 400 }); + }
41-49
: Avoid 500 on missing user record.
prisma.user.update
throws if no row matches. UseupdateMany
or handleP2025
.- await prisma.user.update({ + await prisma.user.updateMany({ where: { email, }, data: { subscribed: !unsubscribed, }, });
36-38
: Reduce PII in logs.Consider structured logs without raw emails, or gate behind an env flag.
apps/web/app/api/analytics/route.ts (3)
29-33
: Eliminate ts-expect-error with a type guard.Narrow
oldEvent
to a valid endpoint before reassignment; no suppression needed.- // @ts-expect-error FIXME zod transform error - if (!oldType && oldEvent && VALID_ANALYTICS_ENDPOINTS.includes(oldEvent)) { + const isAnalyticsEndpoint = ( + s: unknown, + ): s is (typeof VALID_ANALYTICS_ENDPOINTS)[number] => + typeof s === "string" && + (VALID_ANALYTICS_ENDPOINTS as readonly string[]).includes(s as any); + + if (!oldType && isAnalyticsEndpoint(oldEvent)) { oldType = oldEvent; oldEvent = undefined; }
53-57
: Avoid reassignment with casts; derive effective params instead.This sidesteps Zod-transform typing friction and keeps types intact without suppressions.
- // @ts-expect-error FIXME - event = oldEvent || event; - // @ts-expect-error FIXME - groupBy = oldType || groupBy; + const effectiveEvent = (oldEvent ?? event); + const effectiveGroupBy = (oldType ?? groupBy);
120-133
: Thread effective params through the call and timeseries check.Use the derived variables to keep type consistency throughout.
- const response = await getAnalytics({ - ...parsedParams, - event, - groupBy, + const response = await getAnalytics({ + ...parsedParams, + event: effectiveEvent, + groupBy: effectiveGroupBy, ...(link && { linkId: link.id }), folderIds, workspaceId: workspace.id, isMegaFolder: workspace.totalLinks > 1_000_000, isDeprecatedClicksEndpoint, // dataAvailableFrom is only relevant for timeseries groupBy - ...(groupBy === "timeseries" && { + ...(effectiveGroupBy === "timeseries" && { dataAvailableFrom: workspace.createdAt, }), });Also applies to: 130-133
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/api/analytics/route.ts
(2 hunks)apps/web/app/api/resend/webhook/route.ts
(1 hunks)apps/web/lib/analytics/types.ts
(0 hunks)apps/web/lib/zod/schemas/links.ts
(0 hunks)apps/web/lib/zod/schemas/partners.ts
(0 hunks)apps/web/lib/zod/schemas/payouts.ts
(0 hunks)
💤 Files with no reviewable changes (4)
- apps/web/lib/zod/schemas/payouts.ts
- apps/web/lib/analytics/types.ts
- apps/web/lib/zod/schemas/partners.ts
- apps/web/lib/zod/schemas/links.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vade Review
🔇 Additional comments (1)
apps/web/app/api/analytics/route.ts (1)
35-50
: No action required:domain
is defined onanalyticsQuerySchema
(line 73–76) and retained byeventsQuerySchema
(line 337), so it’s parsed across all callers.
// Need to base64 decode the secret | ||
const secretBytes = Buffer.from(secret.split("_")[1], "base64"); | ||
const signature = crypto |
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.
🛠️ Refactor suggestion
Harden secret decoding; current split can throw.
secret.split("_")[1]
can be undefined
if the format changes, causing a 500. Validate before decoding.
- // Need to base64 decode the secret
- const secretBytes = Buffer.from(secret.split("_")[1], "base64");
+ // Need to base64 decode the secret (expecting "resend_<base64>")
+ const parts = secret.split("_");
+ if (parts.length < 2 || !parts[1]) {
+ return NextResponse.json({ message: "Invalid secret" }, { status: 400 });
+ }
+ const secretBytes = Buffer.from(parts[1], "base64");
📝 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.
// Need to base64 decode the secret | |
const secretBytes = Buffer.from(secret.split("_")[1], "base64"); | |
const signature = crypto | |
// Need to base64 decode the secret (expecting "resend_<base64>") | |
const parts = secret.split("_"); | |
if (parts.length < 2 || !parts[1]) { | |
return NextResponse.json({ message: "Invalid secret" }, { status: 400 }); | |
} | |
const secretBytes = Buffer.from(parts[1], "base64"); | |
const signature = crypto |
🤖 Prompt for AI Agents
In apps/web/app/api/resend/webhook/route.ts around lines 20 to 22, the code
directly uses secret.split("_")[1] which can be undefined and cause Buffer.from
to throw; validate the secret format before decoding by checking secret is a
string, contains an underscore, and that split yields a non-empty second
segment, and handle the invalid case gracefully (return a 400/unauthorized
response or log and abort) instead of letting it throw; then safely
base64-decode the validated segment (and wrap decoding in a try/catch to handle
malformed base64) and proceed to compute the signature.
// @ts-expect-error FIXME | ||
.createHmac("sha256", secretBytes) | ||
.update(signedContent) | ||
.digest("base64"); |
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.
🛠️ Refactor suggestion
Remove ts-expect-error; use Node crypto explicitly and mark runtime.
TypeScript is likely resolving crypto
to the Web Crypto Crypto
type (no createHmac
). Avoid the suppression by:
- Importing from node:crypto with named APIs.
- Declaring the route’s runtime as nodejs.
-import crypto from "crypto";
+import { createHmac, timingSafeEqual } from "node:crypto";
+export const runtime = "nodejs";
...
- const signature = crypto
- // @ts-expect-error FIXME
- .createHmac("sha256", secretBytes)
+ const signature = createHmac("sha256", secretBytes)
.update(signedContent)
.digest("base64");
Also applies to: 2-2
🤖 Prompt for AI Agents
In apps/web/app/api/resend/webhook/route.ts around lines 23 to 26, remove the
"// @ts-expect-error FIXME" and stop relying on the ambient Web Crypto type;
instead import the Node API explicitly (e.g., import { createHmac } from
"node:crypto") and use that named function to compute the HMAC, and declare the
route runtime as Node.js by exporting the runtime constant (export const runtime
= "nodejs") at the top of the file so TypeScript resolves node:crypto correctly
at runtime.
Close #2546
Summary by CodeRabbit
New Features
Bug Fixes