From 4284a4d16d5238c022ea40d6c1d6b528eebb1c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efra=C3=ADn=20Roch=C3=ADn?= Date: Wed, 14 Jun 2023 17:40:47 -0700 Subject: [PATCH] feat: Organizations users calendar cache (#9498) * Initial commit * Adding feature flag * feat: Orgs Schema Changing `scopedMembers` to `orgUsers` (#9209) * Change scopedMembers to orgMembers * Change to orgUsers * Letting duplicate slugs for teams to support orgs * Covering null on unique clauses * Supporting having the orgId in the session cookie * feat: organization event type filter (#9253) Signed-off-by: Udit Takkar * Missing changes to support orgs schema changes * feat: Onboarding process to create an organization (#9184) * Desktop first banner, mobile pending * Removing dead code and img * WIP * Adds Email verification template+translations for organizations (#9202) * First step done * Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding * Step 2 done, avatar not working * Covering null on unique clauses * Onboarding admins step * Last step to create teams * Moving change password handler, improving verifying code flow * Clearing error before submitting * Reverting email testing api changes * Reverting having the banner for now * Consistent exported components * Remove unneeded files from banner * Removing uneeded code * Fixing avatar selector * Using meta component for head/descr * Missing i18n strings * Feedback * Making an org avatar (temp) * Check for subteams slug clashes with usernames * Fixing create teams onsuccess * feedback * Making sure we check requestedSlug now --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * feat: [CAL-1816] Organization subdomain support (#9345) * Desktop first banner, mobile pending * Removing dead code and img * WIP * Adds Email verification template+translations for organizations (#9202) * First step done * Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding * Step 2 done, avatar not working * Covering null on unique clauses * Onboarding admins step * Last step to create teams * Moving change password handler, improving verifying code flow * Clearing error before submitting * Reverting email testing api changes * Reverting having the banner for now * Consistent exported components * Remove unneeded files from banner * Removing uneeded code * Fixing avatar selector * Using meta component for head/descr * Missing i18n strings * Feedback * Making an org avatar (temp) * Check for subteams slug clashes with usernames * Fixing create teams onsuccess * Covering users and subteams, excluding non-org users * Unpublished teams shows correctly * Create subdomain in Vercel * feedback * Renaming Vercel env vars * Vercel domain check before creation * Supporting cal-staging.com * Change to have vercel detect it * vercel domain check data message error * Remove check domain * Making sure we check requestedSlug now * Feedback and unneeded code * Reverting unneeded changes * Unneeded changes --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Vercel subdomain creation in PROD only * Making sure we let localhost still work * Feedback * Type check fixes * feat: Organization branding in side menu (#9279) * Desktop first banner, mobile pending * Removing dead code and img * WIP * Adds Email verification template+translations for organizations (#9202) * First step done * Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding * Step 2 done, avatar not working * Covering null on unique clauses * Onboarding admins step * Last step to create teams * Moving change password handler, improving verifying code flow * Clearing error before submitting * Reverting email testing api changes * Reverting having the banner for now * Consistent exported components * Remove unneeded files from banner * Removing uneeded code * Fixing avatar selector * Org branding provider used in shell sidebar * Using meta component for head/descr * Missing i18n strings * Feedback * Making an org avatar (temp) * Using org avatar (temp) * Not showing org logo if not set * User onboarding with org branding (slug) * Check for subteams slug clashes with usernames * Fixing create teams onsuccess * feedback * Feedback * Org public profile * Public profiles for team event types * Added setup profile alert * Using org avatar on subteams avatar * Making sure we show the set up profile on org only * Profile username availability rely on org hook * Update apps/web/pages/team/[slug].tsx Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Update apps/web/pages/team/[slug].tsx Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * feat: Organization support for event types page (#9449) * Desktop first banner, mobile pending * Removing dead code and img * WIP * Adds Email verification template+translations for organizations (#9202) * First step done * Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding * Step 2 done, avatar not working * Covering null on unique clauses * Onboarding admins step * Last step to create teams * Moving change password handler, improving verifying code flow * Clearing error before submitting * Reverting email testing api changes * Reverting having the banner for now * Consistent exported components * Remove unneeded files from banner * Removing uneeded code * Fixing avatar selector * Org branding provider used in shell sidebar * Using meta component for head/descr * Missing i18n strings * Feedback * Making an org avatar (temp) * Using org avatar (temp) * Not showing org logo if not set * User onboarding with org branding (slug) * Check for subteams slug clashes with usernames * Fixing create teams onsuccess * feedback * Feedback * Org public profile * Public profiles for team event types * Added setup profile alert * Using org avatar on subteams avatar * Processing orgs and children as profile options * Reverting change not belonging to this PR * Making sure we show the set up profile on org only * Removing console.log * Comparing memberships to choose the highest one --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * Type errors * Refactor and type fixes * Update orgDomains.ts * Feedback * Reverting * NIT * fix issue getting org slug from domain * Improving orgDomains util * Host comes with port * Update useRouterQuery.ts * add org users cache-calendar * fix typo * re-use the simple user calendar-cache page * Apply suggestions from code review * Update packages/core/CalendarManager.ts * type fixes --------- Signed-off-by: Udit Takkar Co-authored-by: Leo Giovanetti Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: zomars --- .../calendar-cache/[month]/[orgSlug].tsx | 1 + .../{[month].tsx => [month]/index.tsx} | 0 packages/core/CalendarManager.ts | 26 ++++++++++++------- packages/core/getBusyTimes.ts | 5 +++- packages/core/getUserAvailability.ts | 6 +++++ .../features/bookings/lib/handleNewBooking.ts | 16 +++++++++++- .../trpc/server/routers/viewer/slots/util.ts | 15 +++++++++++ .../teams/getMemberAvailability.handler.ts | 5 ++++ 8 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 apps/web/pages/[user]/calendar-cache/[month]/[orgSlug].tsx rename apps/web/pages/[user]/calendar-cache/{[month].tsx => [month]/index.tsx} (100%) diff --git a/apps/web/pages/[user]/calendar-cache/[month]/[orgSlug].tsx b/apps/web/pages/[user]/calendar-cache/[month]/[orgSlug].tsx new file mode 100644 index 0000000000..647d6f6347 --- /dev/null +++ b/apps/web/pages/[user]/calendar-cache/[month]/[orgSlug].tsx @@ -0,0 +1 @@ +export { getStaticProps, getStaticPaths, default } from "./index"; diff --git a/apps/web/pages/[user]/calendar-cache/[month].tsx b/apps/web/pages/[user]/calendar-cache/[month]/index.tsx similarity index 100% rename from apps/web/pages/[user]/calendar-cache/[month].tsx rename to apps/web/pages/[user]/calendar-cache/[month]/index.tsx diff --git a/packages/core/CalendarManager.ts b/packages/core/CalendarManager.ts index 02107d7001..fe5fd7dc08 100644 --- a/packages/core/CalendarManager.ts +++ b/packages/core/CalendarManager.ts @@ -185,13 +185,20 @@ export const getCachedResults = async ( * On development environment it takes a long time because Next must compiles the whole page. * @param username * @param month A string representing year and month using YYYY-MM format + * @param organizationSlug A string with the organization slug */ -const getNextCache = async (username: string, month: string): Promise => { +const getNextCache = async ( + username: string, + month: string, + organizationSlug?: string | null +): Promise => { let localCache: EventBusyDate[][] = []; const { NODE_ENV } = process.env; const cacheDir = `${NODE_ENV === "development" ? NODE_ENV : process.env.BUILD_ID}`; const baseUrl = `${WEBAPP_URL}/_next/data/${cacheDir}/en`; - const url = `${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`; + const url = organizationSlug + ? `${baseUrl}/${username}/calendar-cache/${month}/${organizationSlug}.json?user=${username}&month=${month}&orgSlug=${organizationSlug}` + : `${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`; try { localCache = await fetch(url) .then((r) => r.json()) @@ -218,17 +225,18 @@ const getMonths = (dateFrom: string, dateTo: string): string[] => { return months; }; -const createCalendarCachePage = (username: string, month: string): void => { +const createCalendarCachePage = (username: string, month: string, organizationSlug?: string | null): void => { // No need to wait for this, the purpose is to force re-validation every second as indicated // in page getStaticProps. - fetch(`${WEBAPP_URL}/${username}/calendar-cache/${month}`).catch(console.log); + fetch(`${WEBAPP_URL}/${username}/calendar-cache/${month}/${organizationSlug}`).catch(console.log); }; export const getBusyCalendarTimes = async ( username: string, withCredentials: CredentialPayload[], dateFrom: string, dateTo: string, - selectedCalendars: SelectedCalendar[] + selectedCalendars: SelectedCalendar[], + organizationSlug?: string | null ) => { let results: EventBusyDate[][] = []; const months = getMonths(dateFrom, dateTo); @@ -239,16 +247,16 @@ export const getBusyCalendarTimes = async ( // Add 14 hours from the start date to avoid problems in UTC+ time zones. const endDate = dayjs(dateTo).endOf("month").add(14, "hours").format(); results = await getCalendarsEvents(withCredentials, startDate, endDate, selectedCalendars); - logger.info("Generating calendar cache in background"); + console.log("Generating calendar cache in background"); // on cold start the calendar cache page generated in the background - Promise.all(months.map((month) => createCalendarCachePage(username, month))); + Promise.all(months.map((month) => createCalendarCachePage(username, month, organizationSlug))); } else { if (months.length === 1) { - results = await getNextCache(username, dayjs(dateFrom).format("YYYY-MM")); + results = await getNextCache(username, dayjs(dateFrom).format("YYYY-MM"), organizationSlug); } else { // if dateFrom and dateTo is from different months get cache by each month const data: EventBusyDate[][][] = await Promise.all( - months.map((month) => getNextCache(username, month)) + months.map((month) => getNextCache(username, month, organizationSlug)) ); results = data.flat(1); } diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index d7cf40e5af..8dfcc749ec 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -13,6 +13,7 @@ export async function getBusyTimes(params: { credentials: Credential[]; userId: number; username: string; + organizationSlug?: string | null | undefined; eventTypeId?: number; startTime: string; beforeEventBuffer?: number; @@ -27,6 +28,7 @@ export async function getBusyTimes(params: { eventTypeId, startTime, endTime, + organizationSlug, beforeEventBuffer, afterEventBuffer, selectedCalendars, @@ -128,7 +130,8 @@ export async function getBusyTimes(params: { credentials, startTime, endTime, - selectedCalendars + selectedCalendars, + organizationSlug ); const endConnectedCalendarsGet = performance.now(); logger.debug( diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 2189e0acbb..b9943fa630 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -74,6 +74,11 @@ const getUser = (where: Prisma.UserWhereInput) => select: { ...availabilityUserSelect, credentials: true, + organization: { + select: { + slug: true, + }, + }, }, }); @@ -162,6 +167,7 @@ export async function getUserAvailability( eventTypeId, userId: user.id, username: `${user.username}`, + organizationSlug: initialData?.user?.organization?.slug, beforeEventBuffer, afterEventBuffer, selectedCalendars: user.selectedCalendars, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 617d6e4812..130eb2ea8b 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -293,6 +293,11 @@ const getEventTypesFromDB = async (eventTypeId: number) => { select: { credentials: true, ...userSelect.select, + organization: { + select: { + slug: true, + }, + }, }, }, }, @@ -318,7 +323,11 @@ const getEventTypesFromDB = async (eventTypeId: number) => { }; }; -type IsFixedAwareUser = User & { isFixed: boolean; credentials: Credential[] }; +type IsFixedAwareUser = User & { + isFixed: boolean; + credentials: Credential[]; + organization: { slug: string }; +}; async function ensureAvailableUsers( eventType: Awaited> & { @@ -694,6 +703,11 @@ async function handler( ...userSelect.select, credentials: true, // Don't leak to client metadata: true, + organization: { + select: { + slug: true, + }, + }, }, }) : eventType.hosts?.length diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 34c9afd7ac..6b853d2aa9 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -115,6 +115,11 @@ export async function getEventType(input: TGetScheduleInputSchema) { select: { credentials: true, // Don't leak credentials to the client ...availabilityUserSelect, + organization: { + select: { + slug: true, + }, + }, }, }, }, @@ -123,6 +128,11 @@ export async function getEventType(input: TGetScheduleInputSchema) { select: { credentials: true, // Don't leak credentials to the client ...availabilityUserSelect, + organization: { + select: { + slug: true, + }, + }, }, }, }, @@ -156,6 +166,11 @@ export async function getDynamicEventType(input: TGetScheduleInputSchema) { allowDynamicBooking: true, credentials: true, // Don't leak credentials to the client ...availabilityUserSelect, + organization: { + select: { + slug: true, + }, + }, }, }); const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking); diff --git a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts index 97b706c599..9155758888 100644 --- a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts @@ -27,6 +27,11 @@ export const getMemberAvailabilityHandler = async ({ ctx, input }: GetMemberAvai select: { credentials: true, // needed for getUserAvailability ...availabilityUserSelect, + organization: { + select: { + slug: true, + }, + }, }, }, },