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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Jul 21, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a "Permanent Retention" option for links, allowing users to exclude specific links from retention cleanup.
    • Added a new API endpoint for automated link retention cleanup, with scheduled execution every minute.
    • Domains can now be configured with a custom link retention period.
  • Improvements

    • Workspace and domain data now include link retention settings for better visibility and management.
    • Enhanced dropdown and options UI for link features, including new icons and improved tooltips.
    • Improved link update validation and support for the new retention exclusion field.
  • Bug Fixes

    • Tooltip links now render only when relevant information is provided, preventing incomplete links.
  • Chores

    • Updated internal scripts and type definitions to support new retention features and data structures.

@steven-tey steven-tey requested a review from devkiran July 21, 2025 04:49
@vercel
Copy link
Contributor

vercel bot commented Jul 21, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
dub 🔄 Building (Inspect) Visit Preview Jul 21, 2025 4:49am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

Walkthrough

This update introduces a domain-level link retention policy, enabling automatic cleanup of old links based on a configurable retention period. It adds a new cron-triggered API endpoint for batch deletion, modifies the Prisma schema to support retention settings, updates validation schemas, and enhances the UI to allow links to be excluded from retention cleanup.

Changes

Files/Paths Change Summary
apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts, apps/web/vercel.json Added a new API route and Vercel cron configuration for periodic link retention cleanup.
packages/prisma/schema/domain.prisma, packages/prisma/schema/link.prisma Added linkRetentionDays to Domain and linkRetentionCleanupDisabledAt to Link models; adjusted index and relation fields.
apps/web/lib/zod/schemas/links.ts, apps/web/lib/zod/schemas/workspaces.ts Extended schemas to support new retention-related fields.
apps/web/lib/types.ts Replaced ExpandedWorkspaceProps with ExtendedWorkspaceProps to include retention settings and updated domain/user typing.
apps/web/lib/swr/use-domains.ts, apps/web/lib/swr/use-workspace.ts Updated types and imports to reflect new workspace/domain retention fields.
apps/web/app/api/links/[linkId]/route.ts PATCH handler now uses extended schema for validation.
apps/web/app/api/workspaces/[idOrSlug]/route.ts GET handler now returns id and linkRetentionDays for domains.
apps/web/ui/links/link-builder/constants.ts, apps/web/ui/links/link-builder/more-dropdown.tsx, apps/web/ui/links/link-builder/link-feature-buttons.tsx, apps/web/ui/links/link-builder/options-list.tsx UI changes to support "Permanent Retention" as a toggleable link feature; added new icon, callbacks, and filtering logic.
packages/ui/src/icons/nucleo/infinity-icon.tsx, packages/ui/src/icons/nucleo/index.ts Added and exported new InfinityIcon component for the UI.
packages/ui/src/tooltip.tsx Made tooltip CTA and href optional; improved conditional rendering.
apps/web/scripts/convert-case-sensitive.ts, apps/web/scripts/stripe/update-stripe-customers.ts Enhanced script logic for case-sensitive links and updated workspace ID in Stripe script.
apps/web/lib/api/links/case-sensitivity.ts Refactored function to return a new object instead of mutating input.
apps/web/tests/utils/schema.ts Added new field to test schema and expected object for link retention cleanup.
apps/web/app/(ee)/api/partners/links/upsert/route.ts Minor formatting change (whitespace only).

Sequence Diagram(s)

sequenceDiagram
    participant VercelCron as Vercel Cron
    participant API as /api/cron/link-retention-cleanup
    participant DB as Prisma DB
    participant Qstash as Qstash
    participant Tinybird as Analytics

    VercelCron->>API: GET /api/cron/link-retention-cleanup
    API->>DB: Fetch domains with linkRetentionDays
    loop For each domain
        API->>DB: Delete old links in batches
        API->>Tinybird: Record deletions
        alt More links remain
            API->>Qstash: Schedule another cleanup (POST)
        end
    end
    API-->>VercelCron: Response

    Qstash->>API: POST /api/cron/link-retention-cleanup (with domain slug)
    API->>DB: Fetch domain by slug
    API->>DB: Delete old links in batches
    API->>Tinybird: Record deletions
    alt More links remain
        API->>Qstash: Schedule another cleanup (POST)
    end
    API-->>Qstash: Response
Loading

Estimated code review effort

4 (approximately one day)

