feat: convert InsightsBookingService to use Prisma.sql raw queries#7
feat: convert InsightsBookingService to use Prisma.sql raw queries#7akshayutture-augment wants to merge 1 commit into
Conversation
…22345) * fix: use raw query at InsightsBookingService * feat: convert InsightsBookingService to use Prisma.sql raw queries - Convert auth conditions from Prisma object notation to Prisma.sql - Convert filter conditions from Prisma object notation to Prisma.sql - Update return types from Prisma.BookingTimeStatusDenormalizedWhereInput to Prisma.Sql - Fix type error in isOrgOwnerOrAdmin method - Follow same pattern as InsightsRoutingService conversion Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * feat: convert InsightsBookingService to use Prisma.sql raw queries - Convert auth conditions from Prisma object notation to Prisma.sql - Convert filter conditions from Prisma object notation to Prisma.sql - Update return types from Prisma.BookingTimeStatusDenormalizedWhereInput to Prisma.Sql - Fix type error in isOrgOwnerOrAdmin method - Follow same pattern as InsightsRoutingService conversion Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: update InsightsBookingService integration tests for Prisma.sql format - Replace Prisma object notation expectations with Prisma.sql template literals - Add NOTHING_CONDITION constant for consistency with InsightsRoutingService - Update all test cases to use direct Prisma.sql comparisons - Use $queryRaw for actual database integration testing - Follow same testing patterns as InsightsRoutingService Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: exclude intentionally skipped jobs from required CI check failure - Remove 'skipped' from failure condition in pr.yml and all-checks.yml - Allow E2E jobs to be skipped without failing the required check - Only actual failures and cancelled jobs will cause required check to fail Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix tests * Revert "fix: exclude intentionally skipped jobs from required CI check failure" This reverts commit 6ff44fc9a8f14ad657f7bba7c2e454e192b66c8f. * clean up tests * address feedback --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@coderabbitai review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughRefactors InsightsBookingService to replace Prisma object-based where conditions with raw SQL fragments. Introduces new public options type with constructor validation. All condition builders rewritten to return Prisma.sql expressions. Corresponding test suite updated to validate SQL-based conditions and utilize new getBaseConditions method. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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)
packages/lib/server/service/insightsBooking.ts (1)
145-175: Possible under-authorization when org has no child teamsIn
buildOrgAuthorizationCondition,userIdsFromOrgis only populated whenteamsFromOrg.length > 0:const userIdsFromOrg = teamsFromOrg.length > 0 ? (await MembershipRepository.findAllByTeamIds({ teamIds, select: { userId: true } })).map( (m) => m.userId ) : [];But
teamIdsalways includesoptions.orgIditself. If an organization has no child teams yet (only the org “team”), this branch will skip the membership lookup entirely, souserIdsFromOrgis empty and you never add the"userId" = ANY(...) AND "isTeamBooking" = falsecondition. That means non-team bookings for org members would be excluded for such orgs.Unless you can guarantee that every org always has at least one child team, this is a real edge-case bug.
You can fix it by always querying memberships using
teamIdsand then deduplicating:- const userIdsFromOrg = - teamsFromOrg.length > 0 - ? (await MembershipRepository.findAllByTeamIds({ teamIds, select: { userId: true } })).map( - (m) => m.userId - ) - : []; + const userIdsFromOrg = ( + await MembershipRepository.findAllByTeamIds({ + teamIds, + select: { userId: true }, + }) + ).map((m) => m.userId);The later
if (userIdsFromOrg.length > 0)guard still ensures you only add the userId condition when there are actual memberships.
🧹 Nitpick comments (2)
packages/lib/server/service/insightsBooking.ts (2)
29-36: TightenInsightsBookingServicePublicOptionsto mirror the zod discriminated unionRight now
InsightsBookingServicePublicOptionsallows invalid combinations likescope: "team"withoutteamIdat compile time; these are only rejected at runtime and then silently treated asoptions = null, yieldingNOTHING_CONDITION. That can hide misconfigurations and be hard to debug.Consider making the public options type a discriminated union matching
insightsBookingServiceOptionsSchemaso invalid shapes are rejected by TypeScript instead of only by zod:-export type InsightsBookingServicePublicOptions = { - scope: "user" | "org" | "team"; - userId: number; - orgId: number; - teamId?: number; -}; +export type InsightsBookingServicePublicOptions = + | { + scope: "user"; + userId: number; + orgId: number; + } + | { + scope: "org"; + userId: number; + orgId: number; + } + | { + scope: "team"; + userId: number; + orgId: number; + teamId: number; + };This keeps the runtime validation but also gives callers stronger static guarantees.
Also applies to: 52-66
68-81: SimplifygetBaseConditionssince auth conditions are always present
getAuthorizationConditions()always returns aPrisma.Sql(either real conditions orNOTHING_CONDITION), soauthConditionsis never falsy. The lastelsebranch is therefore unreachable, and the secondelse if (authConditions)is redundant.You can simplify the control flow to make the intent clearer:
- async getBaseConditions(): Promise<Prisma.Sql> { - const authConditions = await this.getAuthorizationConditions(); - const filterConditions = await this.getFilterConditions(); - - if (authConditions && filterConditions) { - return Prisma.sql`(${authConditions}) AND (${filterConditions})`; - } else if (authConditions) { - return authConditions; - } else if (filterConditions) { - return filterConditions; - } else { - return NOTHING_CONDITION; - } - } + async getBaseConditions(): Promise<Prisma.Sql> { + const authConditions = await this.getAuthorizationConditions(); + const filterConditions = await this.getFilterConditions(); + + if (!filterConditions) { + return authConditions; + } + + return Prisma.sql`(${authConditions}) AND (${filterConditions})`; + }Behavior stays the same while reducing branches.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/lib/server/service/__tests__/insightsBooking.integration-test.ts(10 hunks)packages/lib/server/service/insightsBooking.ts(6 hunks)
🔇 Additional comments (7)
packages/lib/server/service/insightsBooking.ts (4)
97-123: Filter condition builder is correct and safely parameterizedThe
buildFilterConditionsimplementation correctly:
- Returns
nullwhen no filters are provided or no conditions are added.- Builds
eventTypeIdconditions aseventTypeId = ? OR eventParentId = ?.- Builds
memberUserIdconditions as"userId" = ?.- Joins multiple filters with
ANDwhile wrapping each fragment in parentheses.All values are injected via
Prisma.sqltemplate parameters, so you get proper parameter binding and avoid SQL injection pitfalls.
125-143: Authorization builder correctly normalizes invalid/unauthorized cases toNOTHING_CONDITION
buildAuthorizationConditionscentralizes the checks for:
- Invalid options (failed zod parse ⇒
this.optionsisnull).- Non-owner/non-admin users (via
isOrgOwnerOrAdmin).- Scope-specific SQL fragments for
"user","org", and"team".In all non-authorized cases you consistently return
NOTHING_CONDITION, which keeps downstream combining logic simple and safe. This looks good.
177-208: Team-scope authorization SQL composition looks correctFor team scope:
- You first assert that the given team is actually a child of the org (
findByIdAndParentId), falling back toNOTHING_CONDITIONotherwise.- You include team bookings for that team (
"teamId" = ? AND "isTeamBooking" = true).- You include non-team bookings for users belonging to that team (
"userId" = ANY(?) AND "isTeamBooking" = false).- You OR these clauses together with proper parentheses.
The use of
Prisma.sqlkeeps parameters bound safely, and the earlyNOTHING_CONDITIONwhenchildTeamOfOrgis missing protects against cross-org access.
210-218:isOrgOwnerOrAdminsimplification is clear and preciseThe role check now directly compares
membership.roletoMembershipRole.OWNERandMembershipRole.ADMINwith anacceptedguard, which is simple and unambiguous. Returning a boolean viaBoolean(...)is fine here and keeps the intent obvious.packages/lib/server/service/__tests__/insightsBooking.integration-test.ts (3)
2-11: UsingPrisma.sql+NOTHING_CONDITIONin tests aligns well with the service behaviorImporting
Prismaand definingNOTHING_CONDITIONasPrisma.sql\1=0`` lets the tests assert exactly the same SQL fragments the service produces, including for invalid options and non-owner/admin users.The expectations:
expect(conditions).toEqual(NOTHING_CONDITION);are a good way to ensure the service normalizes “no access” correctly at the SQL level, not just structurally.
Also applies to: 210-211, 245-246
273-274: Authorization and filter condition expectations thoroughly cover SQL outputThe updated expectations that compare against
Prisma.sqlfragments for:
- User scope:
"userId" = ? AND "teamId" IS NULL.- Team scope: OR between team bookings and team-user non-team bookings using
ANY([...]).- Org scope: OR between
teamId = ANY([...])anduserId = ANY([...]).- Filter conditions for
eventTypeId,memberUserId, and their combination.provide strong coverage that the refactored service builds the right SQL. This is especially helpful now that the implementation has moved away from object-shaped where clauses.
Also applies to: 295-299, 332-343, 384-386, 407-407, 429-431
437-492:getBaseConditionsintegration test is a solid end-to-end verificationThe new
getBaseConditionstest:
- Creates a user-scoped event type and booking.
- Instantiates
InsightsBookingServicewith matching auth + filter options.- Uses
baseConditionsdirectly in a$queryRawagainst"BookingTimeStatusDenormalized"and asserts only the expected booking ID is returned.This is an effective end-to-end check that auth and filter fragments are combined correctly and that they behave as intended when applied to the actual denormalized view.
Test 5
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.
Replicated from ai-code-review-evaluation/cal.com-coderabbit#5