-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Allow deepview customization + improve domain modal #2802
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
|
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 3 minutes and 47 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 selected for processing (1)
WalkthroughAdds a nullable JSON deepviewData field across DB, schemas, APIs, UI, deeplink validation, tests, and a backfill script; wires deepviewData through create/patch flows with pro-plan gating and converts multiple JSON-null write sites to use Prisma.DbNull/AnyNull. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant API as Domains API
participant Auth as Plan Checker
participant DB as Prisma/DB
Client->>API: POST/PATCH /domains { deepviewData, ... }
API->>Auth: Check plan & pro-feature fields (incl. deepviewData)
alt Free plan with pro fields
Auth-->>API: Block
API-->>Client: 403 + pro-features message (includes "Deep View")
else Allowed
API->>API: parse assetLinks / appleAppSiteAssociation / deepviewData
API->>DB: Create/Update Domain (persist deepviewData or Prisma.DbNull)
DB-->>API: OK
API-->>Client: 200 with domain payload
end
sequenceDiagram
autonumber
participant Page as Deeplink Page
participant Fetch as Load shortDomain
participant Validate as deepViewDataSchema
participant Router as Redirect Logic
Page->>Fetch: Get domain data (AASA, deepviewData)
Fetch-->>Page: shortDomain
Page->>Validate: Parse shortDomain.deepviewData
alt Missing/invalid deepviewData or AASA
Page-->>Router: Prevent redirect / show fallback
else Valid
Page-->>Router: Proceed with deeplink redirect
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
The latest updates on your projects. Learn more about Vercel 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/scripts/migrations/backfill-deepview.ts (1)
1-26: Simplify and harden backfill script
- Replace the two-step findMany + updateMany with a single updateMany({ where: { deepviewData: { equals: Prisma.AnyNull } }, data: { deepviewData: {} } })
- Wrap in try/finally and call await prisma.$disconnect() to close the client and prevent hanging
import { prisma } from "@dub/prisma"; import { Prisma } from "@prisma/client"; import "dotenv-flow/config"; async function main() { - const domains = await prisma.domain.findMany({ - where: { - deepviewData: { - equals: Prisma.AnyNull, - }, - }, - take: 1000, - }); - - const deepviewData = await prisma.domain.updateMany({ - where: { - id: { in: domains.map((domain) => domain.id) }, - }, - data: { deepviewData: {} }, - }); - - console.log(deepviewData); + try { + const res = await prisma.domain.updateMany({ + where: { deepviewData: { equals: Prisma.AnyNull } }, + data: { deepviewData: {} }, + }); + console.log(res); + } finally { + await prisma.$disconnect(); + } } main();apps/web/ui/domains/add-edit-domain-form.tsx (1)
147-156: Ensure Advanced settings auto-expand when only Deep View is present.When
props.deepviewDataexists (but neither AASA nor assetLinks), the Advanced section stays collapsed.Apply:
useEffect(() => { - if (props?.appleAppSiteAssociation || props?.assetLinks) { + if ( + props?.appleAppSiteAssociation || + props?.assetLinks || + props?.deepviewData + ) { setShowAdvancedOptions(true); setShowOptionStates((prev) => ({ ...prev, appleAppSiteAssociation: !!props?.appleAppSiteAssociation?.trim(), assetLinks: !!props?.assetLinks?.trim(), })); } - if (props?.deepviewData) { + if (props?.deepviewData) { setShowOptionStates((prev) => ({ ...prev, deepviewData: !!props?.deepviewData?.trim(), })); } }, [props]);Also applies to: 157-162
🧹 Nitpick comments (11)
apps/web/lib/types.ts (1)
247-247: Align DomainProps.deepviewData type with API/DB usageUpstream schemas use
.nullish()and the DB stores JSON. To avoid TS/runtime mismatches whennullis returned, make thisstring | null(still serialized JSON on the client).- deepviewData?: string; + deepviewData?: string | null;packages/prisma/schema/domain.prisma (1)
14-15: Default{}on optional JSON can conflict with caller expectationsNew rows won’t be
null, while older rows are backfilled. If callers (e.g., UI/schema) expect “missing” to be falsy, consider either removing the default or ensuring all reads use a safe validator instead of.parse(). At minimum, document this semantic difference fromappleAppSiteAssociation/assetLinks.apps/web/lib/api/domains/transform-domain.ts (1)
19-21: Avoid returning "{}" for unset Deep View configIf
deepviewDatadefaults to{}, this will serialize to"{}"and look “configured” to clients. Align withassetLinks/appleAppSiteAssociationby treating empty objects as null.Apply:
- deepviewData: domain.deepviewData - ? JSON.stringify(domain.deepviewData) - : null, + deepviewData: + domain.deepviewData && + typeof domain.deepviewData === "object" && + Object.keys(domain.deepviewData as Record<string, unknown>).length > 0 + ? JSON.stringify(domain.deepviewData) + : null,Confirm whether the intended default is “unset” (null) vs “empty config” ({}). If the latter, keep as-is and ensure downstream UI handles "{}" gracefully.
apps/web/lib/zod/schemas/deep-links.ts (1)
3-8: Tighten schema: replace z.any() with safer types
z.any()defeats validation. Prefer permissive-but-safe shapes or unknown with passthrough.Apply:
export const deepViewDataSchema = z .object({ - ios: z.any(), - android: z.any(), + // If exact shapes are not finalized, accept arbitrary key/values safely. + ios: z.record(z.string(), z.unknown()).optional(), + android: z.record(z.string(), z.unknown()).optional(), }) .nullish();Optionally, refine to require at least one platform:
.refine((v) => !v || v.ios || v.android, { message: "Provide iOS or Android config" })Does the deep link page parse JSON and validate with this schema after JSON.parse? If not, add parsing before validation to avoid validating raw strings.
apps/web/ui/modals/add-edit-domain-modal.tsx (2)
33-35: Restore semantic heading inside sticky headerUse an h2 for accessibility while preserving the sticky container.
Apply:
- <div className="sticky top-0 z-10 border-b border-neutral-200 bg-white px-4 py-4 text-lg font-medium sm:px-6"> - {props ? "Update" : "Add"} Domain - </div> + <div className="sticky top-0 z-10 border-b border-neutral-200 bg-white px-4 py-4 sm:px-6"> + <h2 className="text-lg font-medium"> + {props ? "Update" : "Add"} Domain + </h2> + </div>
36-44: Ensure modal body remains scrollable on small viewportsRemoving the previous
overflow-autocontainer can trap content beyond viewport height. Add vertical scrolling to the body wrapper.Apply:
- <div className="bg-neutral-50"> + <div className="bg-neutral-50 overflow-y-auto"> <AddEditDomainFormManually test on a 320×568 viewport with long forms to confirm the sticky header + body scroll behave correctly.
apps/web/app/api/domains/route.ts (1)
100-101: Consider bounding deepviewData size at schema levelTo prevent oversized payloads, enforce a reasonable byte/length limit in
createDomainBodySchema(viaparseJsonSchemaorz.string().max(N)) before parsing.Confirm whether
parseJsonSchemaalready enforces size; if not, add a max (e.g., 50KB) and mirror inupdateDomainBodySchema.apps/web/lib/zod/schemas/domains.ts (1)
75-78: Optional: align null handling and examples.Consider
.nullable().default(null)for consistency withassetLinks/appleAppSiteAssociation, and add an OpenAPI example clarifying this is a JSON string in responses (while API accepts JSON object on input).apps/web/ui/domains/add-edit-domain-form.tsx (3)
165-177: No-op effect due to double-spread; simplify initialization.
{ ...prev, x:false, ...prev }restores prior values, making the effect inert.Apply:
useEffect(() => { - setShowOptionStates((prev) => ({ - ...prev, - appleAppSiteAssociation: false, - assetLinks: false, - logo: false, - expiredUrl: false, - notFoundUrl: false, - placeholder: false, - deepviewData: false, - ...prev, - })); + setShowOptionStates((prev) => ({ + appleAppSiteAssociation: prev.appleAppSiteAssociation ?? false, + assetLinks: prev.assetLinks ?? false, + logo: prev.logo ?? false, + expiredUrl: prev.expiredUrl ?? false, + notFoundUrl: prev.notFoundUrl ?? false, + placeholder: prev.placeholder ?? false, + deepviewData: prev.deepviewData ?? false, + })); }, []);
219-221: Enter-to-submit can hijack newlines in JSON textareas—gate on Cmd/Ctrl+Enter.Prevent accidental submits while editing JSON.
Apply:
-const { handleKeyDown } = useEnterSubmit(formRef); +const { handleKeyDown } = useEnterSubmit(formRef);And for the textarea:
- onKeyDown={handleKeyDown} + onKeyDown={(e) => { + if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { + handleKeyDown(e); + } + }}Also applies to: 553-554
288-296: Wire label to input for a11y.
label htmlFor="domain"doesn't target the input (noid). Add an id.Apply:
-<label htmlFor="domain" className="flex items-center gap-x-2"> +<label htmlFor="domain" className="flex items-center gap-x-2"> ... - <input + <input + id="domain" {...register("slug", { onChange: (e) => { setDomainStatus("idle"); debouncedValidateDomain(e.target.value); }, })}Also applies to: 331-341
📜 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 (12)
apps/web/app/api/domains/[domain]/route.ts(3 hunks)apps/web/app/api/domains/route.ts(3 hunks)apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[key]/page.tsx(3 hunks)apps/web/lib/api/domains/transform-domain.ts(1 hunks)apps/web/lib/types.ts(1 hunks)apps/web/lib/zod/schemas/deep-links.ts(1 hunks)apps/web/lib/zod/schemas/domains.ts(4 hunks)apps/web/lib/zod/schemas/utils.ts(2 hunks)apps/web/scripts/migrations/backfill-deepview.ts(1 hunks)apps/web/ui/domains/add-edit-domain-form.tsx(9 hunks)apps/web/ui/modals/add-edit-domain-modal.tsx(1 hunks)packages/prisma/schema/domain.prisma(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/web/scripts/migrations/backfill-deepview.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[key]/page.tsx (1)
apps/web/lib/zod/schemas/deep-links.ts (1)
deepViewDataSchema(3-8)
apps/web/lib/zod/schemas/domains.ts (1)
apps/web/lib/zod/schemas/utils.ts (1)
parseJsonSchema(37-50)
apps/web/ui/domains/add-edit-domain-form.tsx (2)
packages/ui/src/tooltip.tsx (2)
InfoTooltip(193-199)SimpleTooltipContent(130-158)packages/ui/src/shimmer-dots.tsx (1)
ShimmerDots(52-188)
⏰ 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: Vade Review
- GitHub Check: build
🔇 Additional comments (10)
apps/web/lib/zod/schemas/utils.ts (1)
3-3: Import OKZodIssueCode is fine to keep if we add a validator using
superRefine(see next comment).apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[key]/page.tsx (3)
1-1: Import OK
33-34: Selecting deepviewData is correct
44-49: Use safeParse instead of parse and confirm schema strictness
- Replace
deepViewDataSchema.parse(...)withdeepViewDataSchema.safeParse(...).successto avoid runtime errors whenlink.shortDomain.deepviewDatais{}or invalid.- const deepViewData = deepViewDataSchema.parse(link.shortDomain.deepviewData); - // if the link domain doesn't have an AASA file configured (or deepviewData is null) - if (!link.shortDomain.appleAppSiteAssociation || !deepViewData) { + const hasDeepViewData = deepViewDataSchema.safeParse( + link.shortDomain.deepviewData, + ).success; + // if the link domain doesn't have an AASA file configured (or deepviewData is missing/invalid) + if (!link.shortDomain.appleAppSiteAssociation || !hasDeepViewData) { redirect(link.ios ?? link.url); }
- deepViewDataSchema currently requires both
iosandandroid. Should we relax the schema (e.g., make keys optional or partial) or change the runtime check to allow either platform?apps/web/app/api/domains/[domain]/route.ts (1)
69-93: Pro-feature gating additions look correctIncluding Deep View in free-plan gating and message composition reads well and matches the new field.
apps/web/app/api/domains/route.ts (1)
186-188: Create path correctly persists deepviewDataConditional spread with JSON.parse only when provided is correct and avoids unintended defaults.
apps/web/lib/zod/schemas/domains.ts (2)
60-60: LGTM: domain logo field addition.Shape and nullability align with usage on the UI.
8-8: Imports look good.
parseUrlSchemaAllowEmptyandparseJsonSchemausage is appropriate after the fix above.apps/web/ui/domains/add-edit-domain-form.tsx (2)
141-144: LGTM: default deepviewData wiring.Pre-populating with pretty-printed JSON mirrors existing behavior for AASA/assetLinks.
240-242: LGTM: deepviewData submission path.Sanitization mirrors existing fields; empty strings are omitted.
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/tests/domains/index.test.ts (1)
32-32: Make JSON assertion resilientIn apps/web/tests/domains/index.test.ts:32, avoid brittle string equality for JSON serialization:
- deepviewData: "{}", + deepviewData: expect.stringMatching(/^\{\}$/),Optional: the codebase currently uses both deepviewData and deepViewData variants—consider unifying casing for consistency.
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (1)
462-478: Deduplicate the enabled condition into a single const.You repeat the same Boolean for three hooks; hoisting reduces drift risk and improves readability.
const { payoutsCount: pendingPayoutsCount } = usePayoutsCount< number | undefined >({ eligibility: "eligible", status: "pending", - enabled: Boolean(currentArea === "program" && defaultProgramId), + enabled: programEnabled, }); - const applicationsCount = useProgramApplicationsCount({ - enabled: Boolean(currentArea === "program" && defaultProgramId), - }); + const applicationsCount = useProgramApplicationsCount({ + enabled: programEnabled, + }); const { submissionsCount } = useBountySubmissionsCount< SubmissionsCountByStatus[] >({ - enabled: Boolean(currentArea === "program" && defaultProgramId), + enabled: programEnabled, });And define once:
}, [slug, pathname]); + const programEnabled = currentArea === "program" && Boolean(defaultProgramId);apps/web/lib/swr/use-payouts-count.ts (1)
10-13: Avoid leakingenabledinto the query and fixloadingwhen disabled.
enabledcurrently lives inoptswhich is spread intogetQueryString; depending on its behavior, this can add?enabled=trueto the URL. Strip it out explicitly.- When disabled,
loadingshould befalseto avoid spinner flicker.-export default function usePayoutsCount<T>( - opts: z.input<typeof payoutsCountQuerySchema> & { enabled?: boolean } = { - enabled: true, - }, -) { +type UsePayoutsCountOpts = z.input<typeof payoutsCountQuerySchema> & { + enabled?: boolean; +}; + +export default function usePayoutsCount<T>( + opts: UsePayoutsCountOpts = { enabled: true }, +) { const { id: workspaceId, defaultProgramId } = useWorkspace(); const { getQueryString } = useRouterStuff(); - const { data: payoutsCount, error } = useSWR<PayoutsCount[]>( + const { enabled = true, ...query } = opts || {}; + + const { data: payoutsCount, error } = useSWR<PayoutsCount[]>( workspaceId && defaultProgramId && - opts.enabled && + enabled && `/api/programs/${defaultProgramId}/payouts/count${getQueryString( - { - ...opts, - workspaceId, - }, - { - include: ["status", "partnerId"], - }, + { ...query, workspaceId }, + { include: ["status", "partnerId"] }, )}`, fetcher, ); return { payoutsCount: payoutsCount as T, error, - loading: payoutsCount === undefined && !error, + loading: enabled ? payoutsCount === undefined && !error : false, }; }Also applies to: 20-21, 33-37
📜 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 (5)
apps/web/app/api/domains/[domain]/route.ts(3 hunks)apps/web/lib/swr/use-payouts-count.ts(1 hunks)apps/web/lib/zod/schemas/domains.ts(3 hunks)apps/web/tests/domains/index.test.ts(1 hunks)apps/web/ui/layout/sidebar/app-sidebar-nav.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/api/domains/[domain]/route.ts
- apps/web/lib/zod/schemas/domains.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/swr/use-payouts-count.ts (3)
apps/web/lib/zod/schemas/payouts.ts (1)
payoutsCountQuerySchema(38-51)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-45)apps/web/lib/types.ts (1)
PayoutsCount(445-449)
⏰ 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: Vade Review
- GitHub Check: build
🔇 Additional comments (2)
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (1)
462-468: Gating payouts fetch behind program context — solid.This prevents unnecessary requests (and potential 404s) when not in Program or without a defaultProgramId. Matches the pattern used for applications and bounty submissions.
apps/web/lib/swr/use-payouts-count.ts (1)
20-21: Enabled-gated SWR key — good addition.Using opts.enabled to short-circuit the key is the right approach to stop the fetch cleanly.
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/lib/api/links/update-link.ts (1)
207-210: Schedule A/B completion based on persisted values, not input.If the client omits
testVariants, this check skips scheduling even when variants already exist. Use the updatedresponseto reflect DB state.- changedTestCompletedAt && - testVariants && - testCompletedAt && - scheduleABTestCompletion(response), + changedTestCompletedAt && + response.testVariants && + response.testCompletedAt && + scheduleABTestCompletion(response),apps/web/ui/domains/add-edit-domain-form.tsx (1)
222-243: Preflight-validate JSON fields client-side to avoid 500s.Validate appleAppSiteAssociation/assetLinks/deepviewData before sending; show inline errors if invalid.
Apply:
const onSubmit = async (formData: FormData) => { try { + // Client-side JSON validation for advanced fields + const jsonFields = [ + "appleAppSiteAssociation", + "assetLinks", + "deepviewData", + ] as const; + const invalid: string[] = []; + for (const f of jsonFields) { + const v = formData[f]; + if (typeof v === "string" && v.trim()) { + try { + JSON.parse(v); + } catch { + invalid.push(f); + setError(f as any, { type: "validate", message: "Invalid JSON" }); + } + } + } + if (invalid.length) { + toast.error(`Fix invalid JSON in: ${invalid.join(", ")}`); + return; + } + const res = await fetch(endpoint.url, { method: endpoint.method, headers: { "Content-Type": "application/json", }, body: JSON.stringify({
♻️ Duplicate comments (2)
apps/web/lib/zod/schemas/domains.ts (1)
166-168: Validate deepviewData is JSON (or accept parsed object).Right now any string passes; add JSON validation to prevent bad payloads, or parse into an object.
Apply:
-export const createDomainBodySchemaExtended = createDomainBodySchema.extend({ - deepviewData: z.string().nullish(), -}); +export const createDomainBodySchemaExtended = createDomainBodySchema.extend({ + deepviewData: z + .string() + .nullish() + .refine( + (s) => { + if (!s) return true; + try { + JSON.parse(s); + return true; + } catch { + return false; + } + }, + { message: "deepviewData must be valid JSON." }, + ), +});Alternative: accept object post-preprocess
+// Alternative: +// deepviewData: z.preprocess((v) => (typeof v === "string" ? JSON.parse(v) : v), z.record(z.any()).nullish()),apps/web/app/api/domains/[domain]/route.ts (1)
149-160: Nice: conditional spreads avoid clobbering omitted fields.Only updating keys when present prevents accidental nulling on PATCH. Matches prior feedback.
🧹 Nitpick comments (9)
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts (1)
173-181: Double-check re-notification behavior when startsAt moves.If a previous notify job exists, publishing another without cancel/dedupe may double-notify. Confirm QStash dedupe strategy or include an idempotency key.
apps/web/lib/api/links/create-link.ts (1)
70-73: Switch to DbNull on create is fine; align bulk-create/read paths.Ensure all writers use the same defaulting (DbNull) for
geo/testVariants, and that readers don’t rely on JSON-null semantics.apps/web/lib/api/links/bulk-create-links.ts (1)
74-76: Use DbNull for geo to match create-link semantics.Make
createManyexplicit instead of relying on omitted fields/defaults.Apply:
- expiresAt: link.expiresAt ? new Date(link.expiresAt) : null, - geo: link.geo || undefined, - testVariants: link.testVariants || Prisma.DbNull, + expiresAt: link.expiresAt ? new Date(link.expiresAt) : null, + geo: link.geo || Prisma.DbNull, + testVariants: link.testVariants || Prisma.DbNull,apps/web/lib/actions/partners/update-program.ts (1)
87-89: Clear publishedAt and revalidate when landerData is cleared.When
landerDatais explicitly null, also clearlanderPublishedAt. Revalidation should run on both set and clear.Apply:
- landerData: landerData === null ? Prisma.DbNull : landerData, - landerPublishedAt: landerData ? new Date() : undefined, + landerData: landerData === null ? Prisma.DbNull : landerData, + landerPublishedAt: + landerData === null ? null : landerData ? new Date() : undefined,And below:
- ...(name !== program.name || - logoUrl || - wordmarkUrl || - brandColor !== program.brandColor || - landerData + ...(name !== program.name || + logoUrl || + wordmarkUrl || + brandColor !== program.brandColor || + landerData !== undefined ? [apps/web/lib/actions/partners/create-reward.ts (2)
55-56: Use nullish coalescing to avoid accidental DbNull.
||treats some valid (albeit unlikely) falsy JSON values as empty. Prefer??so onlynull/undefinedfall back.- modifiers: modifiers || Prisma.DbNull, + modifiers: modifiers ?? Prisma.DbNull,
27-31: Gate advanced logic on presence, not generic truthiness.If the client sends an empty object/array,
if (modifiers && …)may block unnecessarily. Consider an explicit “has rules” check or at leastmodifiers != null.- if (modifiers && !canUseAdvancedRewardLogic) { + if (modifiers != null && !canUseAdvancedRewardLogic) {apps/web/app/api/domains/[domain]/route.ts (1)
21-24: Validate deepviewData at the schema boundary (optional).Consider parsing/validating deepviewData as JSON in the Zod extension so bad inputs fail fast with a 400 instead of surfacing later during update.
Example:
-const updateDomainBodySchemaExtended = updateDomainBodySchema.extend({ - deepviewData: z.string().nullish(), - autoRenew: z.boolean().nullish(), -}); +const updateDomainBodySchemaExtended = updateDomainBodySchema.extend({ + deepviewData: z + .string() + .nullish() + .refine((v) => v == null || isValidJson(v), "deepviewData must be valid JSON"), + autoRenew: z.boolean().nullish(), +}); + +function isValidJson(s?: string | null) { + if (!s) return true; + try { + JSON.parse(s); + return true; + } catch { + return false; + } +}apps/web/ui/domains/add-edit-domain-form.tsx (2)
123-131: Expose setError from useForm for client-side JSON validation.You’ll need setError to surface field errors before submit.
Apply:
- const { + const { register, control, handleSubmit, watch, setValue, + setError, formState: { isSubmitting, isDirty }, } = useForm<FormData>({
657-662: Consistency: “Asset Links” (plural) to match API messaging.UI shows “Asset Link” but server toasts/gating say “Asset Links”. Consider plural for consistency.
Apply:
- title: "Asset Link", + title: "Asset Links",
📜 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 (14)
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts(1 hunks)apps/web/app/api/domains/[domain]/route.ts(5 hunks)apps/web/app/api/domains/route.ts(3 hunks)apps/web/app/sitemap.ts(1 hunks)apps/web/lib/actions/partners/create-reward.ts(1 hunks)apps/web/lib/actions/partners/update-program.ts(1 hunks)apps/web/lib/actions/partners/update-reward.ts(1 hunks)apps/web/lib/api/domains/transform-domain.ts(1 hunks)apps/web/lib/api/links/bulk-create-links.ts(1 hunks)apps/web/lib/api/links/bulk-update-links.ts(1 hunks)apps/web/lib/api/links/create-link.ts(1 hunks)apps/web/lib/api/links/update-link.ts(1 hunks)apps/web/lib/zod/schemas/domains.ts(2 hunks)apps/web/ui/domains/add-edit-domain-form.tsx(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/lib/api/domains/transform-domain.ts
- apps/web/app/api/domains/route.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
PR: dubinc/dub#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/lib/actions/partners/create-reward.tsapps/web/lib/actions/partners/update-reward.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
PR: dubinc/dub#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/api/links/create-link.tsapps/web/lib/api/links/bulk-update-links.tsapps/web/lib/api/links/bulk-create-links.ts
📚 Learning: 2025-08-26T14:32:33.851Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/lib/actions/partners/create-bounty-submission.ts:105-112
Timestamp: 2025-08-26T14:32:33.851Z
Learning: Non-performance bounties are required to have submissionRequirements. In create-bounty-submission.ts, it's appropriate to let the parsing fail if submissionRequirements is null for non-performance bounties, as this indicates a data integrity issue that should be caught.
Applied to files:
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts
📚 Learning: 2025-08-26T14:20:23.943Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
Applied to files:
apps/web/app/(ee)/api/bounties/[bountyId]/route.tsapps/web/lib/actions/partners/update-reward.ts
📚 Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
PR: dubinc/dub#2735
File: apps/web/lib/actions/partners/delete-reward.ts:33-41
Timestamp: 2025-08-14T05:17:51.825Z
Learning: In the partner groups system, a rewardId can only belong to one group, establishing a one-to-one relationship between rewards and groups. This means using Prisma's `update` method (rather than `updateMany`) is appropriate when updating groups by rewardId.
Applied to files:
apps/web/app/(ee)/api/bounties/[bountyId]/route.tsapps/web/lib/actions/partners/update-reward.ts
🧬 Code graph analysis (7)
apps/web/lib/api/links/update-link.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/lib/actions/partners/create-reward.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/lib/api/links/create-link.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/app/sitemap.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts (1)
packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/app/api/domains/[domain]/route.ts (2)
apps/web/lib/zod/schemas/domains.ts (1)
updateDomainBodySchema(170-170)packages/prisma/client.ts (1)
Prisma(20-20)
apps/web/ui/domains/add-edit-domain-form.tsx (1)
apps/web/lib/zod/schemas/domains.ts (1)
createDomainBodySchemaExtended(166-168)
⏰ 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 (9)
apps/web/lib/zod/schemas/domains.ts (1)
60-60: LGTM: DomainSchema.logo surfaced on read shape.apps/web/lib/actions/partners/update-reward.ts (1)
43-44: LGTM: precise null semantics for JSON field.
modifiers === null ? Prisma.DbNull : modifierscorrectly distinguishesnull(clear) fromundefined(no-op) and aligns with Prisma’s JSON null types.apps/web/app/sitemap.ts (1)
20-21: LGTM: broadened null filter is correct.Switching to
not: Prisma.AnyNullcorrectly excludes both DB NULL and JSON NULL forlanderData, matching upstream writes that may useDbNull.apps/web/lib/api/links/bulk-update-links.ts (1)
57-59: LGTM: safe null handling for JSON fields.Using
=== null ? Prisma.DbNull : valuepreserves existing data when fields are omitted and only clears on explicitnull. Matches Prisma best practices and is consistent with update semantics.apps/web/app/api/domains/[domain]/route.ts (1)
71-96: Confirm intended behavior: free users can clear Pro fields.With the current gating, empty strings (falsy) won’t trigger the Pro check, allowing free users to clear Asset Links/AASA/Deep View but not set them. If that’s intended, all good; if not, tighten the condition to check key presence rather than truthiness.
apps/web/ui/domains/add-edit-domain-form.tsx (4)
546-583: Good UX: JSON paste formatting with fallback.Nice touch to auto-format JSON while preserving raw text on parse failure.
219-221: Enter-to-submit wiring looks solid.Form ref + useEnterSubmit applied to textareas; consider also applying to the domain input if desired.
141-144: Prepopulate Deep View editor with formatted JSON.Formatting default deepviewData improves readability. LGTM.
5-5: No staleclassNameprops found in AddEditDomainForm usage. Verified that call sites in the onboarding form and modal only passpropsandonSuccess, matching the updated schema.
| ...(bounty.type === "submission" && | ||
| submissionRequirements !== undefined && { | ||
| submissionRequirements: submissionRequirements ?? Prisma.JsonNull, | ||
| submissionRequirements: submissionRequirements ?? Prisma.DbNull, | ||
| }), | ||
| ...(updatedPartnerGroups && { |
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.
💡 Verification agent
🧩 Analysis chain
Do not allow null submissionRequirements for submission bounties.
Allowing submissionRequirements to be set to DbNull on submission-type bounties breaks the invariant that non-performance/submission bounties must have requirements (per past learnings). Guard against nulls before the update.
Apply:
@@
- const data = await prisma.$transaction(async (tx) => {
+ // Enforce invariant: submission bounties require submissionRequirements
+ if (bounty.type === "submission" && submissionRequirements === null) {
+ throw new DubApiError({
+ message: "submissionRequirements is required for submission bounties.",
+ code: "bad_request",
+ });
+ }
+
+ const data = await prisma.$transaction(async (tx) => {
@@
- ...(bounty.type === "submission" &&
- submissionRequirements !== undefined && {
- submissionRequirements: submissionRequirements ?? Prisma.DbNull,
- }),
+ ...(bounty.type === "submission" &&
+ submissionRequirements !== undefined && {
+ // for submission bounties, only accept defined (non-null) values
+ submissionRequirements,
+ }),
+ ...(
+ bounty.type !== "submission" &&
+ submissionRequirements !== undefined && {
+ submissionRequirements: submissionRequirements ?? Prisma.DbNull,
+ }
+ ),Verification (ensures no other code assumes JSON-null for this field):
🏁 Script executed:
#!/bin/bash
# Find legacy JsonNull usage or is-null checks on submissionRequirements
rg -nP --type=ts '(submissionRequirements[^:\n]*:\s*Prisma\.JsonNull)|(\bJsonNull\b.*submissionRequirements)|(\bsubmissionRequirements\b[^}]*\bnot:\s*Prisma\.JsonNull)'Length of output: 170
🏁 Script executed:
#!/bin/bash
set -e
# Search all TypeScript files for any usage of submissionRequirements
rg -n --type=ts "submissionRequirements" -C3
# Check for explicit null comparisons (=== null or == null)
rg -nP --type=ts "submissionRequirements\s*===?\s*null"
# Check for nullish coalescing usage (??)
rg -nP --type=ts "submissionRequirements\s*\?\?"Length of output: 14208
Enforce non-null submissionRequirements on submission bounties
In apps/web/app/(ee)/api/bounties/[bountyId]/route.ts, before the transaction throw a DubApiError if bounty.type is "submission" and submissionRequirements is null, and remove the Prisma.DbNull fallback so that only defined arrays are persisted.
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/bounties/[bountyId]/route.ts around lines 109 to 113,
enforce that submission bounties have non-null submissionRequirements and stop
persisting Prisma.DbNull; before the DB transaction, add a guard that if
bounty.type === "submission" and submissionRequirements == null then throw a
DubApiError with an appropriate message, and change the update payload so the
submissionRequirements property is only spread when submissionRequirements is
not undefined (remove the Prisma.DbNull fallback) so only defined arrays are
persisted.
| where: { | ||
| deepviewData: { | ||
| equals: Prisma.AnyNull, |
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.
The migration script uses an incorrect Prisma query condition that will likely return no results, preventing the backfill operation from working.
View Details
📝 Patch Details
diff --git a/apps/web/scripts/migrations/backfill-deepview.ts b/apps/web/scripts/migrations/backfill-deepview.ts
index 09ea20e5e..e08312ba8 100644
--- a/apps/web/scripts/migrations/backfill-deepview.ts
+++ b/apps/web/scripts/migrations/backfill-deepview.ts
@@ -1,13 +1,10 @@
import { prisma } from "@dub/prisma";
-import { Prisma } from "@prisma/client";
import "dotenv-flow/config";
async function main() {
const domains = await prisma.domain.findMany({
where: {
- deepviewData: {
- equals: Prisma.AnyNull,
- },
+ deepviewData: null,
},
take: 1000,
});
Analysis
The migration script uses equals: Prisma.AnyNull to find domains where deepviewData is null, but this is incorrect Prisma syntax. Prisma.AnyNull is used for setting values to null in updates, not for querying null values.
The correct query condition should be:
where: {
deepviewData: null,
},Or if you want to find domains where deepviewData is either null or the JSON value is null:
where: {
OR: [
{ deepviewData: null },
{ deepviewData: Prisma.JsonNull },
],
},The current code will likely return no results, making the migration ineffective.
| ].includes(id) | ||
| ) { | ||
| e.preventDefault(); | ||
| const pastedText = | ||
| e.clipboardData.getData("text"); | ||
|
|
||
| try { |
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.
Developer explicitly marked this Deep View initialization as a "hacky frontend workaround" indicating incomplete implementation.
View Details
📝 Patch Details
diff --git a/apps/web/ui/domains/add-edit-domain-form.tsx b/apps/web/ui/domains/add-edit-domain-form.tsx
index 8b9501d09..0c07a646f 100644
--- a/apps/web/ui/domains/add-edit-domain-form.tsx
+++ b/apps/web/ui/domains/add-edit-domain-form.tsx
@@ -534,15 +534,7 @@ export function AddEditDomainForm({
...prev,
[id]: checked,
}));
- if (checked) {
- // hacky frontend workaround since we don't have a way
- // to customize the actual appearance of the deepview page yet
- if (id === "deepviewData") {
- setValue("deepviewData", "{}", {
- shouldDirty: true,
- });
- }
- } else {
+ if (!checked) {
setValue(id, "", {
shouldDirty: true,
});
@@ -552,7 +544,7 @@ export function AddEditDomainForm({
/>
</div>
- {showOptionStates[id] && id !== "deepviewData" && (
+ {showOptionStates[id] && (
<div className="rounded-md border border-neutral-200 bg-white">
<textarea
{...register(id)}
@@ -565,6 +557,7 @@ export function AddEditDomainForm({
[
"appleAppSiteAssociation",
"assetLinks",
+ "deepviewData",
].includes(id)
) {
e.preventDefault();
Analysis
The comment on lines 538-539 states this is a "hacky frontend workaround since we don't have a way to customize the actual appearance of the deepview page yet." This indicates the Deep View feature is not fully implemented and is using a temporary solution.
When developers explicitly mark code with comments like "hacky frontend workaround," it typically signals that the implementation needs proper completion before release. The current approach of simply setting deepviewData to "{}" when enabled suggests the feature may not be fully functional, and the developer is acknowledging this is temporary code that should be improved.
This type of explicit developer intent comment should be addressed before merging, as it indicates known technical debt or incomplete functionality.
Summary by CodeRabbit
New Features
Style
Tests
Messaging