Skip to content

Comments

feat: ✨member based limits controlled by org admins#23233

Draft
shaun-ak wants to merge 25 commits intocalcom:mainfrom
shaun-ak:feat-member-based-limits-controlled-by-org-admins
Draft

feat: ✨member based limits controlled by org admins#23233
shaun-ak wants to merge 25 commits intocalcom:mainfrom
shaun-ak:feat-member-based-limits-controlled-by-org-admins

Conversation

@shaun-ak
Copy link
Contributor

What does this PR do?

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.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

@shaun-ak shaun-ak requested a review from a team August 21, 2025 03:14
@vercel
Copy link

vercel bot commented Aug 21, 2025

@shaun-ak is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

Walkthrough

Adds per-member booking limits via a nullable Json? bookingLimits field on Membership with a Prisma schema change and DB migration. Exposes membership bookingLimits in viewer getUser responses. Introduces Zod input schema and a TRPC route + handler updateMembershipBookingLimits with org membership and admin/owner validations. UI: read/write support in organization member edit sheet and form (display component, IntervalLimitsManager, toggle, validation util). API membership create/patch normalize null bookingLimits to undefined. Adds i18n key booking_limits_description.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The pull request fulfills the database schema updates, UI additions, localization, and persistence mechanisms for member-based booking limits (#22626, CAL-6126) but lacks the enforcement logic in the booking validation flow to block bookings when limits are reached. Implement the enforcement of member-based limits in the booking validation service alongside existing per-event-type and team limits, and add tests to verify bookings are blocked appropriately.
Description Check ❓ Inconclusive The description correctly references the linked issues and includes a checklist but lacks a summary of the actual code changes and their impact on the system. Please enhance the description to include an overview of the key implementation details, such as new UI components, database schema changes, and backend mutation logic.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly summarizes the primary feature of allowing organization admins to control member-based booking limits, directly reflecting the main functionality introduced in the pull request.
Out of Scope Changes Check ✅ Passed All modifications in this pull request are directly focused on the member-based booking limits feature, with no unrelated or extraneous changes introduced.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fb511e1 and 89ee605.

📒 Files selected for processing (2)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/prisma/schema.prisma

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Aug 21, 2025
@graphite-app graphite-app bot requested a review from a team August 21, 2025 03:14
@shaun-ak shaun-ak marked this pull request as draft August 21, 2025 03:14
@github-actions github-actions bot added consumer Medium priority Created by Linear-GitHub Sync organizations area: organizations, orgs ❗️ migrations contains migration files and removed Medium priority Created by Linear-GitHub Sync organizations area: organizations, orgs consumer labels Aug 21, 2025
@graphite-app
Copy link

graphite-app bot commented Aug 21, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/21/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (08/21/25)

1 label was added to this PR based on Keith Williams's automation.

@dosubot dosubot bot added the ✨ feature New feature or request label Aug 21, 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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/server/routers/viewer/organizations/getUser.handler.ts (1)

61-79: Ensure organization-level bookingLimits are returned explicitly (not per-team limits)

• In packages/trpc/server/routers/viewer/organizations/getUser.handler.ts:

  • Update the existing prisma.membership.findFirst({ …, select: { … } }) call to also select bookingLimits from the organization membership.
  • In the prisma.membership.findMany({ …, select: { … } }) call for sub-teams, remove bookingLimits from the selection so callers can’t accidentally use per-team limits.
  • When constructing the foundUser payload:
    • Remove bookingLimits from each entry in teams.map(...).
    • Add a new top-level property orgBookingLimits: membership.bookingLimits.

• Follow-up: Update any UI or consumer code (e.g. EditUserSheet) to consume orgBookingLimits instead of teams?.[0]?.bookingLimits.

Would you like me to also patch the EditUserSheet to use orgBookingLimits and drop its reliance on per-team limits?

🧹 Nitpick comments (18)
apps/web/public/static/locales/en/common.json (1)

3475-3476: String added in correct place; consider punctuation consistency.

The key is added above the sentinel to avoid conflicts. Consider ending the sentence with a period to match many other description strings in this file.

Apply this minimal tweak if you want strict consistency:

-  "booking_limits_description": "These limits apply to all bookings for this member across personal and team event types",
+  "booking_limits_description": "These limits apply to all bookings for this member across personal and team event types.",
packages/trpc/server/routers/viewer/organizations/getUser.handler.ts (2)

43-47: Filter profiles by organization to avoid picking an arbitrary username.

You return requestedUser.profiles[0]?.username || requestedUser.username, but profiles is not filtered by organizationId. If a user belongs to multiple orgs, you may grab a profile for a different org.

Apply this diff to scope profiles to the current org:

-        profiles: {
-          select: {
-            username: true,
-          },
-        },
+        profiles: {
+          where: { organizationId: currentUser.organizationId },
+          select: { username: true },
+        },

100-100: Prefer named exports only for handlers.

We already have a named export for getUserHandler. Dropping the default export improves tree-shaking and refactors.

-export default getUserHandler;
+// Prefer named export only
packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx (2)

11-16: Early return is fine; consider i18n’d empty-state instead of null (optional).

Currently returns null when limits are absent. If UX wants explicit “No limits set” messaging, we can render a subtle label instead. Fine to keep as-is.


25-37: Stable ordering of intervals for predictable UI.

Object.entries iteration order can vary by insertion. For consistent display (day → week → month → year), sort keys explicitly.

Apply this tweak:

-          {Object.entries(bookingLimits).map(([key, value]) => (
+          {(["day","week","month","year"] as const)
+            .filter((k) => k in bookingLimits)
+            .map((key) => {
+              const value = bookingLimits[key]!;
+              return (
                 <div key={key} className="flex items-center justify-between">
                   <div className="flex items-center space-x-3">
                     <span className="text-default font-medium">{value}</span>
                     <span className="text-subtle">
-                  {intervalLimitKeyToUnit(key as keyof typeof intervalLimitKeyToUnit)}
+                      {intervalLimitKeyToUnit(key)}
                     </span>
                   </div>
                 </div>
-          ))}
+              );
+            })}
packages/features/users/components/UserTable/EditSheet/utils.ts (1)

3-6: Type the return for safer interop and future refactors

Explicitly annotate the return type to the zod-inferred shape (or undefined) to help downstream callers and prevent accidental widening to any.

-import { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
+import { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
+import type { z } from "zod";
+type IntervalLimits = z.infer<typeof intervalLimitsType>;

-export function validateBookingLimits(data: unknown) {
+export function validateBookingLimits(
+  data: unknown
+): IntervalLimits | undefined {
   const result = intervalLimitsType.safeParse(data);
   return result.success ? result.data : undefined;
 }
packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx (3)

83-83: Typo in className: likely meant border-subtle

The class "border-sublte" looks like a typo and won’t apply the intended style.

-                  <div className="border-sublte bg-default w-full rounded-xl border p-4">
+                  <div className="border-subtle bg-default w-full rounded-xl border p-4">

89-93: Localize embedded strings per guidelines

Direct strings should use t(). Convert "Nameless User", "This user does not have a bio...", and "Cal" to i18n keys.

-                    <h2 className="text-emphasis font-sans text-2xl font-semibold">
-                      {loadedUser?.name || "Nameless User"}
-                    </h2>
+                    <h2 className="text-emphasis font-sans text-2xl font-semibold">
+                      {loadedUser?.name || t("nameless_user")}
+                    </h2>
-                    <p className="text-subtle max-h-[3em] overflow-hidden text-ellipsis text-sm font-normal">
-                      {loadedUser?.bio || "This user does not have a bio..."}
-                    </p>
+                    <p className="text-subtle max-h-[3em] overflow-hidden text-ellipsis text-sm font-normal">
+                      {loadedUser?.bio || t("no_bio")}
+                    </p>
...
-                    <DisplayInfo
-                      label="Cal"
+                    <DisplayInfo
+                      label={t("cal")}
                       value={removeProtocol(
                         `${orgBranding?.fullDomain ?? WEBAPP_URL}/${loadedUser?.username}`
                       )}

Follow-up: please ensure the new keys exist in apps/web/public/static/locales/*/common.json. If not, I can add them.

Also applies to: 100-101


145-145: Redundant fallback

validateBookingLimits already returns undefined on failure; the trailing “|| undefined” is redundant.

packages/trpc/server/routers/viewer/organizations/_router.tsx (1)

102-107: Harden authorization at the router: use authedOrgAdminProcedure and named handler import

You already recheck in the handler, but gating here reduces attack surface and avoids running handler code for ineligible users. Also, prefer named exports per repo guideline to avoid default exports.

-import { ZUpdateMembershipBookingLimitsInputSchema } from "./updateMembershipBookingLimits.schema";
+import { ZUpdateMembershipBookingLimitsInputSchema } from "./updateMembershipBookingLimits.schema";

...
-  updateMembershipBookingLimits: authedProcedure
+  updateMembershipBookingLimits: authedOrgAdminProcedure
     .input(ZUpdateMembershipBookingLimitsInputSchema)
     .mutation(async (opts) => {
-      const { default: handler } = await import("./updateMembershipBookingLimits.handler");
-      return handler(opts);
+      const { updateMembershipBookingLimitsHandler } = await import("./updateMembershipBookingLimits.handler");
+      return updateMembershipBookingLimitsHandler(opts);
     }),
packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts (1)

5-9: Allow clearing limits and restrict unknown keys

The column is Json?; support clearing by accepting null/undefined, and prevent stray keys via strict(). If intervalLimitsType isn’t strict, wrap it.

-import { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
+import { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";

 export const ZUpdateMembershipBookingLimitsInputSchema = z.object({
   userId: z.number(),
   teamId: z.number(),
-  bookingLimits: intervalLimitsType,
+  bookingLimits: intervalLimitsType.strict().nullish(),
 });

Follow-up: update the handler to set bookingLimits to null when input.bookingLimits is null/undefined.

packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts (5)

31-37: Select only needed fields in Prisma queries

Per guidelines, avoid fetching entire rows. Here you only need role to authorize.

-  const userMembership = await prisma.membership.findFirst({
-    where: {
-      userId: user.id,
-      teamId: organizationId,
-      accepted: true,
-    },
-  });
+  const userMembership = await prisma.membership.findFirst({
+    where: { userId: user.id, teamId: organizationId, accepted: true },
+    select: { role: true },
+  });

58-65: Select only the membership id for the update

You only need id here; fetch minimal data.

-  const membership = await prisma.membership.findFirst({
-    where: {
-      userId: input.userId,
-      teamId: input.teamId,
-      accepted: true,
-    },
-  });
+  const membership = await prisma.membership.findFirst({
+    where: { userId: input.userId, teamId: input.teamId, accepted: true },
+    select: { id: true },
+  });

71-79: Handle clearing limits and keep types precise

If the schema accepts nullish, set bookingLimits to null when absent. Also, avoid casting; Prisma accepts JsonValue directly.

-  await prisma.membership.update({
+  await prisma.membership.update({
     where: {
       id: membership.id,
     },
     data: {
-      bookingLimits: input.bookingLimits as Prisma.InputJsonValue,
+      bookingLimits: input.bookingLimits ?? null,
     },
   });

86-86: Prefer named export only; drop default export

Repository guidelines recommend named exports for clearer imports and tree-shaking. The router can import the named handler.

-export default updateMembershipBookingLimitsHandler;
+// no default export

25-44: Authorization is good; consider early exit with authedOrgAdminProcedure

You correctly enforce admin/owner here. For defense-in-depth and faster failures, also gate at the router with authedOrgAdminProcedure. See router comment for diff.

packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx (2)

9-14: Good reuse, but consider de-coupling from EventTypes domain

Reusing IntervalLimitsManager and the intervalLimitsType is pragmatic. To reduce cross-domain coupling and path churn, consider re-exporting these in a shared, domain-agnostic module (for example, features/limits) and consuming from there.


174-184: Mutation wiring and cache invalidations look fine; minor nit on consolidation

The success and error handlers are correct, and invalidations target the right queries. Optionally, consolidate invalidations with utils.invalidate().then(...) or batch where available to reduce network chatter.

📜 Review details

Configuration used: .coderabbit.yaml
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.

📥 Commits

Reviewing files that changed from the base of the PR and between b9aee3b and e2ed5f3.

📒 Files selected for processing (11)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx (7 hunks)
  • packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx (3 hunks)
  • packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx (1 hunks)
  • packages/features/users/components/UserTable/EditSheet/utils.ts (1 hunks)
  • packages/prisma/migrations/20250816142522_add_booking_limits_to_membership/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/_router.tsx (2 hunks)
  • packages/trpc/server/routers/viewer/organizations/getUser.handler.ts (2 hunks)
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

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

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/users/components/UserTable/EditSheet/utils.ts
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts
  • packages/trpc/server/routers/viewer/organizations/getUser.handler.ts
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts
**/*.{ts,tsx}

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

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/users/components/UserTable/EditSheet/utils.ts
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts
  • packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/getUser.handler.ts
  • packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx
  • packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit Configuration File

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/users/components/UserTable/EditSheet/utils.ts
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts
  • packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/getUser.handler.ts
  • packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx
  • packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx
  • packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts
**/*.tsx

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

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx
  • packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx
🧬 Code Graph Analysis (5)
packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts (1)
packages/prisma/zod-utils.ts (1)
  • intervalLimitsType (229-229)
packages/trpc/server/routers/viewer/organizations/_router.tsx (1)
packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts (1)
  • ZUpdateMembershipBookingLimitsInputSchema (5-9)
packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx (3)
apps/web/app/_trpc/trpc.ts (1)
  • trpc (7-7)
packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx (1)
  • UserBookingLimitsDisplay (11-41)
packages/features/users/components/UserTable/EditSheet/utils.ts (1)
  • validateBookingLimits (3-6)
packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx (2)
packages/features/users/components/UserTable/EditSheet/utils.ts (1)
  • validateBookingLimits (3-6)
packages/features/eventtypes/components/tabs/limits/EventLimitsTab.tsx (1)
  • IntervalLimitsManager (909-1014)
packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.handler.ts (1)
packages/trpc/server/routers/viewer/organizations/updateMembershipBookingLimits.schema.ts (1)
  • TUpdateMembershipBookingLimitsInputSchema (11-13)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (6)
packages/prisma/schema.prisma (1)

661-663: Modeling looks right; aligns with existing pattern for Team/EventType.

Adding bookingLimits Json? on Membership with the zod annotation keeps parity with Team.bookingLimits/EventType.bookingLimits. Optional + no default is a safe migration and avoids heavy table locking.

Please confirm that the zod import intervalLimitsType already supports the same shape you expect for membership-level limits (e.g., { day, week, month, year } ascending validation). If not, I can help update the schema and validator.

packages/prisma/migrations/20250816142522_add_booking_limits_to_membership/migration.sql (1)

1-2: Safe, minimal migration.

Nullable JSONB without default avoids long rewrites. No further changes needed.

packages/features/users/components/UserTable/EditSheet/UserBookingLimitsDisplay.tsx (1)

21-23: Good localization usage.

Uses t() for title and description; string key added to en locale. LGTM.

packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx (3)

104-109: RBAC-derived UI gating: LGTM

The belongsToOrg and isOrgAdminOrOwner checks are clear and match the intended visibility constraints.


290-292: Good i18n coverage for the new UI copy

Using t("booking_limits") and t("booking_limits_description") aligns with our localization guidelines.


68-69: Empty object is valid for intervalLimitsType

The intervalLimitsType is defined as

z.object({
  PER_DAY: z.number().optional(),
  PER_WEEK: z.number().optional(),
  PER_MONTH: z.number().optional(),
  PER_YEAR: z.number().optional(),
}).nullable()

so all interval keys are optional and the object itself may be null. In Zod, an object schema with only optional properties accepts {} without error (e.g. z.object({ age: z.number().optional() }).parse({}) succeeds) (v4.zod.dev). Chaining .optional() on that schema also allows undefined as a valid value, so leaving bookingLimits undefined (or even defaulting it to {}) will never cause the resolver to fail.

No change needed to the bookingLimits: intervalLimitsType.optional() usage.

Likely an incorrect or invalid review comment.

@github-actions github-actions bot added consumer Medium priority Created by Linear-GitHub Sync organizations area: organizations, orgs labels Aug 21, 2025
devin-ai-integration bot added a commit that referenced this pull request Aug 28, 2025
…mits

- Import validateIntervalLimitOrder from intervalLimits library
- Add validation logic similar to team booking limits
- Ensure booking limits are in ascending order (day ≤ week ≤ month ≤ year)
- Throw BAD_REQUEST error with descriptive message for invalid limits

This incorporates the key improvement from PR #23233 to maintain consistency
with existing team booking limits validation patterns.

Co-Authored-By: Devanshu Sharma <devanshusharma658@gmail.com>
@shaun-ak shaun-ak marked this pull request as ready for review September 21, 2025 08:41
@shaun-ak shaun-ak requested a review from a team as a code owner September 21, 2025 08:41
@shaun-ak shaun-ak requested a review from a team as a code owner October 5, 2025 07:50
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/v1/pages/api/memberships/[id]/_patch.ts (1)

61-81: Critical: Missing permission check for bookingLimits field.

The checkPermissions function only validates admin/owner status when modifying the role field (lines 74-80). However, according to the PR objectives, only organization admins/owners should be able to set booking limits for members. A regular member could potentially modify their own bookingLimits by sending a PATCH request that doesn't include role.

Compare with the POST handler in _post.ts (lines 54-57), which requires admin/owner status for all membership creation, properly protecting the bookingLimits field.

Add a permission check for bookingLimits:

   // Only team OWNERS and ADMINS can modify `role`
   if ("role" in data) {
     const membership = await prisma.membership.findFirst({
       where: { userId, teamId, role: { in: ["ADMIN", "OWNER"] } },
     });
     if (!membership || (membership.role !== "OWNER" && req.body.role === "OWNER"))
       throw new HttpError({ statusCode: 403, message: "Forbidden" });
   }
+  // Only team OWNERS and ADMINS can modify `bookingLimits`
+  if ("bookingLimits" in data) {
+    const membership = await prisma.membership.findFirst({
+      where: { userId, teamId, role: { in: ["ADMIN", "OWNER"] } },
+    });
+    if (!membership)
+      throw new HttpError({ statusCode: 403, message: "Only admins and owners can set booking limits" });
+  }
 }
🧹 Nitpick comments (1)
apps/api/v1/pages/api/memberships/_post.ts (1)

27-31: Consider inlining the normalization for consistency.

The normalization logic is correct and handles Prisma's preference for undefined over null. However, the PATCH handler (in _patch.ts line 50) performs this normalization inline within the args construction, while here you create an intermediate normalizedData variable. Consider inlining for consistency:

-  // Ensure bookingLimits is undefined instead of null
-  const normalizedData = {
-    ...data,
-    bookingLimits: data.bookingLimits === null ? undefined : data.bookingLimits,
-  };
   const args: Prisma.MembershipCreateArgs = {
-    data: normalizedData,
+    data: {
+      ...data,
+      bookingLimits: data.bookingLimits === null ? undefined : data.bookingLimits,
+    },
   };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e2ed5f3 and f33e997.

📒 Files selected for processing (2)
  • apps/api/v1/pages/api/memberships/[id]/_patch.ts (1 hunks)
  • apps/api/v1/pages/api/memberships/_post.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/api/v1/pages/api/memberships/_post.ts
  • apps/api/v1/pages/api/memberships/[id]/_patch.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/api/v1/pages/api/memberships/_post.ts
  • apps/api/v1/pages/api/memberships/[id]/_patch.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/api/v1/pages/api/memberships/_post.ts
  • apps/api/v1/pages/api/memberships/[id]/_patch.ts
⏰ Context from checks skipped due to timeout of 180000ms. 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 (2)
apps/api/v1/pages/api/memberships/_post.ts (1)

32-38: LGTM!

The args construction correctly uses the normalized data, and the permission check appropriately ensures only team admins/owners can create memberships with booking limits.

apps/api/v1/pages/api/memberships/[id]/_patch.ts (1)

46-53: LGTM with a note on style consistency.

The inline normalization correctly converts null to undefined for Prisma compatibility. Note that the POST handler uses a separate normalizedData variable (lines 27-31 in _post.ts), while here you normalize inline. Both approaches work, but consistency would improve maintainability.

where: { userId_teamId },
data: {
...data,
bookingLimits: data.bookingLimits === null ? undefined : data.bookingLimits,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For null values, we set it to undefined for type matching.

// Ensure bookingLimits is undefined instead of null
const normalizedData = {
...data,
bookingLimits: data.bookingLimits === null ? undefined : data.bookingLimits,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here

@shaun-ak
Copy link
Contributor Author

shaun-ak commented Oct 5, 2025

video.proof.1.mp4

Copy link
Contributor

@pallava-joshi pallava-joshi left a comment

Choose a reason for hiding this comment

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

hey can you please fix the merge conflicts.

@pallava-joshi pallava-joshi marked this pull request as draft January 2, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Created by Linear-GitHub Sync consumer ✨ feature New feature or request Medium priority Created by Linear-GitHub Sync ❗️ migrations contains migration files organizations area: organizations, orgs size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Member based limits controlled by org admins

2 participants