Possibly related PRs

  • Add domain, createdAt index #2481: Adds a [domain, createdAt] index to optimize queries for bulk link deletion workflows, which directly supports the new link retention cleanup logic introduced in this PR.

Poem

🐇
A hop, a skip, a cleanup run,
Old links retire, their work is done.
Retention days set by each domain,
The cron job sweeps, again and again.
Some links stay, marked infinite and bright—
The rabbit keeps your garden light!
♾️

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (3)
apps/web/scripts/stripe/update-stripe-customers.ts (2)

17-31: Script processes only 20 customers — iterate until none remain
take: 20 limits the update to the first page; re-running the script is required to finish the backlog. Prefer a loop with cursor pagination so the whole dataset is covered in one run.


34-40: Escape email when building the Stripe search query
If an email contains a single quote ('), the query will break. Use encodeURIComponent or Stripe’s parameterized search filters instead of string interpolation.

-        query: `email:'${customer.email}'`,
+        query: `email:"${customer.email.replace(/"/g, '\\"')}"`,
apps/web/lib/types.ts (1)

162-166: Reintroduce id on domains or update all domain.id references

Removing the id field from WorkspaceProps.domains breaks several active code paths that still expect domain.id. You must either restore id: string in the domain type or refactor these usages to use another identifier (e.g. slug).

Affected locations:

  • apps/web/ui/links/use-available-domains.ts
    • Line where activeDefaultDomains.some(({ id }) => id === domain.id)
  • apps/web/scripts/migrate-domains.ts
    • Returning { id: domain.id, … }
  • apps/web/app/api/workspaces/[idOrSlug]/import/rebrandly/route.ts
    • Query string interpolation: ?domain.id=${id}

Please address these breaks before merging.

🧹 Nitpick comments (7)
apps/web/scripts/stripe/update-stripe-customers.ts (1)

33-83: Consider batching requests to improve throughput
The script makes one network round-trip per customer. Using Promise.all in small batches or Stripe’s bulk retrieval (if available) will cut runtime significantly for large customer sets.

packages/ui/src/icons/nucleo/infinity-icon.tsx (1)

12-20: Redundant fill attribute on wrapping <g>

<g fill="currentColor"> is unnecessary because the nested <path> sets fill="none". Dropping the redundant attribute slightly reduces markup noise and eliminates confusion about intended fill behavior.

-      <g fill="currentColor">
+      <g>
apps/web/app/(ee)/api/partners/links/upsert/route.ts (1)

100-101: Whitespace-only change — consider squashing

This blank line has no functional impact but adds noise to git blame. Unless intentional for readability, consider reverting or squashing with a future change.

apps/web/lib/swr/use-domains.ts (1)

25-27: Verify type consistency with schema definition.

The type extension correctly adds linkRetentionDays to the domain objects. However, consider whether the optional property (?) aligns with the schema's nullish() definition, which allows null values in addition to undefined.

Consider making the type more explicit to match the schema:

- const { data, error, mutate } = useSWR<
-   (DomainProps & { linkRetentionDays?: number })[]
- >(
+ const { data, error, mutate } = useSWR<
+   (DomainProps & { linkRetentionDays?: number | null })[]
+ >(
apps/web/scripts/convert-case-sensitive.ts (1)

38-44: Consider making the key length assumption more robust.

The assumption that keys longer than 7 characters are already encoded might not always be accurate. Consider adding additional validation or making this configurable.

-      if (link.key.length > 7) {
+      if (link.key.length > 7 || link.key.includes('=')) {
         console.log(
-          `Skipping link ${link.id} because key length is greater than 7 (already encoded)`,
+          `Skipping link ${link.id} because it appears to be already encoded`,
         );
         return;
       }
apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts (2)

22-30: Consider adding database index for performance.

The query filters domains by linkRetentionDays: { not: null }. For optimal performance, ensure there's a database index on the linkRetentionDays column.

#!/bin/bash
# Description: Check if there's an index on linkRetentionDays in the Prisma schema
# Expected: Should find index definition for linkRetentionDays

# Search for domain schema and index definitions
ast-grep --pattern 'model Domain {
  $$$
}'

# Look for index definitions
rg -A 5 -B 5 "linkRetentionDays.*index|@@index.*linkRetentionDays" --type prisma

126-133: Consider the commented concern about Tinybird storage.

The comment raises a valid question about whether to delete records from Tinybird entirely versus marking them as deleted. This affects analytics history and storage costs.

Consider documenting the decision rationale or implementing a configurable approach for Tinybird record handling based on business requirements.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f83b62 and 77d3a3c.

📒 Files selected for processing (23)
  • apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partners/links/upsert/route.ts (1 hunks)
  • apps/web/app/api/links/[linkId]/route.ts (2 hunks)
  • apps/web/app/api/workspaces/[idOrSlug]/route.ts (1 hunks)
  • apps/web/lib/api/links/case-sensitivity.ts (1 hunks)
  • apps/web/lib/swr/use-domains.ts (1 hunks)
  • apps/web/lib/swr/use-workspace.ts (2 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/lib/zod/schemas/links.ts (1 hunks)
  • apps/web/lib/zod/schemas/workspaces.ts (1 hunks)
  • apps/web/scripts/convert-case-sensitive.ts (1 hunks)
  • apps/web/scripts/stripe/update-stripe-customers.ts (1 hunks)
  • apps/web/tests/utils/schema.ts (2 hunks)
  • apps/web/ui/links/link-builder/constants.ts (3 hunks)
  • apps/web/ui/links/link-builder/link-feature-buttons.tsx (1 hunks)
  • apps/web/ui/links/link-builder/more-dropdown.tsx (6 hunks)
  • apps/web/ui/links/link-builder/options-list.tsx (2 hunks)
  • apps/web/vercel.json (1 hunks)
  • packages/prisma/schema/domain.prisma (1 hunks)
  • packages/prisma/schema/link.prisma (3 hunks)
  • packages/ui/src/icons/nucleo/index.ts (1 hunks)
  • packages/ui/src/icons/nucleo/infinity-icon.tsx (1 hunks)
  • packages/ui/src/tooltip.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (15)
apps/web/app/(ee)/api/partners/links/upsert/route.ts (1)

Learnt from: devkiran
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.

apps/web/lib/zod/schemas/links.ts (1)

Learnt from: devkiran
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.

apps/web/app/api/links/[linkId]/route.ts (1)

Learnt from: devkiran
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.

packages/ui/src/icons/nucleo/infinity-icon.tsx (1)

Learnt from: TWilson023
PR: #2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation 3 - i * -0.15 is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

apps/web/tests/utils/schema.ts (1)

Learnt from: devkiran
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.

packages/prisma/schema/link.prisma (1)

Learnt from: devkiran
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.

apps/web/scripts/convert-case-sensitive.ts (2)

Learnt from: devkiran
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.

Learnt from: devkiran
PR: #2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.598Z
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.

apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts (1)

Learnt from: devkiran
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.

apps/web/lib/types.ts (1)

Learnt from: TWilson023
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.

apps/web/app/api/workspaces/[idOrSlug]/route.ts (2)

Learnt from: devkiran
PR: #2433
File: apps/web/ui/modals/add-payment-method-modal.tsx:60-62
Timestamp: 2025-05-29T09:49:19.604Z
Learning: The /api/workspaces/${slug}/billing/payment-methods POST endpoint in the billing API returns either an error (handled by response.ok check) or a response object containing a url property for successful requests.

Learnt from: devkiran
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.

apps/web/lib/swr/use-workspace.ts (1)

Learnt from: TWilson023
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.

apps/web/ui/links/link-builder/options-list.tsx (1)

Learnt from: TWilson023
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.

apps/web/ui/links/link-builder/constants.ts (2)

Learnt from: devkiran
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.

Learnt from: devkiran
PR: #2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

apps/web/lib/api/links/case-sensitivity.ts (2)

Learnt from: devkiran
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.

Learnt from: devkiran
PR: #2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.598Z
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.

apps/web/scripts/stripe/update-stripe-customers.ts (1)

Learnt from: TWilson023
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.

🧬 Code Graph Analysis (4)
apps/web/app/api/links/[linkId]/route.ts (1)
apps/web/lib/zod/schemas/links.ts (1)
  • updateLinkBodySchemaExtended (467-469)
apps/web/ui/links/link-builder/link-feature-buttons.tsx (1)
apps/web/ui/links/link-builder/more-dropdown.tsx (1)
  • MoreDropdown (18-178)
apps/web/lib/swr/use-domains.ts (1)
apps/web/lib/types.ts (1)
  • DomainProps (216-231)
apps/web/lib/swr/use-workspace.ts (1)
apps/web/lib/types.ts (1)
  • ExtendedWorkspaceProps (177-186)
⏰ 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 (39)
apps/web/vercel.json (1)

50-54: Verify 1-minute cron interval for retention cleanup

Running link-retention cleanup every minute may cause unnecessary load and higher invocations cost. Double-check expected volume and adjust to a coarser cadence (e.g., hourly) if real-time cleanup isn’t required.

packages/ui/src/icons/nucleo/index.ts (1)

131-131: Export looks good

InfinityIcon is correctly re-exported, maintaining alphabetic order. No further action needed.

apps/web/lib/swr/use-workspace.ts (1)

1-1: Type alias update aligns with new schema

Renaming to ExtendedWorkspaceProps and updating the SWR generic keeps typings consistent with the enriched workspace shape. ✅

Also applies to: 21-22

packages/prisma/schema/domain.prisma (1)

12-12: LGTM: Clear schema addition for link retention feature.

The new linkRetentionDays field is well-designed with appropriate nullability and clear documentation of the default behavior. This supports the domain-level link retention policy implementation.

apps/web/app/api/links/[linkId]/route.ts (1)

14-17: LGTM: Clean schema extension for link retention feature.

The change appropriately extends the validation schema to support the new linkRetentionCleanupDisabledAt field while maintaining all existing functionality. The import and usage are correctly updated.

Also applies to: 81-83

apps/web/lib/zod/schemas/links.ts (1)

467-469: LGTM: Appropriate schema extension for retention cleanup control.

The extended schema cleanly adds the linkRetentionCleanupDisabledAt field with appropriate typing (string().nullish()) for optional timestamp values. This follows established patterns in the codebase.

apps/web/ui/links/link-builder/link-feature-buttons.tsx (1)

47-47: LGTM: Proper variant prop propagation.

The variant prop is correctly passed to the MoreDropdown component, enabling context-aware option filtering for the link retention feature.

apps/web/app/api/workspaces/[idOrSlug]/route.ts (1)

28-28: LGTM: Domain data extended for retention feature support.

The addition of id and linkRetentionDays fields to the domain selection appropriately extends the workspace API to support the new link retention cleanup functionality. These fields will be needed by frontend components to manage retention settings.

Also applies to: 32-32

apps/web/lib/zod/schemas/workspaces.ts (1)

163-167: LGTM! Schema extension is well-implemented.

The addition of linkRetentionDays as a nullable number field to the domains array is correctly implemented using nullish(), which appropriately handles the optional nature of this retention policy field.

apps/web/lib/api/links/case-sensitivity.ts (1)

76-82: Excellent refactor to eliminate mutation.

The change from mutating the input link object to returning a new object with spread syntax is a great improvement. This follows immutability principles and eliminates potential side effects, making the function more predictable and safer to use.

apps/web/tests/utils/schema.ts (2)

8-8: Test schema properly extended.

The addition of linkRetentionCleanupDisabledAt field to the test schema is correctly implemented and aligns with the broader feature implementation.


62-62: Test expectation correctly updated.

The expectedLink object properly includes the new field with a null default value, maintaining consistency with the schema definition.

packages/ui/src/tooltip.tsx (2)

136-137: Good improvement to component flexibility.

Making cta and href optional props increases the component's reusability and prevents potential issues with undefined values.


141-155: Proper conditional rendering implementation.

The updated logic correctly renders the title always and only shows the CTA link when both cta and href are provided. This prevents rendering incomplete links and improves the component's robustness.

apps/web/ui/links/link-builder/options-list.tsx (3)

25-31: Enhanced filtering logic looks well-implemented.

The updated filtering logic correctly supports both the new enabled function approach and the legacy data[key] truthiness check, maintaining backward compatibility while enabling more sophisticated toggle state determination.


65-70: Conditional prop spreading is clean and follows good patterns.

The conditional spreading of props based on the enabled function result is well-structured and allows for dynamic icon assignment based on toggle state.


1-83: Inconsistency between PR title and implemented changes.

The PR title indicates "Fix decode" but the changes implement link retention functionality. This appears to be an inconsistency between the stated objectives and actual implementation.

Likely an incorrect or invalid review comment.

apps/web/scripts/convert-case-sensitive.ts (2)

9-19: Improved filtering logic with proper typing.

The typed where object and additional filtering criteria (date-based and excluding already encoded keys) are well-designed improvements that prevent unnecessary processing.


29-35: Good addition of progress reporting.

The remaining links count provides valuable feedback during script execution, especially useful for large-scale operations.

packages/prisma/schema/link.prisma (3)

28-28: Well-designed field addition for retention feature.

The linkRetentionCleanupDisabledAt field is appropriately typed as optional DateTime and has a clear, descriptive name that indicates its purpose for tracking when retention cleanup was explicitly disabled.


76-77: Good organizational improvement.

Moving the programId field and its relation together improves the schema organization and readability.


90-90: Enhanced index documentation.

The updated comment for the [domain, createdAt] index clearly explains its usage for bulk link deletion workflows and deleting old short-lived links, which is helpful for maintenance.

apps/web/ui/links/link-builder/constants.ts (3)

17-17: Appropriate import addition.

The UseFormSetValue import is correctly added to support the new callback functionality.


29-31: Well-designed type extensions.

The optional enable and remove callbacks are properly typed and extend the MoreItem interface cleanly to support dynamic toggle state management.


55-69: Excellent implementation of the permanent retention feature.

The new "Permanent Retention" item is well-designed with:

  • Appropriate InfinityIcon for the concept
  • Clear description explaining its purpose
  • Proper enabled function checking linkRetentionCleanupDisabledAt
  • Correct enable/remove callbacks using setValue with shouldDirty: true
apps/web/lib/types.ts (1)

177-186: Well-structured type enhancements for retention feature.

The new ExtendedWorkspaceProps interface properly extends WorkspaceProps and adds the necessary linkRetentionDays field to support the retention functionality. The additional fields (defaultProgramId, allowedHostnames, workspacePreferences) enhance the workspace data model appropriately.

apps/web/ui/links/link-builder/more-dropdown.tsx (6)

18-22: LGTM! Clean component interface extension.

The addition of the optional variant prop with sensible defaults follows good TypeScript practices.


35-40: Well-implemented conditional filtering logic.

The filtering logic properly checks for domain-specific linkRetentionDays configuration and restricts the link retention cleanup option to the page variant only. This provides appropriate context-aware functionality.


69-77: Robust callback handling for enable/remove actions.

The enhanced keyboard shortcut handler properly checks for the existence of enable and remove callbacks before invoking them, providing a clean extension point for different option types.


110-117: Consistent click handler implementation.

The click handler mirrors the keyboard shortcut logic, ensuring consistent behavior across interaction methods. The callback pattern is well-implemented.


148-151: Smart conditional rendering for learn more links.

The conditional rendering of the "Learn more" CTA only when learnMoreUrl is provided prevents broken or empty links, improving user experience.


23-23: No action needed: useWorkspace already exposes domains.linkRetentionDays
The hook spreads ExtendedWorkspaceProps (which includes domains: DomainProps[]), and DomainProps defines

linkRetentionDays: number | null

so domains?.find(...).linkRetentionDays is correctly typed.

apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts (7)

12-12: Good choice for dynamic execution.

The force-dynamic export ensures this cron endpoint executes dynamically on each request, which is appropriate for cleanup operations that need fresh data.


60-61: Well-chosen batch processing limits.

The constants LINKS_PER_BATCH = 100 and MAX_LINK_BATCHES = 10 provide reasonable limits to prevent memory issues while ensuring efficient processing.


66-71: Robust input validation and early returns.

The function properly validates the domain has required fields and positive retention days before proceeding. This prevents unnecessary processing and potential errors.


77-91: Efficient query with proper filtering.

The query correctly filters by domain, creation date, and cleanup exemption status. The ordering by createdAt: "asc" ensures oldest links are processed first, which is logical for retention cleanup.


108-124: Proper transaction handling for data consistency.

The transaction ensures atomic deletion of links and decrementing of the project's total link count. This prevents data inconsistency if either operation fails.


144-152: Smart scheduling logic prevents infinite loops.

The condition processedBatches >= MAX_LINK_BATCHES && hasMoreLinks properly ensures continuation only when necessary, preventing both infinite scheduling and premature termination.


1-156: Flag inconsistency between PR title and implementation.

The PR is titled "Fix decode" but implements link retention cleanup functionality. This suggests either an incorrect PR title or mismatched objectives.

Please verify that this PR has the correct title and objectives, as the implemented functionality doesn't appear related to "decode" operations.

Likely an incorrect or invalid review comment.

Comment on lines +39 to +49
const { domain: passedDomain } = z
.object({
domain: z.string(),
})
.parse(JSON.parse(rawBody));

const domain = await prisma.domain.findUniqueOrThrow({
where: {
slug: passedDomain,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential error handling issue with findUniqueOrThrow.

Using findUniqueOrThrow will cause the entire cron job to fail if a domain doesn't exist. Consider whether this should be a soft failure that logs the error but continues processing.

-      const domain = await prisma.domain.findUniqueOrThrow({
+      const domain = await prisma.domain.findUnique({
         where: {
           slug: passedDomain,
         },
       });
+
+      if (!domain) {
+        console.warn(`Domain ${passedDomain} not found, skipping cleanup`);
+        return NextResponse.json("Domain not found");
+      }
📝 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.

Suggested change
const { domain: passedDomain } = z
.object({
domain: z.string(),
})
.parse(JSON.parse(rawBody));
const domain = await prisma.domain.findUniqueOrThrow({
where: {
slug: passedDomain,
},
});
const { domain: passedDomain } = z
.object({
domain: z.string(),
})
.parse(JSON.parse(rawBody));
const domain = await prisma.domain.findUnique({
where: {
slug: passedDomain,
},
});
if (!domain) {
console.warn(`Domain ${passedDomain} not found, skipping cleanup`);
return NextResponse.json("Domain not found");
}
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/link-retention-cleanup/route.ts around lines 39 to
49, the use of findUniqueOrThrow causes the cron job to fail completely if the
domain is not found. Modify the code to use findUnique instead, then check if
the result is null. If null, log an error message and continue processing
without throwing, to allow the cron job to handle missing domains gracefully.

where: {
domain: "domain.com",
const where: Prisma.LinkWhereInput = {
domain: "domain.com",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make domain configurable for production use.

The hardcoded domain "domain.com" should be made configurable to support different environments and use cases.

+const DOMAIN = process.env.TARGET_DOMAIN || "domain.com";
+
 const where: Prisma.LinkWhereInput = {
-  domain: "domain.com",
+  domain: DOMAIN,
📝 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.

Suggested change
domain: "domain.com",
// Make the target domain configurable via environment variable
const DOMAIN = process.env.TARGET_DOMAIN || "domain.com";
const where: Prisma.LinkWhereInput = {
domain: DOMAIN,
// …other filters
};
🤖 Prompt for AI Agents
In apps/web/scripts/convert-case-sensitive.ts at line 10, the domain is
hardcoded as "domain.com", which limits flexibility for different environments.
Modify the code to read the domain value from a configuration source such as an
environment variable or a config file, allowing it to be set dynamically for
production and other use cases.

Comment on lines 8 to 11
where: {
id: "xxx",
id: "ws_1JRV43Y6B85PGH7B54KA79M81",
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Avoid hard-coding workspace IDs — parameterize via env/CLI instead
Embedding a production workspace ID directly in source control is brittle and can lead to accidental data corruption if this script is copied to other environments. Surface the ID as an environment variable or CLI flag so it can be swapped safely at runtime.

-      id: "ws_1JRV43Y6B85PGH7B54KA79M81",
+      // e.g. export WORKSPACE_ID=ws_...
+      id: process.env.WORKSPACE_ID!,

Parameterize the workspace ID in update-stripe-customers.ts

Hard-coding a production workspace ID in this script is brittle and risks accidental data corruption across environments. Load the ID from an environment variable or accept it as a CLI flag.

Affected file:

  • apps/web/scripts/stripe/update-stripe-customers.ts

Suggested fix:

@@ apps/web/scripts/stripe/update-stripe-customers.ts
-    where: {
-      id: "ws_1JRV43Y6B85PGH7B54KA79M81",
-    },
+    // e.g. export WORKSPACE_ID=ws_...
+    const workspaceId = process.env.WORKSPACE_ID!;
+    where: { id: workspaceId },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/scripts/stripe/update-stripe-customers.ts around lines 8 to 11, the
workspace ID is hard-coded, which is risky for production use. Modify the script
to load the workspace ID from an environment variable or accept it as a
command-line argument instead of hard-coding it. Update the code to read the
workspace ID dynamically and use that variable in the query to ensure safer and
more flexible execution across environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants