diff --git a/apps/web/playwright/bookings-list.e2e.ts b/apps/web/playwright/bookings-list.e2e.ts index 52404b8718..de5ccb55a3 100644 --- a/apps/web/playwright/bookings-list.e2e.ts +++ b/apps/web/playwright/bookings-list.e2e.ts @@ -1,15 +1,9 @@ import { expect } from "@playwright/test"; -import { randomString } from "@calcom/lib/random"; -import slugify from "@calcom/lib/slugify"; -import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/client"; -import { SchedulingType } from "@calcom/prisma/enums"; -import { createTeamEventType } from "./fixtures/users"; import type { Fixtures } from "./lib/fixtures"; import { test } from "./lib/fixtures"; -import { assertBookingVisibleFor, bookTeamEvent, bookUserEvent } from "./lib/testUtils"; test.afterEach(({ users }) => users.deleteAll()); @@ -67,137 +61,6 @@ test.describe("Bookings", () => { secondUpcomingBooking.locator(`text=${bookingWhereFirstUserIsOrganizer!.title}`) ).toBeVisible(); }); - test("collective eventType booking should be visible to team admin", async ({ page, users }) => { - const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users); - - const { team: team1 } = await owner1.getFirstTeamMembership(); - const scenario = { - schedulingType: SchedulingType.COLLECTIVE, - teamEventTitle: `collective-team-event`, - teamEventSlug: slugify(`collective-team-event-${randomString(5)}`), - }; - - const eventType = await createTeamEventType(owner1, team1, scenario); - const { id: eventId, slug: evnetSlug } = eventType; - - await prisma.host.createMany({ - data: [ - { - userId: commonUser.id, - eventTypeId: eventId, - isFixed: true, - }, - { - userId: team1_teammate1.id, - eventTypeId: eventId, - isFixed: true, - }, - ], - }); - - // to test if booking is visible to team admin/owner even if he is not part of the booking - await prisma.host.deleteMany({ - where: { - userId: owner1.id, - eventTypeId: eventId, - }, - }); - - await bookTeamEvent(page, team1, eventType); - - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(owner1, page, eventType, true); - // booking should not be visible for other team host - await assertBookingVisibleFor(owner2, page, eventType, false); - // booking should be visible for commonUser as he is part of the booking - await assertBookingVisibleFor(commonUser, page, eventType, true); - // booking should be visible for team1_teammate1 as he is part of the booking - await assertBookingVisibleFor(team1_teammate1, page, eventType, true); - }); - test("round-robin eventType booking should be visible to team admin", async ({ page, users }) => { - const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users); - - const { team: team1 } = await owner1.getFirstTeamMembership(); - const scenario = { - schedulingType: SchedulingType.ROUND_ROBIN, - teamEventTitle: `round-robin-team-event`, - teamEventSlug: slugify(`round-robin-team-event-${randomString(5)}`), - }; - - const eventType = await createTeamEventType(owner1, team1, scenario); - const { id: eventId } = eventType; - - await prisma.host.createMany({ - data: [ - { - userId: commonUser.id, - eventTypeId: eventId, - }, - { - userId: team1_teammate1.id, - eventTypeId: eventId, - }, - ], - }); - - await prisma.host.deleteMany({ - where: { - userId: owner1.id, - eventTypeId: eventId, - }, - }); - - await bookTeamEvent(page, team1, eventType); - - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(owner1, page, eventType, true); - // bookings should not be visible for other team host - await assertBookingVisibleFor(owner2, page, eventType, false); - }); - test("managed eventType booking should be visible to team admin", async ({ page, users }) => { - const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users); - - const { team: team1 } = await owner1.getFirstTeamMembership(); - - await owner1.apiLogin(); - await page.goto(`/event-types?dialog=new&eventPage=team%2F${team1.slug}&teamId=${team1.id}`); - await page.getByTestId("managed-event-type").click(); - await page.getByTestId("event-type-quick-chat").click(); - await page.getByTestId("event-type-quick-chat").fill("managed-event"); - await page.getByRole("button", { name: "Continue" }).click(); - await page.getByTestId("vertical-tab-assignment").click(); - - await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("commonUser"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("team1_teammate1"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter"); - await page.getByTestId("update-eventtype").click(); - - const eventType = await owner1.getFirstTeamEvent(team1.id); - - await bookUserEvent(page, commonUser, eventType); - - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(owner1, page, eventType, true); - // booking should not be visible for other team host - await assertBookingVisibleFor(owner2, page, eventType, false); - // booking should be visible for commonUser as he is part of the booking - await assertBookingVisibleFor(commonUser, page, eventType, true); - // booking should not be visible for team1_teammate1 as we booked for commonUser - await assertBookingVisibleFor(team1_teammate1, page, eventType, false); - }); - test("individual team members booking should not be visible to team admin", async ({ page, users }) => { - const { owner1, owner2, commonUser, team1_teammate1 } = await createTeams(users); - - const eventType = await commonUser.getFirstEventAsOwner(); - await bookUserEvent(page, commonUser, eventType); - // non-team bookings should not be visible for the team host - await assertBookingVisibleFor(owner1, page, eventType, false); - // non-team bookings should not be visible for other team host - await assertBookingVisibleFor(owner2, page, eventType, false); - // booking should be visible for commonUser as he is part of the booking - await assertBookingVisibleFor(commonUser, page, eventType, true); - }); }); }); @@ -248,75 +111,3 @@ async function createBooking({ }, }); } - -const createTeams = async (userFixture: Fixtures["users"]) => { - const owner1 = await userFixture.create({ name: "team-owner-1" }); - const owner2 = await userFixture.create({ name: "team-owner-2" }); - const commonUser = await userFixture.create({ name: "commonUser" }); - const team1_teammate1 = await userFixture.create({ name: "team1_teammate1" }); - const team2_teammate1 = await userFixture.create({ name: "team2_teammate1" }); - const teamOne = await prisma.team.create({ - data: { - name: "bookings-test-team-1", - slug: slugify(`bookings-test-team-2-${randomString(5)}`), - }, - }); - - const teamTwo = await prisma.team.create({ - data: { - name: "bookings-test-team-2", - slug: slugify(`bookings-test-team-2-${randomString(5)}`), - }, - }); - - // create memberships - await prisma.membership.createMany({ - data: [ - { - userId: owner1.id, - teamId: teamOne.id, - accepted: true, - role: "OWNER", - }, - { - userId: commonUser.id, - teamId: teamOne.id, - accepted: true, - role: "MEMBER", - }, - { - userId: team1_teammate1.id, - teamId: teamOne.id, - accepted: true, - role: "MEMBER", - }, - { - userId: owner2.id, - teamId: teamTwo.id, - accepted: true, - role: "OWNER", - }, - { - userId: commonUser.id, - teamId: teamTwo.id, - accepted: true, - role: "MEMBER", - }, - { - userId: team2_teammate1.id, - teamId: teamTwo.id, - accepted: true, - role: "MEMBER", - }, - ], - }); - return { - owner1, - owner2, - commonUser, - team1_teammate1, - team2_teammate1, - teamOne, - teamTwo, - }; -}; diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts index 4e7badbaa3..e9b368c58f 100644 --- a/apps/web/playwright/fixtures/users.ts +++ b/apps/web/playwright/fixtures/users.ts @@ -58,7 +58,7 @@ const createTeamWorkflow = async (user: { id: number }, team: { id: number }) => }); }; -export const createTeamEventType = async ( +const createTeamEventType = async ( user: { id: number }, team: { id: number }, scenario?: { diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 1968ad66fa..01720d5947 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -1,5 +1,4 @@ import type { Frame, Page } from "@playwright/test"; -import type { APIResponse } from "@playwright/test"; import { expect } from "@playwright/test"; import { createHash } from "crypto"; import EventEmitter from "events"; @@ -372,53 +371,3 @@ export async function doOnOrgDomain( // When App directory is there, this is the 404 page text. We should work on fixing the 404 page as it changed due to app directory. export const NotFoundPageTextAppDir = "This page does not exist."; // export const NotFoundPageText = "ERROR 404"; - -export type Team = { - id: number; - slug: string | null; - name: string; -}; - -export type EventType = { - id: number; - title: string; - slug: string; -}; -export type User = { - id: number; - name: string | null; - username: string | null; -}; - -export async function bookTeamEvent(page: Page, team: Team, eventType: EventType) { - await page.goto(`/team/${team.slug}/${eventType.slug}/`); - await bookEventOnThisPage(page); - const bookingTitle = `${eventType.title} between ${team.name} and ${testName}`; - await assertBookingIsCorrect(page, bookingTitle); -} -export async function bookUserEvent(page: Page, user: User, eventType: EventType) { - await page.goto(`/${user.username}/${eventType.slug}/`); - await bookEventOnThisPage(page); - const bookingTitle = `${eventType.title} between ${user.name} and ${testName}`; - await assertBookingIsCorrect(page, bookingTitle); -} - -export async function assertBookingIsCorrect(page: Page, bookingTitle: string) { - await expect(page.locator("[data-testid=booking-title]")).toHaveText(bookingTitle); - // The booker should be in the attendee list - await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); -} - -export const assertBookingVisibleFor = async ( - user: { apiLogin: () => Promise }, - pageFixture: Fixtures["page"], - eventType: EventType, - shouldBeVisible: boolean -) => { - await user.apiLogin(); - await pageFixture.goto(`/bookings/upcoming`); - const upcomingBookingList = pageFixture.locator('[data-testid="booking-item"]'); - shouldBeVisible - ? await expect(upcomingBookingList.locator(`text=${eventType.title}`)).toBeVisible() - : await expect(upcomingBookingList.locator(`text=${eventType.title}`)).toBeHidden(); -}; diff --git a/apps/web/playwright/organization/booking-list.e2e.ts b/apps/web/playwright/organization/booking-list.e2e.ts deleted file mode 100644 index 05cefbe0a3..0000000000 --- a/apps/web/playwright/organization/booking-list.e2e.ts +++ /dev/null @@ -1,391 +0,0 @@ -import type { Page } from "@playwright/test"; -import { expect } from "@playwright/test"; - -import { randomString } from "@calcom/lib/random"; -import slugify from "@calcom/lib/slugify"; -import prisma from "@calcom/prisma"; -import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; - -import { createTeamEventType } from "../fixtures/users"; -import { test } from "../lib/fixtures"; -import type { Fixtures } from "../lib/fixtures"; -import { - assertBookingVisibleFor, - bookTimeSlot, - doOnOrgDomain, - NotFoundPageTextAppDir, - selectFirstAvailableTimeSlotNextMonth, - testName, -} from "../lib/testUtils"; - -test.describe("Bookings list for organizations", () => { - test.afterEach(({ orgs, users }) => { - orgs.deleteAll(); - users.deleteAll(); - }); - - test.describe("Team Event", () => { - test("Collective EventType booking list", async ({ page, users, orgs }) => { - const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } = - await createOrg(orgs, users); - - const { team: team1 } = await team1owner.getFirstTeamMembership(); - const scenario = { - schedulingType: SchedulingType.COLLECTIVE, - teamEventTitle: `collective-team-event`, - teamEventSlug: slugify(`collective-team-event-${randomString(5)}`), - }; - const eventType = await createTeamEventType(team1owner, team1, scenario); - const { id: eventId, slug: evnetSlug } = eventType; - - await prisma.host.createMany({ - data: [ - { - userId: commonUser.id, - eventTypeId: eventId, - isFixed: true, - }, - { - userId: team1_teammate1.id, - eventTypeId: eventId, - isFixed: true, - }, - ], - }); - - // to test if booking is visible to team admin/owner even if he is not part of the booking - await prisma.host.deleteMany({ - where: { - userId: team1owner.id, - eventTypeId: eventId, - }, - }); - - await expectPageToBeNotFound({ page, url: `/team/${team1.slug}/${eventType.slug}` }); - await doOnOrgDomain( - { - orgSlug: org.slug, - page, - }, - async () => { - await bookTeamEvent({ page, team: team1, event: eventType }); - } - ); - // booking should be visible for the Org OWNER even though he is not part of the booking - await assertBookingVisibleFor(orgOwner, page, eventType, true); - // booking should be visible for the Org ADMIN even though he is not part of the booking - await assertBookingVisibleFor(orgAdmin, page, eventType, true); - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(team1owner, page, eventType, true); - // booking should not be visible for other team host, because its a team1 team-event - await assertBookingVisibleFor(team2owner, page, eventType, false); - // booking should be visible for commonUser as he is part of the booking - await assertBookingVisibleFor(commonUser, page, eventType, true); - // booking should be visible for team1_teammate1 as he is part of the booking - await assertBookingVisibleFor(team1_teammate1, page, eventType, true); - }); - - test("Round Robin EventType booking list", async ({ page, users, orgs }) => { - const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } = - await createOrg(orgs, users); - - const { team: team1 } = await team1owner.getFirstTeamMembership(); - const scenario = { - schedulingType: SchedulingType.ROUND_ROBIN, - teamEventTitle: `round-robin-team-event`, - teamEventSlug: slugify(`round-robin-team-event-${randomString(5)}`), - }; - - const eventType = await createTeamEventType(team1owner, team1, scenario); - const { id: eventId } = eventType; - await prisma.host.createMany({ - data: [ - { - userId: commonUser.id, - eventTypeId: eventId, - }, - { - userId: team1_teammate1.id, - eventTypeId: eventId, - }, - ], - }); - - await prisma.host.deleteMany({ - where: { - userId: team1owner.id, - eventTypeId: eventId, - }, - }); - - await expectPageToBeNotFound({ page, url: `/team/${team1.slug}/${eventType.slug}` }); - - await doOnOrgDomain( - { - orgSlug: org.slug, - page, - }, - async () => { - await bookTeamEvent({ page, team: team1, event: eventType }); - } - ); - // booking should be visible for the Org OWNER even though he is not part of the booking - await assertBookingVisibleFor(orgOwner, page, eventType, true); - // booking should be visible for the Org ADMIN even though he is not part of the booking - await assertBookingVisibleFor(orgAdmin, page, eventType, true); - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(team1owner, page, eventType, true); - // booking should not be visible for other team host - await assertBookingVisibleFor(team2owner, page, eventType, false); - }); - - test("Managed EventType booking list", async ({ page, users, orgs }) => { - const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } = - await createOrg(orgs, users); - - const { team: team1 } = await team1owner.getFirstTeamMembership(); - - await team1owner.apiLogin(); - await page.goto(`/event-types?dialog=new&eventPage=team%2F${team1.slug}&teamId=${team1.id}`); - await page.getByTestId("managed-event-type").click(); - await page.getByTestId("event-type-quick-chat").click(); - await page.getByTestId("event-type-quick-chat").fill("managed-event"); - await page.getByRole("button", { name: "Continue" }).click(); - await page.getByTestId("vertical-tab-assignment").click(); - - await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("commonUser"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).fill("team1_teammate1"); - await page.getByRole("combobox", { name: "assignment-dropdown" }).press("Enter"); - await page.getByTestId("update-eventtype").click(); - - const eventType = await team1owner.getFirstTeamEvent(team1.id); - - await expectPageToBeNotFound({ page, url: `/${commonUser.username}/${eventType.slug}` }); - - await doOnOrgDomain( - { - orgSlug: org.slug, - page, - }, - async () => { - await bookUserEvent({ page, user: commonUser, event: eventType }); - } - ); - - // booking should be visible for the Org OWNER even though he is not part of the booking - await assertBookingVisibleFor(orgOwner, page, eventType, true); - // booking should be visible for the Org ADMIN even though he is not part of the booking - await assertBookingVisibleFor(orgAdmin, page, eventType, true); - // booking should be visible for the team host even though he is not part of the booking - await assertBookingVisibleFor(team1owner, page, eventType, true); - // booking should not be visible for other team host - await assertBookingVisibleFor(team2owner, page, eventType, false); - }); - }); - - test.describe("User Event", () => { - test("User personal booking list", async ({ page, users, orgs }) => { - const { org, orgOwner, orgAdmin, team1owner, team2owner, commonUser, team1_teammate1 } = - await createOrg(orgs, users); - - const event = await commonUser.getFirstEventAsOwner(); - - await page.goto(`/${commonUser.username}/${event.slug}`); - // Shouldn't be servable on the non-org domain - await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible(); - - await doOnOrgDomain( - { - orgSlug: org.slug, - page, - }, - async () => { - await bookUserEvent({ page, user: commonUser, event }); - } - ); - // booking should be visible for the Org OWNER - await assertBookingVisibleFor(orgOwner, page, event, true); - // booking should be visible for the Org ADMIN - await assertBookingVisibleFor(orgAdmin, page, event, true); - // booking should be visible for the team host - await assertBookingVisibleFor(team1owner, page, event, true); - // booking should be visible for other team host because commonUser is part of both teams - await assertBookingVisibleFor(team2owner, page, event, true); - // booking should be visible for commonUser as he is part of the booking - await assertBookingVisibleFor(commonUser, page, event, true); - // booking should not be visible for team1_teammate1 - await assertBookingVisibleFor(team1_teammate1, page, event, false); - }); - }); -}); - -async function bookUserEvent({ - page, - user, - event, -}: { - page: Page; - user: { - username: string | null; - name: string | null; - }; - event: { slug: string; title: string }; -}) { - await page.goto(`/${user.username}/${event.slug}`); - - await selectFirstAvailableTimeSlotNextMonth(page); - await bookTimeSlot(page); - await expect(page.getByTestId("success-page")).toBeVisible(); - - // The title of the booking - const BookingTitle = `${event.title} between ${user.name} and ${testName}`; - await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle); - // The booker should be in the attendee list - await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName); -} - -async function bookTeamEvent({ - page, - team, - event, -}: { - page: Page; - team: { - slug: string | null; - name: string | null; - }; - event: { slug: string; title: string }; -}) { - // Note that even though the default way to access a team booking in an organization is to not use /team in the URL, but it isn't testable with playwright as the rewrite is taken care of by Next.js config which can't handle on the fly org slug's handling - // So, we are using /team in the URL to access the team booking - // There are separate tests to verify that the next.config.js rewrites are working - // Also there are additional checkly tests that verify absolute e2e flow. They are in __checks__/organization.spec.ts - await page.goto(`/team/${team.slug}/${event.slug}`); - - await selectFirstAvailableTimeSlotNextMonth(page); - await bookTimeSlot(page); - await expect(page.getByTestId("success-page")).toBeVisible(); - - // The title of the booking - const BookingTitle = `${event.title} between ${team.name} and ${testName}`; - await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle); - // The booker should be in the attendee list - await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName); -} - -async function expectPageToBeNotFound({ page, url }: { page: Page; url: string }) { - await page.goto(`${url}`); - await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible(); -} - -const createOrg = async (orgs: Fixtures["orgs"], userFixture: Fixtures["users"]) => { - const org = await orgs.create({ - name: "TestOrg", - }); - const orgOwner = await userFixture.create({ - name: "org-owner", - organizationId: org.id, - roleInOrganization: MembershipRole.OWNER, - }); - - const orgAdmin = await userFixture.create({ - name: "org-admin", - organizationId: org.id, - roleInOrganization: MembershipRole.ADMIN, - }); - const team1owner = await userFixture.create({ - name: "team-owner-1", - organizationId: org.id, - roleInOrganization: MembershipRole.MEMBER, - }); - const team2owner = await userFixture.create({ - name: "team-owner-2", - organizationId: org.id, - roleInOrganization: MembershipRole.MEMBER, - }); - const commonUser = await userFixture.create({ - name: "commonUser", - organizationId: org.id, - roleInOrganization: MembershipRole.MEMBER, - }); - const team1_teammate1 = await userFixture.create({ - name: "team1_teammate1", - organizationId: org.id, - roleInOrganization: MembershipRole.MEMBER, - }); - const team2_teammate1 = await userFixture.create({ - name: "team2_teammate1", - organizationId: org.id, - roleInOrganization: MembershipRole.MEMBER, - }); - const teamOne = await prisma.team.create({ - data: { - name: "bookings-test-team-1", - slug: slugify(`bookings-test-team-2-${randomString(5)}`), - parentId: org.id, - }, - }); - - const teamTwo = await prisma.team.create({ - data: { - name: "bookings-test-team-2", - slug: slugify(`bookings-test-team-2-${randomString(5)}`), - parentId: org.id, - }, - }); - - // create memberships - await prisma.membership.createMany({ - data: [ - { - userId: team1owner.id, - teamId: teamOne.id, - accepted: true, - role: "OWNER", - }, - { - userId: commonUser.id, - teamId: teamOne.id, - accepted: true, - role: "MEMBER", - }, - { - userId: team1_teammate1.id, - teamId: teamOne.id, - accepted: true, - role: "MEMBER", - }, - { - userId: team2owner.id, - teamId: teamTwo.id, - accepted: true, - role: "OWNER", - }, - { - userId: commonUser.id, - teamId: teamTwo.id, - accepted: true, - role: "MEMBER", - }, - { - userId: team2_teammate1.id, - teamId: teamTwo.id, - accepted: true, - role: "MEMBER", - }, - ], - }); - return { - org, - orgOwner, - orgAdmin, - team1owner, - team2owner, - commonUser, - team1_teammate1, - team2_teammate1, - teamOne, - teamTwo, - }; -}; diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index b2e96c3b50..7851374e7e 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -40,12 +40,15 @@ export const getHandler = async ({ ctx, input }: GetOptions) => { }; }; -const getUniqueBookings = (arr: T[]) => { - const unique = new Set(); - arr.forEach((booking) => { - unique.add(booking.id); +const set = new Set(); +const getUniqueBookings = (arr: T[]) => { + const unique = arr.filter((booking) => { + const duplicate = set.has(booking.uid); + set.add(booking.uid); + return !duplicate; }); - return Array.from(unique.values()); + set.clear(); + return unique; }; export async function getBookings({ @@ -125,6 +128,7 @@ export async function getBookings({ ], }, }; + const filtersCombined: Prisma.BookingWhereInput[] = !filters ? [] : Object.keys(filters) @@ -192,171 +196,15 @@ export async function getBookings({ }, }; - const userBookingsFilter: Prisma.BookingWhereInput = { - userId: user.id, - }; - const userAttendingBookingsFilter: Prisma.BookingWhereInput = { - attendees: { - some: { - email: user.email, - }, - }, - }; - // all (collective + round-robin) team bookings should be visible to team-owner and team-admins - const teamBookingsFilter: Prisma.BookingWhereInput = { - eventType: { - team: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }; - // all (collective + round-robin) team bookings should be visible to org-owner and org-admins - const organizationBookingsFilter: Prisma.BookingWhereInput = { - eventType: { - team: { - parent: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }, - }; - - // all managed team bookings should be visible to team-owner and team-admins - const teamManagedBookingsFilter: Prisma.BookingWhereInput = { - eventType: { - parent: { - team: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }, - }; - - // all managed team bookings should be visible to org-owner and org-admins - const organizationManagedBookingsFilter: Prisma.BookingWhereInput = { - eventType: { - parent: { - team: { - parent: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }, - }, - }; - - // show personal booking to org-owner and org-admins - const organizationPersonalBookingsFilter: Prisma.BookingWhereInput = { - AND: [ - { - user: { - teams: { - some: { - team: { - parentId: { gt: 0 }, - }, - }, - }, - }, - }, - { - OR: [ - { - user: { - teams: { - some: { - team: { - parent: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - user: { - teams: { - some: { - team: { - members: { - some: { - userId: user.id, - role: { - in: ["ADMIN", "OWNER"], - }, - }, - }, - }, - }, - }, - }, - }, - ], - }, - // filter out all (collective + round-robin) event bookings - { - eventType: { - teamId: null, - }, - }, - // filter out all (managed) event bookings - { - eventType: { - parentId: null, - }, - }, - ], - }; - - const seatsReferenceFilter: Prisma.BookingWhereInput = { - seatsReferences: { - some: { - attendee: { - email: user.email, - }, - }, - }, - }; const [ // Quering these in parallel to save time. // Note that because we are applying `take` to individual queries, we will usually get more bookings then we need. It is okay to have more bookings faster than having what we need slower bookingsQueryUserId, + bookingsQueryAttendees, + bookingsQueryTeamMember, + bookingsQuerySeatReference, + ////////////////////////// + recurringInfoBasic, recurringInfoExtended, // We need all promises to be successful, so we are not using Promise.allSettled @@ -364,19 +212,73 @@ export async function getBookings({ prisma.booking.findMany({ where: { OR: [ - userBookingsFilter, - userAttendingBookingsFilter, - teamBookingsFilter, - organizationBookingsFilter, - teamManagedBookingsFilter, - organizationManagedBookingsFilter, - organizationPersonalBookingsFilter, - seatsReferenceFilter, + { + userId: user.id, + }, + ], + AND: [passedBookingsStatusFilter, ...filtersCombined], + }, + orderBy, + take: take + 1, + skip, + }), + prisma.booking.findMany({ + where: { + OR: [ + { + attendees: { + some: { + email: user.email, + }, + }, + }, + ], + AND: [passedBookingsStatusFilter, ...filtersCombined], + }, + orderBy, + take: take + 1, + skip, + }), + prisma.booking.findMany({ + where: { + OR: [ + { + eventType: { + team: { + members: { + some: { + userId: user.id, + role: { + in: ["ADMIN", "OWNER"], + }, + }, + }, + }, + }, + }, + ], + AND: [passedBookingsStatusFilter, ...filtersCombined], + }, + orderBy, + take: take + 1, + skip, + }), + prisma.booking.findMany({ + where: { + OR: [ + { + seatsReferences: { + some: { + attendee: { + email: user.email, + }, + }, + }, + }, ], AND: [passedBookingsStatusFilter, ...filtersCombined], }, orderBy, - select: { id: true }, take: take + 1, skip, }), @@ -443,6 +345,9 @@ export async function getBookings({ const plainBookings = getUniqueBookings( // It's going to mess up the orderBy as we are concatenating independent queries results bookingsQueryUserId + .concat(bookingsQueryAttendees) + .concat(bookingsQueryTeamMember) + .concat(bookingsQuerySeatReference) ); // Now enrich bookings with relation data. We could have queried the relation data along with the bookings, but that would cause unnecessary queries to the database. @@ -451,7 +356,7 @@ export async function getBookings({ await prisma.booking.findMany({ where: { id: { - in: plainBookings, + in: plainBookings.map((booking) => booking.id), }, }, select: bookingSelect,