Skip to content

feat: Enhance private link expiration with usage and date limits #22304

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

Merged
merged 61 commits into from
Jul 24, 2025

Conversation

alishaz-polymath
Copy link
Member

@alishaz-polymath alishaz-polymath commented Jul 7, 2025

What does this PR do?

Continues from the PR #20118 by @retrogtx for clean start 🙏

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox. N/A.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

Summary by cubic

Added expiration controls to private booking links, allowing links to expire after a set date or a specific number of uses.

  • New Features
    • Private links can now be set to expire on a chosen date or after a maximum number of uses.
    • UI updated to let users manage expiration settings for each link.
    • Expired or overused links are blocked from booking.
    • Added tests for time-based and usage-based expiration.

@alishaz-polymath alishaz-polymath requested review from a team as code owners July 7, 2025 15:53
Copy link

linear bot commented Jul 7, 2025

@github-actions github-actions bot added ❗️ migrations contains migration files enterprise area: enterprise, audit log, organisation, SAML, SSO Medium priority Created by Linear-GitHub Sync ✨ feature New feature or request labels Jul 7, 2025
@keithwillcode keithwillcode added the core area: core, team members only label Jul 7, 2025
@dosubot dosubot bot added this to the v5.5 milestone Jul 7, 2025
@dosubot dosubot bot added the event-types area: event types, event-types label Jul 7, 2025
Copy link

delve-auditor bot commented Jul 7, 2025

No security or compliance issues detected. Reviewed everything up to 812dab5.

Security Overview
  • 🔎 Scanned files: 29 changed file(s)
Detected Code Changes
Change Type Relevant files
Bug Fix ► getServerSideProps.tsx
    Remove console log
► d-type-view.tsx
    Remove console log
► event-types-listing-view.tsx
    Remove console log
► hash-my-url.e2e.ts
    Remove console log
Enhancement ► MultiplePrivateLinksController.tsx
    Add expiration and usage controls
► hashedLinksUtils.ts
    Add link validation utilities
► EventAdvancedTab.tsx
    Update private links interface
Configuration changes ► migration.sql
    Add private links expiration capability
► schema.prisma
    Update HashedLink model

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic found 9 issues across 22 files. Review them in cubic.dev

React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai to give specific feedback.


