Files
cal.diy/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts
T
be77ac133c feat: Domain-wide delegation for Google Calendar and Google Meet (#16622)
* Add endpoints for testing the flow

* Add MVP

* new route

* Fixes

* Fixes

* Remove enable toggle support from domainWideDelegation

* Fixes

* Revert "Remove enable toggle support from domainWideDelegation"

This reverts commit c29e729206cd1fa063f9c9ce0cf148ef1577d60b.

* Revert yarn.lock

* More fixes

* Fix new workspace platform add

* refactor: improvements

* refactor: bug fixes and improvements

* fix: type errors

* fix: conflicts

* chore: update test

* fix: logic

* chore: improvements

* fix: toglle

* fix: bugs

* fix: type err

* chore: check number type

* fix: after conflicts

* chore: fix type err

* fix: type errors and tests

* fix: tets

* test

* chore: remove unused

* fix: google meet url on booking page and secondary calendar

* fix: add property

* fix: type err

* fix: re assingment bug

* fix: use getAllCredentials

* chore: fix import

* fix: installed count

* fix: pass event type

* fix: import

* fix: [Stacked PR] Review fixes (#17958)

* Review fixes

* fix: destination calendar bug

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>

* refactor: use repository

* chore: remove duplicate

* fix: More review fixes for domain wide delegation (#17969)

* Reuse buildCredentialPayloadForCalendar

* fixes

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* fix ts error

* Fix getSchedule not using dwd credentials (#17995)

* fix: Remove direct DWD table access from google-calendar and remove delegatedToId column from Credential (#18015)

* Remove fn rename to reduce number of files changed

* chore: check feature globally

* test: add unit tests

* chore: move function

* test: add booking test

* wip

* Remove domain-wide-delegation team feature flag

* Make sure duplicate calendars are not shown due to DWD. Show DWD when there is conflict

* Fix tests and their ts errors

* Fix more tests

* Move findUsersForAvailabilityCheck to separate file as it has AppStore dependency causing problem with Routing Forms. Build crashes

* fix: Multiple calendar connections from Google not showing up in apps/installed

* DestinationCalendar must have either credentialId or domainWideDeelgationId. Also handle the case when DWD is disabled and there was a non-dwd credential that could be used

* Disable deletetion of DWD as it is destructive and prefer disabling instead

* Show DWD credential calendars at the top

* Fixed tests

* Calendar Cache DWD support

* Self-Review: Verify email required and add more selectedCalendar tests

* Self Review: shorten names

* Self ReviewL Dead code removal

* Revert "Calendar Cache DWD support"

This reverts commit 009f236470fa21eba5986117d4f6e4d4c5e38c34.

* Some misc fixes

* fixes

* Performance improvememt in slots loading and booking

* More cases for handling dwd credentials

* simplify the logic. Ensure that EventManager and the modules below it in the flow have CredentialForCalendarService available

* Fix tests

* Fix installed/conferencing not showing gogole meet

* Shorten name

* Fix ApI v2 tests

* Add some more tests

* add getSchedule tests

* Improve tests

* Make Google Meet default when DWD for google is enabled

* Enable feature flagging for DWD

* chore: bump libraries

* Encrypt serviceaccount private key

* Fix ts errors

* bump platform libraries

* org add dwd

* org add dwd

* bump platform libraries

* fix selected calendars

* fix remove selected dwd calendar

* remove oauthclient id aliad in authedCalendar

* remove oauthclient id aliad in authedCalendar

* refactor: OrganizationsDwdController

* chore: export toggleDwdEnabled from platform-libraries

* feat: v2 update (enable / disable) dwd

* refactor: SelectedCalendarsController check if user belongs to dwd org & for not dwd use previous logic

* wip: DestinationCalendar send domainWideDelegationCredentialId from frontend to api

* try fix set destination calendar api v2

* fixup! try fix set destination calendar api v2

* setting google meet as default location working for dwd

* bump platform libraries version

* allow office 365 workspace slug

* allow office 365 workspace slug

* chore: v2 create dwd MS and Google service keys input

* fix: CreateDwdInput serviceAccountKey

* fix: CredentialForCalendarService type

* fix: check workspace slug

* feat: update serviceAccessKey of DWD

* testing

* fixup! testing

* fix: getAuthUtl bug

* fix: unit tests

* fix: type err and unit test

* fix: e2e test

* fix: credentials bug and failing unit tests

* Update CalendarService.ts

* chore: refactor office365 calendar service and add testDomainWideDelegationSetup

* fix: office365Calendar use correct clientId/Secret for DWD

* fix: office365Calendar dwd no need refresh token

* test: add unit test for outlook dwd and setup

* feat: added dwd support for office365 calendar

* feat: added dwd support for office365 video

* test: finish test for outlook dwd

* fix: create dialog bug

* chore: remove console logs

* fix: refreshToken bug

* bump version libraries

* refactor: fetch dwd credentials only in findQualifiedHosts

* fixup! refactor: fetch dwd credentials only in findQualifiedHosts

* fix: type err

* fix: type err

* fix: getUserDisplayName

* fix: unit test

* chore: bump platform lib

---------

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-03-02 16:52:57 -03:00

209 lines
6.5 KiB
TypeScript

import type { Prisma } from "@prisma/client";
import type { IncomingMessage } from "http";
import type { Logger } from "tslog";
import { findQualifiedHostsWithDwdCredentials } from "@calcom/lib/bookings/findQualifiedHostsWithDwdCredentials";
import { enrichUsersWithDwdCredentials } from "@calcom/lib/domainWideDelegation/server";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import { HttpError } from "@calcom/lib/http-error";
import { getPiiFreeUser } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import type { RoutingFormResponse } from "@calcom/lib/server/getLuckyUser";
import { withSelectedCalendars } from "@calcom/lib/server/repository/user";
import { userSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { CredentialForCalendarService } from "@calcom/types/Credential";
import { loadUsers } from "./loadUsers";
import type { NewBookingEventType } from "./types";
type Users = (Awaited<ReturnType<typeof loadUsers>>[number] & {
isFixed?: boolean;
metadata?: Prisma.JsonValue;
createdAt?: Date;
})[];
export type UsersWithDwdCredentials = (Omit<Awaited<ReturnType<typeof loadUsers>>[number], "credentials"> & {
isFixed?: boolean;
metadata?: Prisma.JsonValue;
createdAt?: Date;
credentials: CredentialForCalendarService[];
})[];
type EventType = Pick<
NewBookingEventType,
| "hosts"
| "users"
| "id"
| "userId"
| "schedulingType"
| "maxLeadThreshold"
| "team"
| "assignAllTeamMembers"
| "assignRRMembersUsingSegment"
| "rrSegmentQueryValue"
| "isRRWeightsEnabled"
| "rescheduleWithSameRoundRobinHost"
| "teamId"
>;
type InputProps = {
req: IncomingMessage;
eventType: EventType;
eventTypeId: number;
dynamicUserList: string[];
logger: Logger<unknown>;
routedTeamMemberIds: number[] | null;
contactOwnerEmail: string | null;
rescheduleUid: string | null;
routingFormResponse: RoutingFormResponse | null;
};
export async function loadAndValidateUsers({
req,
eventType,
eventTypeId,
dynamicUserList,
logger,
routedTeamMemberIds,
contactOwnerEmail,
rescheduleUid,
routingFormResponse,
}: InputProps): Promise<{
qualifiedRRUsers: UsersWithDwdCredentials;
additionalFallbackRRUsers: UsersWithDwdCredentials;
fixedUsers: UsersWithDwdCredentials;
}> {
let users: Users = await loadUsers({
eventType,
dynamicUserList,
req,
routedTeamMemberIds,
contactOwnerEmail,
});
const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking);
if (!isDynamicAllowed && !eventTypeId) {
logger.warn({
message: "NewBooking: Some of the users in this group do not allow dynamic booking",
});
throw new HttpError({
message: "Some of the users in this group do not allow dynamic booking",
statusCode: 400,
});
}
// If this event was pre-relationship migration
// TODO: Establish whether this is dead code.
if (!users.length && eventType.userId) {
const eventTypeUser = await prisma.user.findUnique({
where: {
id: eventType.userId,
},
select: {
credentials: {
select: credentialForCalendarServiceSelect,
}, // Don't leak to client
...userSelect.select,
},
});
if (!eventTypeUser) {
logger.warn({ message: "NewBooking: eventTypeUser.notFound" });
throw new HttpError({ statusCode: 404, message: "eventTypeUser.notFound" });
}
users.push(withSelectedCalendars(eventTypeUser));
}
if (!users) throw new HttpError({ statusCode: 404, message: "eventTypeUser.notFound" });
// map fixed users
users = users.map((user) => ({
...user,
isFixed:
user.isFixed === false
? false
: user.isFixed || eventType.schedulingType !== SchedulingType.ROUND_ROBIN,
}));
const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = await findQualifiedHostsWithDwdCredentials({
eventType,
routedTeamMemberIds: routedTeamMemberIds || [],
rescheduleUid,
contactOwnerEmail,
routingFormResponse,
});
const allQualifiedHostsHashMap = [...qualifiedRRHosts, ...(allFallbackRRHosts ?? []), ...fixedHosts].reduce(
(acc, host) => {
if (host.user.id) {
return { ...acc, [host.user.id]: host };
}
return acc;
},
{} as {
[key: number]: Awaited<
ReturnType<typeof findQualifiedHostsWithDwdCredentials>
>["qualifiedRRHosts"][number];
}
);
let qualifiedRRUsers: UsersWithDwdCredentials = [];
let allFallbackRRUsers: UsersWithDwdCredentials = [];
let fixedUsers: UsersWithDwdCredentials = [];
if (qualifiedRRHosts.length) {
// remove users that are not in the qualified hosts array
const qualifiedHostIds = new Set(qualifiedRRHosts.map((qualifiedHost) => qualifiedHost.user.id));
qualifiedRRUsers = users
.filter((user) => qualifiedHostIds.has(user.id))
.map((user) => ({ ...user, credentials: allQualifiedHostsHashMap[user.id].user.credentials }));
}
if (allFallbackRRHosts?.length) {
const fallbackHostIds = new Set(allFallbackRRHosts.map((fallbackHost) => fallbackHost.user.id));
allFallbackRRUsers = users
.filter((user) => fallbackHostIds.has(user.id))
.map((user) => ({ ...user, credentials: allQualifiedHostsHashMap[user.id].user.credentials }));
}
if (fixedHosts?.length) {
const fixedHostIds = new Set(fixedHosts.map((fixedHost) => fixedHost.user.id));
fixedUsers = users
.filter((user) => fixedHostIds.has(user.id))
.map((user) => ({ ...user, credentials: allQualifiedHostsHashMap[user.id].user.credentials }));
}
logger.debug(
"Concerned users",
safeStringify({
users: users.map(getPiiFreeUser),
})
);
const additionalFallbackRRUsers = allFallbackRRUsers.filter(
(fallbackUser) => !qualifiedRRUsers.find((qualifiedUser) => qualifiedUser.id === fallbackUser.id)
);
if (!qualifiedRRUsers.length && !fixedUsers.length) {
const firstUser = users[0];
const firstUserOrgId = await getOrgIdFromMemberOrTeamId({
memberId: firstUser.id ?? null,
teamId: eventType.teamId,
});
const usersEnrichedWithDwd = await enrichUsersWithDwdCredentials({
orgId: firstUserOrgId ?? null,
users,
});
return {
qualifiedRRUsers,
additionalFallbackRRUsers, // without qualified
fixedUsers: usersEnrichedWithDwd,
};
}
return {
qualifiedRRUsers,
additionalFallbackRRUsers, // without qualified
fixedUsers,
};
}