// If the event type belongs to a team, check if the user is part of that team
if (link.eventType.teamId) {
const membership = await prisma.membership.findFirst({
Copy link
Contributor

Choose a reason for hiding this comment

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

Running a separate membership.findFirst() inside the map introduces an N+1 query pattern (one additional DB query for every hashed link). Fetch memberships in bulk or embed the permission check in the original findMany to avoid potential performance and connection–exhaustion issues.

Copy link

vercel bot commented Jul 7, 2025

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

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
cal ⬜️ Ignored (Inspect) Visit Preview Jul 24, 2025 3:47pm
cal-eu ⬜️ Ignored (Inspect) Visit Preview Jul 24, 2025 3:47pm

CarinaWolli

This comment was marked as resolved.

@@ -72,6 +79,17 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
if (!hashedLink) {
return notFound;
}

const isExpired = hashedLink.expiresAt ? new Date(hashedLink.expiresAt).getTime() < Date.now() : false;
Copy link
Member Author

Choose a reason for hiding this comment

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

We store in user's timezone EOD converted to UTC, so this checks against that and compares to current UTC time

Copy link
Member

Choose a reason for hiding this comment

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

Why don’t we store expiresAt as UTC midnight instead? Then we can always convert it to the user’s local midnight when needed. I think storing it in user's local midnight in UTC causes issues whem the user changes their profile timezone later

This comment was marked as resolved.

@alishaz-polymath alishaz-polymath marked this pull request as ready for review July 24, 2025 12:24
@dosubot dosubot bot added the ui area: UI, frontend, button, form, input label Jul 24, 2025
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: 1

🧹 Nitpick comments (1)
packages/lib/server/repository/hashedLinkRepository.ts (1)

10-80: Consider creating focused select objects for different use cases.

The hashedLinkSelect object fetches extensive nested data including users, teams, profiles, and timezone information. According to best practices for Prisma queries, you should only select data you need. Consider creating multiple focused select objects for different use cases to improve performance and reduce data transfer.

For example:

export const hashedLinkBasicSelect = {
  id: true,
  link: true,
  eventTypeId: true,
  expiresAt: true,
  maxUsageCount: true,
  usageCount: true,
} satisfies Prisma.HashedLinkSelect;

export const hashedLinkWithEventTypeSelect = {
  ...hashedLinkBasicSelect,
  eventType: {
    select: {
      userId: true,
      teamId: true,
    },
  },
} satisfies Prisma.HashedLinkSelect;

// Use the full select only when all data is actually needed
export const hashedLinkFullSelect = { /* current hashedLinkSelect */ };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f8584e9 and 29160dc.

📒 Files selected for processing (10)
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx (4 hunks)
  • apps/web/public/static/locales/en/common.json (3 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (2 hunks)
  • packages/features/eventtypes/lib/types.ts (2 hunks)
  • packages/lib/server/repository/hashedLinkRepository.ts (1 hunks)
  • packages/lib/server/service/hashedLinkService.ts (1 hunks)
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLink.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLinks.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/platform/atoms/event-types/hooks/useEventTypeForm.ts
  • packages/features/eventtypes/lib/types.ts
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLink.handler.ts
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLinks.handler.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • apps/web/public/static/locales/en/common.json
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/lib/server/repository/hashedLinkRepository.ts
  • packages/lib/server/service/hashedLinkService.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the `maxUsageCount` field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
packages/lib/server/repository/hashedLinkRepository.ts (2)

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : For Prisma queries: Only select data you need.

packages/lib/server/service/hashedLinkService.ts (2)

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: alishaz-polymath
PR: #22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the maxUsageCount field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.

🧬 Code Graph Analysis (1)
packages/lib/server/service/hashedLinkService.ts (4)
packages/lib/server/repository/hashedLinkRepository.ts (2)
  • HashedLinkRepository (82-257)
  • HashedLinkInputType (4-8)
packages/prisma/index.ts (1)
  • PrismaClient (83-83)
packages/platform/libraries/index.ts (1)
  • ErrorCode (77-77)
packages/lib/hashedLinksUtils.ts (1)
  • validateHashedLinkData (103-111)
⏰ 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: Codacy Static Code Analysis
🔇 Additional comments (13)
packages/lib/server/service/hashedLinkService.ts (6)

13-18: Well-structured service with proper dependency injection.

The service follows good architectural patterns with dependency injection and repository abstraction.


20-33: Clean normalization logic.

Good abstraction for handling different input formats consistently.


35-77: Efficient bulk link management implementation.

Good use of Set data structure for O(1) lookups and proper orchestration of create/update/delete operations.


79-100: Robust validation with proper error handling.

Good separation of concerns by delegating to utility function and consistent error code usage.


102-122: Correct usage tracking logic with atomic updates.

Properly handles both time-based and usage-based expiration, with atomic increment operation in the repository.


124-162: Secure permission checking implementation.

Properly validates ownership and team membership without leaking sensitive information.

packages/lib/server/repository/hashedLinkRepository.ts (7)

85-94: Efficient bulk delete implementation.

Good use of early return optimization and Prisma's deleteMany for performance.


96-111: Appropriate validation for maxUsageCount.

The Number.isFinite check ensures valid numeric values. Note that the truthy check excludes 0, which aligns with the business logic where usage counts should be positive.


134-146: Focused data selection for listing links.

Good practice of selecting only the necessary fields for this use case.


148-177: Good separation of concerns with focused queries.

The findLinkWithEventTypeDetails method demonstrates good practice by selecting only necessary fields. Consider whether findLinkWithDetails always needs the full hashedLinkSelect data.


179-201: Efficient bulk query with focused selection.

Good implementation for fetching multiple links with only necessary event type data.


203-244: Appropriate data selection for validation context.

The extensive timezone data selection is justified for the validation use case where timezone-aware expiration checking is required.


246-256: Excellent atomic increment implementation.

The use of usageCount: { lt: maxUsageCount } in the where clause prevents race conditions and ensures the usage count never exceeds the maximum. This is a textbook example of handling concurrent updates safely.

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

🧹 Nitpick comments (1)
packages/lib/server/service/hashedLinkService.ts (1)

108-121: Consider clarifying usage count in return value.

The method logic is correct, but for usage-based links that get incremented, the returned hashedLink object still contains the old usageCount value. Consider whether callers need the updated count or if this is intentional.

If callers need the updated count, you could refetch the link after increment or update the object before returning.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 29160dc and 5bca163.

📒 Files selected for processing (8)
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx (4 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (2 hunks)
  • packages/lib/server/repository/hashedLinkRepository.ts (1 hunks)
  • packages/lib/server/service/hashedLinkService.ts (1 hunks)
  • packages/lib/server/service/membershipService.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLink.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLinks.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/trpc/server/routers/viewer/eventTypes/update.handler.ts
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLinks.handler.ts
  • packages/trpc/server/routers/viewer/eventTypes/getHashedLink.handler.ts
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx
  • packages/lib/server/repository/hashedLinkRepository.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/lib/server/service/membershipService.ts
  • packages/lib/server/service/hashedLinkService.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the `maxUsageCount` field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
packages/lib/server/service/hashedLinkService.ts (2)

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: alishaz-polymath
PR: #22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the maxUsageCount field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.

⏰ 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: Install dependencies / Yarn install & cache
🔇 Additional comments (6)
packages/lib/server/service/membershipService.ts (3)

1-10: LGTM! Clean imports and well-structured type definition.

The type definition comprehensively captures membership status with boolean flags and includes the optional role for consumers who need the actual role value.


12-13: LGTM! Good dependency injection pattern.

The constructor properly implements dependency injection with a sensible default, making the class both testable and convenient to use.


21-34: LGTM! Solid membership validation logic.

The method correctly handles null/non-accepted memberships and properly determines admin/owner status based on roles. The return object structure is consistent across both branches.

packages/lib/server/service/hashedLinkService.ts (3)

1-17: LGTM! Clean imports and proper dependency injection.

The type definition and constructor follow good practices with dependency injection and sensible defaults for testing and production use.


19-32: LGTM! Clean input normalization.

The method effectively handles both string and object inputs, providing a consistent interface for downstream operations.


78-99: LGTM! Well-structured validation with proper error handling.

The method follows a logical validation flow with appropriate input validation and delegates complex validation logic to a utility function for reusability.

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: 1

♻️ Duplicate comments (1)
packages/lib/server/service/hashedLinkService.ts (1)

40-76: Transaction handling needed for data consistency.

The method performs multiple database operations (delete, create, update) separately, which could leave the database in an inconsistent state if any operation fails partway through.

Consider wrapping all operations in a database transaction:

  async handleMultiplePrivateLinks({
    eventTypeId,
    multiplePrivateLinks,
    connectedMultiplePrivateLinks,
  }: {
    eventTypeId: number;
    multiplePrivateLinks?: (string | HashedLinkInputType)[];
    connectedMultiplePrivateLinks: string[];
  }) {
+   return await this.hashedLinkRepository.prismaClient.$transaction(async (tx) => {
      // ... existing validation logic ...
      
-     await this.hashedLinkRepository.deleteLinks(eventTypeId, linksToDelete);
+     await tx.hashedLink.deleteMany({
+       where: { eventTypeId, link: { in: linksToDelete } }
+     });

      // Similar changes for create and update operations
+   });
  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5bca163 and e8d5923.

📒 Files selected for processing (4)
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx (4 hunks)
  • packages/lib/server/repository/membership.ts (3 hunks)
  • packages/lib/server/service/hashedLinkService.ts (1 hunks)
  • packages/lib/server/service/membershipService.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/lib/server/service/membershipService.ts
  • apps/web/lib/d/[link]/[slug]/getServerSideProps.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/lib/server/repository/membership.ts
  • packages/lib/server/service/hashedLinkService.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the `maxUsageCount` field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.
Learnt from: alishaz-polymath
PR: calcom/cal.com#22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the `currentLink.maxUsageCount ?? 1` fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.
packages/lib/server/repository/membership.ts (3)

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : For Prisma queries: Select selects only the fields you specify explicitly.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : For Prisma queries: Only select data you need.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : For Prisma queries: Include selects the relationship AND all the fields of the table the relationship belongs to.

packages/lib/server/service/hashedLinkService.ts (4)

Learnt from: alishaz-polymath
PR: #22304
File: packages/features/eventtypes/components/MultiplePrivateLinksController.tsx:92-94
Timestamp: 2025-07-16T06:42:27.024Z
Learning: In the MultiplePrivateLinksController component (packages/features/eventtypes/components/MultiplePrivateLinksController.tsx), the currentLink.maxUsageCount ?? 1 fallback in the openSettingsDialog function is intentional. Missing maxUsageCount values indicate old/legacy private links that existed before the expiration feature was added, and they should default to single-use behavior (1) for backward compatibility.

Learnt from: alishaz-polymath
PR: #22304
File: packages/prisma/schema.prisma:1068-1071
Timestamp: 2025-07-16T05:10:22.891Z
Learning: In PR #22304 for Cal.com private link expiration features, the maxUsageCount field was intentionally set to default to 1 (non-nullable) as a breaking change, making all existing private links single-use after migration. This was a deliberate design decision by alishaz-polymath.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : Check if there's any O(n^2) logic in backend code; we should aim for O(n log n) or O(n) ideally.

Learnt from: Anshumancanrock
PR: #22570
File: apps/web/modules/signup-view.tsx:253-253
Timestamp: 2025-07-21T21:33:23.371Z
Learning: In signup-view.tsx, when checking if redirectUrl contains certain strings, using explicit && checks (redirectUrl && redirectUrl.includes()) is preferred over optional chaining (redirectUrl?.includes()) to ensure the result is always a boolean rather than potentially undefined. This approach provides cleaner boolean contracts for downstream conditional logic.

🧬 Code Graph Analysis (1)
packages/lib/server/service/hashedLinkService.ts (4)
packages/lib/server/repository/hashedLinkRepository.ts (2)
  • HashedLinkRepository (83-262)
  • HashedLinkInputType (5-9)
packages/lib/server/service/membershipService.ts (1)
  • MembershipService (12-41)
packages/platform/libraries/index.ts (1)
  • ErrorCode (77-77)
packages/lib/hashedLinksUtils.ts (1)
  • validateHashedLinkData (103-111)
⏰ 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: Install dependencies / Yarn install & cache
🔇 Additional comments (8)
packages/lib/server/repository/membership.ts (2)

44-44: LGTM: Clean addition of timezone field.

The addition of the timeZone field to userSelect is consistent with the existing pattern and supports the timezone-aware expiration validation features introduced in this PR.


177-191: LGTM: Efficient nested selection for timezone data.

The addition of the nested team selection with take: 1 efficiently retrieves the necessary timezone information for expiration validation while minimizing data transfer. The structure follows Prisma best practices by selecting only the required fields.

packages/lib/server/service/hashedLinkService.ts (6)

13-17: LGTM: Well-structured dependency injection.

The constructor properly implements dependency injection with sensible defaults, making the service both easy to use and testable with mock dependencies.


24-32: LGTM: Clean input normalization.

The method effectively handles union types and provides consistent output format with proper null handling using the null coalescing operator.


85-99: LGTM: Comprehensive validation logic.

The method properly validates inputs, handles null cases, and leverages the utility function for expiration checks. Good separation of concerns and appropriate error handling.


108-121: LGTM: Proper handling of dual expiration modes.

The method correctly distinguishes between time-based and usage-based expiration, skipping usage increment for time-based links and handling atomic increment failures appropriately.


123-130: LGTM: Clean repository wrapper.

Simple, clean implementation that properly handles null cases and provides a clear interface to the underlying repository method.


138-158: LGTM: Permission logic correctly implemented.

The permission checking logic properly validates user ownership with explicit comparison and uses the correct membership.isMember flag from the membership service. The method handles all edge cases appropriately and follows a logical flow from user ownership to team membership.

Copy link
Contributor

@emrysal emrysal left a comment

Choose a reason for hiding this comment

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

LGTM, excellent 👍

@emrysal emrysal merged commit 66b52bb into main Jul 24, 2025
61 of 66 checks passed
@emrysal emrysal deleted the feat/private-links-2 branch July 24, 2025 18:54
zomars pushed a commit that referenced this pull request Jul 25, 2025
)

* init

* fix type

* fix a re-render infinite loop because of missing readOnly (╯°□°)╯︵ ┻━┻)

* further fixes

* improvement

* fix expiry datetime check

* remove unnecessary prismaMock def

* revert

* fix test

* add test ids

* remove unit tests in favor of e2e

* e2e test update

* fix e2e

* fix e2e

* remove unnecessary change

* abstract into injectable object

* further improvements

* fix label not selecting radio

* fix type

* code improvement

* DI implementation

* fix type

* fix quick copy

* code improvement and a few fixes

* further improvements and NITS

* further into DI

* select

* improve link list sorting

* prep for easier conflict resolution

* add back translations

* using useCopy instead

* improvement

* add index to update salt and have different hash generation

* fix private link description

* fix increment regression in expiry logic

* fixes

* address feedback

* use extractHostTimezone in event type listing

* remove unused function

* remove translationBundler

* -_-

* address feedback

* further changes

* address more feedback

* NIT

* address improvement suggestions

* use extractHostTimezone

* remove console log

* pre update

* code improvement

* further fixes

* cleanup

* -_-
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automated-tests area: unit tests, e2e tests, playwright core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO event-types area: event types, event-types ✨ feature New feature or request Medium priority Created by Linear-GitHub Sync ❗️ migrations contains migration files ready-for-e2e ui area: UI, frontend, button, form, input
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CAL-5283] Expiration capabilities to private links
7 participants