diff --git a/apps/api/v1/pages/api/credential-sync/_post.ts b/apps/api/v1/pages/api/credential-sync/_post.ts index edf8b3dcb7..5a3ef66cfa 100644 --- a/apps/api/v1/pages/api/credential-sync/_post.ts +++ b/apps/api/v1/pages/api/credential-sync/_post.ts @@ -108,7 +108,7 @@ async function handler(req: NextApiRequest) { // ^ Workaround for the select in `create` not working if (createCalendarResources) { - const calendar = await getCalendar(credential); + const calendar = await getCalendar({ ...credential, delegatedTo: null }); if (!calendar) throw new HttpError({ message: "Calendar missing for credential", statusCode: 500 }); const calendars = await calendar.listCalendars(); const calendarToCreate = calendars.find((calendar) => calendar.primary) || calendars[0]; diff --git a/apps/api/v1/pages/api/destination-calendars/[id]/_patch.ts b/apps/api/v1/pages/api/destination-calendars/[id]/_patch.ts index 29a08c23d3..965790f130 100644 --- a/apps/api/v1/pages/api/destination-calendars/[id]/_patch.ts +++ b/apps/api/v1/pages/api/destination-calendars/[id]/_patch.ts @@ -2,7 +2,7 @@ import type { Prisma } from "@prisma/client"; import type { NextApiRequest } from "next"; import type { z } from "zod"; -import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; +import { getCalendarCredentialsWithoutDwd, getConnectedCalendars } from "@calcom/core/CalendarManager"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import prisma from "@calcom/prisma"; @@ -185,7 +185,7 @@ async function verifyCredentialsAndGetId({ currentCredentialId: number | null; }) { if (parsedBody.integration && parsedBody.externalId) { - const calendarCredentials = getCalendarCredentials(userCredentials); + const calendarCredentials = getCalendarCredentialsWithoutDwd(userCredentials); const { connectedCalendars } = await getConnectedCalendars( calendarCredentials, diff --git a/apps/api/v1/pages/api/destination-calendars/_post.ts b/apps/api/v1/pages/api/destination-calendars/_post.ts index 256b0b4b7f..5875efedb5 100644 --- a/apps/api/v1/pages/api/destination-calendars/_post.ts +++ b/apps/api/v1/pages/api/destination-calendars/_post.ts @@ -1,6 +1,6 @@ import type { NextApiRequest } from "next"; -import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; +import { getCalendarCredentialsWithoutDwd, getConnectedCalendars } from "@calcom/core/CalendarManager"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import prisma from "@calcom/prisma"; @@ -82,7 +82,7 @@ async function postHandler(req: NextApiRequest) { message: "Bad request, credential id invalid", }); - const calendarCredentials = getCalendarCredentials(userCredentials); + const calendarCredentials = getCalendarCredentialsWithoutDwd(userCredentials); const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, [], parsedBody.externalId); diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 58b6c84d84..a5e61b9103 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -30,7 +30,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.121", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.123", "@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", diff --git a/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts b/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts index 1adbcebdc6..d87cb59a2e 100644 --- a/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts +++ b/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts @@ -127,6 +127,11 @@ class Primary { @IsInt() @ApiProperty() credentialId!: number; + + @IsString() + @IsOptional() + @ApiPropertyOptional({ type: String, nullable: true }) + domainWideDelegationCredentialId?: string | null; } export class Calendar { @@ -165,6 +170,11 @@ export class Calendar { @IsInt() @ApiProperty() credentialId!: number; + + @IsString() + @IsOptional() + @ApiPropertyOptional({ type: String, nullable: true }) + domainWideDelegationCredentialId?: string | null; } export class ConnectedCalendar { @@ -177,6 +187,11 @@ export class ConnectedCalendar { @ApiProperty() credentialId!: number; + @IsString() + @IsOptional() + @ApiPropertyOptional({ type: String, nullable: true }) + domainWideDelegationCredentialId?: string | null; + @ValidateNested() @IsObject() @IsOptional() @@ -220,6 +235,11 @@ class DestinationCalendar { @ApiProperty({ type: Number, nullable: true }) credentialId!: number | null; + @IsString() + @IsOptional() + @ApiPropertyOptional({ type: String, nullable: true }) + domainWideDelegationCredentialId?: string | null; + @IsString() @IsOptional() @ApiPropertyOptional({ type: String, nullable: true }) diff --git a/apps/api/v2/src/ee/calendars/services/calendars.service.ts b/apps/api/v2/src/ee/calendars/services/calendars.service.ts index 6f4a23e839..2f8018bb7c 100644 --- a/apps/api/v2/src/ee/calendars/services/calendars.service.ts +++ b/apps/api/v2/src/ee/calendars/services/calendars.service.ts @@ -39,6 +39,16 @@ export class CalendarsService { private readonly selectedCalendarsRepository: SelectedCalendarsRepository ) {} + private buildNonDwdCredentials(credentials: TCredential[]) { + return credentials + .map((credential) => ({ + ...credential, + delegatedTo: null, + delegatedToId: null, + })) + .filter((credential) => !!credential); + } + async getCalendars(userId: number) { const userWithCalendars = await this.usersRepository.findByIdWithCalendars(userId); if (!userWithCalendars) { @@ -73,7 +83,7 @@ export class CalendarsService { ); try { const calendarBusyTimes = await getBusyCalendarTimes( - credentials, + this.buildNonDwdCredentials(credentials), dateFrom, dateTo, composedSelectedCalendars diff --git a/apps/api/v2/src/modules/atoms/services/event-types-atom.service.ts b/apps/api/v2/src/modules/atoms/services/event-types-atom.service.ts index c7c4869260..e38d630bb9 100644 --- a/apps/api/v2/src/modules/atoms/services/event-types-atom.service.ts +++ b/apps/api/v2/src/modules/atoms/services/event-types-atom.service.ts @@ -210,6 +210,7 @@ export class EventTypesAtomService { credentials = credentials.concat(teamAppCredentials); } } + //TODO: enrich credentials for DWD const enabledApps = await getEnabledAppsFromCredentials(credentials, { where: { slug }, }); diff --git a/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts index 4ad3517bab..aa84ae4fb9 100644 --- a/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts +++ b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts @@ -196,10 +196,10 @@ export class ConferencingController { @UseGuards(ApiAuthGuard) @ApiOperation({ summary: "Set your default conferencing application" }) async default( - @GetUser("id") userId: number, + @GetUser() user: UserWithProfile, @Param("app") app: string ): Promise { - await this.conferencingService.setDefaultConferencingApp(userId, app); + await this.conferencingService.setDefaultConferencingApp(user, app); return { status: SUCCESS_STATUS }; } diff --git a/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts b/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts index a1be0f7098..3f38209c70 100644 --- a/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts +++ b/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts @@ -5,7 +5,12 @@ import { BadRequestException, InternalServerErrorException, Logger } from "@nest import { Injectable } from "@nestjs/common"; import { CONFERENCING_APPS, CAL_VIDEO } from "@calcom/platform-constants"; -import { userMetadata, handleDeleteCredential } from "@calcom/platform-libraries"; +import { + userMetadata, + handleDeleteCredential, + getApps, + getUsersCredentials, +} from "@calcom/platform-libraries"; @Injectable() export class ConferencingService { @@ -25,20 +30,24 @@ export class ConferencingService { return userMetadata.parse(user?.metadata)?.defaultConferencingApp; } - async checkAppIsValidAndConnected(userId: number, app: string) { - if (!CONFERENCING_APPS.includes(app)) { + async checkAppIsValidAndConnected(user: UserWithProfile, appSlug: string) { + if (!CONFERENCING_APPS.includes(appSlug)) { throw new BadRequestException("Invalid app, available apps are: ", CONFERENCING_APPS.join(", ")); } - const credential = await this.conferencingRepository.findConferencingApp(userId, app); + const credentials = await getUsersCredentials(user); - if (!credential) { - throw new BadRequestException(`${app} not connected.`); + const foundApp = getApps(credentials, true).filter((app) => app.slug === appSlug)[0]; + + const appLocation = foundApp?.appData?.location; + + if (!foundApp || !appLocation) { + throw new BadRequestException(`${appSlug} not connected.`); } - return credential; + return foundApp.credential; } async disconnectConferencingApp(user: UserWithProfile, app: string) { - const credential = await this.checkAppIsValidAndConnected(user.id, app); + const credential = await this.checkAppIsValidAndConnected(user, app); return handleDeleteCredential({ userId: user.id, userMetadata: user?.metadata, @@ -46,13 +55,13 @@ export class ConferencingService { }); } - async setDefaultConferencingApp(userId: number, app: string) { + async setDefaultConferencingApp(user: UserWithProfile, app: string) { // cal-video is global, so we can skip this check if (app !== CAL_VIDEO) { - await this.checkAppIsValidAndConnected(userId, app); + await this.checkAppIsValidAndConnected(user, app); } - const user = await this.usersRepository.setDefaultConferencingApp(userId, app); - const metadata = user.metadata as { defaultConferencingApp?: { appSlug?: string } }; + const updatedUser = await this.usersRepository.setDefaultConferencingApp(user.id, app); + const metadata = updatedUser.metadata as { defaultConferencingApp?: { appSlug?: string } }; if (metadata?.defaultConferencingApp?.appSlug !== app) { throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`); } diff --git a/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts index bac55c9d4c..6fad47ba4e 100644 --- a/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts +++ b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts @@ -29,11 +29,12 @@ export class DestinationCalendarsController { @Body() input: DestinationCalendarsInputBodyDto, @GetUser() user: UserWithProfile ): Promise { - const { integration, externalId } = input; + const { integration, externalId, domainWideDelegationCredentialId } = input; const updatedDestinationCalendar = await this.destinationCalendarsService.updateDestinationCalendars( integration, externalId, - user.id + user.id, + domainWideDelegationCredentialId ); return { diff --git a/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts b/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts index 412a886bdc..a95165204c 100644 --- a/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts +++ b/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts @@ -8,21 +8,25 @@ export class DestinationCalendarsRepository { async updateCalendar( integration: string, externalId: string, - credentialId: number, + userId: number, - primaryEmail: string | null + primaryEmail: string | null, + credentialId?: number, + domainWideDelegationCredentialId?: string ) { return await this.dbWrite.prisma.destinationCalendar.upsert({ update: { integration, externalId, - credentialId, + ...(credentialId ? { credentialId } : {}), + ...(domainWideDelegationCredentialId ? { domainWideDelegationCredentialId } : {}), primaryEmail, }, create: { integration, externalId, - credentialId, + ...(credentialId ? { credentialId } : {}), + ...(domainWideDelegationCredentialId ? { domainWideDelegationCredentialId } : {}), primaryEmail, userId, }, diff --git a/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts b/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts index 76cbc16055..cf6d566eb2 100644 --- a/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts +++ b/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts @@ -1,6 +1,6 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { Expose } from "class-transformer"; -import { IsString, IsEnum } from "class-validator"; +import { IsString, IsEnum, IsOptional } from "class-validator"; import { APPLE_CALENDAR_TYPE, @@ -33,4 +33,9 @@ export class DestinationCalendarsInputBodyDto { required: true, }) readonly externalId!: string; + + @IsString() + @IsOptional() + @ApiPropertyOptional() + readonly domainWideDelegationCredentialId?: string; } diff --git a/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts b/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts index b80fe4beb6..a67bde2162 100644 --- a/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts +++ b/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts @@ -10,9 +10,16 @@ export class DestinationCalendarsService { private readonly destinationCalendarsRepository: DestinationCalendarsRepository ) {} - async updateDestinationCalendars(integration: string, externalId: string, userId: number) { + async updateDestinationCalendars( + integration: string, + externalId: string, + userId: number, + domainWideDelegationCredentialId?: string + ) { + // note(Lauris): todo remove the log but leaving this now to confirm domainWideDelegationCredentialId is received + console.log("debug: domainWideDelegationCredentialId", domainWideDelegationCredentialId); const userCalendars = await this.calendarsService.getCalendars(userId); - const allCalendars = userCalendars.connectedCalendars + const allCalendars: Calendar[] = userCalendars.connectedCalendars .map((cal: ConnectedCalendar) => cal.calendars ?? []) .flat(); const credentialId = allCalendars.find( @@ -20,12 +27,26 @@ export class DestinationCalendarsService { cal.externalId === externalId && cal.integration === integration && cal.readOnly === false )?.credentialId; - if (!credentialId) { + if (!domainWideDelegationCredentialId && !credentialId) { throw new NotFoundException(`Could not find calendar ${externalId}`); } - const primaryEmail = - allCalendars.find((cal: Calendar) => cal.primary && cal.credentialId === credentialId)?.email ?? null; + const dwdCalendar = domainWideDelegationCredentialId + ? allCalendars.find( + (cal: Calendar) => + cal.externalId === externalId && + cal.integration === integration && + cal.domainWideDelegationCredentialId === domainWideDelegationCredentialId + ) + : undefined; + + if (domainWideDelegationCredentialId && !dwdCalendar) { + throw new NotFoundException(`Could not find calendar ${externalId}`); + } + + const primaryEmail = dwdCalendar + ? (dwdCalendar.primary && dwdCalendar?.email) || undefined + : allCalendars.find((cal: Calendar) => cal.primary && cal.credentialId === credentialId)?.email; const { integration: updatedCalendarIntegration, @@ -35,9 +56,10 @@ export class DestinationCalendarsService { } = await this.destinationCalendarsRepository.updateCalendar( integration, externalId, - credentialId, userId, - primaryEmail + primaryEmail ?? null, + dwdCalendar ? undefined : credentialId, + dwdCalendar ? domainWideDelegationCredentialId : undefined ); return { diff --git a/apps/api/v2/src/modules/organizations/dwd/inputs/create-dwd.input.ts b/apps/api/v2/src/modules/organizations/dwd/inputs/create-dwd.input.ts new file mode 100644 index 0000000000..81c2f078b0 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/inputs/create-dwd.input.ts @@ -0,0 +1,34 @@ +import { + GoogleServiceAccountKeyInput, + ServiceAccountKeyValidator, + MicrosoftServiceAccountKeyInput, +} from "@/modules/organizations/dwd/inputs/service-account-key.input"; +import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; +import { Expose, Type } from "class-transformer"; +import { IsString, IsNotEmpty, ValidateNested, Validate } from "class-validator"; + +export class CreateDwdInput { + @IsString() + @IsNotEmpty() + @ApiProperty() + @Expose() + workspacePlatformSlug!: string; + + @IsString() + @IsNotEmpty() + @ApiProperty() + @Expose() + domain!: string; + + @Validate(ServiceAccountKeyValidator) + @ApiProperty({ + type: [GoogleServiceAccountKeyInput, MicrosoftServiceAccountKeyInput], + oneOf: [ + { $ref: getSchemaPath(GoogleServiceAccountKeyInput) }, + { $ref: getSchemaPath(MicrosoftServiceAccountKeyInput) }, + ], + }) + @Expose() + @Type(() => Object) + serviceAccountKey!: GoogleServiceAccountKeyInput | MicrosoftServiceAccountKeyInput; +} diff --git a/apps/api/v2/src/modules/organizations/dwd/inputs/service-account-key.input.ts b/apps/api/v2/src/modules/organizations/dwd/inputs/service-account-key.input.ts new file mode 100644 index 0000000000..e505ddc16c --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/inputs/service-account-key.input.ts @@ -0,0 +1,99 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Type, plainToClass, Expose } from "class-transformer"; +import { + IsString, + IsNotEmpty, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, + validateSync, +} from "class-validator"; + +export class MicrosoftServiceAccountKeyInput { + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + private_key!: string; + + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + tenant_id!: string; + + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + client_id!: string; +} + +export class GoogleServiceAccountKeyInput { + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + private_key!: string; + + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + client_email!: string; + + @Expose() + @IsString() + @IsNotEmpty() + @ApiProperty() + client_id!: string; +} + +@ValidatorConstraint({ name: "isServiceAccountKey", async: false }) +export class ServiceAccountKeyValidator implements ValidatorConstraintInterface { + validate(value: any) { + if (!value || typeof value !== "object") return false; + + if ("client_email" in value) { + const googleKey = plainToClass(GoogleServiceAccountKeyInput, value, { + excludeExtraneousValues: true, + }); + const googleErrors = validateSync(googleKey, { + whitelist: true, + forbidNonWhitelisted: true, + skipMissingProperties: false, + }); + + if (googleErrors.length === 0) return true; + this.errors = googleErrors; + return false; + } + + if ("tenant_id" in value) { + const msKey = plainToClass(MicrosoftServiceAccountKeyInput, value, { + excludeExtraneousValues: true, + }); + const msErrors = validateSync(msKey, { + whitelist: true, + forbidNonWhitelisted: true, + skipMissingProperties: false, + }); + + if (msErrors.length === 0) return true; + this.errors = msErrors; + return false; + } + + return false; + } + + private errors?: any[]; + + defaultMessage() { + if (this.errors?.length) { + return this.errors.map((error) => Object.values(error.constraints || {}).join(", ")).join("; "); + } + + return "Service account key must be either a Google service account key (with client_email, private_key, client_id) or Microsoft service account key (with tenant_id, private_key, client_id)"; + } +} diff --git a/apps/api/v2/src/modules/organizations/dwd/inputs/update-dwd.input.ts b/apps/api/v2/src/modules/organizations/dwd/inputs/update-dwd.input.ts new file mode 100644 index 0000000000..e87b37685d --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/inputs/update-dwd.input.ts @@ -0,0 +1,29 @@ +import { + GoogleServiceAccountKeyInput, + MicrosoftServiceAccountKeyInput, + ServiceAccountKeyValidator, +} from "@/modules/organizations/dwd/inputs/service-account-key.input"; +import { ApiPropertyOptional, getSchemaPath } from "@nestjs/swagger"; +import { Expose, plainToClass, Transform, Type } from "class-transformer"; +import { IsBoolean, IsOptional, Validate, ValidateNested } from "class-validator"; + +export class UpdateDwdInput { + @IsBoolean() + @IsOptional() + @ApiPropertyOptional() + @Expose() + enabled?: boolean; + + @IsOptional() + @Validate(ServiceAccountKeyValidator) + @ApiPropertyOptional({ + type: [GoogleServiceAccountKeyInput, MicrosoftServiceAccountKeyInput], + oneOf: [ + { $ref: getSchemaPath(GoogleServiceAccountKeyInput) }, + { $ref: getSchemaPath(MicrosoftServiceAccountKeyInput) }, + ], + }) + @Expose() + @Type(() => Object) + serviceAccountKey?: GoogleServiceAccountKeyInput | MicrosoftServiceAccountKeyInput; +} diff --git a/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.controller.ts b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.controller.ts new file mode 100644 index 0000000000..d5e6344015 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.controller.ts @@ -0,0 +1,76 @@ +import { API_VERSIONS_VALUES } from "@/lib/api-versions"; +import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator"; +import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; +import { Roles } from "@/modules/auth/decorators/roles/roles.decorator"; +import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; +import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard"; +import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard"; +import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard"; +import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard"; +import { UpdateDwdInput } from "@/modules/organizations/dwd/inputs/update-dwd.input"; +import { CreateDwdOutput } from "@/modules/organizations/dwd/outputs/create-dwd.output"; +import { DwdOutput } from "@/modules/organizations/dwd/outputs/dwd.output"; +import { UpdateDwdOutput } from "@/modules/organizations/dwd/outputs/update-dwd.output"; +import { OrganizationsDwdService } from "@/modules/organizations/dwd/services/organizations-dwd.service"; +import { + Controller, + UseGuards, + Param, + ParseIntPipe, + Post, + Body, + HttpCode, + HttpStatus, + Patch, +} from "@nestjs/common"; +import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; +import { User } from "@prisma/client"; +import { plainToClass } from "class-transformer"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; + +import { CreateDwdInput } from "./inputs/create-dwd.input"; + +@Controller({ + path: "/v2/organizations/:orgId/delegation-credentials", + version: API_VERSIONS_VALUES, +}) +@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, PlatformPlanGuard, IsAdminAPIEnabledGuard) +@DocsTags("Orgs / Delegation Credentials") +export class OrganizationsDwdController { + constructor(private readonly dwdService: OrganizationsDwdService) {} + + @Post("/") + @HttpCode(HttpStatus.CREATED) + @Roles("ORG_ADMIN") + @PlatformPlan("SCALE") + @ApiOperation({ summary: "Save delegation credentials for your organization." }) + async createDwd( + @Param("orgId", ParseIntPipe) orgId: number, + @GetUser() dwdServiceAccountUser: User, + @Body() body: CreateDwdInput + ): Promise { + const dwd = await this.dwdService.createDwd(orgId, dwdServiceAccountUser, body); + return { + status: SUCCESS_STATUS, + data: plainToClass(DwdOutput, dwd, { strategy: "excludeAll" }), + }; + } + + @Patch("/:credentialId") + @Roles("ORG_ADMIN") + @PlatformPlan("SCALE") + @ApiOperation({ summary: "Update delegation credentials of your organization." }) + async updateDwd( + @Param("orgId", ParseIntPipe) orgId: number, + @GetUser() dwdServiceAccountUser: User, + @Body() body: UpdateDwdInput, + @Param("credentialId") credentialId: string + ): Promise { + const dwd = await this.dwdService.updateDwd(orgId, credentialId, dwdServiceAccountUser, body); + return { + status: SUCCESS_STATUS, + data: plainToClass(DwdOutput, dwd, { strategy: "excludeAll" }), + }; + } +} diff --git a/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.module.ts b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.module.ts new file mode 100644 index 0000000000..72906a8f20 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.module.ts @@ -0,0 +1,17 @@ +import { MembershipsModule } from "@/modules/memberships/memberships.module"; +import { OrganizationsDwdController } from "@/modules/organizations/dwd/organizations-dwd.controller"; +import { OrganizationsDwdRepository } from "@/modules/organizations/dwd/organizations-dwd.repository"; +import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { RedisModule } from "@/modules/redis/redis.module"; +import { StripeModule } from "@/modules/stripe/stripe.module"; +import { Module } from "@nestjs/common"; + +import { OrganizationsDwdService } from "./services/organizations-dwd.service"; + +@Module({ + imports: [PrismaModule, StripeModule, RedisModule, MembershipsModule], + providers: [OrganizationsDwdService, OrganizationsDwdRepository, OrganizationsRepository], + controllers: [OrganizationsDwdController], +}) +export class OrganizationsDwdModule {} diff --git a/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.repository.ts b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.repository.ts new file mode 100644 index 0000000000..da537651ea --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/organizations-dwd.repository.ts @@ -0,0 +1,28 @@ +import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; +import { Injectable } from "@nestjs/common"; + +import { Prisma } from "@calcom/prisma/client"; + +@Injectable() +export class OrganizationsDwdRepository { + constructor(private readonly dbRead: PrismaReadService) {} + + async findById(dwdId: string) { + return this.dbRead.prisma.domainWideDelegation.findUnique({ where: { id: dwdId } }); + } + + async findByIdWithWorkspacePlatform(dwdId: string) { + return this.dbRead.prisma.domainWideDelegation.findUnique({ + where: { id: dwdId }, + include: { workspacePlatform: true }, + }); + } + + async updateIncludeWorkspacePlatform(dwdId: string, data: Prisma.DomainWideDelegationUncheckedUpdateInput) { + return this.dbRead.prisma.domainWideDelegation.update({ + where: { id: dwdId }, + data, + include: { workspacePlatform: true }, + }); + } +} diff --git a/apps/api/v2/src/modules/organizations/dwd/outputs/create-dwd.output.ts b/apps/api/v2/src/modules/organizations/dwd/outputs/create-dwd.output.ts new file mode 100644 index 0000000000..9651a7d08f --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/outputs/create-dwd.output.ts @@ -0,0 +1,20 @@ +import { DwdOutput } from "@/modules/organizations/dwd/outputs/dwd.output"; +import { ApiProperty } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator"; + +import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; + +export class CreateDwdOutput { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @ApiProperty({ + type: DwdOutput, + }) + @IsNotEmptyObject() + @ValidateNested() + @Type(() => DwdOutput) + data!: DwdOutput; +} diff --git a/apps/api/v2/src/modules/organizations/dwd/outputs/dwd.output.ts b/apps/api/v2/src/modules/organizations/dwd/outputs/dwd.output.ts new file mode 100644 index 0000000000..6a7247c9a3 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/outputs/dwd.output.ts @@ -0,0 +1,56 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Expose, Type } from "class-transformer"; +import { IsBoolean, IsDate, IsInt, IsObject, IsString, ValidateNested } from "class-validator"; + +class WorkspacePlatformDto { + @ApiProperty() + @IsString() + @Expose() + name!: string; + + @ApiProperty() + @IsString() + @Expose() + slug!: string; +} + +export class DwdOutput { + @ApiProperty() + @IsString() + @Expose() + id!: string; + + @ApiProperty() + @IsBoolean() + @Expose() + enabled!: boolean; + + @ApiProperty() + @IsString() + @Expose() + domain!: string; + + @ApiProperty() + @IsInt() + @Expose() + organizationId!: number; + + @ApiProperty({ type: WorkspacePlatformDto }) + @Type(() => WorkspacePlatformDto) + @IsObject() + @ValidateNested() + @Expose() + workspacePlatform!: WorkspacePlatformDto; + + @ApiProperty() + @Type(() => Date) + @IsDate() + @Expose() + createdAt!: Date; + + @ApiProperty() + @Type(() => Date) + @IsDate() + @Expose() + updatedAt!: Date; +} diff --git a/apps/api/v2/src/modules/organizations/dwd/outputs/update-dwd.output.ts b/apps/api/v2/src/modules/organizations/dwd/outputs/update-dwd.output.ts new file mode 100644 index 0000000000..06feaa15d6 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/outputs/update-dwd.output.ts @@ -0,0 +1,20 @@ +import { DwdOutput } from "@/modules/organizations/dwd/outputs/dwd.output"; +import { ApiProperty } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator"; + +import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; + +export class UpdateDwdOutput { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @ApiProperty({ + type: DwdOutput, + }) + @IsNotEmptyObject() + @ValidateNested() + @Type(() => DwdOutput) + data!: DwdOutput; +} diff --git a/apps/api/v2/src/modules/organizations/dwd/services/organizations-dwd.service.ts b/apps/api/v2/src/modules/organizations/dwd/services/organizations-dwd.service.ts new file mode 100644 index 0000000000..70914a4a1a --- /dev/null +++ b/apps/api/v2/src/modules/organizations/dwd/services/organizations-dwd.service.ts @@ -0,0 +1,62 @@ +import { CreateDwdInput } from "@/modules/organizations/dwd/inputs/create-dwd.input"; +import { + GoogleServiceAccountKeyInput, + MicrosoftServiceAccountKeyInput, +} from "@/modules/organizations/dwd/inputs/service-account-key.input"; +import { UpdateDwdInput } from "@/modules/organizations/dwd/inputs/update-dwd.input"; +import { OrganizationsDwdRepository } from "@/modules/organizations/dwd/organizations-dwd.repository"; +import { DwdOutput } from "@/modules/organizations/dwd/outputs/dwd.output"; +import { Injectable, NotFoundException } from "@nestjs/common"; +import { User } from "@prisma/client"; + +import { addDwd, toggleDwdEnabled, encryptServiceAccountKey } from "@calcom/platform-libraries"; + +@Injectable() +export class OrganizationsDwdService { + constructor(private readonly organizationsDwdRepository: OrganizationsDwdRepository) {} + async createDwd(orgId: number, dwdServiceAccountUser: User, body: CreateDwdInput) { + console.log("asap body", JSON.stringify(body, null, 2)); + const dwd = await addDwd({ + input: body, + ctx: { user: { id: dwdServiceAccountUser.id, organizationId: orgId } }, + }); + return dwd; + } + + async updateDwd(orgId: number, dwdId: string, dwdServiceAccountUser: User, body: UpdateDwdInput) { + if (body.enabled !== undefined) { + await this.updateDwdEnabled(orgId, dwdId, dwdServiceAccountUser, body.enabled); + } + if (body.serviceAccountKey !== undefined) { + await this.updateDwdServiceAccountKey(dwdId, body.serviceAccountKey); + } + const dwd = await this.organizationsDwdRepository.findByIdWithWorkspacePlatform(dwdId); + if (!dwd) { + throw new NotFoundException(`DWD with id ${dwdId} not found`); + } + + return dwd; + } + + async updateDwdEnabled(orgId: number, dwdId: string, dwdServiceAccountUser: User, enabled: boolean) { + const handlerUser = { + id: dwdServiceAccountUser.id, + email: dwdServiceAccountUser.email, + organizationId: orgId, + }; + const handlerBody = { id: dwdId, enabled }; + const dwd = await toggleDwdEnabled(handlerUser, handlerBody); + return dwd; + } + + async updateDwdServiceAccountKey( + dwdId: string, + serviceAccountKey: GoogleServiceAccountKeyInput | MicrosoftServiceAccountKeyInput + ) { + const encryptedServiceAccountKey = encryptServiceAccountKey(serviceAccountKey); + const dwd = await this.organizationsDwdRepository.updateIncludeWorkspacePlatform(dwdId, { + serviceAccountKey: encryptedServiceAccountKey, + }); + return dwd; + } +} diff --git a/apps/api/v2/src/modules/organizations/organizations.module.ts b/apps/api/v2/src/modules/organizations/organizations.module.ts index e9326504f2..d468faea35 100644 --- a/apps/api/v2/src/modules/organizations/organizations.module.ts +++ b/apps/api/v2/src/modules/organizations/organizations.module.ts @@ -11,6 +11,7 @@ import { OrganizationAttributesService } from "@/modules/organizations/attribute import { OrganizationAttributeOptionRepository } from "@/modules/organizations/attributes/options/organization-attribute-options.repository"; import { OrganizationsAttributesOptionsController } from "@/modules/organizations/attributes/options/organizations-attributes-options.controller"; import { OrganizationAttributeOptionService } from "@/modules/organizations/attributes/options/services/organization-attributes-option.service"; +import { OrganizationsDwdModule } from "@/modules/organizations/dwd/organizations-dwd.module"; import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller"; import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository"; import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer"; @@ -64,6 +65,7 @@ import { Module } from "@nestjs/common"; EventTypesModule_2024_06_14, TeamsEventTypesModule, TeamsModule, + OrganizationsDwdModule, OrganizationsOrganizationsModule, OrganizationsTeamsRoutingFormsModule, ], diff --git a/apps/api/v2/src/modules/selected-calendars/controllers/selected-calendars.controller.ts b/apps/api/v2/src/modules/selected-calendars/controllers/selected-calendars.controller.ts index 373cda9bff..da13b5e6da 100644 --- a/apps/api/v2/src/modules/selected-calendars/controllers/selected-calendars.controller.ts +++ b/apps/api/v2/src/modules/selected-calendars/controllers/selected-calendars.controller.ts @@ -1,5 +1,3 @@ -import { CalendarsRepository } from "@/ee/calendars/calendars.repository"; -import { CalendarsService } from "@/ee/calendars/services/calendars.service"; import { API_VERSIONS_VALUES } from "@/lib/api-versions"; import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; @@ -11,7 +9,7 @@ import { SelectedCalendarOutputResponseDto, SelectedCalendarOutputDto, } from "@/modules/selected-calendars/outputs/selected-calendars.output"; -import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository"; +import { SelectedCalendarsService } from "@/modules/selected-calendars/services/selected-calendars.service"; import { UserWithProfile } from "@/modules/users/users.repository"; import { Body, Controller, Post, UseGuards, Delete, Query } from "@nestjs/common"; import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; @@ -25,11 +23,7 @@ import { SUCCESS_STATUS } from "@calcom/platform-constants"; }) @DocsTags("Selected Calendars") export class SelectedCalendarsController { - constructor( - private readonly calendarsRepository: CalendarsRepository, - private readonly selectedCalendarsRepository: SelectedCalendarsRepository, - private readonly calendarsService: CalendarsService - ) {} + constructor(private readonly selectedCalendarsService: SelectedCalendarsService) {} @Post("/") @UseGuards(ApiAuthGuard) @@ -38,36 +32,24 @@ export class SelectedCalendarsController { @Body() input: SelectedCalendarsInputDto, @GetUser() user: UserWithProfile ): Promise { - const { integration, externalId, credentialId } = input; - await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id); - - const newlyAddedCalendarEntry = await this.selectedCalendarsRepository.addUserSelectedCalendar( - user.id, - integration, - externalId, - credentialId - ); + const selectedCalendar = await this.selectedCalendarsService.addSelectedCalendar(user, input); return { status: SUCCESS_STATUS, - data: plainToClass(SelectedCalendarOutputDto, newlyAddedCalendarEntry, { strategy: "excludeAll" }), + data: plainToClass(SelectedCalendarOutputDto, selectedCalendar, { strategy: "excludeAll" }), }; } @Delete("/") @UseGuards(ApiAuthGuard) @ApiOperation({ summary: "Delete a selected calendar" }) - async removeSelectedCalendar( + async deleteSelectedCalendar( @Query() queryParams: SelectedCalendarsQueryParamsInputDto, @GetUser() user: UserWithProfile ): Promise { - const { integration, externalId, credentialId } = queryParams; - await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id); - - const removedCalendarEntry = await this.selectedCalendarsRepository.removeUserSelectedCalendar( - user.id, - integration, - externalId + const removedCalendarEntry = await this.selectedCalendarsService.deleteSelectedCalendar( + queryParams, + user ); return { diff --git a/apps/api/v2/src/modules/selected-calendars/inputs/selected-calendars.input.ts b/apps/api/v2/src/modules/selected-calendars/inputs/selected-calendars.input.ts index 1339cc36d7..3d24593667 100644 --- a/apps/api/v2/src/modules/selected-calendars/inputs/selected-calendars.input.ts +++ b/apps/api/v2/src/modules/selected-calendars/inputs/selected-calendars.input.ts @@ -1,4 +1,4 @@ -import { IsInt, IsString } from "class-validator"; +import { IsInt, IsOptional, IsString } from "class-validator"; export class SelectedCalendarsInputDto { @IsString() @@ -9,6 +9,10 @@ export class SelectedCalendarsInputDto { @IsInt() readonly credentialId!: number; + + @IsString() + @IsOptional() + readonly domainWideDelegationCredentialId?: string; } export class SelectedCalendarsQueryParamsInputDto { @@ -20,4 +24,8 @@ export class SelectedCalendarsQueryParamsInputDto { @IsString() readonly credentialId!: string; + + @IsString() + @IsOptional() + readonly domainWideDelegationCredentialId?: string; } diff --git a/apps/api/v2/src/modules/selected-calendars/selected-calendars.module.ts b/apps/api/v2/src/modules/selected-calendars/selected-calendars.module.ts index 0537eb4f8b..0665bb53a7 100644 --- a/apps/api/v2/src/modules/selected-calendars/selected-calendars.module.ts +++ b/apps/api/v2/src/modules/selected-calendars/selected-calendars.module.ts @@ -2,9 +2,13 @@ import { CalendarsRepository } from "@/ee/calendars/calendars.repository"; import { CalendarsService } from "@/ee/calendars/services/calendars.service"; import { AppsRepository } from "@/modules/apps/apps.repository"; import { CredentialsRepository } from "@/modules/credentials/credentials.repository"; +import { OrganizationsDwdRepository } from "@/modules/organizations/dwd/organizations-dwd.repository"; +import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository"; +import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; import { SelectedCalendarsController } from "@/modules/selected-calendars/controllers/selected-calendars.controller"; import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository"; +import { SelectedCalendarsService } from "@/modules/selected-calendars/services/selected-calendars.service"; import { UsersRepository } from "@/modules/users/users.repository"; import { Module } from "@nestjs/common"; @@ -17,6 +21,10 @@ import { Module } from "@nestjs/common"; UsersRepository, CredentialsRepository, AppsRepository, + OrganizationsMembershipService, + OrganizationsDwdRepository, + OrganizationsMembershipRepository, + SelectedCalendarsService, ], controllers: [SelectedCalendarsController], exports: [SelectedCalendarsRepository], diff --git a/apps/api/v2/src/modules/selected-calendars/selected-calendars.repository.ts b/apps/api/v2/src/modules/selected-calendars/selected-calendars.repository.ts index 03134c56a8..7f9b17eeac 100644 --- a/apps/api/v2/src/modules/selected-calendars/selected-calendars.repository.ts +++ b/apps/api/v2/src/modules/selected-calendars/selected-calendars.repository.ts @@ -85,13 +85,19 @@ export class SelectedCalendarsRepository { }); } - async removeUserSelectedCalendar(userId: number, integration: string, externalId: string) { + async removeUserSelectedCalendar( + userId: number, + integration: string, + externalId: string, + domainWideDelegationCredentialId?: string + ) { // Using deleteMany because userId_externalId_integration_eventTypeId is a unique constraint but with eventTypeId being nullable, causing it to be not used as a unique constraint const records = await this.dbWrite.prisma.selectedCalendar.findMany({ where: { userId, externalId, integration, + domainWideDelegationCredentialId, ...ensureUserLevelWhere, }, }); diff --git a/apps/api/v2/src/modules/selected-calendars/services/selected-calendars.service.ts b/apps/api/v2/src/modules/selected-calendars/services/selected-calendars.service.ts new file mode 100644 index 0000000000..ac94840bd8 --- /dev/null +++ b/apps/api/v2/src/modules/selected-calendars/services/selected-calendars.service.ts @@ -0,0 +1,112 @@ +import { CalendarsService } from "@/ee/calendars/services/calendars.service"; +import { OrganizationsDwdRepository } from "@/modules/organizations/dwd/organizations-dwd.repository"; +import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service"; +import { + SelectedCalendarsInputDto, + SelectedCalendarsQueryParamsInputDto, +} from "@/modules/selected-calendars/inputs/selected-calendars.input"; +import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository"; +import { UserWithProfile } from "@/modules/users/users.repository"; +import { Injectable, NotFoundException } from "@nestjs/common"; + +import { SelectedCalendarRepository } from "@calcom/platform-libraries"; + +type SelectedCalendarsInputDwd = SelectedCalendarsInputDto & { domainWideDelegationCredentialId: string }; + +@Injectable() +export class SelectedCalendarsService { + constructor( + private readonly calendarsService: CalendarsService, + private readonly selectedCalendarsRepository: SelectedCalendarsRepository, + private readonly organizationsMembershipService: OrganizationsMembershipService, + private readonly organizationsDwdRepository: OrganizationsDwdRepository + ) {} + + async addSelectedCalendar(user: UserWithProfile, input: SelectedCalendarsInputDto) { + if (input.domainWideDelegationCredentialId) { + const dwdInput = { + ...input, + domainWideDelegationCredentialId: input.domainWideDelegationCredentialId, + }; + return this.addSelectedCalendarDwd(user, dwdInput); + } + return this.addSelectedCalendarUser(user, input); + } + + private async addSelectedCalendarUser(user: UserWithProfile, selectedCalendar: SelectedCalendarsInputDto) { + const { integration, externalId, credentialId } = selectedCalendar; + await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id); + + const userSelectedCalendar = await this.selectedCalendarsRepository.addUserSelectedCalendar( + user.id, + integration, + externalId, + credentialId + ); + + return userSelectedCalendar; + } + + private async addSelectedCalendarDwd(user: UserWithProfile, selectedCalendar: SelectedCalendarsInputDwd) { + const isMemberOfOrganization = await this.isMemberOfDwdOrganization( + user.id, + selectedCalendar.domainWideDelegationCredentialId + ); + if (!isMemberOfOrganization) { + throw new NotFoundException("User is not a member of the organization that owns the DWD credential"); + } + + const { integration, externalId, credentialId, domainWideDelegationCredentialId } = selectedCalendar; + const dwdSelectedCalendar = await SelectedCalendarRepository.upsert({ + userId: user.id, + integration, + externalId, + credentialId, + domainWideDelegationCredentialId, + eventTypeId: null, + }); + return dwdSelectedCalendar; + } + + private async isMemberOfDwdOrganization(userId: number, domainWideDelegationCredentialId: string) { + const dwd = await this.organizationsDwdRepository.findById(domainWideDelegationCredentialId); + if (!dwd) { + throw new NotFoundException("DWD with provided domainWideDelegationCredentialId not found"); + } + + const isMemberOfOrganization = await this.organizationsMembershipService.getOrgMembershipByUserId( + dwd.organizationId, + userId + ); + + return isMemberOfOrganization; + } + + async deleteSelectedCalendar( + selectedCalendar: SelectedCalendarsQueryParamsInputDto, + user: UserWithProfile + ) { + const { integration, externalId, credentialId, domainWideDelegationCredentialId } = selectedCalendar; + + if (!domainWideDelegationCredentialId) { + await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id); + } else { + const isMemberOfOrganization = await this.isMemberOfDwdOrganization( + user.id, + domainWideDelegationCredentialId + ); + if (!isMemberOfOrganization) { + throw new NotFoundException("User is not a member of the organization that owns the DWD credential"); + } + } + + const removedCalendarEntry = await this.selectedCalendarsRepository.removeUserSelectedCalendar( + user.id, + integration, + externalId, + domainWideDelegationCredentialId + ); + + return removedCalendarEntry; + } +} diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 8245c00548..a11b8d2657 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -1091,6 +1091,96 @@ ] } }, + "/v2/organizations/{orgId}/delegation-credentials": { + "post": { + "operationId": "OrganizationsDwdController_createDwd", + "summary": "Save delegation credentials for your organization.", + "parameters": [ + { + "name": "orgId", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDwdInput" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDwdOutput" + } + } + } + } + }, + "tags": [ + "Orgs / Delegation Credentials" + ] + } + }, + "/v2/organizations/{orgId}/delegation-credentials/{credentialId}": { + "patch": { + "operationId": "OrganizationsDwdController_updateDwd", + "summary": "Update delegation credentials of your organization.", + "parameters": [ + { + "name": "orgId", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + }, + { + "name": "credentialId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDwdInput" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDwdOutput" + } + } + } + } + }, + "tags": [ + "Orgs / Delegation Credentials" + ] + } + }, "/v2/organizations/{orgId}/teams/{teamId}/event-types": { "post": { "operationId": "OrganizationsEventTypesController_createTeamEventType", @@ -5819,7 +5909,7 @@ ] }, "delete": { - "operationId": "SelectedCalendarsController_removeSelectedCalendar", + "operationId": "SelectedCalendarsController_deleteSelectedCalendar", "summary": "Delete a selected calendar", "parameters": [ { @@ -5845,6 +5935,14 @@ "schema": { "type": "string" } + }, + { + "name": "domainWideDelegationCredentialId", + "required": false, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -7980,7 +8078,9 @@ "example": "cal-video", "enum": [ "cal-video", - "google-meet" + "google-meet", + "office365-video", + "zoom" ] } }, @@ -11374,6 +11474,9 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string" } }, "required": [ @@ -15018,6 +15121,167 @@ "data" ] }, + "GoogleServiceAccountKeyInput": { + "type": "object", + "properties": { + "private_key": { + "type": "string" + }, + "client_email": { + "type": "string" + }, + "client_id": { + "type": "string" + } + }, + "required": [ + "private_key", + "client_email", + "client_id" + ] + }, + "CreateDwdInput": { + "type": "object", + "properties": { + "workspacePlatformSlug": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "serviceAccountKey": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/GoogleServiceAccountKeyInput" + }, + { + "$ref": "#/components/schemas/MicrosoftServiceAccountKeyInput" + } + ] + } + } + }, + "required": [ + "workspacePlatformSlug", + "domain", + "serviceAccountKey" + ] + }, + "WorkspacePlatformDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "name", + "slug" + ] + }, + "DwdOutput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "domain": { + "type": "string" + }, + "organizationId": { + "type": "number" + }, + "workspacePlatform": { + "$ref": "#/components/schemas/WorkspacePlatformDto" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "enabled", + "domain", + "organizationId", + "workspacePlatform", + "createdAt", + "updatedAt" + ] + }, + "CreateDwdOutput": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "$ref": "#/components/schemas/DwdOutput" + } + }, + "required": [ + "status", + "data" + ] + }, + "UpdateDwdInput": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "serviceAccountKey": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/GoogleServiceAccountKeyInput" + }, + { + "$ref": "#/components/schemas/MicrosoftServiceAccountKeyInput" + } + ] + } + } + } + }, + "UpdateDwdOutput": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "$ref": "#/components/schemas/DwdOutput" + } + }, + "required": [ + "status", + "data" + ] + }, "CreateOrganizationInput": { "type": "object", "properties": { @@ -15619,6 +15883,10 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true } }, "required": [ @@ -15656,6 +15924,10 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true } }, "required": [ @@ -15674,6 +15946,10 @@ "credentialId": { "type": "number" }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true + }, "primary": { "$ref": "#/components/schemas/Primary" }, @@ -15717,6 +15993,10 @@ "type": "number", "nullable": true }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true + }, "name": { "type": "string", "nullable": true @@ -18072,6 +18352,9 @@ "type": "string", "example": "https://caldav.icloud.com/26962146906/calendars/1644422A-1945-4438-BBC0-4F0Q23A57R7S/", "description": "Unique identifier used to represent the specfic calendar, as returned by the /calendars endpoint" + }, + "domainWideDelegationCredentialId": { + "type": "string" } }, "required": [ diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx index e6ee80b705..15c3472443 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx @@ -109,10 +109,6 @@ const getTabs = (orgBranding: OrganizationBranding | null) => { name: "admin_api", href: "https://cal.com/docs/enterprise-features/api/api-reference/bookings#admin-access", }, - // { - // name: "domain_wide_delegation", - // href: "/settings/organizations/domain-wide-delegation", - // }, ], }, { @@ -166,9 +162,16 @@ const getTabs = (orgBranding: OrganizationBranding | null) => { // The following keys are assigned to admin only const adminRequiredKeys = ["admin"]; const organizationRequiredKeys = ["organization"]; -const organizationAdminKeys = ["privacy", "billing", "OAuth Clients", "SSO", "directory_sync"]; +const organizationAdminKeys = [ + "privacy", + "billing", + "OAuth Clients", + "SSO", + "directory_sync", + "domain_wide_delegation", +]; -const useTabs = () => { +const useTabs = ({ isDwdEnabled }: { isDwdEnabled: boolean }) => { const session = useSession(); const { data: user } = trpc.viewer.me.useQuery({ includePasswordAdded: true }); const orgBranding = useOrgBranding(); @@ -199,6 +202,14 @@ const useTabs = () => { }); } + // Add domain-wide-delegation menu item only if feature flag is enabled + if (isDwdEnabled) { + newArray.push({ + name: "domain_wide_delegation", + href: "/settings/organizations/domain-wide-delegation", + }); + } + return { ...tab, children: newArray, @@ -232,7 +243,7 @@ const useTabs = () => { if (isAdmin) return true; return !adminRequiredKeys.includes(tab.name); }); - }, [isAdmin, orgBranding, isOrgAdminOrOwner, user]); + }, [isAdmin, orgBranding, isOrgAdminOrOwner, user, isDwdEnabled]); return processTabsMemod; }; @@ -418,7 +429,6 @@ const SettingsSidebarContainer = ({ }: SettingsSidebarContainerProps) => { const searchParams = useCompatSearchParams(); const { t } = useLocale(); - const tabsWithPermissions = useTabs(); const [otherTeamMenuState, setOtherTeamMenuState] = useState< { teamId: number | undefined; @@ -430,6 +440,8 @@ const SettingsSidebarContainer = ({ enabled: !!session.data?.user?.org && !currentOrgProp, }); + const tabsWithPermissions = useTabs({ isDwdEnabled: !!_currentOrg?.features?.domainWideDelegation }); + const { data: _otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery(undefined, { enabled: !!session.data?.user?.org && !otherTeamsProp, }); diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation-TODO.md b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation-TODO.md new file mode 100644 index 0000000000..10d76ca090 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation-TODO.md @@ -0,0 +1,112 @@ +## Version 1.0 +### Release Plan + 1. Read the document(domain-wide-delegation.md) and acknowledge it. + 3. Deploy: + 1. Follow "Setting up Domain-Wide Delegation for Google Calendar API" in domain-wide-delegation.md to create Service Account and create a workspace. + 2. Merge PR(without Calendar Cache Support) and then deploy. + 4. Enable for i.cal.com: + 1. Disable Calendar Cache for i.cal.com + 2. Enable DWD for i.cal.com first and then test there + 3. Wait for 1-2 days and keep monitoring the errors in Sentry and Axiom. + 5. Merge & Deploy the Calendar Cache support(with DWD) PR + 1. Enable Calendar Cache back for i.cal.com + 2. Observe the errors in Sentry and Axiom. + 6. Enable for a big customer: + 1. Wait for a week and keep monitoring the errors in Sentry and Axiom. + 7. Use delegatedCredentialsFirst instead of delegatedCredentialsLast in EventManager.ts + - Observe errors in Sentry and Axiom. + +## Manual Testing + - V2/V1 APIs - There could be problem if DWD credentials aren't supported there. Because as organization enables DWD and new users won't need to connect their calendars in the old way. e.g. getBusyCalendarTimes seem to be used there in V2. + - [x] Request reshedule - cancellation of meeting in calendar verification + - [x] Isolation of DWD credentials for different organizations + - [x] Using same domain name for different organization isn't allowed. It is restricted during creation of DWD. + - [x] Location Change of a booking to Google Meet(from Cal Video) + - [x] Google Meet/Google Calendar + - [x] Shows up installed on apps/google-meet and apps/instaled/conferencing + - [x] Can't remove the app + - [x] Seated Event + - [x] Seat cancellation removes calendar invite + - [x] Onboarding + - [x] When DWD is not enabled, the flow works. + - [x] When DWD is enabled, Google Calendar is pre-installed and Destination Calendar and Selected Calendar are configurable. On next step, Google Meet is pre-installed and shown at the top and could be set as default. + - [x] User Level Selected Calendar and Destination Calendars are honored - verified through booking the user. + - [ ] Event Type Selected Calendar and Destination Calendar + - [ ] Dynamic Group Booking + - [x] Owner must have verified email to enable DWD + - [x] RR Team Event + - [x] Booking + - Unavailable slot isn't available for booking. Unavailable user isn't used. + - [x] Reroute + - [x] Reassign + - [x] Troubleshooter + - [x] Shows busy times from Calendar + - [ ] Calendar Cache[TO BE TESTED LATER] + - [ ] Event Type Selected Calendar caching test + - [ ] User Selected Calendar caching test + +### Important + - **Performance** + - There could be 100s of users in an organization with already connected calendars. Enabling DWD adds a duplicate credential(in-memory) for each of them. + - Because a credential isn't aware of which externalIds it has access to(without connecting with Google Calendar API itself), we can't identify which credential is for which SelectedCalendar and thus we can't de-duplicate them. This has an impact on fetching the availability and thus for a team event with x participants, we could have 2x requests to Google Calendar API. Because for big value of x, user might be using routing already with team members and thus the actual value of x might be much lower. So, we could be fine. Also, we would have Calendar Cache enabled there already to reduce the requests. + - Bugs + - [x] For RR event, if I am able to see the slots for a blocked day in calendar(by temporarily unselecting the calendar), and then we enable the calendar back, the blocked day can be booked. + - [x] Not using Goole Meet with Default conferencing app. + - [x] Duplicate Calendar Events in Google Calendar when choosing non-primary calendar as destination. No idea why this is happening. + - The issue was in getAttendees in Google CalendarService not using externalCalendarId. + - [x] Restrict toggling based on email verified + - [x] Duplicate Calendar connections in 'apps/installed' if a user already had connected calendar and DWD is enabled. + - [x] Calendar Cache has credentialId column which isn't applicable for DWD(Solution: Added userId there) + - [x] Troubleshooter + - [x] Shows busy times from Claendar + - [x] If a user has connected a calendar, and then DWD is enabled. + - Tested various scenarios for it + - [x] Inviting a new user. + - Verified that Google Calendar is shown pre-installed. + - How about Google Meet(which depends on Google Calendar) - Correctly shows up as installed. + - TODO: + - [ ] Add experimental/Beta flag in DWD + - [ ] Consideration of multiple domains email in a single team event. We might need to consider all the member's domains in the team event when fetching availabilituy. + - [x] Performance + - [x] Available Slots and booking flow shouldn't slow down. Right now the querying logic is not optimized. We query per team member, we should do one query - This is fixed and took quite a refactor but it is cleaner code now. + - [ ] Tag all DWD related logs and errors with "DWD:" + - [x] Troubleshooter + - [x] Google CalendarService unit tests to verify that if DWD credential is provided it uses impersonation to access API otherwise it uses regular user credential API. + - [x] setDestinationCalendar.handler.ts tests to verify that when DWD is enabled it still correctly sets the destination calendar. + - [x] getConnectedDestinationCalendars tests. + - [x] Creating DWD shouldn't immediately enable it. Enabling has separate check to confirm if it is actually configured in google workspace + - [x] Added check to avoid adding same domain for a workspace platform in another organization if it is already enabled in some other organization + - [x] Don't show dwd in menu for non-org-admin users - It errors with something_went_wrong right now + - [x] Don't allow disabled platform to be selected in the UI for creation. + - We have disabled coming the disabled platform to be coming into the list that effectively disables edit of existing dwd and creation of new dwd for that platform. + - [x] Where should we show the user the client ID to enable domain wide delegation? + - [x] It must be shown to the organization owner/admin only + - [x] There could be multiple checkboxes per domain to enable domain wide delegation for a domain + - [x] Which domain to allow + - Any domain can be added by a user + - [x] Support multiple domains in DomainWideDelegation schema for an organization + - [x] Use the domain as well to identify if the domain wide delegation is enabled + - [x] Before enabling Domain-wide delegation, there should be a check to ensure that the clientID has been added to the Workspace Platform + - [x] We should allow setting default conferencing app during onboarding + +### Follow-up release + - [ ] Confirmation for DwD toggling off + - [ ] Confirmation for DwD deletion - Deletion isn't there at the moment. + - [ ] Make Google Meet "show up" as default conferencing app when DWD for Google is enabled. + - [ ] Profile pic from Google with DWD might not be working - Fix it. + +### Security + - [x] We don't let any one user see the added service account key from UI. + - [ ] We intend to implement Workload Identity Federation in the future. + +### Documentation +- After enabling domain-wide delegation, the credential is shown pre-installed and the connection can't be removed(or the app can't be uninstalled by user) +- Steps + - App admin will first create a Workspace Platform and then organization owner/admin can enable domain-wide delegation for a domain + - As soon as domain-wide delegation is created, it would start taking preference over the personal credentials of the organization members and it would be used for that. + +Version-2.0 +- Workload Identity Federation to ensure that the service account key is never stored in DB. + + + diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation.md b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation.md new file mode 100644 index 0000000000..157162ee5b --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/domain-wide-delegation/domain-wide-delegation.md @@ -0,0 +1,104 @@ +## Setting up Domain-Wide Delegation for Google Calendar API + +Step 1: Create a Google Cloud Project + +Before you can create a service account, you'll need to set up a Google Cloud project. + + 1. Create a Google Cloud Project: + - Go to the Google Cloud Console + - Select Create Project + - Give your project a name and select your billing account (if applicable) + - Click Create + 2. Enable the Google Calendar API: + 1. Go to the Google Cloud Console + 2. Select API & Services → Library + 3. Search for "Google Calendar API" + 4. Click Enable + +Step 2: Create a Service Account + +A service account is needed to act on behalf of users + + 1. Navigate to the Service Accounts page: + - In the Google Cloud Console, go to IAM & Admin → Service Accounts + 2. Create a New Service Account: + - Click on Create Service Account + - Give your service account a name and description + - Click Create and Continue + +Step 3: To Be taken by Cal.com instance admin: + - Create a Workspace Platform with slug="google". Slug has to be exactly this. This is how we know we need to use Google Calendar and Google Meet. + +Last Step (To Be Taken By Cal.com organization Owner/Admin): Assign Specific API Permissions via OAuth Scopes: + - Create DWD with workspace platform "google" + - User must be a member of the Google Workspace to be able to enable DWD as there is a validation if the user's calendar can be accessed through the service account + - Get the Client ID from there + - Go to your Google Admin Console (admin-google-com) + - Navigate to Security → Access and Data Controls -> API controls -> Manage Domain-Wide Delegation + - Here, you'll authorize the Client ID(Unique ID) to access the Google Calendar API + - Add the necessary API scopes for Google Calendar(Full access to Google Calendar) + https://www.googleapis.com/auth/calendar + + +## Onboarding Improvement +- Just adding a member to the organization would do the following: + - Member to receive events in their calendar, even if they don't login to their account and complete the onboarding process. + - The booking location would be Google Meet, even if the user hasn't set it as default(Though Cal Video would show up as default, but we still use Google Meet in this case. We will fix it later.) + - It would still not use their calendar for conflict checking, but user can complete the onboarding(just select one calendar there for conflict checking) +- Onboarding process: Google Calendar is pre-installed for any new member of the organization(assuming the user has an email of the DWD domain) and Destination Calendar and Selected Calendar are configurable. On next step, Google Meet is pre-installed and shown at the top and could be set as default. + +## Restrictions after enabling DWD +- Enabling DWD for a particular workspace in Cal.com(only google supported at the moment) disables the user from disconnecting that credential. + +## Who can create DWD and enable it? +- Only the owner/admin of the organization can create DWD +- Only the owner/admin of the organization can enable the created DWD. Following additional requirements are there: + - The client ID must be added to the Google Admin Console + - The user's email must be a member of the Google Workspace + - The user's email must be verified. + +## Disabling DWD + - Disabling DWD maintains the user's preferences in terms of SelectedCalendars and DestinationCalendar. + +## Developer Notes +### Terminology +- DWD Credential: A DWD service account key along with user's email becomes the DWD Credential which is an alternative to regular Credential in DB. +- DWD: Domain Wide Delegation +- non-dwd credential: Regular credentials that are stored in Credentials table + +### How DWD works +- We use the Cal.com user's email to impersonate that user using DWD Credential(which is just a service account key at the moment) + - That gives us read/write permission to get availability of the user and create new events in their calendar. + +### What is a DWD Credential? +- A DWD service account key along with user's email becomes the DWD Credential which is an alternative to regular Credential in DB. +- DWD doesn't completely replace the regular credentials. DWD Credential gives access to the cal.com user's email in Google Calendar. So, if the user needs to connect to some other email's calendar, we need to use the regular credentials. + +### Important Points +- No Credential table entry is created when enabling DWD. The workspace platform's related apps will be considered as "installed" for the users with email matching dwd domain. An in-memory credential like object is created for this purpose. It allows avoiding creation of thousands of records for all the members of the organization when dwd is enabled. +- DWD Credential is applicable to Users only. + - For team, we don't use dwd credential as you can impersonate a user and not team through Dwd credential. Currently supported apps(Google Calendar and Google Meet) don't support team installation, so we could simply allow enabling DWD without any issues. +- Disabling a workspace platform stops it from being used for any new organizations and also disables any DWD using the workspace platform from being edited. + - It still all existing DWDs to keep on working +- Adding any number of DWDs for a particular workspace always gives the same Client ID as DWD uses the workspace's default Service Account. +- We should disable DWD and not delete it when we want to stop using it temporarily. Deleting DWD also removes all the seletedCalendar entries connected to it. + +### How apps/installed loads the credentials +1. Identify the logged in user's email +2. Identify the domainWideDelegations for that email's domain +3. Build in-memory credentials for the domainWideDelegations and use them along with the actual credentials(that user might have connected) of the user +4. We don't show the non-dwd connected calendar(if there is a corresponding dwd connected calendar). Though we use the non-dwd credentials to identify the selected calendars, for the dwd connected calendar. + +### Impact on existing users booking flow +- There should be no impact on availability on enabling DWD because we keep on using the existing credentials along with new DWD credential. +- When booking the event, we sort the credentials with DWD credentials last, so there should be no impact on creating calendar events. + - NOTE: We will followup with sorting the credentials with DWD credentials first in a followup PR(They are preferred because they don't expire) + +### Impact on APIs - [ To Verify ] +- We don't support DWD through APIs yet. So, all existing APIs would continue to work with non-dwd credentials only. + +### Notes when testing locally +- You need to enable the feature through feature flag. +- You could use Acme org and login as owner1-acme@example.com +- Make sure to change the email of the user above to your workspace owner's email(other member's email might also work). This is necessary otherwise you won't be able to enable DWD for the organization. + - Note: After changing the email, you would have to logout and login again as required by NextAuth \ No newline at end of file diff --git a/apps/web/app/api/availability/calendar/route.ts b/apps/web/app/api/availability/calendar/route.ts index 6cc6ea7d87..796b0ce2f3 100644 --- a/apps/web/app/api/availability/calendar/route.ts +++ b/apps/web/app/api/availability/calendar/route.ts @@ -17,6 +17,7 @@ const selectedCalendarSelectSchema = z.object({ integration: z.string(), externalId: z.string(), credentialId: z.coerce.number(), + domainWideDelegationCredentialId: z.string().nullish().default(null), eventTypeId: z.coerce.number().nullish(), }); @@ -62,14 +63,17 @@ async function getHandler() { async function postHandler(req: NextRequest) { const user = await authMiddleware(); + const body = await req.json(); - const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse(body); + const { integration, externalId, credentialId, eventTypeId, domainWideDelegationCredentialId } = + selectedCalendarSelectSchema.parse(body); await SelectedCalendarRepository.upsert({ userId: user.id, integration, externalId, credentialId, + domainWideDelegationCredentialId, eventTypeId: eventTypeId ?? null, }); diff --git a/apps/web/components/apps/AppPage.tsx b/apps/web/components/apps/AppPage.tsx index 65bb84bbff..495376ca1c 100644 --- a/apps/web/components/apps/AppPage.tsx +++ b/apps/web/components/apps/AppPage.tsx @@ -80,12 +80,14 @@ export const AppPage = ({ const searchParams = useCompatSearchParams(); const hasDescriptionItems = descriptionItems && descriptionItems.length > 0; + const utils = trpc.useUtils(); const mutation = useAddAppMutation(null, { - onSuccess: (data) => { + onSuccess: async (data) => { if (data?.setupPending) return; setIsLoading(false); - showToast(t("app_successfully_installed"), "success"); + showToast(data?.message || t("app_successfully_installed"), "success"); + await utils.viewer.appCredentialsByType.invalidate({ appType: type }); }, onError: (error) => { if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error"); @@ -170,6 +172,7 @@ export const AppPage = ({ // variant not other allows, an app to be shown in calendar category without requiring an actual calendar connection e.g. vimcal // Such apps, can only be installed once. + const allowedMultipleInstalls = categories.indexOf("calendar") > -1 && variant !== "other"; useEffect(() => { if (searchParams?.get("defaultInstall") === "true") { diff --git a/apps/web/components/apps/InstallAppButtonChild.tsx b/apps/web/components/apps/InstallAppButtonChild.tsx index 572680ba9f..635ccebff6 100644 --- a/apps/web/components/apps/InstallAppButtonChild.tsx +++ b/apps/web/components/apps/InstallAppButtonChild.tsx @@ -36,10 +36,10 @@ export const InstallAppButtonChild = ({ return ( ); diff --git a/apps/web/components/apps/MultiDisconnectIntegration.tsx b/apps/web/components/apps/MultiDisconnectIntegration.tsx index ed2b8d3762..ad9d48b5ba 100644 --- a/apps/web/components/apps/MultiDisconnectIntegration.tsx +++ b/apps/web/components/apps/MultiDisconnectIntegration.tsx @@ -52,6 +52,15 @@ export function MultiDisconnectIntegration({ credentials, onSuccess }: Props) { }, }); + const getUserDisplayName = (user: (typeof credentials)[number]["user"]) => { + if (!user) return null; + // Check if 'name' property exists on user + if ("name" in user) return user.name; + // Otherwise use email if available + if ("email" in user) return user.email; + return null; + }; + return ( <> @@ -73,12 +82,12 @@ export function MultiDisconnectIntegration({ credentials, onSuccess }: Props) { setCredentialToDelete({ id: cred.id, teamId: cred.teamId, - name: cred.team?.name || cred.user?.name || null, + name: cred.team?.name || getUserDisplayName(cred.user) || null, }); setConfirmationDialogOpen(true); }}>
- {cred.team?.name || cred.user?.name || t("unnamed")} + {cred.team?.name || getUserDisplayName(cred.user) || t("unnamed")}
diff --git a/apps/web/components/getting-started/components/AppConnectionItem.tsx b/apps/web/components/getting-started/components/AppConnectionItem.tsx index 55f6464af5..174f1941fd 100644 --- a/apps/web/components/getting-started/components/AppConnectionItem.tsx +++ b/apps/web/components/getting-started/components/AppConnectionItem.tsx @@ -24,11 +24,19 @@ interface IAppConnectionItem { const AppConnectionItem = (props: IAppConnectionItem) => { const { title, logo, type, installed, isDefault, defaultInstall, slug } = props; const { t } = useLocale(); - const setDefaultConferencingApp = trpc.viewer.appsRouter.setDefaultConferencingApp.useMutation(); + const utils = trpc.useUtils(); + const setDefaultConferencingApp = trpc.viewer.appsRouter.setDefaultConferencingApp.useMutation({ + onSuccess: async () => { + await utils.viewer.me.invalidate(); + }, + onError: (error) => { + showToast(t("something_went_wrong"), "error"); + console.error(error); + }, + }); const dependency = props.dependencyData?.find((data) => !data.installed); const [isInstalling, setInstalling] = useState(false); - const utils = trpc.useUtils(); return (
@@ -37,72 +45,90 @@ const AppConnectionItem = (props: IAppConnectionItem) => {

{title}

{isDefault && {t("default")}}
- { - if (defaultInstall && slug) { - setDefaultConferencingApp.mutate({ slug }); - } - setInstalling(false); - utils.viewer.integrations.invalidate(); - showToast(t("app_successfully_installed"), "success"); - }, - onError: (error) => { - if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error"); - }, - }} - render={(buttonProps) => ( - + )} + /> + {/* It is possible that app is already installed here during onboarding due to Domain Wide Delegation enabled at organization level. We allow the user to set it as default */} + {installed && !isDefault && ( + )} - /> + ); }; diff --git a/apps/web/components/getting-started/components/ConnectedCalendarItem.tsx b/apps/web/components/getting-started/components/ConnectedCalendarItem.tsx index ce3adc6e60..d3f19043ec 100644 --- a/apps/web/components/getting-started/components/ConnectedCalendarItem.tsx +++ b/apps/web/components/getting-started/components/ConnectedCalendarItem.tsx @@ -14,6 +14,7 @@ interface IConnectedCalendarItem { userId?: number | undefined; integration?: string | undefined; externalId: string; + domainWideDelegationCredentialId: string | null; }[]; } @@ -61,6 +62,7 @@ const ConnectedCalendarItem = (prop: IConnectedCalendarItem) => { type={integrationType} isChecked={calendar.isSelected} isLastItemInList={i === calendars.length - 1} + domainWideDelegationCredentialId={calendar.domainWideDelegationCredentialId} /> ))} diff --git a/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx b/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx index 4f81150dc9..4edace7be5 100644 --- a/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx +++ b/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx @@ -17,7 +17,16 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => { const { data: queryConnectedVideoApps, isPending } = trpc.viewer.integrations.useQuery({ variant: "conferencing", onlyInstalled: false, + + /** + * Both props together sort by most popular first, then by installed first. + * So, installed apps are always shown at the top, followed by remaining apps sorted by descending popularity. + * + * This is done because there could be not so popular app already installed by the admin(e.g. through Domain-Wide Delegation) + * and we want to show it at the top so that user can set it as default if he wants to. + */ sortByMostPopular: true, + sortByInstalledFirst: true, }); const { data } = useMeQuery(); const { t } = useLocale(); diff --git a/apps/web/pages/api/book/event.ts b/apps/web/pages/api/book/event.ts index bc71b83f1e..cb02fb0434 100644 --- a/apps/web/pages/api/book/event.ts +++ b/apps/web/pages/api/book/event.ts @@ -4,8 +4,8 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import getIP from "@calcom/lib/getIP"; -import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken"; +import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { CreationSource } from "@calcom/prisma/enums"; async function handler(req: NextApiRequest & { userId?: number }) { diff --git a/apps/web/pages/api/book/recurring-event.ts b/apps/web/pages/api/book/recurring-event.ts index a7775ffa59..bd99ccef6a 100644 --- a/apps/web/pages/api/book/recurring-event.ts +++ b/apps/web/pages/api/book/recurring-event.ts @@ -5,8 +5,8 @@ import { handleNewRecurringBooking } from "@calcom/features/bookings/lib/handleN import type { BookingResponse } from "@calcom/features/bookings/types"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import getIP from "@calcom/lib/getIP"; -import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken"; +import { defaultResponder } from "@calcom/lib/server/defaultResponder"; // @TODO: Didn't look at the contents of this function in order to not break old booking page. diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index ca33f69da3..6c1c1363d8 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2743,6 +2743,26 @@ "limit_team_booking_frequency_description": "Limit how many times members can be booked across all team event types", "booking_limits_updated_successfully": "Booking limits updated successfully", "you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking", + "add_client_id_in_google_workspace_with_below_scope": "Add this Client Id in Google Workspace with the scope below", + "domain_wide_delegation": "Domain-wide delegation", + "domain_wide_delegation_enabled": "Domain-wide delegation enabled", + "domain_wide_delegation_disabled": "Domain-wide delegation disabled", + "domain_wide_delegation_description": "Domain-wide delegation allows you to manage access to Google Workspace calendars for your organization.", + "add_domain_wide_delegation": "Add domain-wide delegation", + "edit_domain_wide_delegation": "Edit domain-wide delegation", + "domain": "Domain", + "no_workspace_platforms": "No workspace platforms", + "workspace_platform": "Workspace platform", + "workspace_platforms": "Workspace platforms", + "workspace_platforms_description": "Manage workspace platforms that can be used for domain-wide delegation", + "edit_workspace_platform": "Edit workspace platform", + "edit_service_account": "Edit service account", + "add_workspace_platform": "Add workspace platform", + "slug": "Slug", + "service_account_key": "Service account key JSON", + "edit_service_account_key": "Edit service account key JSON", + "domain_wide_delegation_restricts_adding_more_than_one_installation": "Domain-wide delegation restricts adding more than one installation", + "app_successfully_installed_and_is_using_delegated_credentials": "App successfully installed and is using delegated credentials", "matching_members": "Matching members", "x_matching_members": "{{x}} matching members", "matching_members_queue_using_attribute_weights": "Matching members queue (using attribute weights)", @@ -2953,6 +2973,7 @@ "k_bar_ai_error": "An unexpected error occured. Please try again later", "select_oAuth_client": "Select Oauth Client", "on_every_instance": "On every instance", + "domain_wide_delegation_feature_not_enabled": "Domain-wide delegation feature is not enabled for your organization", "internal_booking_note": "Internal Note", "internal_note_cancellation_reason": "Reason shared with customer (optional)", "internal_booking_note_description": "This note will be visible to only members of your team", diff --git a/apps/web/test/lib/getSchedule/domain-wide-delegation.test.ts b/apps/web/test/lib/getSchedule/domain-wide-delegation.test.ts new file mode 100644 index 0000000000..b870455811 --- /dev/null +++ b/apps/web/test/lib/getSchedule/domain-wide-delegation.test.ts @@ -0,0 +1,208 @@ +import { + getDate, + createBookingScenario, + Timezones, + TestData, + mockCalendar, + createDwdCredential, + createOrganization, + getOrganizer, +} from "../../utils/bookingScenario/bookingScenario"; +import { expectNoAttemptToGetAvailability } from "../../utils/bookingScenario/expects"; + +import { describe, test } from "vitest"; + +import { MembershipRole } from "@calcom/prisma/enums"; +import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util"; + +import { expect, expectedSlotsForSchedule } from "./expects"; +import { setupAndTeardown } from "./setupAndTeardown"; + +describe("getSchedule", () => { + setupAndTeardown(); + describe("Domain Wide Delegation", () => { + test("correctly identifies unavailable slots using DWD credentials", async () => { + const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); + const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const payloadToMakePartOfOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: org.id, + name: "Test Org", + slug: "testorg", + }, + }, + ]; + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + // User must be part of organization to be able to use that organization's DWD credential + teams: payloadToMakePartOfOrganization, + // No regular credentials provided + credentials: [], + }); + + await createDwdCredential(org.id); + + const googleCalendarMock = mockCalendar("googlecalendar", { + create: { + uid: "MOCK_ID", + iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + }, + busySlots: [ + { + start: `${plus2DateString}T04:45:00.000Z`, + end: `${plus2DateString}T23:00:00.000Z`, + }, + ], + }); + + const scenarioData = { + eventTypes: [ + { + id: 1, + slotInterval: 45, + length: 45, + users: [ + { + id: organizer.id, + }, + ], + }, + ], + users: [organizer], + apps: [TestData.apps["google-calendar"]], + }; + + await createBookingScenario(scenarioData); + + const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({ + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + orgSlug: null, + }, + }); + + // As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available + expect(scheduleForDayWithAGoogleCalendarBooking).toHaveTimeSlots([`04:00:00.000Z`], { + dateString: plus2DateString, + }); + }); + + test("fails to get schedule when user isn't part of the organization with DWD credential", async () => { + const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); + const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const anotherOrg = await createOrganization({ + name: "Another Org", + slug: "anotherorg", + }); + + // User is part of a different org + const payloadToMakePartOfDifferentOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: anotherOrg.id, + name: "Another Org", + slug: "anotherorg", + }, + }, + ]; + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + teams: payloadToMakePartOfDifferentOrganization, + credentials: [], + }); + + // Create DWD credential for the org user isn't part of + await createDwdCredential(org.id); + + const googleCalendarMock = mockCalendar("googlecalendar", { + create: { + uid: "MOCK_ID", + iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + }, + busySlots: [ + { + start: `${plus2DateString}T04:45:00.000Z`, + end: `${plus2DateString}T23:00:00.000Z`, + }, + ], + }); + + const scenarioData = { + eventTypes: [ + { + id: 1, + slotInterval: 60, + length: 60, + users: [ + { + id: organizer.id, + }, + ], + }, + ], + users: [organizer], + apps: [TestData.apps["google-calendar"]], + }; + + await createBookingScenario(scenarioData); + + const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({ + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T18:30:00.000Z`, + endTime: `${plus2DateString}T18:29:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + orgSlug: null, + }, + }); + + expectNoAttemptToGetAvailability(googleCalendarMock); + + // All slots would be available as no DWD credentials are available + expect(scheduleForDayWithAGoogleCalendarBooking).toHaveTimeSlots( + expectedSlotsForSchedule.IstWorkHours.interval["1hr"].allPossibleSlotsStartingAt430, + { + dateString: plus2DateString, + } + ); + }); + }); +}); diff --git a/apps/web/test/utils/bookingScenario/bookingScenario.ts b/apps/web/test/utils/bookingScenario/bookingScenario.ts index a94606e9a5..4033b0d7aa 100644 --- a/apps/web/test/utils/bookingScenario/bookingScenario.ts +++ b/apps/web/test/utils/bookingScenario/bookingScenario.ts @@ -30,8 +30,10 @@ import type { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import type { userMetadataType } from "@calcom/prisma/zod-utils"; import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils"; import type { AppMeta } from "@calcom/types/App"; +import type { CalendarEvent, IntegrationCalendar } from "@calcom/types/Calendar"; import type { NewCalendarEventType } from "@calcom/types/Calendar"; import type { EventBusyDate, IntervalLimit } from "@calcom/types/Calendar"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import { getMockPaymentService } from "./MockPaymentService"; import type { getMockRequestDataForBooking } from "./getMockRequestDataForBooking"; @@ -41,6 +43,17 @@ vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({ default: {}, })); +vi.mock("@calcom/lib/crypto", async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const actual = await importOriginal(); + return { + ...actual, + symmetricEncrypt: vi.fn((serviceAccountKey) => serviceAccountKey), + symmetricDecrypt: vi.fn((serviceAccountKey) => serviceAccountKey), + }; +}); + type Fields = z.infer; logger.settings.minLevel = 1; @@ -906,7 +919,8 @@ export async function createOrganization(orgData: { slug: string; metadata?: z.infer; withTeam?: boolean; -}): Promise }> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any +}): Promise; children: any }> { const org = await prismock.team.create({ data: { name: orgData.name, @@ -933,7 +947,12 @@ export async function createOrganization(orgData: { }); } assertNonNullableSlug(org); - return org; + const team = await prismock.team.findUnique({ where: { id: org.id }, include: { children: true } }); + if (!team) { + throw new Error(`Team with id ${org.id} not found`); + } + assertNonNullableSlug(team); + return team; } export async function createCredentials( @@ -1119,6 +1138,10 @@ export const TestData = { integration: "google_calendar", externalId: "john@example.com", }, + office365: { + integration: "office365_calendar", + externalId: "john@example.com", + }, }, credentials: { google: getGoogleCalendarCredential(), @@ -1320,6 +1343,16 @@ export const TestData = { redirect_uris: ["http://localhost:3000/auth/callback"], }, }, + "office365-calendar": { + ...appStoreMetadata.office365calendar, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + keys: { + expiry_date: Infinity, + client_id: "client_id", + client_secret: "client_secret", + }, + }, "google-meet": { ...appStoreMetadata.googlevideo, // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -1550,6 +1583,52 @@ export const enum BookingLocations { GoogleMeet = "integrations:google:meet", } +export type CalendarServiceMethodMockCallBase = { + calendarServiceConstructorArgs: { + credential: CredentialForCalendarService; + }; +}; + +type CreateEventMethodMockCall = CalendarServiceMethodMockCallBase & { + args: { + calEvent: CalendarEvent; + credentialId: number; + externalCalendarId?: string; + }; +}; + +type UpdateEventMethodMockCall = CalendarServiceMethodMockCallBase & { + args: { + uid: string; + event: CalendarEvent; + externalCalendarId?: string; + }; +}; + +type DeleteEventMethodMockCall = CalendarServiceMethodMockCallBase & { + args: { + uid: string; + event: CalendarEvent; + externalCalendarId?: string; + }; +}; + +type GetAvailabilityMethodMockCall = CalendarServiceMethodMockCallBase & { + args: { + dateFrom: string; + dateTo: string; + selectedCalendars: IntegrationCalendar[]; + shouldServeCache?: boolean; + }; +}; + +export type CalendarServiceMethodMock = { + createEventCalls: CreateEventMethodMockCall[]; + updateEventCalls: UpdateEventMethodMockCall[]; + deleteEventCalls: DeleteEventMethodMockCall[]; + getAvailabilityCalls: GetAvailabilityMethodMockCall[]; +}; + /** * @param metadataLookupKey * @param calendarData Specify uids and other data to be faked to be returned by createEvent and updateEvent @@ -1561,18 +1640,34 @@ export function mockCalendar( id?: string; uid?: string; iCalUID?: string; + appSpecificData?: { + googleCalendar?: { + hangoutLink?: string; + }; + office365Calendar?: { + url?: string; + }; + }; }; update?: { id?: string; uid: string; iCalUID?: string; + appSpecificData?: { + googleCalendar?: { + hangoutLink?: string; + }; + office365Calendar?: { + url?: string; + }; + }; }; busySlots?: { start: `${string}Z`; end: `${string}Z` }[]; creationCrash?: boolean; updationCrash?: boolean; getAvailabilityCrash?: boolean; } -) { +): CalendarServiceMethodMock { const appStoreLookupKey = metadataLookupKey; const normalizedCalendarData = calendarData || { create: { @@ -1583,12 +1678,11 @@ export function mockCalendar( }, }; log.silly(`Mocking ${appStoreLookupKey} on appStoreMock`); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const createEventCalls: any[] = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updateEventCalls: any[] = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const deleteEventCalls: any[] = []; + + const createEventCalls: CreateEventMethodMockCall[] = []; + const updateEventCalls: UpdateEventMethodMockCall[] = []; + const deleteEventCalls: DeleteEventMethodMockCall[] = []; + const getAvailabilityCalls: GetAvailabilityMethodMockCall[] = []; const app = appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata]; const appMock = appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default]; @@ -1599,20 +1693,37 @@ export function mockCalendar( lib: { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore - CalendarService: function MockCalendarService() { + CalendarService: function MockCalendarService(credential: CredentialForCalendarService) { return { // eslint-disable-next-line @typescript-eslint/no-explicit-any createEvent: async function (...rest: any[]): Promise { if (calendarData?.creationCrash) { throw new Error("MockCalendarService.createEvent fake error"); } - const [calEvent, credentialId] = rest; - log.silly("mockCalendar.createEvent", JSON.stringify({ calEvent, credentialId })); - createEventCalls.push(rest); + const [calEvent, credentialId, externalCalendarId] = rest; + log.debug( + "mockCalendar.createEvent", + JSON.stringify({ calEvent, credentialId, externalCalendarId }) + ); + createEventCalls.push({ + args: { + calEvent, + credentialId, + externalCalendarId, + }, + calendarServiceConstructorArgs: { + credential, + }, + }); + const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet; return Promise.resolve({ type: app.type, additionalInfo: {}, uid: "PROBABLY_UNUSED_UID", + hangoutLink: + (isGoogleMeetLocation + ? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink + : null) || "https://UNUSED_URL", // A Calendar is always expected to return an id. id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID", iCalUID: normalizedCalendarData.create?.iCalUID, @@ -1629,21 +1740,32 @@ export function mockCalendar( const [uid, event, externalCalendarId] = rest; log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId })); // eslint-disable-next-line prefer-rest-params - updateEventCalls.push(rest); + updateEventCalls.push({ + args: { + uid, + event, + externalCalendarId, + }, + calendarServiceConstructorArgs: { + credential, + }, + }); const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet; return Promise.resolve({ type: app.type, additionalInfo: {}, uid: "PROBABLY_UNUSED_UID", iCalUID: normalizedCalendarData.update?.iCalUID, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID", // Password and URL seems useless for CalendarService, plan to remove them if that's the case password: "MOCK_PASSWORD", url: "https://UNUSED_URL", location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined, - hangoutLink: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined, + hangoutLink: + (isGoogleMeetLocation + ? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink + : null) || "https://UNUSED_URL", conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined, }); }, @@ -1651,12 +1773,33 @@ export function mockCalendar( deleteEvent: async (...rest: any[]) => { log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest })); // eslint-disable-next-line prefer-rest-params - deleteEventCalls.push(rest); + deleteEventCalls.push({ + args: { + uid: rest[0], + event: rest[1], + externalCalendarId: rest[2], + }, + calendarServiceConstructorArgs: { + credential, + }, + }); }, - getAvailability: async (): Promise => { + getAvailability: async (...rest: any[]): Promise => { if (calendarData?.getAvailabilityCrash) { throw new Error("MockCalendarService.getAvailability fake error"); } + const [dateFrom, dateTo, selectedCalendars, shouldServeCache] = rest; + getAvailabilityCalls.push({ + args: { + dateFrom, + dateTo, + selectedCalendars, + shouldServeCache, + }, + calendarServiceConstructorArgs: { + credential, + }, + }); return new Promise((resolve) => { resolve(calendarData?.busySlots || []); }); @@ -1669,6 +1812,7 @@ export function mockCalendar( createEventCalls, deleteEventCalls, updateEventCalls, + getAvailabilityCalls, }; } @@ -2164,3 +2308,123 @@ export const getDefaultBookingFields = ({ ...bookingFields, ] as Fields; }; + +export const createDwdCredential = async (orgId: number, type: "google" | "office365" = "google") => { + if (type === "google") { + const encryptedServiceAccountKey = { + type: "service_account", + auth_uri: "https://accounts.google.com/o/oauth2/auth", + client_id: "CLIENT_ID", + token_uri: "https://oauth2.googleapis.com/token", + project_id: "PROJECT_ID", + encrypted_credentials: `{"private_key": "PRIVATE_KEY"}`, + client_email: "CLIENT_EMAIL", + private_key_id: "PRIVATE_KEY_ID", + universe_domain: "googleapis.com", + client_x509_cert_url: "CLIENT_X509_CERT_URL", + auth_provider_x509_cert_url: "AUTH_PROVIDER_X509_CERT_URL", + }; + + const workspace = await prismock.workspacePlatform.create({ + data: { + name: "Test Workspace", + slug: "google", + description: "Test Workspace", + defaultServiceAccountKey: encryptedServiceAccountKey, + enabled: true, + }, + }); + + const decryptedServiceAccountKey = { + type: "service_account", + auth_uri: "https://accounts.google.com/o/oauth2/auth", + client_id: "CLIENT_ID", + token_uri: "https://oauth2.googleapis.com/token", + project_id: "PROJECT_ID", + private_key: "PRIVATE_KEY", + client_email: "CLIENT_EMAIL", + private_key_id: "PRIVATE_KEY_ID", + universe_domain: "googleapis.com", + client_x509_cert_url: "CLIENT_X509_CERT_URL", + auth_provider_x509_cert_url: "AUTH_PROVIDER_X509_CERT_URL", + }; + + const dwd = await prismock.domainWideDelegation.create({ + data: { + workspacePlatform: { + connect: { + id: workspace.id, + }, + }, + domain: "example.com", + enabled: true, + organization: { + connect: { + id: orgId, + }, + }, + // @ts-expect-error - TODO: fix this + serviceAccountKey: workspace.defaultServiceAccountKey, + }, + }); + + return { ...dwd, serviceAccountKey: decryptedServiceAccountKey }; + } else if (type === "office365") { + const encryptedServiceAccountKey = { + client_id: "CLIENT_ID", + encrypted_credentials: `{"private_key": "PRIVATE_KEY"}`, + tenant_id: "TENANT_ID", + }; + + const workspace = await prismock.workspacePlatform.create({ + data: { + name: "Test Workspace", + slug: "office365", + description: "Test Workspace", + defaultServiceAccountKey: encryptedServiceAccountKey, + enabled: true, + }, + }); + + const decryptedServiceAccountKey = { + client_id: "CLIENT_ID", + private_key: "PRIVATE_KEY", + tenant_id: "TENANT_ID", + }; + + const dwd = await prismock.domainWideDelegation.create({ + data: { + workspacePlatform: { + connect: { + id: workspace.id, + }, + }, + domain: "example.com", + enabled: true, + organization: { + connect: { + id: orgId, + }, + }, + // @ts-expect-error - TODO: fix this + serviceAccountKey: workspace.defaultServiceAccountKey, + }, + }); + + return { ...dwd, serviceAccountKey: decryptedServiceAccountKey }; + } + throw new Error(`Unsupported type: ${type}`); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const buildDwdCredential = ({ serviceAccountKey }: { serviceAccountKey: any }) => { + return { + id: -1, + key: { + access_token: "NOOP_UNUSED_DELEGATION_TOKEN", + }, + delegatedTo: { + serviceAccountKey, + }, + }; +}; diff --git a/apps/web/test/utils/bookingScenario/expects.ts b/apps/web/test/utils/bookingScenario/expects.ts index 7d36ffcef8..d7937de506 100644 --- a/apps/web/test/utils/bookingScenario/expects.ts +++ b/apps/web/test/utils/bookingScenario/expects.ts @@ -1,6 +1,6 @@ import prismaMock from "../../../../../tests/libs/__mocks__/prisma"; -import type { InputEventType, getOrganizer } from "./bookingScenario"; +import type { InputEventType, getOrganizer, CalendarServiceMethodMock } from "./bookingScenario"; import type { WebhookTriggerEvents, Booking, BookingReference, DestinationCalendar } from "@prisma/client"; import { parse } from "node-html-parser"; @@ -16,6 +16,7 @@ import { safeStringify } from "@calcom/lib/safeStringify"; import { BookingStatus } from "@calcom/prisma/enums"; import type { AppsStatus } from "@calcom/types/Calendar"; import type { CalendarEvent } from "@calcom/types/Calendar"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import type { Fixtures } from "@calcom/web/test/fixtures/fixtures"; import { DEFAULT_TIMEZONE_BOOKER } from "./getMockRequestDataForBooking"; @@ -448,6 +449,7 @@ export function expectSuccessfulBookingCreationEmails({ bookingTimeRange, booking, destinationEmail, + calendarType, }: { emails: Fixtures["emails"]; organizer: { email: string; name: string; timeZone: string }; @@ -460,6 +462,7 @@ export function expectSuccessfulBookingCreationEmails({ bookingTimeRange?: { start: Date; end: Date }; booking: { uid: string; urlOrigin?: string }; destinationEmail?: string; + calendarType?: string; }) { const bookingUrlOrigin = booking.urlOrigin || WEBSITE_URL; expect(emails).toHaveEmail( @@ -495,12 +498,16 @@ export function expectSuccessfulBookingCreationEmails({ } : null), to: `${destinationEmail ?? organizer.email}`, - ics: { - filename: "event.ics", - iCalUID: `${iCalUID}`, - recurrence, - method: "REQUEST", - }, + ...(calendarType !== "office365_calendar" + ? { + ics: { + filename: "event.ics", + iCalUID: `${iCalUID}`, + recurrence, + method: "REQUEST", + }, + } + : {}), }, `${destinationEmail ?? organizer.email}` ); @@ -1170,32 +1177,32 @@ export function expectBookingPaymentIntiatedWebhookToHaveBeenFired({ }); } +type ExpectedForSuccessfulCalendarEventCreationInCalendar = { + calendarId?: string | null; + /** + * explciityl set to null if you don't want to match on videoCallUrl + */ + videoCallUrl: string | null; + destinationCalendars?: Partial[]; + credential?: Partial; +}; + export function expectSuccessfulCalendarEventCreationInCalendar( - calendarMock: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createEventCalls: any[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - updateEventCalls: any[]; - }, + calendarMock: CalendarServiceMethodMock, expected: - | { - calendarId?: string | null; - videoCallUrl: string; - destinationCalendars?: Partial[]; - } - | { - calendarId?: string | null; - videoCallUrl: string; - destinationCalendars?: Partial[]; - }[] + | ExpectedForSuccessfulCalendarEventCreationInCalendar + | ExpectedForSuccessfulCalendarEventCreationInCalendar[] ) { const expecteds = expected instanceof Array ? expected : [expected]; expect(calendarMock.createEventCalls.length).toBe(expecteds.length); for (let i = 0; i < calendarMock.createEventCalls.length; i++) { const expected = expecteds[i]; - - const calEvent = calendarMock.createEventCalls[i][0]; - + const createEventCall = calendarMock.createEventCalls[i]; + const { credential } = createEventCall.calendarServiceConstructorArgs; + const { calEvent } = createEventCall.args; + if (expected.credential) { + expect(credential).toEqual(expect.objectContaining(expected.credential)); + } expect(calEvent).toEqual( expect.objectContaining({ destinationCalendar: expected.calendarId @@ -1207,21 +1214,21 @@ export function expectSuccessfulCalendarEventCreationInCalendar( : expected.destinationCalendars ? expect.arrayContaining(expected.destinationCalendars.map((cal) => expect.objectContaining(cal))) : null, - videoCallData: expect.objectContaining({ - url: expected.videoCallUrl, - }), + + ...(expected.videoCallUrl !== null + ? { + videoCallData: expect.objectContaining({ + url: expected.videoCallUrl, + }), + } + : {}), }) ); } } export function expectSuccessfulCalendarEventUpdationInCalendar( - calendarMock: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createEventCalls: any[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - updateEventCalls: any[]; - }, + calendarMock: CalendarServiceMethodMock, expected: { externalCalendarId: string; calEvent: Partial; @@ -1230,23 +1237,14 @@ export function expectSuccessfulCalendarEventUpdationInCalendar( ) { expect(calendarMock.updateEventCalls.length).toBe(1); const call = calendarMock.updateEventCalls[0]; - const uid = call[0]; - const calendarEvent = call[1]; - const externalId = call[2]; + const { uid, event, externalCalendarId } = call.args; expect(uid).toBe(expected.uid); - expect(calendarEvent).toEqual(expect.objectContaining(expected.calEvent)); - expect(externalId).toBe(expected.externalCalendarId); + expect(event).toEqual(expect.objectContaining(expected.calEvent)); + expect(externalCalendarId).toBe(expected.externalCalendarId); } export function expectSuccessfulCalendarEventDeletionInCalendar( - calendarMock: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createEventCalls: any[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - updateEventCalls: any[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - deleteEventCalls: any[]; - }, + calendarMock: CalendarServiceMethodMock, expected: { externalCalendarId: string; calEvent: Partial; @@ -1255,12 +1253,10 @@ export function expectSuccessfulCalendarEventDeletionInCalendar( ) { expect(calendarMock.deleteEventCalls.length).toBe(1); const call = calendarMock.deleteEventCalls[0]; - const uid = call[0]; - const calendarEvent = call[1]; - const externalId = call[2]; + const { uid, event, externalCalendarId } = call.args; expect(uid).toBe(expected.uid); - expect(calendarEvent).toEqual(expect.objectContaining(expected.calEvent)); - expect(externalId).toBe(expected.externalCalendarId); + expect(event).toEqual(expect.objectContaining(expected.calEvent)); + expect(externalCalendarId).toBe(expected.externalCalendarId); } export function expectSuccessfulVideoMeetingCreation( @@ -1351,3 +1347,27 @@ export function expectICalUIDAsString(iCalUID: string | undefined | null) { return iCalUID; } + +export async function expectBookingToNotHaveReference( + booking: Pick, + reference: Partial +) { + const actualBooking = await prismaMock.booking.findUnique({ + where: { + uid: booking.uid, + }, + include: { + references: true, + }, + }); + + expect(actualBooking?.references).not.toEqual(expect.arrayContaining([expect.objectContaining(reference)])); +} + +export function expectNoAttemptToCreateCalendarEvent(calendarMock: CalendarServiceMethodMock) { + expect(calendarMock.createEventCalls.length).toBe(0); +} + +export function expectNoAttemptToGetAvailability(calendarMock: CalendarServiceMethodMock) { + expect(calendarMock.getAvailabilityCalls.length).toBe(0); +} diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index 15fedb7392..61bbc77ffd 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -1037,6 +1037,92 @@ "tags": ["Orgs / Attributes / Options"] } }, + "/v2/organizations/{orgId}/delegation-credentials": { + "post": { + "operationId": "OrganizationsDwdController_createDwd", + "summary": "Save delegation credentials for your organization.", + "parameters": [ + { + "name": "orgId", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDwdInput" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDwdOutput" + } + } + } + } + }, + "tags": ["Orgs / Delegation Credentials"] + } + }, + "/v2/organizations/{orgId}/delegation-credentials/{credentialId}": { + "patch": { + "operationId": "OrganizationsDwdController_updateDwd", + "summary": "Update delegation credentials of your organization.", + "parameters": [ + { + "name": "orgId", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + }, + { + "name": "credentialId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDwdInput" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDwdOutput" + } + } + } + } + }, + "tags": ["Orgs / Delegation Credentials"] + } + }, "/v2/organizations/{orgId}/teams/{teamId}/event-types": { "post": { "operationId": "OrganizationsEventTypesController_createTeamEventType", @@ -5487,7 +5573,7 @@ "tags": ["Selected Calendars"] }, "delete": { - "operationId": "SelectedCalendarsController_removeSelectedCalendar", + "operationId": "SelectedCalendarsController_deleteSelectedCalendar", "summary": "Delete a selected calendar", "parameters": [ { @@ -5513,6 +5599,14 @@ "schema": { "type": "string" } + }, + { + "name": "domainWideDelegationCredentialId", + "required": false, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -7476,7 +7570,7 @@ "integration": { "type": "string", "example": "cal-video", - "enum": ["cal-video", "google-meet"] + "enum": ["cal-video", "google-meet", "office365-video", "zoom"] } }, "required": ["type", "integration"] @@ -10456,6 +10550,9 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string" } }, "required": ["integration", "externalId", "credentialId"] @@ -13569,6 +13666,144 @@ }, "required": ["status", "data"] }, + "GoogleServiceAccountKeyInput": { + "type": "object", + "properties": { + "private_key": { + "type": "string" + }, + "client_email": { + "type": "string" + }, + "client_id": { + "type": "string" + } + }, + "required": ["private_key", "client_email", "client_id"] + }, + "CreateDwdInput": { + "type": "object", + "properties": { + "workspacePlatformSlug": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "serviceAccountKey": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/GoogleServiceAccountKeyInput" + }, + { + "$ref": "#/components/schemas/MicrosoftServiceAccountKeyInput" + } + ] + } + } + }, + "required": ["workspacePlatformSlug", "domain", "serviceAccountKey"] + }, + "WorkspacePlatformDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": ["name", "slug"] + }, + "DwdOutput": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "domain": { + "type": "string" + }, + "organizationId": { + "type": "number" + }, + "workspacePlatform": { + "$ref": "#/components/schemas/WorkspacePlatformDto" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "enabled", + "domain", + "organizationId", + "workspacePlatform", + "createdAt", + "updatedAt" + ] + }, + "CreateDwdOutput": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": ["success", "error"] + }, + "data": { + "$ref": "#/components/schemas/DwdOutput" + } + }, + "required": ["status", "data"] + }, + "UpdateDwdInput": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "serviceAccountKey": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/GoogleServiceAccountKeyInput" + }, + { + "$ref": "#/components/schemas/MicrosoftServiceAccountKeyInput" + } + ] + } + } + } + }, + "UpdateDwdOutput": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": ["success", "error"] + }, + "data": { + "$ref": "#/components/schemas/DwdOutput" + } + }, + "required": ["status", "data"] + }, "CreateOrganizationInput": { "type": "object", "properties": { @@ -14073,6 +14308,10 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true } }, "required": ["externalId", "primary", "readOnly", "isSelected", "credentialId"] @@ -14104,6 +14343,10 @@ }, "credentialId": { "type": "number" + }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true } }, "required": ["externalId", "readOnly", "isSelected", "credentialId"] @@ -14117,6 +14360,10 @@ "credentialId": { "type": "number" }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true + }, "primary": { "$ref": "#/components/schemas/Primary" }, @@ -14157,6 +14404,10 @@ "type": "number", "nullable": true }, + "domainWideDelegationCredentialId": { + "type": "string", + "nullable": true + }, "name": { "type": "string", "nullable": true @@ -16233,6 +16484,9 @@ "type": "string", "example": "https://caldav.icloud.com/26962146906/calendars/1644422A-1945-4438-BBC0-4F0Q23A57R7S/", "description": "Unique identifier used to represent the specfic calendar, as returned by the /calendars endpoint" + }, + "domainWideDelegationCredentialId": { + "type": "string" } }, "required": ["integration", "externalId"] diff --git a/i18n.json b/i18n.json index fd0797650e..1cdc60fdca 100644 --- a/i18n.json +++ b/i18n.json @@ -42,10 +42,8 @@ }, "buckets": { "json": { - "include": [ - "apps/web/public/static/locales/[locale]/common.json" - ] + "include": ["apps/web/public/static/locales/[locale]/common.json"] } }, "$schema": "https://lingo.dev/schema/i18n.json" -} \ No newline at end of file +} diff --git a/packages/app-store/_appRegistry.ts b/packages/app-store/_appRegistry.ts index 2666e06ac2..6b8b19c351 100644 --- a/packages/app-store/_appRegistry.ts +++ b/packages/app-store/_appRegistry.ts @@ -1,6 +1,7 @@ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { getAppFromSlug } from "@calcom/app-store/utils"; import getInstallCountPerApp from "@calcom/lib/apps/getInstallCountPerApp"; +import { getAllDwdCredentialsForUser } from "@calcom/lib/domainWideDelegation/server"; import type { UserAdminTeams } from "@calcom/lib/server/repository/user"; import prisma, { safeAppSelect, safeCredentialSelect } from "@calcom/prisma"; import { userMetadata } from "@calcom/prisma/zod-utils"; @@ -79,15 +80,22 @@ export async function getAppRegistryWithCredentials(userId: number, userAdminTea }, }, }); + const user = await prisma.user.findUnique({ where: { id: userId, }, select: { + email: true, + id: true, metadata: true, }, }); + const dwdCredentials = user + ? await getAllDwdCredentialsForUser({ user: { id: userId, email: user.email } }) + : []; + const usersDefaultApp = userMetadata.parse(user?.metadata)?.defaultConferencingApp?.appSlug; const apps = [] as (App & { credentials: Credential[]; @@ -95,6 +103,9 @@ export async function getAppRegistryWithCredentials(userId: number, userAdminTea })[]; const installCountPerApp = await getInstallCountPerApp(); for await (const dbapp of dbApps) { + const dwdCredentialsForApp = dwdCredentials.filter((credential) => credential.appId === dbapp.slug); + const nonDwdCredentialsForApp = dbapp.credentials; + const allCredentials = [...dwdCredentialsForApp, ...nonDwdCredentialsForApp]; const app = await getAppWithMetadata(dbapp); if (!app) continue; // Skip if app isn't installed @@ -116,7 +127,7 @@ export async function getAppRegistryWithCredentials(userId: number, userAdminTea apps.push({ ...app, categories: dbapp.categories, - credentials: dbapp.credentials, + credentials: allCredentials, installed: true, installCount: installCountPerApp[dbapp.slug] || 0, isDefault: usersDefaultApp === dbapp.slug, diff --git a/packages/app-store/_utils/getCalendar.ts b/packages/app-store/_utils/getCalendar.ts index 73a9292609..399f92ac75 100644 --- a/packages/app-store/_utils/getCalendar.ts +++ b/packages/app-store/_utils/getCalendar.ts @@ -1,6 +1,6 @@ import logger from "@calcom/lib/logger"; import type { Calendar, CalendarClass } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import appStore from ".."; @@ -23,7 +23,9 @@ const isCalendarService = (x: unknown): x is CalendarApp => !!x.lib && "CalendarService" in x.lib; -export const getCalendar = async (credential: CredentialPayload | null): Promise => { +export const getCalendar = async ( + credential: CredentialForCalendarService | null +): Promise => { if (!credential || !credential.key) return null; let { type: calendarType } = credential; if (calendarType?.endsWith("_other_calendar")) { diff --git a/packages/app-store/_utils/installation.ts b/packages/app-store/_utils/installation.ts index 16d45bed7f..bad4c82807 100644 --- a/packages/app-store/_utils/installation.ts +++ b/packages/app-store/_utils/installation.ts @@ -1,21 +1,22 @@ import type { Prisma } from "@prisma/client"; import { HttpError } from "@calcom/lib/http-error"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; import type { UserProfile } from "@calcom/types/UserProfile"; export async function checkInstalled(slug: string, userId: number) { - const alreadyInstalled = await prisma.credential.findFirst({ - where: { - appId: slug, - userId: userId, - }, - }); + const alreadyInstalled = await CredentialRepository.findByAppIdAndUserId({ appId: slug, userId }); if (alreadyInstalled) { throw new HttpError({ statusCode: 422, message: "Already installed" }); } } +export async function isAppInstalled({ appId, userId }: { appId: string; userId: number }) { + const alreadyInstalled = await CredentialRepository.findByAppIdAndUserId({ appId, userId }); + return !!alreadyInstalled; +} + type InstallationArgs = { appType: string; user: { diff --git a/packages/app-store/_utils/useAddAppMutation.ts b/packages/app-store/_utils/useAddAppMutation.ts index b3ef6e6784..819c841e5b 100644 --- a/packages/app-store/_utils/useAddAppMutation.ts +++ b/packages/app-store/_utils/useAddAppMutation.ts @@ -18,7 +18,7 @@ type CustomUseMutationOptions = | Omit, "mutationKey" | "mutationFn" | "onSuccess"> | undefined; -type AddAppMutationData = { setupPending: boolean } | void; +type AddAppMutationData = { setupPending: boolean; message?: string } | void; export type UseAddAppMutationOptions = CustomUseMutationOptions & { onSuccess?: (data: AddAppMutationData) => void; installGoogleVideo?: boolean; @@ -94,18 +94,19 @@ function useAddAppMutation(_type: App["type"] | null, options?: UseAddAppMutatio if (externalUrl) { // TODO: For Omni installation to authenticate and come back to the page where installation was initiated, some changes need to be done in all apps' add callbacks gotoUrl(json.url, json.newTab); - return { setupPending: !json.newTab }; + return { setupPending: !json.newTab, message: json.message }; } else if (json.url) { gotoUrl(json.url, json.newTab); return { setupPending: json?.url?.endsWith("/setup") || json?.url?.includes("/apps/installation/event-types"), + message: json.message, }; } else if (returnTo) { gotoUrl(returnTo, false); - return { setupPending: true }; + return { setupPending: true, message: json.message }; } else { - return { setupPending: false }; + return { setupPending: false, message: json.message }; } }, }); diff --git a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts index c7eda4e035..37276b3e40 100644 --- a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts @@ -10,7 +10,7 @@ import { recordingItemSchema, } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; @@ -43,7 +43,7 @@ export interface DailyVideoCallData { } /** @deprecated use metadata on index file */ -export const FAKE_DAILY_CREDENTIAL: CredentialPayload & { invalid: boolean } = { +export const FAKE_DAILY_CREDENTIAL: CredentialForCalendarService & { invalid: boolean } = { id: 0, type: "daily_video", key: { apikey: process.env.DAILY_API_KEY }, @@ -52,6 +52,8 @@ export const FAKE_DAILY_CREDENTIAL: CredentialPayload & { invalid: boolean } = { appId: "daily-video", invalid: false, teamId: null, + delegatedToId: null, + delegatedTo: null, }; function postToDailyAPI(endpoint: string, body: Record) { diff --git a/packages/app-store/googlecalendar/_metadata.ts b/packages/app-store/googlecalendar/_metadata.ts index 4701bde435..e40949d8ba 100644 --- a/packages/app-store/googlecalendar/_metadata.ts +++ b/packages/app-store/googlecalendar/_metadata.ts @@ -19,6 +19,11 @@ export const metadata = { email: "help@cal.com", dirName: "googlecalendar", isOAuth: true, + domainWideDelegation: { + // This is unused at the moment but should be used in future + // For now, we have hardcoded imports in the codebase that are supported with Google Workspace(i.e. Google Calendar and Google Meet) + workspacePlatformSlug: "google", + }, } as AppMeta; export default metadata; diff --git a/packages/app-store/googlecalendar/api/add.ts b/packages/app-store/googlecalendar/api/add.ts index d2342dd0ee..b9c30c9cd2 100644 --- a/packages/app-store/googlecalendar/api/add.ts +++ b/packages/app-store/googlecalendar/api/add.ts @@ -2,6 +2,7 @@ import { OAuth2Client } from "googleapis-common"; import type { NextApiRequest, NextApiResponse } from "next"; import { GOOGLE_CALENDAR_SCOPES, SCOPE_USERINFO_PROFILE, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; +import { HttpError } from "@calcom/lib/http-error"; import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; @@ -9,7 +10,18 @@ import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; import { getGoogleAppKeys } from "../lib/getGoogleAppKeys"; async function getHandler(req: NextApiRequest, res: NextApiResponse) { - // Get token from Google Calendar API + const loggedInUser = req.session?.user; + + if (!loggedInUser) { + throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" }); + } + + // Ideally this should never happen, as email is there in session user but typings aren't accurate it seems + // TODO: So, confirm and later fix the typings + if (!loggedInUser.email) { + throw new HttpError({ statusCode: 400, message: "Session user must have an email" }); + } + const { client_id, client_secret } = await getGoogleAppKeys(); const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`; const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri); diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts index d68de5b052..949350ecc6 100644 --- a/packages/app-store/googlecalendar/api/callback.ts +++ b/packages/app-store/googlecalendar/api/callback.ts @@ -81,6 +81,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const gCalService = new GoogleCalendarService({ ...gcalCredential, user: null, + delegatedTo: null, }); const calendar = new calendar_v3.Calendar({ diff --git a/packages/app-store/googlecalendar/api/webhook.ts b/packages/app-store/googlecalendar/api/webhook.ts index c6f34e2f4f..4cb5428ed8 100644 --- a/packages/app-store/googlecalendar/api/webhook.ts +++ b/packages/app-store/googlecalendar/api/webhook.ts @@ -1,5 +1,6 @@ import type { NextApiRequest } from "next"; +import { buildNonDwdCredential } from "@calcom/lib/domainWideDelegation/server"; import { HttpError } from "@calcom/lib/http-error"; import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; @@ -36,7 +37,8 @@ async function postHandler(req: NextApiRequest) { message: `No credential found for selected calendar for googleChannelId: ${req.headers["x-goog-channel-id"]}`, }); const { selectedCalendars } = credential; - const calendar = await getCalendar(credential); + + const calendar = await getCalendar(buildNonDwdCredential(credential)); // Make sure to pass unique SelectedCalendars to avoid unnecessary third party api calls // Necessary to do here so that it is ensure for all calendar apps diff --git a/packages/app-store/googlecalendar/lib/CalendarService.test.ts b/packages/app-store/googlecalendar/lib/CalendarService.test.ts index c7fa7440fa..c371016c16 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.test.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.test.ts @@ -2,16 +2,39 @@ import prismock from "../../../../tests/libs/__mocks__/prisma"; import oAuthManagerMock, { defaultMockOAuthManager } from "../../tests/__mocks__/OAuthManager"; import { adminMock, calendarMock, setCredentialsMock } from "./__mocks__/googleapis"; -import { expect, describe, test, beforeEach, vi } from "vitest"; +import { JWT } from "googleapis-common"; +import { expect, test, beforeEach, vi, describe } from "vitest"; import "vitest-fetch-mock"; import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache"; import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential"; import CalendarService from "./CalendarService"; +import { getGoogleAppKeys } from "./getGoogleAppKeys"; vi.stubEnv("GOOGLE_WEBHOOK_TOKEN", "test-webhook-token"); +interface MockJWT { + type: "jwt"; + config: { + email: string; + key: string; + scopes: string[]; + subject: string; + }; + authorize: () => Promise; +} + +interface MockOAuth2Client { + type: "oauth2"; + args: [string, string, string]; + setCredentials: typeof setCredentialsMock; +} + +let lastCreatedJWT: MockJWT | null = null; +let lastCreatedOAuth2Client: MockOAuth2Client | null = null; + vi.mock("@calcom/features/flags/server/utils", () => ({ getFeatureFlag: vi.fn().mockReturnValue(true), })); @@ -24,11 +47,28 @@ vi.mock("./getGoogleAppKeys", () => ({ }), })); -vi.mock("googleapis-common", () => ({ - OAuth2Client: vi.fn().mockImplementation(() => ({ - setCredentials: setCredentialsMock, - })), -})); +vi.mock("googleapis-common", async () => { + const actual = await vi.importActual("googleapis-common"); + return { + ...actual, + OAuth2Client: vi.fn().mockImplementation((...args: [string, string, string]) => { + lastCreatedOAuth2Client = { + type: "oauth2", + args, + setCredentials: setCredentialsMock, + }; + return lastCreatedOAuth2Client; + }), + JWT: vi.fn().mockImplementation((config: MockJWT["config"]) => { + lastCreatedJWT = { + type: "jwt", + config, + authorize: vi.fn().mockResolvedValue(undefined), + }; + return lastCreatedJWT; + }), + }; +}); vi.mock("@googleapis/admin", () => adminMock); vi.mock("@googleapis/calendar", () => calendarMock); @@ -563,10 +603,16 @@ test("`updateTokenObject` should update credential in DB as well as myGoogleAuth expect(setCredentialsMock).toHaveBeenCalledWith(newTokenObject); }); -async function createCredentialInDb() { - const user = await prismock.user.create({ +async function createCredentialInDb({ + user = undefined, + delegatedTo = null, +}: { + user?: { email: string | null }; + delegatedTo?: NonNullable | null; +} = {}): Promise { + const defaultUser = await prismock.user.create({ data: { - email: "", + email: user?.email ?? "", }, }); @@ -590,7 +636,7 @@ async function createCredentialInDb() { ...credential, user: { connect: { - id: user.id, + id: defaultUser.id, }, }, app: { @@ -604,5 +650,196 @@ async function createCredentialInDb() { }, }); - return credentialInDb; + return { + ...credentialInDb, + delegatedTo: delegatedTo ?? null, + user: user ? { email: user.email ?? "" } : null, + } as CredentialForCalendarServiceWithEmail; } + +describe("GoogleCalendarService credential handling", () => { + beforeEach(() => { + lastCreatedJWT = null; + lastCreatedOAuth2Client = null; + }); + + const delegatedCredential = { + serviceAccountKey: { + client_email: "service@example.com", + client_id: "service-client-id", + private_key: "service-private-key", + }, + } as const; + + const createMockJWTInstance = ({ + email = "user@example.com", + authorizeError, + }: { + email?: string; + authorizeError?: { response?: { data?: { error?: string } } } | Error; + }) => { + const mockJWTInstance = { + type: "jwt", + config: { + email: delegatedCredential.serviceAccountKey.client_email, + key: delegatedCredential.serviceAccountKey.private_key, + scopes: ["https://www.googleapis.com/auth/calendar"], + subject: email, + }, + authorize: vi.fn().mockRejectedValue(authorizeError ?? new Error("Default error")), + createScoped: vi.fn(), + getRequestMetadataAsync: vi.fn(), + fetchIdToken: vi.fn(), + hasUserScopes: vi.fn(), + getAccessToken: vi.fn(), + getRefreshToken: vi.fn(), + getTokenInfo: vi.fn(), + refreshAccessToken: vi.fn(), + revokeCredentials: vi.fn(), + revokeToken: vi.fn(), + verifyIdToken: vi.fn(), + on: vi.fn(), + setCredentials: vi.fn(), + getCredentials: vi.fn(), + hasAnyScopes: vi.fn(), + authorizeAsync: vi.fn(), + refreshTokenNoCache: vi.fn(), + createGToken: vi.fn(), + }; + + vi.mocked(JWT).mockImplementation(() => mockJWTInstance as unknown as JWT); + return mockJWTInstance; + }; + + test("uses JWT auth with impersonation when DWD credential is provided", async () => { + const credentialWithDWD = await createCredentialInDb({ + user: { email: "user@example.com" }, + delegatedTo: delegatedCredential, + }); + + const calendarService = new CalendarService(credentialWithDWD); + await calendarService.listCalendars(); + + const expectedJWTConfig: MockJWT = { + type: "jwt", + config: { + email: delegatedCredential.serviceAccountKey.client_email, + key: delegatedCredential.serviceAccountKey.private_key, + scopes: ["https://www.googleapis.com/auth/calendar"], + subject: "user@example.com", + }, + authorize: expect.any(Function) as () => Promise, + }; + + expect(lastCreatedJWT).toEqual(expectedJWTConfig); + + expect(calendarMock.calendar_v3.Calendar).toHaveBeenCalledWith({ + auth: lastCreatedJWT, + }); + }); + + test("uses OAuth2 auth when no DWD credential is provided", async () => { + const regularCredential = await createCredentialInDb(); + const { client_id, client_secret, redirect_uris } = await getGoogleAppKeys(); + + const calendarService = new CalendarService(regularCredential); + await calendarService.listCalendars(); + + expect(lastCreatedJWT).toBeNull(); + + const expectedOAuth2Client: MockOAuth2Client = { + type: "oauth2", + args: [client_id, client_secret, redirect_uris[0]], + setCredentials: setCredentialsMock, + }; + + expect(lastCreatedOAuth2Client).toEqual(expectedOAuth2Client); + + expect(setCredentialsMock).toHaveBeenCalledWith(regularCredential.key); + + expect(calendarMock.calendar_v3.Calendar).toHaveBeenCalledWith({ + auth: lastCreatedOAuth2Client, + }); + }); + + test("handles DWD authorization errors appropriately", async () => { + const credentialWithDWD = await createCredentialInDb({ + user: { email: "user@example.com" }, + delegatedTo: delegatedCredential, + }); + + createMockJWTInstance({ + authorizeError: { + response: { + data: { + error: "unauthorized_client", + }, + }, + }, + }); + + const calendarService = new CalendarService(credentialWithDWD); + + await expect(calendarService.listCalendars()).rejects.toThrow( + "Make sure that the Client ID for the domain wide delegation is added to the Google Workspace Admin Console" + ); + }); + + test("handles invalid_grant error (user not in workspace) appropriately", async () => { + const credentialWithDWD = await createCredentialInDb({ + user: { email: "user@example.com" }, + delegatedTo: delegatedCredential, + }); + + createMockJWTInstance({ + authorizeError: { + response: { + data: { + error: "invalid_grant", + }, + }, + }, + }); + + const calendarService = new CalendarService(credentialWithDWD); + + await expect(calendarService.listCalendars()).rejects.toThrow("User might not exist in Google Workspace"); + }); + + test("handles general DWD authorization errors appropriately", async () => { + const credentialWithDWD = await createCredentialInDb({ + user: { email: "user@example.com" }, + delegatedTo: delegatedCredential, + }); + + createMockJWTInstance({ + authorizeError: new Error("Some unexpected error"), + }); + + const calendarService = new CalendarService(credentialWithDWD); + + await expect(calendarService.listCalendars()).rejects.toThrow("Error authorizing domain wide delegation"); + }); + + test("handles missing user email for DWD appropriately", async () => { + const credentialWithDWD = await createCredentialInDb({ + user: { email: null }, + delegatedTo: delegatedCredential, + }); + + const calendarService = new CalendarService(credentialWithDWD); + const { client_id, client_secret, redirect_uris } = await getGoogleAppKeys(); + + await calendarService.listCalendars(); + + expect(lastCreatedJWT).toBeNull(); + + const expectedOAuth2Client: MockOAuth2Client = { + type: "oauth2", + args: [client_id, client_secret, redirect_uris[0]], + setCredentials: setCredentialsMock, + }; + + expect(lastCreatedOAuth2Client).toEqual(expectedOAuth2Client); + }); +}); diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index e3e68691cc..0b7abd49af 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { calendar_v3 } from "@googleapis/calendar"; import type { Prisma } from "@prisma/client"; +import { OAuth2Client, JWT } from "googleapis-common"; import type { GaxiosResponse } from "googleapis-common"; -import { OAuth2Client } from "googleapis-common"; import { RRule } from "rrule"; import { v4 as uuid } from "uuid"; @@ -12,6 +12,11 @@ import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache"; import type { FreeBusyArgs } from "@calcom/features/calendar-cache/calendar-cache.repository.interface"; import { getTimeMax, getTimeMin } from "@calcom/features/calendar-cache/lib/datesForCache"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; +import { + CalendarAppDomainWideDelegationClientIdNotAuthorizedError, + CalendarAppDomainWideDelegationInvalidGrantError, + CalendarAppDomainWideDelegationError, +} from "@calcom/lib/CalendarAppError"; import { uniqueBy } from "@calcom/lib/array"; import { APP_CREDENTIAL_SHARING_ENABLED, @@ -32,7 +37,7 @@ import type { NewCalendarEventType, } from "@calcom/types/Calendar"; import type { SelectedCalendarEventTypeIds } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential"; import { invalidateCredential } from "../../_utils/invalidateCredential"; import { AxiosLikeResponseToFetchResponse } from "../../_utils/oauth/AxiosLikeResponseToFetchResponse"; @@ -70,10 +75,10 @@ export default class GoogleCalendarService implements Calendar { private integrationName = ""; private auth: ReturnType; private log: typeof logger; - private credential: CredentialPayload; + private credential: CredentialForCalendarServiceWithEmail; private myGoogleAuth!: MyGoogleAuth; private oAuthManagerInstance!: OAuthManager; - constructor(credential: CredentialPayload) { + constructor(credential: CredentialForCalendarServiceWithEmail) { this.integrationName = "google_calendar"; this.credential = credential; this.auth = this.initGoogleAuth(credential); @@ -91,7 +96,7 @@ export default class GoogleCalendarService implements Calendar { return this.myGoogleAuth; } - private initGoogleAuth = (credential: CredentialPayload) => { + private initGoogleAuth = (credential: CredentialForCalendarServiceWithEmail) => { const currentTokenObject = getTokenObjectFromCredential(credential); const auth = new OAuthManager({ // Keep it false because we are not using auth.request everywhere. That would be done later as it involves many google calendar sdk functionc calls and needs to be tested well. @@ -175,7 +180,84 @@ export default class GoogleCalendarService implements Calendar { }; }; + private getAuthedCalendarFromDwd = async ({ + domainWideDelegation, + emailToImpersonate, + }: { + emailToImpersonate: string; + domainWideDelegation: { + serviceAccountKey: { + client_email: string; + client_id: string; + private_key: string; + }; + }; + }) => { + const serviceAccountClientEmail = domainWideDelegation.serviceAccountKey.client_email; + const serviceAccountClientId = domainWideDelegation.serviceAccountKey.client_id; + const serviceAccountPrivateKey = domainWideDelegation.serviceAccountKey.private_key; + + const authClient = new JWT({ + email: serviceAccountClientEmail, + key: serviceAccountPrivateKey, + scopes: ["https://www.googleapis.com/auth/calendar"], + subject: emailToImpersonate, + }); + + try { + await authClient.authorize(); + } catch (error) { + this.log.error("DWD: Error authorizing domain wide delegation", JSON.stringify(error)); + + if ((error as any).response?.data?.error === "unauthorized_client") { + throw new CalendarAppDomainWideDelegationClientIdNotAuthorizedError( + "Make sure that the Client ID for the domain wide delegation is added to the Google Workspace Admin Console" + ); + } + + if ((error as any).response?.data?.error === "invalid_grant") { + throw new CalendarAppDomainWideDelegationInvalidGrantError( + "User might not exist in Google Workspace" + ); + } + + // Catch all error + throw new CalendarAppDomainWideDelegationError("Error authorizing domain wide delegation"); + } + + this.log.debug( + "Using domain wide delegation with service account email", + safeStringify({ + serviceAccountClientEmail, + serviceAccountClientId, + emailToImpersonate, + }) + ); + + return new calendar_v3.Calendar({ + auth: authClient, + }); + }; + public authedCalendar = async () => { + let dwdAuthedCalendar; + + if (this.credential.delegatedTo) { + if (!this.credential.user?.email) { + this.log.error("DWD: No email to impersonate found for domain wide delegation"); + } else { + const oauthClientIdAliasRegex = /\+[a-zA-Z0-9]{25}/; + dwdAuthedCalendar = await this.getAuthedCalendarFromDwd({ + domainWideDelegation: this.credential.delegatedTo, + emailToImpersonate: this.credential.user.email.replace(oauthClientIdAliasRegex, ""), + }); + } + } + + if (dwdAuthedCalendar) { + return dwdAuthedCalendar; + } + const myGoogleAuth = await this.auth.getMyGoogleAuthWithRefreshedToken(); const calendar = new calendar_v3.Calendar({ auth: myGoogleAuth, @@ -183,7 +265,13 @@ export default class GoogleCalendarService implements Calendar { return calendar; }; - private getAttendees = (event: CalendarEvent) => { + private getAttendees = ({ + event, + hostExternalCalendarId, + }: { + event: CalendarEvent; + hostExternalCalendarId?: string; + }) => { // When rescheduling events we know the external id of the calendar so we can just look for it in the destinationCalendar array. const selectedHostDestinationCalendar = event.destinationCalendar?.find( (cal) => cal.credentialId === this.credential.id @@ -200,7 +288,7 @@ export default class GoogleCalendarService implements Calendar { organizer: true, // Tried changing the display name to the user but GCal will not let you do that. It will only display the name of the external calendar. Leaving this in just incase it works in the future. displayName: event.organizer.name, - email: selectedHostDestinationCalendar?.externalId ?? event.organizer.email, + email: hostExternalCalendarId ?? selectedHostDestinationCalendar?.externalId ?? event.organizer.email, }, ...eventAttendees, ]; @@ -269,7 +357,11 @@ export default class GoogleCalendarService implements Calendar { return res.data; } - async createEvent(calEventRaw: CalendarEvent, credentialId: number): Promise { + async createEvent( + calEventRaw: CalendarEvent, + credentialId: number, + externalCalendarId?: string + ): Promise { this.log.debug("Creating event"); const formattedCalEvent = formatCalEvent(calEventRaw); @@ -284,7 +376,7 @@ export default class GoogleCalendarService implements Calendar { dateTime: formattedCalEvent.endTime, timeZone: formattedCalEvent.organizer.timeZone, }, - attendees: this.getAttendees(formattedCalEvent), + attendees: this.getAttendees({ event: formattedCalEvent, hostExternalCalendarId: externalCalendarId }), reminders: { useDefault: true, }, @@ -318,8 +410,9 @@ export default class GoogleCalendarService implements Calendar { // Find in formattedCalEvent.destinationCalendar the one with the same credentialId const selectedCalendar = - formattedCalEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.externalId || - "primary"; + externalCalendarId ?? + (formattedCalEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.externalId || + "primary"); try { let event: calendar_v3.Schema$Event | undefined; @@ -451,7 +544,7 @@ export default class GoogleCalendarService implements Calendar { dateTime: formattedCalEvent.endTime, timeZone: formattedCalEvent.organizer.timeZone, }, - attendees: this.getAttendees(formattedCalEvent), + attendees: this.getAttendees({ event: formattedCalEvent, hostExternalCalendarId: externalCalendarId }), reminders: { useDefault: true, }, @@ -772,6 +865,13 @@ export default class GoogleCalendarService implements Calendar { } } + // It would error if the domain wide delegation is not set up correctly + async testDomainWideDelegationSetup() { + const calendar = await this.authedCalendar(); + const cals = await calendar.calendarList.list({ fields: "items(id)" }); + return !!cals.data.items; + } + /** * It doesn't check if the subscription has expired or not. * It just creates a new subscription. diff --git a/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts b/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts index 5c38243161..91199d3c48 100644 --- a/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts +++ b/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts @@ -4,7 +4,7 @@ import type { Page } from "@playwright/test"; import dayjs from "@calcom/dayjs"; import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential"; import { test } from "@calcom/web/playwright/lib/fixtures"; import { selectSecondAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils"; @@ -17,7 +17,7 @@ test.describe("Google Calendar", async () => { // eslint-disable-next-line playwright/no-skipped-test test.describe.skip("Test using the primary calendar", async () => { let qaUsername: string; - let qaGCalCredential: CredentialPayload; + let qaGCalCredential: CredentialForCalendarServiceWithEmail; test.beforeAll(async () => { let runIntegrationTest = false; const errorMessage = "Could not run test"; @@ -42,22 +42,25 @@ test.describe("Google Calendar", async () => { test.skip(!process.env.E2E_TEST_CALCOM_QA_PASSWORD, "QA password not found"); if (process.env.E2E_TEST_CALCOM_QA_EMAIL && process.env.E2E_TEST_CALCOM_QA_PASSWORD) { - qaGCalCredential = await prisma.credential.findFirstOrThrow({ - where: { - user: { - email: process.env.E2E_TEST_CALCOM_QA_EMAIL, + qaGCalCredential = { + ...(await prisma.credential.findFirstOrThrow({ + where: { + user: { + email: process.env.E2E_TEST_CALCOM_QA_EMAIL, + }, + type: metadata.type, }, - type: metadata.type, - }, - include: { - user: { - select: { - email: true, + include: { + user: { + select: { + email: true, + }, }, }, - }, - }); - test.skip(!qaGCalCredential, "Google QA credential not found"); + })), + delegatedTo: null, + } as CredentialForCalendarServiceWithEmail; + test.skip(!qaGCalCredential?.id, "Google QA credential not found"); const qaUserQuery = await prisma.user.findFirstOrThrow({ where: { diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index ff3951f1aa..dc9fb379ad 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -3,6 +3,10 @@ import type { DefaultBodyType } from "msw"; import dayjs from "@calcom/dayjs"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; +import { + CalendarAppDomainWideDelegationInvalidGrantError, + CalendarAppDomainWideDelegationConfigurationError, +} from "@calcom/lib/CalendarAppError"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; import logger from "@calcom/lib/logger"; import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; @@ -13,7 +17,7 @@ import type { IntegrationCalendar, NewCalendarEventType, } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarServiceWithTenantId } from "@calcom/types/Credential"; import { OAuthManager } from "../../_utils/oauth/OAuthManager"; import { getTokenObjectFromCredential } from "../../_utils/oauth/getTokenObjectFromCredential"; @@ -53,12 +57,12 @@ export default class Office365CalendarService implements Calendar { private log: typeof logger; private auth: OAuthManager; private apiGraphUrl = "https://graph.microsoft.com/v1.0"; - private credential: CredentialPayload; + private credential: CredentialForCalendarServiceWithTenantId; + private azureUserId?: string; - constructor(credential: CredentialPayload) { + constructor(credential: CredentialForCalendarServiceWithTenantId) { this.integrationName = "office365_calendar"; const tokenResponse = getTokenObjectFromCredential(credential); - this.auth = new OAuthManager({ credentialSyncVariables: oAuthManagerHelper.credentialSyncVariables, resourceOwner: { @@ -68,20 +72,33 @@ export default class Office365CalendarService implements Calendar { appSlug: metadata.slug, currentTokenObject: tokenResponse, fetchNewTokenObject: async ({ refreshToken }: { refreshToken: string | null }) => { - if (!refreshToken) { + const isDomainWideDelegated = Boolean(credential?.delegatedTo); + + if (!isDomainWideDelegated && !refreshToken) { return null; } - const { client_id, client_secret } = await getOfficeAppKeys(); - return await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { + + const { client_id, client_secret } = await this.getAuthCredentials(isDomainWideDelegated); + + const url = this.getAuthUrl( + isDomainWideDelegated, + credential?.delegatedTo?.serviceAccountKey?.tenant_id + ); + + const bodyParams = { + scope: isDomainWideDelegated + ? "https://graph.microsoft.com/.default" + : "User.Read Calendars.Read Calendars.ReadWrite", + client_id, + client_secret, + grant_type: isDomainWideDelegated ? "client_credentials" : "refresh_token", + ...(isDomainWideDelegated ? {} : { refresh_token: refreshToken ?? "" }), + }; + + return fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - scope: "User.Read Calendars.Read Calendars.ReadWrite", - client_id, - refresh_token: refreshToken, - grant_type: "refresh_token", - client_secret, - }), + body: new URLSearchParams(bodyParams), }); }, isTokenObjectUnusable: async function () { @@ -93,16 +110,138 @@ export default class Office365CalendarService implements Calendar { // TODO: Implement this return null; }, + invalidateTokenObject: () => oAuthManagerHelper.invalidateCredential(credential.id), expireAccessToken: () => oAuthManagerHelper.markTokenAsExpired(credential), - updateTokenObject: (tokenObject) => - oAuthManagerHelper.updateTokenObject({ tokenObject, credentialId: credential.id }), + updateTokenObject: (tokenObject) => { + if (!Boolean(credential.delegatedTo)) { + return oAuthManagerHelper.updateTokenObject({ tokenObject, credentialId: credential.id }); + } + return Promise.resolve(); + }, }); - this.credential = credential; this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] }); } + private getAuthUrl(delegatedTo: boolean, tenantId?: string): string { + if (delegatedTo) { + if (!tenantId) { + throw new CalendarAppDomainWideDelegationInvalidGrantError( + "Invalid DomainWideDelegation Settings: tenantId is missing" + ); + } + return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + } + + return "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + } + + private async getAuthCredentials(isDomainWideDelegated: boolean) { + if (isDomainWideDelegated) { + const client_id = this.credential?.delegatedTo?.serviceAccountKey?.client_id; + const client_secret = this.credential?.delegatedTo?.serviceAccountKey?.private_key; + + if (!client_id || !client_secret) { + throw new CalendarAppDomainWideDelegationConfigurationError( + "Domain Wide Delegated credential without clientId or Secret" + ); + } + + return { client_id, client_secret }; + } + + return getOfficeAppKeys(); + } + + private async getAzureUserId(credential: CredentialForCalendarServiceWithTenantId) { + if (this.azureUserId) return this.azureUserId; + + const isDomainWideDelegated = Boolean(credential?.delegatedTo); + + if (!isDomainWideDelegated) return; + + const url = this.getAuthUrl(isDomainWideDelegated, credential?.delegatedTo?.serviceAccountKey?.tenant_id); + + const dwdClientId = credential.delegatedTo?.serviceAccountKey?.client_id; + const dwdClientSecret = credential.delegatedTo?.serviceAccountKey?.private_key; + + if (!dwdClientId || !dwdClientSecret) { + throw new CalendarAppDomainWideDelegationConfigurationError( + "Domain Wide Delegated credential without clientId or Secret" + ); + } + const loginResponse = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + scope: "https://graph.microsoft.com/.default", + client_id: dwdClientId, + grant_type: "client_credentials", + client_secret: dwdClientSecret, + }), + }); + + if (!this.azureUserId && credential?.delegatedTo) { + const clonedResponse = loginResponse.clone(); + const parsedLoginResponse = await clonedResponse.json(); + const token = parsedLoginResponse?.access_token; + const oauthClientIdAliasRegex = /\+[a-zA-Z0-9]{25}/; + const email = this.credential?.user?.email.replace(oauthClientIdAliasRegex, ""); + const encodedFilter = encodeURIComponent(`mail eq '${email}'`); + const queryParams = `$filter=${encodedFilter}`; + + const response = await fetch(`https://graph.microsoft.com/v1.0/users?${queryParams}`, { + method: "GET", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Bearer ${token}`, + }, + }); + + const parsedBody = await response.json(); + + if (!parsedBody?.value?.[0]?.id) { + throw new CalendarAppDomainWideDelegationInvalidGrantError( + "User might not exist in Microsoft Azure Active Directory" + ); + } + this.azureUserId = parsedBody.value[0].id; + } + return this.azureUserId; + } + + // It would error if the domain wide delegation is not set up correctly + async testDomainWideDelegationSetup(): Promise { + const dwdClientId = this.credential.delegatedTo?.serviceAccountKey?.client_id; + const dwdClientSecret = this.credential.delegatedTo?.serviceAccountKey?.private_key; + const url = this.getAuthUrl( + Boolean(this.credential?.delegatedTo), + this.credential?.delegatedTo?.serviceAccountKey?.tenant_id + ); + + if (!dwdClientId || !dwdClientSecret) { + return false; + } + const loginResponse = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + scope: "https://graph.microsoft.com/.default", + client_id: dwdClientId, + grant_type: "client_credentials", + client_secret: dwdClientSecret, + }), + }); + const parsedLoginResponse = await loginResponse.json(); + return Boolean(parsedLoginResponse?.access_token); + } + + async getUserEndpoint(): Promise { + const azureUserId = await this.getAzureUserId(this.credential); + return azureUserId ? `/users/${this.azureUserId}` : "/me"; + } + async createEvent(event: CalendarEvent, credentialId: number): Promise { const mainHostDestinationCalendar = event.destinationCalendar ? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ?? @@ -110,8 +249,8 @@ export default class Office365CalendarService implements Calendar { : undefined; try { const eventsUrl = mainHostDestinationCalendar?.externalId - ? `/me/calendars/${mainHostDestinationCalendar?.externalId}/events` - : "/me/calendar/events"; + ? `${await this.getUserEndpoint()}/calendars/${mainHostDestinationCalendar?.externalId}/events` + : `${await this.getUserEndpoint()}/calendar/events`; const response = await this.fetcher(eventsUrl, { method: "POST", @@ -130,7 +269,7 @@ export default class Office365CalendarService implements Calendar { async updateEvent(uid: string, event: CalendarEvent): Promise { try { - const response = await this.fetcher(`/me/calendar/events/${uid}`, { + const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, { method: "PATCH", body: JSON.stringify(this.translateEvent(event)), }); @@ -147,7 +286,7 @@ export default class Office365CalendarService implements Calendar { async deleteEvent(uid: string): Promise { try { - const response = await this.fetcher(`/me/calendar/events/${uid}`, { + const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, { method: "DELETE", }); @@ -189,11 +328,12 @@ export default class Office365CalendarService implements Calendar { const ids = await (selectedCalendarIds.length === 0 ? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || []) : Promise.resolve(selectedCalendarIds)); - const requests = ids.map((calendarId, id) => ({ + const requestsPromises = ids.map(async (calendarId, id) => ({ id, method: "GET", - url: `/me/calendars/${calendarId}/calendarView${filter}&${calendarSelectParams}`, + url: `${await this.getUserEndpoint()}/calendars/${calendarId}/calendarView${filter}&${calendarSelectParams}`, })); + const requests = await Promise.all(requestsPromises); const response = await this.apiGraphBatchCall(requests); const responseBody = await this.handleErrorJsonOffice365Calendar(response); let responseBatchApi: IBatchResponse = { responses: [] }; @@ -215,7 +355,6 @@ export default class Office365CalendarService implements Calendar { return alreadySuccessResponse ? this.processBusyTimes(alreadySuccessResponse) : []; } catch (err) { - console.log(err); return Promise.reject([]); } } @@ -227,7 +366,7 @@ export default class Office365CalendarService implements Calendar { const calendarFilterParam = "$select=id,name,isDefaultCalendar,canEdit"; // Store @odata.nextLink if in response - let requestLink = `/me/calendars?${calendarFilterParam}`; + let requestLink = `${await this.getUserEndpoint()}/calendars?${calendarFilterParam}`; while (!finishedParsingCalendars) { const response = await this.fetcher(requestLink); @@ -248,7 +387,7 @@ export default class Office365CalendarService implements Calendar { } } - const user = await this.fetcher("/me"); + const user = await this.fetcher(`${await this.getUserEndpoint()}`); const userResponseBody = await handleErrorsJson(user); const email = userResponseBody.mail ?? userResponseBody.userPrincipalName; diff --git a/packages/app-store/office365video/_metadata.ts b/packages/app-store/office365video/_metadata.ts new file mode 100644 index 0000000000..644048c122 --- /dev/null +++ b/packages/app-store/office365video/_metadata.ts @@ -0,0 +1,29 @@ +import type { AppMeta } from "@calcom/types/App"; + +import _package from "./package.json"; + +export const metadata = { + name: "Microsoft 365/Teams (Requires work/school account)", + description: _package.description, + appData: { + location: { + linkType: "dynamic", + type: "integrations:office365_video", + label: "MS Teams (Requires work/school account)", + }, + }, + type: "office365_video", + title: "MS Teams (Requires work/school account)", + variant: "conferencing", + category: "conferencing", + categories: ["conferencing"], + logo: "icon.svg", + publisher: "Cal.com", + slug: "msteams", + dirName: "office365video", + url: "https://www.microsoft.com/en-ca/microsoft-teams/group-chat-software", + email: "help@cal.com", + isOAuth: true, +} as AppMeta; + +export default metadata; diff --git a/packages/app-store/office365video/lib/VideoApiAdapter.test.ts b/packages/app-store/office365video/lib/VideoApiAdapter.test.ts index 413968be3c..8567f5bb61 100644 --- a/packages/app-store/office365video/lib/VideoApiAdapter.test.ts +++ b/packages/app-store/office365video/lib/VideoApiAdapter.test.ts @@ -54,6 +54,7 @@ const testCredential = { userId: 1, user: { email: "example@cal.com" }, teamId: 1, + delegatedTo: null, }; describe("createMeeting", () => { diff --git a/packages/app-store/office365video/lib/VideoApiAdapter.ts b/packages/app-store/office365video/lib/VideoApiAdapter.ts index 8d15c51f8d..ee181e3af8 100644 --- a/packages/app-store/office365video/lib/VideoApiAdapter.ts +++ b/packages/app-store/office365video/lib/VideoApiAdapter.ts @@ -1,9 +1,13 @@ import { z } from "zod"; +import { + CalendarAppDomainWideDelegationConfigurationError, + CalendarAppDomainWideDelegationInvalidGrantError, +} from "@calcom/lib/CalendarAppError"; import { handleErrorsRaw } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarServiceWithTenantId } from "@calcom/types/Credential"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; @@ -31,7 +35,9 @@ const getO365VideoAppKeys = async () => { return getParsedAppKeysFromSlug(config.slug, o365VideoAppKeysSchema); }; -const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => { +const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenantId): VideoApiAdapter => { + console.log("TeamsVideoApiAdapter--credential: ", credential); + let azureUserId: string | null; const tokenResponse = oAuthManagerHelper.getTokenObjectFromCredential(credential); const auth = new OAuthManager({ @@ -43,19 +49,41 @@ const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => appSlug: config.slug, currentTokenObject: tokenResponse, fetchNewTokenObject: async ({ refreshToken }: { refreshToken: string | null }) => { - if (!refreshToken) { + const isDomainWideDelegated = Boolean(credential?.delegatedTo); + if (!isDomainWideDelegated && !refreshToken) { return null; } - const { client_id, client_secret } = await getO365VideoAppKeys(); - return await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { + + const credentials = isDomainWideDelegated + ? { + client_id: credential?.delegatedTo?.serviceAccountKey?.client_id, + client_secret: credential?.delegatedTo?.serviceAccountKey?.private_key, + } + : await getO365VideoAppKeys(); + + if (isDomainWideDelegated && (!credentials.client_id || !credentials.client_secret)) { + throw new CalendarAppDomainWideDelegationConfigurationError( + "Domain Wide Delegated credential without clientId or Secret" + ); + } + + const url = getAuthUrl(isDomainWideDelegated, credential?.delegatedTo?.serviceAccountKey?.tenant_id); + const scope = isDomainWideDelegated + ? "https://graph.microsoft.com/.default" + : "User.Read Calendars.Read Calendars.ReadWrite"; + + const params: Record = { + scope, + client_id: credentials.client_id || "", + client_secret: credentials.client_secret || "", + grant_type: isDomainWideDelegated ? "client_credentials" : "refresh_token", + ...(isDomainWideDelegated ? {} : { refresh_token: refreshToken ?? "" }), + }; + + return await fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id, - refresh_token: refreshToken, - grant_type: "refresh_token", - client_secret, - }), + body: new URLSearchParams(params), }); }, isTokenObjectUnusable: async function () { @@ -69,10 +97,27 @@ const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => }, invalidateTokenObject: () => oAuthManagerHelper.invalidateCredential(credential.id), expireAccessToken: () => oAuthManagerHelper.markTokenAsExpired(credential), - updateTokenObject: (tokenObject) => - oAuthManagerHelper.updateTokenObject({ tokenObject, credentialId: credential.id }), + updateTokenObject: (tokenObject) => { + if (!Boolean(credential.delegatedTo)) { + return oAuthManagerHelper.updateTokenObject({ tokenObject, credentialId: credential.id }); + } + return Promise.resolve(); + }, }); + function getAuthUrl(delegatedTo: boolean, tenantId?: string): string { + if (delegatedTo) { + if (!tenantId) { + throw new CalendarAppDomainWideDelegationInvalidGrantError( + "Invalid DomainWideDelegation Settings: tenantId is missing" + ); + } + return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + } + + return "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + } + const translateEvent = (event: CalendarEvent) => { return { startDateTime: event.startTime, @@ -81,6 +126,68 @@ const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => }; }; + async function getAzureUserId(credential: CredentialForCalendarServiceWithTenantId) { + if (azureUserId) return azureUserId; + + const isDomainWideDelegated = Boolean(credential?.delegatedTo); + + if (!isDomainWideDelegated) return null; + + const url = getAuthUrl(isDomainWideDelegated, credential?.delegatedTo?.serviceAccountKey?.tenant_id); + + const dwdClientId = credential.delegatedTo?.serviceAccountKey?.client_id; + const dwdClientSecret = credential.delegatedTo?.serviceAccountKey?.private_key; + + if (!dwdClientId || !dwdClientSecret) { + throw new CalendarAppDomainWideDelegationConfigurationError( + "Domain Wide Delegated credential without clientId or Secret" + ); + } + const loginResponse = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + scope: "https://graph.microsoft.com/.default", + client_id: dwdClientId, + grant_type: "client_credentials", + client_secret: dwdClientSecret, + }), + }); + + const clonedResponse = loginResponse.clone(); + const parsedLoginResponse = await clonedResponse.json(); + const token = parsedLoginResponse?.access_token; + const oauthClientIdAliasRegex = /\+[a-zA-Z0-9]{25}/; + const email = credential?.user?.email.replace(oauthClientIdAliasRegex, ""); + const encodedFilter = encodeURIComponent(`mail eq '${email}'`); + const queryParams = `$filter=${encodedFilter}`; + + const response = await fetch(`https://graph.microsoft.com/v1.0/users?${queryParams}`, { + method: "GET", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Bearer ${token}`, + }, + }); + + const parsedBody = await response.json(); + + if (!parsedBody?.value?.[0]?.id) { + throw new CalendarAppDomainWideDelegationInvalidGrantError( + "User might not exist in Microsoft Azure Active Directory" + ); + } + azureUserId = parsedBody.value[0].id; + return azureUserId; + } + + async function getUserEndpoint(): Promise { + const azureUserId = await getAzureUserId(credential); + return azureUserId + ? `https://graph.microsoft.com/v1.0/users/${azureUserId}` + : "https://graph.microsoft.com/v1.0/me"; + } + // Since the meeting link is not tied to an event we only need the create and update functions return { getAvailability: () => { @@ -89,7 +196,7 @@ const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent) => { const resultString = await auth .requestRaw({ - url: "https://graph.microsoft.com/v1.0/me/onlineMeetings", + url: `${await getUserEndpoint()}/onlineMeetings`, options: { method: "POST", body: JSON.stringify(translateEvent(event)), @@ -110,9 +217,14 @@ const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => return Promise.resolve([]); }, createMeeting: async (event: CalendarEvent): Promise => { + console.log("=======>createMeeting: "); + + const url = `${await getUserEndpoint()}/onlineMeetings`; + console.log("urllllllllllll: ", url); + console.log("translateEvent(event): ", translateEvent(event)); const resultString = await auth .requestRaw({ - url: "https://graph.microsoft.com/v1.0/me/onlineMeetings", + url, options: { method: "POST", body: JSON.stringify(translateEvent(event)), diff --git a/packages/app-store/server.ts b/packages/app-store/server.ts index f6ee92e415..4de69de68d 100644 --- a/packages/app-store/server.ts +++ b/packages/app-store/server.ts @@ -3,6 +3,10 @@ import type { TFunction } from "next-i18next"; import { defaultVideoAppCategories } from "@calcom/app-store/utils"; import getEnabledAppsFromCredentials from "@calcom/lib/apps/getEnabledAppsFromCredentials"; +import { + buildNonDwdCredentials, + enrichUserWithDwdConferencingCredentialsWithoutOrgId, +} from "@calcom/lib/domainWideDelegation/server"; import { prisma } from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; @@ -27,7 +31,7 @@ export async function getLocationGroupedOptions( // don't default to {}, when you do TS no longer determines the right types. let idToSearchObject: Prisma.CredentialWhereInput; - + let user = null; if ("teamId" in userOrTeamId) { const teamId = userOrTeamId.teamId; // See if the team event belongs to an org @@ -52,9 +56,14 @@ export async function getLocationGroupedOptions( } } else { idToSearchObject = { userId: userOrTeamId.userId }; + user = await prisma.user.findUnique({ + where: { + id: userOrTeamId.userId, + }, + }); } - const credentials = await prisma.credential.findMany({ + const nonDwdCredentials = await prisma.credential.findMany({ where: { ...idToSearchObject, app: { @@ -73,6 +82,21 @@ export async function getLocationGroupedOptions( }, }); + let credentials; + if (user) { + // We only add dwd credentials if the request for location options is for a user because DWD Credential is applicable to Users only. + const { credentials: allCredentials } = await enrichUserWithDwdConferencingCredentialsWithoutOrgId({ + user: { + ...user, + credentials: nonDwdCredentials, + }, + }); + credentials = allCredentials; + } else { + // TODO: We can avoid calling buildNonDwdCredentials here by moving the above prisma query to the repository and doing it there + credentials = buildNonDwdCredentials(nonDwdCredentials); + } + const integrations = await getEnabledAppsFromCredentials(credentials, { filterOnCredentials: true }); integrations.forEach((app) => { diff --git a/packages/app-store/tsconfig.json b/packages/app-store/tsconfig.json index 8e9faeb56e..8c2e0fb50a 100644 --- a/packages/app-store/tsconfig.json +++ b/packages/app-store/tsconfig.json @@ -8,7 +8,7 @@ "@components/*": ["../../apps/web/components/*"], /* A `package` should never import from `apps` ↓ */ "@lib/*": ["../../apps/web/lib/*"], - "@prisma/client/*": ["@calcom/prisma/client/*"] + "@prisma/client/*": ["@calcom/prisma/client/*"], }, "resolveJsonModule": true, "experimentalDecorators": true diff --git a/packages/app-store/utils.ts b/packages/app-store/utils.ts index 3d4e924624..9f63a3be96 100644 --- a/packages/app-store/utils.ts +++ b/packages/app-store/utils.ts @@ -8,7 +8,7 @@ import logger from "@calcom/lib/logger"; import { getPiiFreeCredential } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { App, AppMeta } from "@calcom/types/App"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; export * from "./_utils/getEventTypeAppData"; @@ -33,7 +33,7 @@ const ALL_APPS_MAP = Object.keys(appStoreMetadata).reduce((store, key) => { return store; }, {} as Record); -export type CredentialDataWithTeamName = CredentialPayload & { +export type CredentialDataWithTeamName = CredentialForCalendarService & { team?: { name: string; } | null; @@ -65,6 +65,8 @@ function getApps(credentials: CredentialDataWithTeamName[], filterOnCredentials? teamId: null, appId: appMeta.slug, invalid: false, + delegatedTo: null, + delegatedToId: null, team: { name: "Global", }, diff --git a/packages/core/CalendarManager.test.ts b/packages/core/CalendarManager.test.ts index 2ec7ce4153..827cf0fcfb 100644 --- a/packages/core/CalendarManager.test.ts +++ b/packages/core/CalendarManager.test.ts @@ -4,7 +4,7 @@ import { getCalendarCredentials } from "./CalendarManager"; describe("CalendarManager tests", () => { describe("fn: getCalendarCredentials", () => { - it("should only return credentials for calendar apps", () => { + it("should only return credentials for calendar apps", async () => { const googleCalendarCredentials = { id: "1", appId: "google-calendar", @@ -14,6 +14,7 @@ describe("CalendarManager tests", () => { access_token: "google_calendar_key", }, invalid: false, + delegatedTo: null, }; const credentials = [ @@ -32,7 +33,7 @@ describe("CalendarManager tests", () => { const calendarCredentials = getCalendarCredentials(credentials); expect(calendarCredentials).toHaveLength(1); - expect(calendarCredentials[0].credential).toBe(googleCalendarCredentials); + expect(calendarCredentials[0].credential).toEqual(googleCalendarCredentials); }); }); }); diff --git a/packages/core/CalendarManager.ts b/packages/core/CalendarManager.ts index cf2a981137..7fad9713b4 100644 --- a/packages/core/CalendarManager.ts +++ b/packages/core/CalendarManager.ts @@ -5,6 +5,8 @@ import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import getApps from "@calcom/app-store/utils"; import dayjs from "@calcom/dayjs"; import { getUid } from "@calcom/lib/CalEventParser"; +import { CalendarAppDomainWideDelegationError } from "@calcom/lib/CalendarAppError"; +import { buildNonDwdCredentials } from "@calcom/lib/domainWideDelegation/clientAndServer"; import logger from "@calcom/lib/logger"; import { getPiiFreeCalendarEvent, getPiiFreeCredential } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -15,7 +17,7 @@ import type { NewCalendarEventType, SelectedCalendar, } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential"; import type { EventResult } from "@calcom/types/EventManager"; import getCalendarsEvents from "./getCalendarsEvents"; @@ -23,7 +25,7 @@ import { getCalendarsEventsWithTimezones } from "./getCalendarsEvents"; const log = logger.getSubLogger({ prefix: ["CalendarManager"] }); -export const getCalendarCredentials = (credentials: Array) => { +export const getCalendarCredentials = (credentials: Array) => { const calendarCredentials = getApps(credentials, true) .filter((app) => app.type.endsWith("_calendar")) .flatMap((app) => { @@ -38,6 +40,10 @@ export const getCalendarCredentials = (credentials: Array) => return calendarCredentials; }; +export const getCalendarCredentialsWithoutDwd = (credentials: CredentialPayload[]) => { + return getCalendarCredentials(buildNonDwdCredentials(credentials)); +}; + export const getConnectedCalendars = async ( calendarCredentials: ReturnType, selectedCalendars: { externalId: string }[], @@ -51,10 +57,12 @@ export const getConnectedCalendars = async ( const calendar = await item.calendar; // Don't leak credentials to the client const credentialId = credential.id; + const domainWideDelegationCredentialId = credential.delegatedToId ?? null; if (!calendar) { return { integration, credentialId, + domainWideDelegationCredentialId, }; } const cals = await calendar.listCalendars(); @@ -67,6 +75,7 @@ export const getConnectedCalendars = async ( primary: cal.primary || null, isSelected: selectedCalendars.some((selected) => selected.externalId === cal.externalId), credentialId, + domainWideDelegationCredentialId, }; }), ["primary"] @@ -91,6 +100,7 @@ export const getConnectedCalendars = async ( return { integration: cleanIntegrationKeys(integration), credentialId, + domainWideDelegationCredentialId, primary, calendars, }; @@ -104,11 +114,16 @@ export const getConnectedCalendars = async ( } } + if (error instanceof CalendarAppDomainWideDelegationError) { + errorMessage = error.message; + } + log.error("getConnectedCalendars failed", safeStringify(error), safeStringify({ item })); return { integration: cleanIntegrationKeys(item.integration), credentialId: item.credential.id, + domainWideDelegationCredentialId: item.credential.delegatedToId, error: { message: errorMessage, }, @@ -126,7 +141,7 @@ export const getConnectedCalendars = async ( * @returns App */ const cleanIntegrationKeys = ( - appIntegration: ReturnType[number]["integration"] & { + appIntegration: Awaited>[number]["integration"] & { credentials?: Array; credential: CredentialPayload; } @@ -136,25 +151,12 @@ const cleanIntegrationKeys = ( return rest; }; -/** - * Get months between given dates - * @returns ["2023-04", "2024-05"] - */ -const getMonths = (dateFrom: string, dateTo: string): string[] => { - const months: string[] = [dayjs(dateFrom).format("YYYY-MM")]; - for ( - let i = 1; - dayjs(dateFrom).add(i, "month").isBefore(dateTo) || - dayjs(dateFrom).add(i, "month").isSame(dateTo, "month"); - i++ - ) { - months.push(dayjs(dateFrom).add(i, "month").format("YYYY-MM")); - } - return months; -}; - export const getBusyCalendarTimes = async ( - withCredentials: CredentialPayload[], + /** + * withCredentials can possibly have a duplicate credential in case DWD is enabled. + * There is no way to deduplicate that at the moment because a `credential` doesn't directly know for which email it is, + */ + withCredentials: CredentialForCalendarService[], dateFrom: string, dateTo: string, selectedCalendars: SelectedCalendar[], @@ -187,7 +189,7 @@ export const getBusyCalendarTimes = async ( }; export const createEvent = async ( - credential: CredentialPayload, + credential: CredentialForCalendarService, calEvent: CalendarEvent, externalId?: string ): Promise> => { @@ -207,10 +209,14 @@ export const createEvent = async ( calEvent.additionalNotes = "Notes have been hidden by the organizer"; // TODO: i18n this string? } + const externalCalendarIdWhenDwdCredentialIsChosen = credential.delegatedToId ? externalId : undefined; + // TODO: Surface success/error messages coming from apps to improve end user visibility const creationResult = calendar ? await calendar - .createEvent(calEvent, credential.id) + // Ideally we should pass externalId always, but let's start with DWD case first as in that case, CalendarService need to handle a special case for DWD to determine the selectedCalendar. + // Such logic shouldn't exist in CalendarService as it would be same for all calendar apps. + .createEvent(calEvent, credential.id, externalCalendarIdWhenDwdCredentialIsChosen) .catch(async (error: { code: number; calError: string }) => { success = false; /** @@ -225,7 +231,8 @@ export const createEvent = async ( } log.error( "createEvent failed", - safeStringify({ error, calEvent: getPiiFreeCalendarEvent(calEvent) }) + safeStringify(error), + safeStringify({ calEvent: getPiiFreeCalendarEvent(calEvent) }) ); // @TODO: This code will be off till we can investigate an error with it //https://github.com/calcom/cal.com/issues/3949 @@ -264,11 +271,12 @@ export const createEvent = async ( calWarnings: creationResult?.additionalInfo?.calWarnings || [], externalId, credentialId: credential.id, + delegatedToId: credential.delegatedToId ?? undefined, }; }; export const updateEvent = async ( - credential: CredentialPayload, + credential: CredentialForCalendarService, calEvent: CalendarEvent, bookingRefUid: string | null, externalCalendarId: string | null @@ -352,7 +360,7 @@ export const deleteEvent = async ({ event, externalCalendarId, }: { - credential: CredentialPayload; + credential: CredentialForCalendarService; bookingRefUid: string; event: CalendarEvent; externalCalendarId?: string | null; diff --git a/packages/core/EventManager.ts b/packages/core/EventManager.ts index 9c4280c4be..17b146d6a1 100644 --- a/packages/core/EventManager.ts +++ b/packages/core/EventManager.ts @@ -20,12 +20,12 @@ import { getPiiFreeCalendarEvent, } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import prisma from "@calcom/prisma"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import { createdEventSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeAppMetadataSchema } from "@calcom/prisma/zod-utils"; import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import type { Event } from "@calcom/types/Event"; import type { CreateUpdateResult, @@ -47,17 +47,18 @@ interface HasId { id: number; } -// The options should have the slug of the apps the option is enabled for -interface AppOptions { - crm: { - skipContactCreation: string[]; - }; -} - const latestCredentialFirst = (a: T, b: T) => { return b.id - a.id; }; +const delegatedCredentialFirst = (a: T, b: T) => { + return (b.delegatedToId ? 1 : 0) - (a.delegatedToId ? 1 : 0); +}; + +const delegatedCredentialLast = (a: T, b: T) => { + return (a.delegatedToId ? 1 : 0) - (b.delegatedToId ? 1 : 0); +}; + export const getLocationRequestFromIntegration = (location: string) => { const eventLocationType = getLocationFromApp(location); if (eventLocationType) { @@ -76,6 +77,21 @@ export const getLocationRequestFromIntegration = (location: string) => { return null; }; +const getCredential = ({ + id, + allCredentials, +}: { + id: { + domainWideDelegationCredentialId: string | null; + credentialId: number | null; + }; + allCredentials: CredentialForCalendarService[]; +}) => { + return id.domainWideDelegationCredentialId + ? allCredentials.find((c) => c.delegatedToId === id.domainWideDelegationCredentialId) + : allCredentials.find((c) => c.id === id.credentialId); +}; + export const processLocation = (event: CalendarEvent): CalendarEvent => { // If location is set to an integration location // Build proper transforms for evt object @@ -91,8 +107,18 @@ export const processLocation = (event: CalendarEvent): CalendarEvent => { return event; }; +/** + * Ensures invalid non-dwd credentialId isn't returned + */ +function getCredentialPayload(result: EventResult>) { + return { + credentialId: result?.credentialId && result.credentialId > 0 ? result.credentialId : undefined, + domainWideDelegationCredentialId: result?.delegatedToId || undefined, + }; +} + export type EventManagerUser = { - credentials: CredentialPayload[]; + credentials: CredentialForCalendarService[]; destinationCalendar: DestinationCalendar | null; }; @@ -104,11 +130,10 @@ export type EventManagerInitParams = { }; export default class EventManager { - calendarCredentials: CredentialPayload[]; - videoCredentials: CredentialPayload[]; - crmCredentials: CredentialPayload[]; + calendarCredentials: CredentialForCalendarService[]; + videoCredentials: CredentialForCalendarService[]; + crmCredentials: CredentialForCalendarService[]; appOptions?: z.infer; - /** * Takes an array of credentials and initializes a new instance of the EventManager. * @@ -125,8 +150,14 @@ export default class EventManager { // Backwards compatibility until CRM manager is implemented (cred) => cred.type.endsWith("_calendar") && !cred.type.includes("other_calendar") ) - //see https://github.com/calcom/cal.com/issues/11671#issue-1923600672 - .sort(latestCredentialFirst); + // see https://github.com/calcom/cal.com/issues/11671#issue-1923600672 + // This sorting is mostly applicable for fallback which happens when there is no explicity destinationCalendar set. That could be true for really old accounts but not for new + .sort(latestCredentialFirst) + // TODO: Change it to delegatedCredentialFirst in a followup PR. + // We are keeping delegated credentials at the end so that there is no impact on existing users connections as we still use their existing credentials + // Soon after DWD is released and stable, we switch it. Could be an env variable also to toggle this. + .sort(delegatedCredentialLast); + this.videoCredentials = appCredentials .filter((cred) => cred.type.endsWith("_video") || cred.type.endsWith("_conferencing")) // Whenever a new video connection is added, latest credentials are added with the highest ID. @@ -173,7 +204,7 @@ export default class EventManager { const [mainHostDestinationCalendar] = (evt.destinationCalendar as [undefined | NonNullable[number]]) ?? []; if (evt.location === MeetLocationType && mainHostDestinationCalendar?.integration !== "google_calendar") { - log.warn("Falling back to Cal Video integration as Google Calendar not installed"); + log.warn("Falling back to Cal Video integration as Google Calendar is not set as destination calendar"); evt["location"] = "integrations:daily"; evt["conferenceCredentialId"] = undefined; } @@ -242,7 +273,7 @@ export default class EventManager { meetingPassword: createdEventObj ? createdEventObj.password : result.createdEvent?.password, meetingUrl: createdEventObj ? createdEventObj.onlineMeetingUrl : result.createdEvent?.url, externalCalendarId: isCalendarType ? result.externalId : undefined, - credentialId: result?.credentialId || undefined, + ...getCredentialPayload(result), }; }); @@ -332,8 +363,10 @@ export default class EventManager { const calendarCredential = await this.getCredentialAndWarnIfNotFound( credentialId, this.calendarCredentials, - credentialType + credentialType, + reference.domainWideDelegationCredentialId ); + if (calendarCredential) { await deleteEvent({ credential: calendarCredential, @@ -361,21 +394,20 @@ export default class EventManager { private async getCredentialAndWarnIfNotFound( credentialId: number | null | undefined, - credentials: CredentialPayload[], - type: string + credentials: CredentialForCalendarService[], + type: string, + domainWideDelegationCredentialId?: string | null ) { + if (domainWideDelegationCredentialId) { + return this.calendarCredentials.find((cred) => cred.delegatedToId === domainWideDelegationCredentialId); + } const credential = credentials.find((cred) => cred.id === credentialId); if (credential) { return credential; } else { const credential = typeof credentialId === "number" && credentialId > 0 - ? await prisma.credential.findUnique({ - where: { - id: credentialId, - }, - select: credentialForCalendarServiceSelect, - }) + ? await CredentialRepository.findCredentialForCalendarServiceById({ id: credentialId }) : // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo this.videoCredentials.find((cred) => cred.type === type) || this.calendarCredentials.find((cred) => cred.type === type) || @@ -637,7 +669,7 @@ export default class EventManager { const [credential] = this.calendarCredentials.filter((cred) => !cred.type.endsWith("other_calendar")); if (credential) { const createdEvent = await createEvent(credential, event); - log.silly("Created Calendar event", safeStringify({ createdEvent })); + log.silly("Created Calendar event using credential", safeStringify({ credential, createdEvent })); if (createdEvent) { createdEvents.push(createdEvent); } @@ -667,27 +699,43 @@ export default class EventManager { for (const destination of destinationCalendars) { if (eventCreated) break; log.silly("Creating Calendar event", JSON.stringify({ destination })); - if (destination.credentialId) { - let credential = this.calendarCredentials.find((c) => c.id === destination.credentialId); + if (destination.credentialId || destination.domainWideDelegationCredentialId) { + let credential = getCredential({ + id: { + credentialId: destination.credentialId, + domainWideDelegationCredentialId: destination.domainWideDelegationCredentialId, + }, + allCredentials: this.calendarCredentials, + }); if (!credential) { - // Fetch credential from DB - const credentialFromDB = await prisma.credential.findUnique({ - where: { + if (destination.credentialId) { + // Fetch credential from DB + const credentialFromDB = await CredentialRepository.findCredentialForCalendarServiceById({ id: destination.credentialId, - }, - select: credentialForCalendarServiceSelect, - }); - if (credentialFromDB && credentialFromDB.appId) { - credential = { - id: credentialFromDB.id, - type: credentialFromDB.type, - key: credentialFromDB.key, - userId: credentialFromDB.userId, - teamId: credentialFromDB.teamId, - invalid: credentialFromDB.invalid, - appId: credentialFromDB.appId, - user: credentialFromDB.user, - }; + }); + + if (credentialFromDB && credentialFromDB.appId) { + credential = { + id: credentialFromDB.id, + type: credentialFromDB.type, + key: credentialFromDB.key, + userId: credentialFromDB.userId, + teamId: credentialFromDB.teamId, + invalid: credentialFromDB.invalid, + appId: credentialFromDB.appId, + user: credentialFromDB.user, + delegatedToId: credentialFromDB.delegatedToId, + delegatedTo: credentialFromDB.delegatedTo, + }; + } + } else if (destination.domainWideDelegationCredentialId) { + log.warn("DWD: DWD seems to be disabled, falling back to first non-dwd credential"); + // In case DWD is disabled, we land here where the destination calendar is connected to a DWD credential, but the credential isn't available(because DWD is disabled) + // In this case, we fallback to the first non-dwd credential. That would be there for all existing users before DWD was enabled + const firstNonDwdCalendarCredential = this.calendarCredentials.find( + (cred) => !cred.type.endsWith("other_calendar") && !cred.delegatedToId + ); + credential = firstNonDwdCalendarCredential; } } if (credential) { @@ -753,7 +801,7 @@ export default class EventManager { * @private */ - private getVideoCredential(event: CalendarEvent): CredentialPayload | undefined { + private getVideoCredential(event: CalendarEvent): CredentialForCalendarService | undefined { if (!event.location) { return undefined; } @@ -766,7 +814,7 @@ export default class EventManager { (credential) => credential.id === event.conferenceCredentialId ); } else { - videoCredential = this.videoCredentials.find((credential: CredentialPayload) => + videoCredential = this.videoCredentials.find((credential: CredentialForCalendarService) => credential.type.includes(integrationName) ); log.warn( @@ -861,11 +909,8 @@ export default class EventManager { )[0]; if (!credential) { // Fetch credential from DB - const credentialFromDB = await prisma.credential.findUnique({ - where: { - id: reference.credentialId, - }, - select: credentialForCalendarServiceSelect, + const credentialFromDB = await CredentialRepository.findCredentialForCalendarServiceById({ + id: reference.credentialId, }); if (credentialFromDB && credentialFromDB.appId) { credential = { @@ -877,6 +922,8 @@ export default class EventManager { invalid: credentialFromDB.invalid, appId: credentialFromDB.appId, user: credentialFromDB.user, + delegatedToId: credentialFromDB.delegatedToId, + delegatedTo: credentialFromDB.delegatedTo, }; } } @@ -896,11 +943,8 @@ export default class EventManager { const oldCalendarEvent = booking.references.find((reference) => reference.type.includes("_calendar")); if (oldCalendarEvent?.credentialId) { - const calendarCredential = await prisma.credential.findUnique({ - where: { - id: oldCalendarEvent.credentialId, - }, - select: credentialForCalendarServiceSelect, + const calendarCredential = await CredentialRepository.findCredentialForCalendarServiceById({ + id: oldCalendarEvent.credentialId, }); const calendar = await getCalendar(calendarCredential); await calendar?.deleteEvent(oldCalendarEvent.uid, event, oldCalendarEvent.externalCalendarId); @@ -1061,7 +1105,7 @@ export default class EventManager { } } - private getAppOptionsFromEventMetadata(credential: CredentialPayload) { + private getAppOptionsFromEventMetadata(credential: CredentialForCalendarService) { if (!this.appOptions || !credential.appId) return {}; if (credential.appId in this.appOptions) diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index e04f0a1ca5..9b2d84b626 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -14,12 +14,12 @@ import type { SelectedCalendar } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; import { stringToDayjs } from "@calcom/prisma/zod-utils"; import type { EventBusyDetails, IntervalLimit } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes"; export async function getBusyTimes(params: { - credentials: CredentialPayload[]; + credentials: CredentialForCalendarService[]; userId: number; userEmail: string; username: string; diff --git a/packages/core/getCalendarsEvents.ts b/packages/core/getCalendarsEvents.ts index a80755d5cb..c3320e4a61 100644 --- a/packages/core/getCalendarsEvents.ts +++ b/packages/core/getCalendarsEvents.ts @@ -4,13 +4,12 @@ import { getPiiFreeCredential, getPiiFreeSelectedCalendar } from "@calcom/lib/pi import { safeStringify } from "@calcom/lib/safeStringify"; import { performance } from "@calcom/lib/server/perfObserver"; import type { EventBusyDate, SelectedCalendar } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; const log = logger.getSubLogger({ prefix: ["getCalendarsEvents"] }); - // only for Google Calendar for now export const getCalendarsEventsWithTimezones = async ( - withCredentials: CredentialPayload[], + withCredentials: CredentialForCalendarService[], dateFrom: string, dateTo: string, selectedCalendars: SelectedCalendar[] @@ -46,7 +45,7 @@ export const getCalendarsEventsWithTimezones = async ( }; const getCalendarsEvents = async ( - withCredentials: CredentialPayload[], + withCredentials: CredentialForCalendarService[], dateFrom: string, dateTo: string, selectedCalendars: SelectedCalendar[], diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 1b38dacc5e..e1efc89b52 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -19,8 +19,8 @@ import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; +import { findUsersForAvailabilityCheck } from "@calcom/lib/server/findUsersForAvailabilityCheck"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; -import { UserRepository } from "@calcom/lib/server/repository/user"; import prisma from "@calcom/prisma"; import { SchedulingType } from "@calcom/prisma/enums"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -152,7 +152,7 @@ const getUser = async (...args: Parameters): Promise { - return UserRepository.findForAvailabilityCheck({ where }); + return findUsersForAvailabilityCheck({ where }); }; type GetUser = Awaited>; diff --git a/packages/features/apps/components/AppList.tsx b/packages/features/apps/components/AppList.tsx index e29c9e3e60..454916eec7 100644 --- a/packages/features/apps/components/AppList.tsx +++ b/packages/features/apps/components/AppList.tsx @@ -12,6 +12,7 @@ import type { EventTypes, } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; +import { isDwdCredential } from "@calcom/lib/domainWideDelegation/clientAndServer"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { AppCategories } from "@calcom/prisma/enums"; import { type RouterOutputs } from "@calcom/trpc"; @@ -229,7 +230,7 @@ function ConnectOrDisconnectIntegrationMenuItem(props: { handleDisconnect(credentialId, app, teamId)} - disabled={isGlobal} + disabled={isGlobal || isDwdCredential({ credentialId })} StartIcon="trash"> {t("remove_app")} diff --git a/packages/features/apps/components/DisconnectIntegration.tsx b/packages/features/apps/components/DisconnectIntegration.tsx index 0e86cbb8a5..c746e27a2d 100644 --- a/packages/features/apps/components/DisconnectIntegration.tsx +++ b/packages/features/apps/components/DisconnectIntegration.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; +import { isDwdCredential } from "@calcom/lib/domainWideDelegation/clientAndServer"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import type { ButtonProps } from "@calcom/ui"; @@ -37,11 +38,14 @@ export default function DisconnectIntegration(props: { }, }); + // Such a credential is added in-memory and removed when Domain-wide delegation is disabled. + const disableDisconnect = isDwdCredential({ credentialId }); return ( mutation.mutate({ id: credentialId, ...(teamId ? { teamId } : {}) })} isModalOpen={modalOpen} onModalOpen={() => setModalOpen((prevValue) => !prevValue)} + disabled={disableDisconnect} {...props} /> ); diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 3ff2006da8..893593e906 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -643,6 +643,7 @@ export const getOptions = ({ const gCalService = new GoogleCalendarService({ ...gcalCredential, user: null, + delegatedTo: null, }); if ( diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts index 7e65d55193..8e9414559b 100644 --- a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials.ts @@ -1,5 +1,6 @@ import type z from "zod"; +import { enrichUserWithDwdCredentialsWithoutOrgId } from "@calcom/lib/domainWideDelegation/server"; import { UserRepository } from "@calcom/lib/server/repository/user"; import prisma from "@calcom/prisma"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; @@ -7,18 +8,20 @@ import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { CredentialPayload } from "@calcom/types/Credential"; +export type EventType = { + userId?: number | null; + team?: { id: number | null; parentId: number | null } | null; + parentId?: number | null; + metadata: z.infer; +} | null; + /** * Gets credentials from the user, team, and org if applicable * */ export const getAllCredentials = async ( - user: { id: number; username: string | null; credentials: CredentialPayload[] }, - eventType: { - userId?: number | null; - team?: { id: number | null; parentId: number | null } | null; - parentId?: number | null; - metadata: z.infer; - } | null + user: { id: number; username: string | null; email: string; credentials: CredentialPayload[] }, + eventType: EventType ) => { let allCredentials = user.credentials; @@ -117,5 +120,9 @@ export const getAllCredentials = async ( } }); - return allCredentials; + const userWithDwdCredentials = await enrichUserWithDwdCredentialsWithoutOrgId({ + user: { ...user, credentials: allCredentials }, + }); + + return userWithDwdCredentials.credentials; }; diff --git a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts index 857992b8ad..65cf216ae0 100644 --- a/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts +++ b/packages/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials.ts @@ -1,6 +1,8 @@ import async from "async"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import { isDwdCredential } from "@calcom/lib/domainWideDelegation/clientAndServer"; +import { buildAllCredentials } from "@calcom/lib/domainWideDelegation/server"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import { refreshCredential } from "./refreshCredential"; @@ -10,7 +12,14 @@ import { refreshCredential } from "./refreshCredential"; * @param credentials */ export async function refreshCredentials( - credentials: Array -): Promise> { - return await async.mapLimit(credentials, 5, refreshCredential); + credentials: Array +): Promise> { + const nonDwdCredentials = credentials.filter( + (cred) => !isDwdCredential({ credentialId: cred.id }) + ); + const dwdCredentials = credentials.filter((cred) => + isDwdCredential({ credentialId: cred.id }) + ); + const refreshedDbCredentials = await async.mapLimit(nonDwdCredentials, 5, refreshCredential); + return buildAllCredentials({ dwdCredentials, existingCredentials: refreshedDbCredentials }); } diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index aca62f41dd..203b1ebda5 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -70,6 +70,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine externalCalendarId: true, credentialId: true, thirdPartyRecurringEventId: true, + domainWideDelegationCredentialId: true, }, }, payment: true, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 86fbf51186..b0f8c2ab9e 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -47,8 +47,12 @@ import { scheduleTrigger, } from "@calcom/features/webhooks/lib/scheduleTrigger"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; -import { isRerouting, shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils"; +import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils"; import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents"; +import { + enrichHostsWithDwdCredentials, + getFirstDwdConferencingCredentialAppLocation, +} from "@calcom/lib/domainWideDelegation/server"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { extractBaseEmail } from "@calcom/lib/extract-base-email"; @@ -76,6 +80,7 @@ import type { PlatformClientParams } from "@calcom/prisma/zod-utils"; import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util"; import type { AdditionalInformation, AppsStatus, CalendarEvent, Person } from "@calcom/types/Calendar"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import type { EventResult, PartialReference } from "@calcom/types/EventManager"; import type { EventPayloadType, EventTypeInfo } from "../../webhooks/lib/sendPayload"; @@ -113,6 +118,10 @@ import handleSeats from "./handleSeats/handleSeats"; const translator = short(); const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); +type IsFixedAwareUserWithCredentials = Omit & { + credentials: CredentialForCalendarService[]; +}; + export const createLoggerWithEventDetails = ( eventTypeId: number, reqBodyUser: string | string[] | undefined, @@ -167,36 +176,6 @@ type BookingDataSchemaGetter = | typeof getBookingDataSchema | typeof import("@calcom/features/bookings/lib/getBookingDataSchemaForApi").default; -/** - * Adds the contact owner to be the only lucky user - * @returns - */ -function buildLuckyUsersWithJustContactOwner({ - contactOwnerEmail, - availableUsers, - fixedUserPool, -}: { - contactOwnerEmail: string | null; - availableUsers: IsFixedAwareUser[]; - fixedUserPool: IsFixedAwareUser[]; -}) { - const luckyUsers: Awaited>["qualifiedRRUsers"] = []; - if (!contactOwnerEmail) { - return luckyUsers; - } - - const isContactOwnerAFixedHostAlready = fixedUserPool.some((user) => user.email === contactOwnerEmail); - if (isContactOwnerAFixedHostAlready) { - return luckyUsers; - } - - const teamMember = availableUsers.find((user) => user.email === contactOwnerEmail); - if (teamMember) { - luckyUsers.push(teamMember); - } - return luckyUsers; -} - type CreatedBooking = Booking & { appsStatus?: AppsStatus[]; paymentUid?: string; paymentId?: number }; const buildDryRunBooking = ({ @@ -478,11 +457,6 @@ async function handler( const contactOwnerFromReq = reqBody.teamMemberEmail ?? null; - const isReroutingCase = isRerouting({ - rescheduleUid: reqBody.rescheduleUid ?? null, - routedTeamMemberIds: routedTeamMemberIds ?? null, - }); - const skipContactOwner = shouldIgnoreContactOwner({ skipContactOwner: reqBody.skipContactOwner ?? null, rescheduleUid: reqBody.rescheduleUid ?? null, @@ -529,11 +503,13 @@ async function handler( // We filter out users but ensure allHostUsers remain same. let users = [...qualifiedRRUsers, ...additionalFallbackRRUsers, ...fixedUsers]; - let { locationBodyString, organizerOrFirstDynamicGroupMemberDefaultLocationUrl } = getLocationValuesForDb( + const firstUser = users[0]; + + let { locationBodyString, organizerOrFirstDynamicGroupMemberDefaultLocationUrl } = getLocationValuesForDb({ dynamicUserList, users, - location - ); + location, + }); await monitorCallbackAsync(checkBookingAndDurationLimits, { eventType, @@ -565,11 +541,11 @@ async function handler( //checks what users are available if (isFirstSeat) { - const eventTypeWithUsers: getEventTypeResponse & { - users: IsFixedAwareUser[]; + const eventTypeWithUsers: Omit & { + users: IsFixedAwareUserWithCredentials[]; } = { ...eventType, - users: users as IsFixedAwareUser[], + users: users as IsFixedAwareUserWithCredentials[], ...(eventType.recurringEvent && { recurringEvent: { ...eventType.recurringEvent, @@ -583,7 +559,7 @@ async function handler( eventType.schedulingType === SchedulingType.ROUND_ROBIN; const fixedUsers = isTeamEvent - ? eventTypeWithUsers.users.filter((user: IsFixedAwareUser) => user.isFixed) + ? eventTypeWithUsers.users.filter((user: IsFixedAwareUserWithCredentials) => user.isFixed) : []; for ( @@ -609,6 +585,7 @@ async function handler( ); } } else { + eventTypeWithUsers.users[0].credentials; await ensureAvailableUsers( eventTypeWithUsers, { @@ -705,13 +682,19 @@ async function handler( // freeUsers is ensured const userIdsSet = new Set(users.map((user) => user.id)); - + const firstUserOrgId = await getOrgIdFromMemberOrTeamId({ + memberId: eventTypeWithUsers.users[0].id ?? null, + teamId: eventType.teamId, + }); const newLuckyUser = await getLuckyUser({ // find a lucky user that is not already in the luckyUsers array availableUsers: freeUsers, - allRRHosts: eventTypeWithUsers.hosts.filter( - (host) => !host.isFixed && userIdsSet.has(host.user.id) - ), // users part of virtual queue + allRRHosts: ( + await enrichHostsWithDwdCredentials({ + orgId: firstUserOrgId ?? null, + hosts: eventTypeWithUsers.hosts, + }) + ).filter((host) => !host.isFixed && userIdsSet.has(host.user.id)), eventType, routingFormResponse, }); @@ -825,6 +808,11 @@ async function handler( locationBodyString = OrganizerDefaultConferencingAppType; } } + + const organizationDefaultLocation = getFirstDwdConferencingCredentialAppLocation({ + credentials: firstUser.credentials, + }); + // use host default if (locationBodyString == OrganizerDefaultConferencingAppType) { const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata); @@ -836,6 +824,8 @@ async function handler( organizerOrFirstDynamicGroupMemberDefaultLocationUrl = organizerMetadata?.defaultConferencingApp?.appLink; } + } else if (organizationDefaultLocation) { + locationBodyString = organizationDefaultLocation; } else { locationBodyString = "integrations:daily"; } @@ -1294,7 +1284,7 @@ async function handler( endTime: reqBody.end, contactOwnerFromReq, contactOwnerEmail, - allHostUsers: [...qualifiedRRUsers, ...additionalFallbackRRUsers, ...fixedUsers], + allHostUsers: users, isManagedEventType, }); booking = dryRunBooking; diff --git a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts index 0169db4b1b..db1527cc51 100644 --- a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts @@ -49,7 +49,7 @@ const hasDateRangeForBooking = ( }; export async function ensureAvailableUsers( - eventType: getEventTypeResponse & { + eventType: Omit & { users: IsFixedAwareUser[]; }, input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType }, diff --git a/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts b/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts index dec930700a..435f9d2b93 100644 --- a/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts +++ b/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts @@ -1,10 +1,12 @@ +import { getFirstDwdConferencingCredentialAppLocation } from "@calcom/lib/domainWideDelegation/server"; +import type { Prisma } from "@calcom/prisma/client"; import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; -import type { loadAndValidateUsers } from "./loadAndValidateUsers"; - -type Users = Awaited>["qualifiedRRUsers"]; - -const sortUsersByDynamicList = (users: Users, dynamicUserList: string[]) => { +const sortUsersByDynamicList = ( + users: TUser[], + dynamicUserList: string[] +) => { return users.sort((a, b) => { const aIndex = (a.username && dynamicUserList.indexOf(a.username)) || 0; const bIndex = (b.username && dynamicUserList.indexOf(b.username)) || 0; @@ -12,19 +14,41 @@ const sortUsersByDynamicList = (users: Users, dynamicUserList: string[]) => { }); }; -export const getLocationValuesForDb = ( - dynamicUserList: string[], - users: Users, - locationBodyString: string -) => { - // TODO: It's definition should be moved to getLocationValueForDb - let organizerOrFirstDynamicGroupMemberDefaultLocationUrl; - if (dynamicUserList.length > 1) { - users = sortUsersByDynamicList(users, dynamicUserList); - const firstUsersMetadata = userMetadataSchema.parse(users[0].metadata); - locationBodyString = firstUsersMetadata?.defaultConferencingApp?.appLink || locationBodyString; - organizerOrFirstDynamicGroupMemberDefaultLocationUrl = - firstUsersMetadata?.defaultConferencingApp?.appLink; +export const getLocationValuesForDb = < + TUser extends { + username: string | null; + metadata: Prisma.JsonValue; + credentials: CredentialForCalendarService[]; } - return { locationBodyString, organizerOrFirstDynamicGroupMemberDefaultLocationUrl }; +>({ + dynamicUserList, + users, + location: locationBodyString, +}: { + dynamicUserList: string[]; + users: TUser[]; + location: string; +}) => { + const isDynamicGroupBookingCase = dynamicUserList.length > 1; + let firstDynamicGroupMemberDefaultLocationUrl; + // TODO: It's definition should be moved to getLocationValueForDb + if (isDynamicGroupBookingCase) { + users = sortUsersByDynamicList(users, dynamicUserList); + const firstDynamicGroupMember = users[0]; + const firstDynamicGroupMemberMetadata = userMetadataSchema.parse(firstDynamicGroupMember.metadata); + const firstDynamicGroupMemberDwdConferencingAppLocation = getFirstDwdConferencingCredentialAppLocation({ + credentials: firstDynamicGroupMember.credentials, + }); + + firstDynamicGroupMemberDefaultLocationUrl = + firstDynamicGroupMemberMetadata?.defaultConferencingApp?.appLink || + firstDynamicGroupMemberDwdConferencingAppLocation; + + locationBodyString = firstDynamicGroupMemberDefaultLocationUrl || locationBodyString; + } + + return { + locationBodyString, + organizerOrFirstDynamicGroupMemberDefaultLocationUrl: firstDynamicGroupMemberDefaultLocationUrl, + }; }; diff --git a/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts b/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts index 37298924d9..7f87aacc35 100644 --- a/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts @@ -2,7 +2,9 @@ import type { Prisma } from "@prisma/client"; import type { IncomingMessage } from "http"; import type { Logger } from "tslog"; -import { findQualifiedHosts } from "@calcom/lib/bookings/findQualifiedHosts"; +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"; @@ -12,6 +14,7 @@ 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"; @@ -22,6 +25,13 @@ type Users = (Awaited>[number] & { createdAt?: Date; })[]; +export type UsersWithDwdCredentials = (Omit>[number], "credentials"> & { + isFixed?: boolean; + metadata?: Prisma.JsonValue; + createdAt?: Date; + credentials: CredentialForCalendarService[]; +})[]; + type EventType = Pick< NewBookingEventType, | "hosts" @@ -36,6 +46,7 @@ type EventType = Pick< | "rrSegmentQueryValue" | "isRRWeightsEnabled" | "rescheduleWithSameRoundRobinHost" + | "teamId" >; type InputProps = { @@ -60,7 +71,11 @@ export async function loadAndValidateUsers({ contactOwnerEmail, rescheduleUid, routingFormResponse, -}: InputProps): Promise<{ qualifiedRRUsers: Users; additionalFallbackRRUsers: Users; fixedUsers: Users }> { +}: InputProps): Promise<{ + qualifiedRRUsers: UsersWithDwdCredentials; + additionalFallbackRRUsers: UsersWithDwdCredentials; + fixedUsers: UsersWithDwdCredentials; +}> { let users: Users = await loadUsers({ eventType, dynamicUserList, @@ -68,6 +83,7 @@ export async function loadAndValidateUsers({ routedTeamMemberIds, contactOwnerEmail, }); + const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking); if (!isDynamicAllowed && !eventTypeId) { logger.warn({ @@ -109,32 +125,51 @@ export async function loadAndValidateUsers({ ? false : user.isFixed || eventType.schedulingType !== SchedulingType.ROUND_ROBIN, })); - const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = await findQualifiedHosts({ + 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 + >["qualifiedRRHosts"][number]; + } + ); - let qualifiedRRUsers: Users = []; - let allFallbackRRUsers: Users = []; - let fixedUsers: Users = []; + 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)); + 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)); + 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)); + fixedUsers = users + .filter((user) => fixedHostIds.has(user.id)) + .map((user) => ({ ...user, credentials: allQualifiedHostsHashMap[user.id].user.credentials })); } logger.debug( @@ -148,9 +183,26 @@ export async function loadAndValidateUsers({ (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: !qualifiedRRUsers.length && !fixedUsers.length ? users : fixedUsers, + fixedUsers, }; } diff --git a/packages/features/bookings/lib/handleNewBooking/test/domain-wide-delegation.test.ts b/packages/features/bookings/lib/handleNewBooking/test/domain-wide-delegation.test.ts new file mode 100644 index 0000000000..c928d7cf52 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/test/domain-wide-delegation.test.ts @@ -0,0 +1,760 @@ +/** + * These tests are integration tests that test the flow from receiving a api/book/event request and then verifying + * - database entries created in In-MEMORY DB using prismock + * - emails sent by checking the testEmails global variable + * - webhooks fired by mocking fetch + * - APIs of various apps called by mocking those apps' modules + * + * They don't intend to test what the apps logic should do, but rather test if the apps are called with the correct data. For testing that, once should write tests within each app. + */ +import { + createBookingScenario, + TestData, + getDate, + getOrganizer, + getBooker, + getScenarioData, + mockSuccessfulVideoMeetingCreation, + mockCalendarToHaveNoBusySlots, + BookingLocations, + createDwdCredential, + createOrganization, + buildDwdCredential, +} from "@calcom/web/test/utils/bookingScenario/bookingScenario"; +import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest"; +import { + expectWorkflowToBeTriggered, + expectSuccessfulBookingCreationEmails, + expectBookingToBeInDatabase, + expectBookingCreatedWebhookToHaveBeenFired, + expectSuccessfulCalendarEventCreationInCalendar, + expectICalUIDAsString, + expectBookingToNotHaveReference, + expectNoAttemptToCreateCalendarEvent, +} from "@calcom/web/test/utils/bookingScenario/expects"; +import { + getMockRequestDataForBooking, + getMockRequestDataForDynamicGroupBooking, +} from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking"; +import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; + +import { describe, expect } from "vitest"; + +import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; +import { BookingStatus } from "@calcom/prisma/enums"; +import { MembershipRole } from "@calcom/prisma/enums"; +import { test } from "@calcom/web/test/fixtures/fixtures"; + +// Local test runs sometime gets too slow +const timeout = process.env.CI ? 5000 : 20000; + +describe("handleNewBooking", () => { + setupAndTeardown(); + describe("Domain Wide Delegation", () => { + test( + `should create a successful booking using the domain wide delegation credential + 1. Should create a booking in the database with reference having DWD credential + 2. Should create an event in calendar with DWD credential + 3. Should use Google Meet as the location even when not explicitly set. +`, + async ({ emails }) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const payloadToMakePartOfOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: org.id, + name: "Test Org", + slug: "testorg", + }, + }, + ]; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + // User must be part of organization to be able to use that organization's DWD credential + teams: payloadToMakePartOfOrganization, + // No regular credentials provided + credentials: [], + destinationCalendar: TestData.selectedCalendars.google, + }); + + const dwd = await createDwdCredential(org.id); + const payloadToCreateUserEventTypeForOrganizer = { + id: 1, + slotInterval: 30, + length: 30, + users: [ + { + id: organizer.id, + }, + ], + }; + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: organizer.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 1, + appId: null, + }, + ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeOn: [1], + }, + ], + eventTypes: [payloadToCreateUserEventTypeForOrganizer], + organizer, + apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], + }) + ); + + // Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID + const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", { + create: { + id: "GOOGLE_CALENDAR_EVENT_ID", + uid: "MOCK_ID", + appSpecificData: { + googleCalendar: { + hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + }, + }, + }, + }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: BookingLocations.GoogleMeet }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + + expect(createdBooking.responses).toEqual( + expect.objectContaining({ + email: booker.email, + name: booker.name, + }) + ); + + expect(createdBooking).toEqual( + expect.objectContaining({ + location: BookingLocations.GoogleMeet, + }) + ); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingData.eventTypeId, + status: BookingStatus.ACCEPTED, + references: [ + { + type: appStoreMetadata.googlecalendar.type, + uid: "GOOGLE_CALENDAR_EVENT_ID", + meetingId: "GOOGLE_CALENDAR_EVENT_ID", + meetingPassword: "MOCK_PASSWORD", + meetingUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + // Verify DWD credential was used + domainWideDelegationCredentialId: dwd.id, + }, + ], + iCalUID: createdBooking.iCalUID, + }); + + expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails }); + expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { + credential: buildDwdCredential({ serviceAccountKey: dwd.serviceAccountKey }), + calendarId: TestData.selectedCalendars.google.externalId, + // There would be no videoCallUrl in this case as it is not a dedicated conferencing case and hangoutLink is used instead + videoCallUrl: null, + }); + + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + + expectSuccessfulBookingCreationEmails({ + booking: { + uid: createdBooking.uid!, + }, + booker, + organizer, + emails, + iCalUID, + }); + + expectBookingCreatedWebhookToHaveBeenFired({ + booker, + organizer, + location: BookingLocations.GoogleMeet, + subscriberUrl: "http://my-webhook.example.com", + videoCallUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + }); + }, + timeout + ); + + test( + `should create a successful booking using the office365 domain wide delegation credential with Cal Video as the location + 1. Should create a booking in the database with reference having DWD credential + 2. Should create an event in Outlook calendar with DWD credential +`, + async ({ emails }) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const payloadToMakePartOfOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: org.id, + name: "Test Org", + slug: "testorg", + }, + }, + ]; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.office365], + // User must be part of organization to be able to use that organization's DWD credential + teams: payloadToMakePartOfOrganization, + // No regular credentials provided + credentials: [], + destinationCalendar: TestData.selectedCalendars.office365, + }); + + const dwd = await createDwdCredential(org.id, "office365"); + const payloadToCreateUserEventTypeForOrganizer = { + id: 1, + slotInterval: 30, + length: 30, + users: [ + { + id: organizer.id, + }, + ], + }; + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: organizer.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 1, + appId: null, + }, + ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeOn: [1], + }, + ], + eventTypes: [payloadToCreateUserEventTypeForOrganizer], + organizer, + apps: [TestData.apps["daily-video"], TestData.apps["office365-calendar"]], + }) + ); + + mockSuccessfulVideoMeetingCreation({ + metadataLookupKey: "dailyvideo", + videoMeetingData: { + id: "MOCK_ID", + password: "MOCK_PASS", + url: `http://mock-dailyvideo.example.com/meeting-1`, + }, + }); + + // Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID + const calendarMock = mockCalendarToHaveNoBusySlots("office365calendar", { + create: { + id: "OFFICE_365_CALENDAR_EVENT_ID", + uid: "MOCK_ID", + appSpecificData: { + office365Calendar: { + url: "http://mock-dailyvideo.example.com/meeting-1", + }, + }, + }, + }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: BookingLocations.CalVideo }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + + expect(createdBooking.responses).toEqual( + expect.objectContaining({ + email: booker.email, + name: booker.name, + }) + ); + + expect(createdBooking).toEqual( + expect.objectContaining({ + location: BookingLocations.CalVideo, + }) + ); + + console.log("createdBooking", createdBooking); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingData.eventTypeId, + status: BookingStatus.ACCEPTED, + references: [ + { + type: appStoreMetadata.dailyvideo.type, + uid: "MOCK_ID", + meetingId: "MOCK_ID", + meetingPassword: "MOCK_PASS", + meetingUrl: "http://mock-dailyvideo.example.com/meeting-1", + }, + { + type: appStoreMetadata.office365calendar.type, + uid: "OFFICE_365_CALENDAR_EVENT_ID", + meetingId: "OFFICE_365_CALENDAR_EVENT_ID", + meetingPassword: "MOCK_PASSWORD", + meetingUrl: "https://UNUSED_URL", + // Verify DWD credential was used + domainWideDelegationCredentialId: dwd.id, + }, + ], + iCalUID: createdBooking.iCalUID, + }); + + expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails }); + expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { + credential: buildDwdCredential({ serviceAccountKey: dwd.serviceAccountKey }), + calendarId: TestData.selectedCalendars.office365.externalId, + // There would be no videoCallUrl in this case as it is not a dedicated conferencing case and hangoutLink is used instead + videoCallUrl: null, + }); + + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + + expectSuccessfulBookingCreationEmails({ + booking: { + uid: createdBooking.uid!, + }, + booker, + organizer, + emails, + iCalUID, + calendarType: "office365_calendar", + }); + + expectBookingCreatedWebhookToHaveBeenFired({ + booker, + organizer, + location: BookingLocations.CalVideo, + subscriberUrl: "http://my-webhook.example.com", + videoCallUrl: `http://app.cal.local:3000/video/${createdBooking.uid}`, + }); + }, + timeout + ); + + test( + `should fail calendar event creation when organizer isn't part of the organization of DWD Credential`, + async () => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + withTeam: true, + }); + + const anotherOrg = await createOrganization({ + name: "Another Org", + slug: "anotherorg", + }); + + const payloadToMakePartOfOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: org.id, + name: "Test Org", + slug: "testorg", + }, + }, + ]; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + // User must be part of organization to be able to use that organization's DWD credential + teams: payloadToMakePartOfOrganization, + // No regular credentials provided + credentials: [], + }); + + await createDwdCredential(anotherOrg.id); + const payloadToCreateUserEventTypeForOrganizer = { + id: 1, + slotInterval: 30, + length: 30, + users: [ + { + id: organizer.id, + }, + ], + }; + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: organizer.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 1, + appId: null, + }, + ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeOn: [1], + }, + ], + eventTypes: [payloadToCreateUserEventTypeForOrganizer], + organizer, + apps: [TestData.apps["daily-video"]], + }) + ); + + mockSuccessfulVideoMeetingCreation({ + metadataLookupKey: "dailyvideo", + videoMeetingData: { + id: "MOCK_ID", + password: "MOCK_PASS", + url: `http://mock-dailyvideo.example.com/meeting-1`, + }, + }); + + // Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID + const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", { + create: { + id: "GOOGLE_CALENDAR_EVENT_ID", + uid: "MOCK_ID", + appSpecificData: { + googleCalendar: { + hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + }, + }, + }, + }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: BookingLocations.CalVideo }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + + expect(createdBooking.responses).toEqual( + expect.objectContaining({ + email: booker.email, + name: booker.name, + }) + ); + + expect(createdBooking).toEqual( + expect.objectContaining({ + location: BookingLocations.CalVideo, + }) + ); + + expectNoAttemptToCreateCalendarEvent(calendarMock); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingData.eventTypeId, + status: BookingStatus.ACCEPTED, + references: [ + { + type: appStoreMetadata.dailyvideo.type, + uid: "MOCK_ID", + meetingId: "MOCK_ID", + meetingPassword: "MOCK_PASS", + meetingUrl: "http://mock-dailyvideo.example.com/meeting-1", + }, + ], + iCalUID: createdBooking.iCalUID, + }); + + // Could not create calendar-event + expectBookingToNotHaveReference(createdBooking, { + type: appStoreMetadata.googlecalendar.type, + uid: "GOOGLE_CALENDAR_EVENT_ID", + }); + }, + timeout + ); + + test( + `should create a successful dynamic group booking using the domain wide delegation credential + 1. Should create a booking in the database with reference having DWD credential + 2. Should create an event in calendar with DWD credential for both users + 3. Should use Google Meet as the location even when not explicitly set. +`, + async () => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const payloadToMakePartOfOrganization = [ + { + membership: { + accepted: true, + role: MembershipRole.ADMIN, + }, + team: { + id: org.id, + name: "Test Org", + slug: "testorg", + }, + }, + ]; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const groupUser1 = getOrganizer({ + name: "group-user-1", + username: "group-user-1", + email: "group-user-1@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + teams: payloadToMakePartOfOrganization, + credentials: [], + destinationCalendar: TestData.selectedCalendars.google, + }); + + const groupUser2 = getOrganizer({ + name: "group-user-2", + username: "group-user-2", + email: "group-user-2@example.com", + id: 102, + schedules: [TestData.schedules.IstWorkHours], + selectedCalendars: [TestData.selectedCalendars.google], + teams: payloadToMakePartOfOrganization, + credentials: [], + destinationCalendar: TestData.selectedCalendars.google, + }); + + const dwd = await createDwdCredential(org.id); + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: groupUser1.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 0, + appId: null, + }, + ], + workflows: [ + { + userId: groupUser1.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeOn: [0], + }, + ], + eventTypes: [], + users: [groupUser1, groupUser2], + apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], + }) + ); + + // Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID + const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", { + create: { + id: "GOOGLE_CALENDAR_EVENT_ID", + uid: "MOCK_ID", + appSpecificData: { + googleCalendar: { + hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + }, + }, + }, + }); + + const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); + + const mockBookingData = getMockRequestDataForDynamicGroupBooking({ + data: { + start: `${plus1DateString}T05:00:00.000Z`, + end: `${plus1DateString}T05:30:00.000Z`, + eventTypeId: 0, + eventTypeSlug: "group-user-1+group-user-2", + user: "group-user-1+group-user-2", + responses: { + email: booker.email, + name: booker.name, + // There is no location option during booking for Dynamic Group Bookings + // location: { optionValue: "", value: BookingLocations.CalVideo }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + + expect(createdBooking.responses).toEqual( + expect.objectContaining({ + email: booker.email, + name: booker.name, + }) + ); + + expect(createdBooking).toEqual( + expect.objectContaining({ + location: BookingLocations.GoogleMeet, + }) + ); + + await expectBookingToBeInDatabase({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: null, + status: BookingStatus.ACCEPTED, + location: BookingLocations.GoogleMeet, + references: [ + { + type: appStoreMetadata.googlecalendar.type, + uid: "GOOGLE_CALENDAR_EVENT_ID", + meetingId: "GOOGLE_CALENDAR_EVENT_ID", + meetingPassword: "MOCK_PASSWORD", + meetingUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT", + // Verify DWD credential was used + domainWideDelegationCredentialId: dwd.id, + }, + ], + iCalUID: createdBooking.iCalUID, + }); + }, + timeout + ); + }); +}); diff --git a/packages/features/bookings/lib/handleNewBooking/types.ts b/packages/features/bookings/lib/handleNewBooking/types.ts index da8e5eb299..cda159e0e3 100644 --- a/packages/features/bookings/lib/handleNewBooking/types.ts +++ b/packages/features/bookings/lib/handleNewBooking/types.ts @@ -7,7 +7,7 @@ import type { DefaultEvent } from "@calcom/lib/defaultEvents"; import type { PaymentAppData } from "@calcom/lib/getPaymentAppData"; import type { userSelect } from "@calcom/prisma"; import type { SelectedCalendar } from "@calcom/prisma/client"; -import type { CredentialPayload } from "@calcom/types/Credential"; +import type { CredentialForCalendarService } from "@calcom/types/Credential"; import type { Booking } from "./createBooking"; import type { @@ -55,7 +55,7 @@ export interface IEventTypePaymentCredentialType { export type IsFixedAwareUser = User & { isFixed: boolean; - credentials: CredentialPayload[]; + credentials: CredentialForCalendarService[]; organization?: { slug: string }; priority?: number; weight?: number; diff --git a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts index d753fb2d07..4a14b91efa 100644 --- a/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts +++ b/packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts @@ -5,6 +5,8 @@ import { updateMeeting } from "@calcom/core/videoClient"; import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; +import { getAllDwdCredentialsForUser } from "@calcom/lib/domainWideDelegation/server"; +import { getDwdOrFindRegularCredential } from "@calcom/lib/domainWideDelegation/server"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -12,7 +14,6 @@ import { getTranslation } from "@calcom/lib/server/i18n"; import { WorkflowRepository } from "@calcom/lib/server/repository/workflow"; import prisma from "@calcom/prisma"; import { WebhookTriggerEvents } from "@calcom/prisma/enums"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import { bookingCancelAttendeeSeatSchema } from "@calcom/prisma/zod-utils"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; @@ -67,6 +68,12 @@ async function cancelAttendeeSeat( (req as NextApiRequest).statusCode = 200; const attendee = bookingToDelete?.attendees.find((attendee) => attendee.id === seatReference.attendeeId); + const bookingToDeleteUser = bookingToDelete.user ?? null; + const dwdCredentials = bookingToDeleteUser + ? await getAllDwdCredentialsForUser({ + user: { email: bookingToDeleteUser.email, id: bookingToDeleteUser.id }, + }) + : []; if (attendee) { /* If there are references then we should update them as well */ @@ -74,12 +81,13 @@ async function cancelAttendeeSeat( const integrationsToUpdate = []; for (const reference of bookingToDelete.references) { - if (reference.credentialId) { - const credential = await prisma.credential.findUnique({ - where: { - id: reference.credentialId, + if (reference.credentialId || reference.domainWideDelegationCredentialId) { + const credential = await getDwdOrFindRegularCredential({ + id: { + credentialId: reference.credentialId, + domainWideDelegationCredentialId: reference.domainWideDelegationCredentialId, }, - select: credentialForCalendarServiceSelect, + dwdCredentials, }); if (credential) { diff --git a/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts b/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts index 4c1eb418cf..97c22aac0b 100644 --- a/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts +++ b/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts @@ -3,9 +3,10 @@ import type { Attendee } from "@prisma/client"; // eslint-disable-next-line no-restricted-imports import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { deleteMeeting } from "@calcom/core/videoClient"; +import { getAllDwdCredentialsForUser } from "@calcom/lib/domainWideDelegation/server"; +import { getDwdOrFindRegularCredential } from "@calcom/lib/domainWideDelegation/server"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import type { CalendarEvent } from "@calcom/types/Calendar"; import type { OriginalRescheduledBooking } from "../../handleNewBooking/types"; @@ -18,16 +19,23 @@ const lastAttendeeDeleteBooking = async ( originalBookingEvt?: CalendarEvent ) => { let deletedReferences = false; + const bookingUser = originalRescheduledBooking?.user; + const dwdCredentials = bookingUser + ? await getAllDwdCredentialsForUser({ + user: { email: bookingUser.email, id: bookingUser.id }, + }) + : []; if ((!filteredAttendees || filteredAttendees.length === 0) && originalRescheduledBooking) { const integrationsToDelete = []; for (const reference of originalRescheduledBooking.references) { - if (reference.credentialId) { - const credential = await prisma.credential.findUnique({ - where: { - id: reference.credentialId, + if (reference.credentialId || reference.domainWideDelegationCredentialId) { + const credential = await getDwdOrFindRegularCredential({ + id: { + credentialId: reference.credentialId, + domainWideDelegationCredentialId: reference.domainWideDelegationCredentialId, }, - select: credentialForCalendarServiceSelect, + dwdCredentials, }); if (credential) { diff --git a/packages/features/calendar-cache/calendar-cache.ts b/packages/features/calendar-cache/calendar-cache.ts index 5ab9c9567f..078a654bbd 100644 --- a/packages/features/calendar-cache/calendar-cache.ts +++ b/packages/features/calendar-cache/calendar-cache.ts @@ -1,7 +1,6 @@ import { getCalendar } from "@calcom/app-store/_utils/getCalendar"; import { FeaturesRepository } from "@calcom/features/flags/features.repository"; -import prisma from "@calcom/prisma"; -import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import { CredentialRepository } from "@calcom/lib/server/repository/credential"; import type { Calendar } from "@calcom/types/Calendar"; import { CalendarCacheRepository } from "./calendar-cache.repository"; @@ -10,9 +9,8 @@ import { CalendarCacheRepositoryMock } from "./calendar-cache.repository.mock"; export class CalendarCache { static async initFromCredentialId(credentialId: number): Promise { - const credential = await prisma.credential.findUnique({ - where: { id: credentialId }, - select: credentialForCalendarServiceSelect, + const credential = await CredentialRepository.findCredentialForCalendarServiceById({ + id: credentialId, }); const calendar = await getCalendar(credential); return await CalendarCache.init(calendar); diff --git a/packages/features/calendars/CalendarSwitch.tsx b/packages/features/calendars/CalendarSwitch.tsx index 4a2ffcadd9..5ba91e4554 100644 --- a/packages/features/calendars/CalendarSwitch.tsx +++ b/packages/features/calendars/CalendarSwitch.tsx @@ -17,6 +17,7 @@ export type ICalendarSwitchProps = { isLastItemInList?: boolean; destination?: boolean; credentialId: number; + domainWideDelegationCredentialId: string | null; eventTypeId: number | null; disabled?: boolean; }; @@ -28,7 +29,17 @@ type EventCalendarSwitchProps = ICalendarSwitchProps & { }; const CalendarSwitch = (props: ICalendarSwitchProps) => { - const { title, externalId, type, isChecked, name, credentialId, eventTypeId, disabled } = props; + const { + title, + externalId, + type, + isChecked, + name, + credentialId, + domainWideDelegationCredentialId, + eventTypeId, + disabled, + } = props; const [checkedInternal, setCheckedInternal] = useState(isChecked); const utils = trpc.useUtils(); const { t } = useLocale(); @@ -37,6 +48,7 @@ const CalendarSwitch = (props: ICalendarSwitchProps) => { const body = { integration: type, externalId: externalId, + ...(domainWideDelegationCredentialId && { domainWideDelegationCredentialId }), // new URLSearchParams does not accept numbers credentialId: String(credentialId), ...(eventTypeId ? { eventTypeId: String(eventTypeId) } : {}), diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 97bf17763f..a32429e52f 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -8,6 +8,7 @@ import { sendCancelledEmailsAndSMS } from "@calcom/emails"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/scheduleTrigger"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; +import { buildNonDwdCredential } from "@calcom/lib/domainWideDelegation/server"; import { deletePayment } from "@calcom/lib/payment/deletePayment"; import { getTranslation } from "@calcom/lib/server/i18n"; import { bookingMinimalSelect, prisma } from "@calcom/prisma"; @@ -429,7 +430,7 @@ const handleDeleteCredential = async ({ // If it's a calendar remove it from the SelectedCalendars if (credential.app?.categories.includes(AppCategories.calendar)) { try { - const calendar = await getCalendar(credential); + const calendar = await getCalendar(buildNonDwdCredential(credential)); const calendars = await calendar?.listCalendars(); diff --git a/packages/features/domain-wide-delegation/domain-wide-delegation-repository.interface.ts b/packages/features/domain-wide-delegation/domain-wide-delegation-repository.interface.ts deleted file mode 100644 index 9c77dacc9f..0000000000 --- a/packages/features/domain-wide-delegation/domain-wide-delegation-repository.interface.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Prisma } from "@prisma/client"; - -type DomainWideDelegationSafeSelect = { - id: string; - enabled: boolean; - domain: string; - createdAt: Date; - updatedAt: Date; - organizationId: number; - workspacePlatform: { - name: string; - slug: string; - }; -}; - -export interface IDomainWideDelegationRepository { - create(args: { - domain: string; - enabled: boolean; - organizationId: number; - workspacePlatformId: number; - serviceAccountKey: Exclude; - }): Promise; - - findById(args: { id: string }): Promise; - - findByIdIncludeSensitiveServiceAccountKey(args: { - id: string; - }): Promise<(DomainWideDelegationSafeSelect & { serviceAccountKey: any }) | null>; - - findUniqueByOrganizationMemberEmail(args: { - email: string; - }): Promise; - - findByUser(args: { user: { email: string } }): Promise; - - updateById(args: { - id: string; - data: Partial<{ - workspacePlatformId: number; - domain: string; - enabled: boolean; - organizationId: number; - }>; - }): Promise; - - deleteById(args: { id: string }): Promise; - - findDelegationsWithServiceAccount(args: { organizationId: number }): Promise< - (DomainWideDelegationSafeSelect & { - workspacePlatform: { - id: number; - name: string; - slug: string; - }; - serviceAccountKey: any; - })[] - >; -} diff --git a/packages/features/domain-wide-delegation/domain-wide-delegation.ts b/packages/features/domain-wide-delegation/domain-wide-delegation.ts deleted file mode 100644 index dfdf7a63dc..0000000000 --- a/packages/features/domain-wide-delegation/domain-wide-delegation.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DomainWideDelegationRepository } from "@calcom/lib/server/repository/domainWideDelegation"; -import prisma from "@calcom/prisma"; - -import { MockDomainWideDelegationRepository } from "./mock-domain-wide-delegation.repository"; - -export class DomainWideDelegation { - static async checkIfDwDIsEnabled(userId: number, teamId: number | null) { - if (!teamId) { - return false; - } - const teamFeature = await prisma.teamFeatures.findFirst({ - where: { - teamId: teamId, - featureId: "domain-wide-delegation", - }, - }); - - const membership = await prisma.membership.findFirst({ - where: { - userId: userId, - teamId: teamId, - accepted: true, - }, - }); - - return !!(teamFeature && membership); - } - - static async init(userId: number, teamId: number | null) { - const domainWideDelegationEnabled = await this.checkIfDwDIsEnabled(userId, teamId); - - if (!domainWideDelegationEnabled) { - return new MockDomainWideDelegationRepository(); - } - - return new DomainWideDelegationRepository(); - } -} diff --git a/packages/features/domain-wide-delegation/mock-domain-wide-delegation.repository.ts b/packages/features/domain-wide-delegation/mock-domain-wide-delegation.repository.ts deleted file mode 100644 index a900466bad..0000000000 --- a/packages/features/domain-wide-delegation/mock-domain-wide-delegation.repository.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Prisma } from "@prisma/client"; - -import type { IDomainWideDelegationRepository } from "./domain-wide-delegation-repository.interface"; - -export class MockDomainWideDelegationRepository implements IDomainWideDelegationRepository { - async create(_data: { - domain: string; - enabled: boolean; - organizationId: number; - workspacePlatformId: number; - serviceAccountKey: Exclude; - }) { - return null; - } - - async findById(_args: { id: string }) { - return null; - } - - async findByIdIncludeSensitiveServiceAccountKey(_args: { id: string }) { - return null; - } - - async findUniqueByOrganizationMemberEmail(_args: { email: string }) { - return null; - } - - async findByUser(_args: { user: { email: string } }) { - return null; - } - - static async findAllByDomain(_args: { domain: string }) { - return []; - } - - static async findFirstByOrganizationId(_args: { organizationId: number }) { - return null; - } - - async updateById(_args: { - id: string; - data: Partial<{ - workspacePlatformId: number; - domain: string; - enabled: boolean; - organizationId: number; - }>; - }) { - return null; - } - - async deleteById(_args: { id: string }) { - return null; - } - - async findDelegationsWithServiceAccount(_args: { organizationId: number }) { - return []; - } -} diff --git a/packages/features/ee/organizations/pages/settings/admin/WorkspacePlatformPage.tsx b/packages/features/ee/organizations/pages/settings/admin/WorkspacePlatformPage.tsx index 9d2a42a5f6..b921123236 100644 --- a/packages/features/ee/organizations/pages/settings/admin/WorkspacePlatformPage.tsx +++ b/packages/features/ee/organizations/pages/settings/admin/WorkspacePlatformPage.tsx @@ -289,12 +289,12 @@ function PlatformListItem({ platform, onEdit, onToggle }: Pick onEdit(platform, "meta"), icon: "pencil", }, - { - id: "edit-service-account", - label: t("edit_service_account"), - onClick: () => onEdit(platform, "serviceAccount"), - icon: "pencil", - }, + // { + // id: "edit-service-account", + // label: t("edit_service_account"), + // onClick: () => onEdit(platform, "serviceAccount"), + // icon: "pencil", + // }, ]} /> @@ -305,7 +305,7 @@ function PlatformListItem({ platform, onEdit, onToggle }: Pick) { const { t } = useLocale(); - type FormValues = { name: string; description: string; slug: string; defaultServiceAccountKey: string }; + type FormValues = { name: string; description: string; slug: string; defaultServiceAccountKey?: string }; const form = useForm({ defaultValues: { name: "", @@ -318,7 +318,7 @@ function CreatePlatformDialog({ isOpen, onOpenChange }: Pick = (values) => { - const parsedKey = JSON.parse(values.defaultServiceAccountKey); - const validatedKey = serviceAccountKeySchema.safeParse(parsedKey); - if (!validatedKey.success) { - form.setError("defaultServiceAccountKey", { message: t("invalid_service_account_key") }); - return; + // Only validate service account key if it's provided + if (values.defaultServiceAccountKey) { + const parsedKey = JSON.parse(values.defaultServiceAccountKey); + const validatedKey = serviceAccountKeySchema.safeParse(parsedKey); + if (!validatedKey.success) { + form.setError("defaultServiceAccountKey", { message: t("invalid_service_account_key") }); + return; + } + addMutation.mutate({ + ...values, + defaultServiceAccountKey: validatedKey.data, + }); + } else { + // Submit without service account key + addMutation.mutate({ + name: values.name, + description: values.description, + slug: values.slug, + enabled: true, + }); } - addMutation.mutate({ - ...values, - defaultServiceAccountKey: validatedKey.data, - }); }; return ( @@ -419,7 +430,7 @@ function PlatformFormFields({ isCreate }: Pick) { {isCreate && } - {isCreate && } + {isCreate && false && } ); } diff --git a/packages/features/ee/organizations/pages/settings/domainWideDelegation.tsx b/packages/features/ee/organizations/pages/settings/domainWideDelegation.tsx index 500ee066be..a13de2a809 100644 --- a/packages/features/ee/organizations/pages/settings/domainWideDelegation.tsx +++ b/packages/features/ee/organizations/pages/settings/domainWideDelegation.tsx @@ -4,6 +4,8 @@ import { useState } from "react"; import { useForm, Controller, useFormContext } from "react-hook-form"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import type { ServiceAccountKey } from "@calcom/lib/server/serviceAccountKey"; +import { serviceAccountKeySchema } from "@calcom/prisma/zod-utils"; import { trpc } from "@calcom/trpc/react"; import { DropdownActions, @@ -13,6 +15,7 @@ import { DialogFooter, Form, TextField, + TextAreaField, SelectField, showToast, Badge, @@ -90,6 +93,7 @@ function DelegationListItemActions({ { id: "delete", label: t("delete"), + disabled: true, onClick: () => onDelete(delegation.id), icon: "trash", }, @@ -126,31 +130,65 @@ function DelegationListItem({ delegation, toggleDelegation, onEdit, onDelete }: ); } +type CreateDelegationData = { + domain: string; + workspacePlatformSlug: string; + enabled: boolean; + serviceAccountKey: ServiceAccountKey; +}; + +type CreateDelegationFormData = Omit & { + serviceAccountKey: string; +}; + +type EditDelegationData = { + id: string; + domain: string; + workspacePlatformSlug: string; + enabled: boolean; +}; + function CreateDelegationDialog({ isOpen, onClose, - onSubmit, workspacePlatforms, + handleCreate, }: { isOpen: boolean; onClose: () => void; - onSubmit: (data: { domain: string; workspacePlatformSlug: string; enabled: boolean }) => void; workspacePlatforms: WorkspacePlatform[]; + handleCreate: (data: CreateDelegationData) => void; }) { const { t } = useLocale(); - const form = useForm<{ domain: string; workspacePlatformSlug: string; enabled: boolean }>({ - defaultValues: { - domain: "", - workspacePlatformSlug: "", - }, - }); + const form = useForm(); + + const handleSubmit = (values: CreateDelegationFormData) => { + try { + const parsedKey = JSON.parse(values.serviceAccountKey); + const validatedKey = serviceAccountKeySchema.safeParse(parsedKey); + + if (!validatedKey.success) { + form.setError("serviceAccountKey", { message: t("invalid_service_account_key") }); + return; + } + + handleCreate({ + ...values, + serviceAccountKey: validatedKey.data, + }); + } catch (e) { + console.log("error", e); + form.setError("serviceAccountKey", { message: t("invalid_service_account_key") }); + return; + } + }; return ( -
- + + diff --git a/tests/libs/__mocks__/app-store.ts b/tests/libs/__mocks__/app-store.ts index 2b74913d72..88ee6a355a 100644 --- a/tests/libs/__mocks__/app-store.ts +++ b/tests/libs/__mocks__/app-store.ts @@ -11,7 +11,7 @@ beforeEach(() => { const appStoreMock = mockDeep({ fallbackMockImplementation: () => { - throw new Error("Unimplemented"); + throw new Error("Unimplemented appStoreMock. You seem to have mocked the app that you are trying to use"); }, }); export default appStoreMock; diff --git a/turbo.json b/turbo.json index 84a6e02446..3ad6b7700a 100644 --- a/turbo.json +++ b/turbo.json @@ -465,6 +465,7 @@ "GOOGLE_CLIENT_SECRET", "ZOOM_REFRESH_TOKEN", "CALCOM_ADMIN_API_KEY", - "NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED" + "NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED", + "CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY" ] } diff --git a/vitest.config.ts b/vitest.config.ts index b710d9cdcc..e3109fbcc2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -18,4 +18,5 @@ function setEnvVariablesThatAreUsedBeforeSetup() { process.env.DAILY_API_KEY = "MOCK_DAILY_API_KEY"; // With same env variable, we can test both non org and org booking scenarios process.env.NEXT_PUBLIC_WEBAPP_URL = "http://app.cal.local:3000"; + process.env.CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY = "UNIT_TEST_ENCRYPTION_KEY"; } diff --git a/yarn.lock b/yarn.lock index 31b49b1496..dc9a8901ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,46 @@ __metadata: version: 6 cacheKey: 8 +"@0no-co/graphql.web@npm:^1.0.1": + version: 1.1.1 + resolution: "@0no-co/graphql.web@npm:1.1.1" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + checksum: 42af588a80ce4526c09b664b614ce61a5a5f45f56a2fa177ba851d028bb95676bcfc8a3ad04dfeaaeda801de6af414b049a8a1e31c2dc809dca1db4ccf7cce90 + languageName: node + linkType: hard + +"@47ng/cloak@npm:^1.2.0": + version: 1.2.0 + resolution: "@47ng/cloak@npm:1.2.0" + dependencies: + "@47ng/codec": ^1.0.1 + "@stablelib/base64": ^1.0.1 + "@stablelib/hex": ^1.0.1 + "@stablelib/utf8": ^1.0.1 + chalk: ^4.1.2 + commander: ^8.3.0 + dotenv: ^10.0.0 + s-ago: ^2.2.0 + bin: + cloak: dist/cli.js + checksum: bb023a28a4c8ecd112ec19679cfa23cfbc279bb995180187cb1dedee39468810590c6dc5f57f786e4137b884d1f407a85fb93194c82f75109515d7cbd73ee341 + languageName: node + linkType: hard + +"@47ng/codec@npm:^1.0.1": + version: 1.1.0 + resolution: "@47ng/codec@npm:1.1.0" + dependencies: + "@stablelib/base64": ^1.0.1 + "@stablelib/hex": ^1.0.1 + checksum: 4f780c4413fe78bbedbaff4135340c0e5f5a30df88f5cffbec51349eb0a1c909728e6c2bbda52506ff8c12653bf39b78c67b78bbe9501b0b9741da0cdaeec6ff + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.0.1": version: 4.2.0 resolution: "@adobe/css-tools@npm:4.2.0" @@ -12,6 +52,17 @@ __metadata: languageName: node linkType: hard +"@algora/sdk@npm:^0.1.2": + version: 0.1.3 + resolution: "@algora/sdk@npm:0.1.3" + dependencies: + "@trpc/client": ^10.0.0 + "@trpc/server": ^10.0.0 + superjson: ^1.9.1 + checksum: 1b99e0f155181beefe12b625969166f4ecfa42d334706224d0a9a4e9ad72e2cda7335712c47290df7aeeeb003a9773ff5babce7cbee8fb8d1c5ded4ad81c80c1 + languageName: node + linkType: hard + "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -141,6 +192,28 @@ __metadata: languageName: node linkType: hard +"@ardatan/relay-compiler@npm:^12.0.1": + version: 12.0.2 + resolution: "@ardatan/relay-compiler@npm:12.0.2" + dependencies: + "@babel/generator": ^7.14.0 + "@babel/parser": ^7.14.0 + "@babel/runtime": ^7.0.0 + chalk: ^4.0.0 + fb-watchman: ^2.0.0 + immutable: ~3.7.6 + invariant: ^2.2.4 + nullthrows: ^1.1.1 + relay-runtime: 12.0.0 + signedsource: ^1.0.0 + peerDependencies: + graphql: "*" + bin: + relay-compiler: bin/relay-compiler + checksum: 30e3759cd725221daf0d10ad2bce5e5ee8e45374281ab17e7ed973d55e0a8f344dbbfc1ce984ffaa66bf37301ad0e13c0eab3f16cb84e3a605f9e025dd4443b3 + languageName: node + linkType: hard + "@aw-web-design/x-default-browser@npm:1.4.126": version: 1.4.126 resolution: "@aw-web-design/x-default-browser@npm:1.4.126" @@ -1075,6 +1148,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": ^7.25.9 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: db13f5c42d54b76c1480916485e6900748bbcb0014a8aca87f50a091f70ff4e0d0a6db63cade75eb41fcc3d2b6ba0a7f89e343def4f96f00269b41b8ab8dd7b8 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9": version: 7.22.9 resolution: "@babel/compat-data@npm:7.22.9" @@ -1096,6 +1180,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.26.5": + version: 7.26.8 + resolution: "@babel/compat-data@npm:7.26.8" + checksum: 1bb04c6860c8c9555b933cb9c3caf5ef1dac331a37a351efb67956fc679f695d487aea76e792dd43823702c1300f7906f2a298e50b4a8d7ec199ada9c340c365 + languageName: node + linkType: hard + "@babel/core@npm:^7.11.6, @babel/core@npm:^7.18.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2": version: 7.23.5 resolution: "@babel/core@npm:7.23.5" @@ -1165,6 +1256,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.22.9": + version: 7.26.9 + resolution: "@babel/core@npm:7.26.9" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.26.2 + "@babel/generator": ^7.26.9 + "@babel/helper-compilation-targets": ^7.26.5 + "@babel/helper-module-transforms": ^7.26.0 + "@babel/helpers": ^7.26.9 + "@babel/parser": ^7.26.9 + "@babel/template": ^7.26.9 + "@babel/traverse": ^7.26.9 + "@babel/types": ^7.26.9 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: b6e33bdcbb8a5c929760548be400d18cbde1f07922a784586752fd544fbf13c71331406ffdb4fcfe53f79c69ceae602efdca654ad4e9ac0c2af47efe87e7fccd + languageName: node + linkType: hard + "@babel/core@npm:^7.23.5, @babel/core@npm:^7.23.9": version: 7.24.0 resolution: "@babel/core@npm:7.24.0" @@ -1199,6 +1313,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/generator@npm:7.26.9" + dependencies: + "@babel/parser": ^7.26.9 + "@babel/types": ^7.26.9 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 + jsesc: ^3.0.2 + checksum: 57d034fb6c77dfd5e0c8ef368ff544e19cb6a27cb70d6ed5ff0552c618153dc6692d31e7d0f3a408e0fec3a519514b846c909316c3078290f3a3c1e463372eae + languageName: node + linkType: hard + "@babel/generator@npm:^7.17.3, @babel/generator@npm:^7.22.10": version: 7.22.10 resolution: "@babel/generator@npm:7.22.10" @@ -1259,6 +1386,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" + dependencies: + "@babel/types": ^7.25.9 + checksum: 41edda10df1ae106a9b4fe617bf7c6df77db992992afd46192534f5cff29f9e49a303231733782dd65c5f9409714a529f215325569f14282046e9d3b7a1ffb6c + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" @@ -1329,6 +1465,36 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.26.5": + version: 7.26.5 + resolution: "@babel/helper-compilation-targets@npm:7.26.5" + dependencies: + "@babel/compat-data": ^7.26.5 + "@babel/helper-validator-option": ^7.25.9 + browserslist: ^4.24.0 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: 6bc0107613bf1d4d21913606e8e517194e5099a24db2a8374568e56ef4626e8140f9b8f8a4aabc35479f5904459a0aead2a91ee0dc63aae110ccbc2bc4b4fda1 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.21.0": + version: 7.26.9 + resolution: "@babel/helper-create-class-features-plugin@npm:7.26.9" + dependencies: + "@babel/helper-annotate-as-pure": ^7.25.9 + "@babel/helper-member-expression-to-functions": ^7.25.9 + "@babel/helper-optimise-call-expression": ^7.25.9 + "@babel/helper-replace-supers": ^7.26.5 + "@babel/helper-skip-transparent-expression-wrappers": ^7.25.9 + "@babel/traverse": ^7.26.9 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: d445a660d2cdd92e83c04a60f52a304e54e5cc338796b6add9dec00048f1ad12125f78145ab688d029569a9559ef64f8e0de86f456b9e2630ea46f664ffb8e45 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.5": version: 7.23.5 resolution: "@babel/helper-create-class-features-plugin@npm:7.23.5" @@ -1497,6 +1663,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" + dependencies: + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 8e2f1979b6d596ac2a8cbf17f2cf709180fefc274ac3331408b48203fe19134ed87800774ef18838d0275c3965130bae22980d90caed756b7493631d4b2cf961 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-imports@npm:7.22.5" @@ -1525,6 +1701,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-module-imports@npm:7.25.9" + dependencies: + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 1b411ce4ca825422ef7065dffae7d8acef52023e51ad096351e3e2c05837e9bf9fca2af9ca7f28dc26d596a588863d0fedd40711a88e350b736c619a80e704e6 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.22.9": version: 7.22.9 resolution: "@babel/helper-module-transforms@npm:7.22.9" @@ -1570,6 +1756,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helper-module-transforms@npm:7.26.0" + dependencies: + "@babel/helper-module-imports": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + "@babel/traverse": ^7.25.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 942eee3adf2b387443c247a2c190c17c4fd45ba92a23087abab4c804f40541790d51ad5277e4b5b1ed8d5ba5b62de73857446b7742f835c18ebd350384e63917 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" @@ -1579,6 +1778,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-optimise-call-expression@npm:7.25.9" + dependencies: + "@babel/types": ^7.25.9 + checksum: f09d0ad60c0715b9a60c31841b3246b47d67650c512ce85bbe24a3124f1a4d66377df793af393273bc6e1015b0a9c799626c48e53747581c1582b99167cc65dc + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": version: 7.22.5 resolution: "@babel/helper-plugin-utils@npm:7.22.5" @@ -1586,6 +1794,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.25.9": + version: 7.26.5 + resolution: "@babel/helper-plugin-utils@npm:7.26.5" + checksum: 4771fbb1711c624c62d12deabc2ed7435a6e6994b6ce09d5ede1bc1bf19be59c3775461a1e693bdd596af865685e87bb2abc778f62ceadc1b2095a8e2aa74180 + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-remap-async-to-generator@npm:7.22.20" @@ -1625,6 +1840,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.26.5": + version: 7.26.5 + resolution: "@babel/helper-replace-supers@npm:7.26.5" + dependencies: + "@babel/helper-member-expression-to-functions": ^7.25.9 + "@babel/helper-optimise-call-expression": ^7.25.9 + "@babel/traverse": ^7.26.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: c5ab31b29c7cc09e30278f8860ecdb873ce6c84b5c08bc5239c369c7c4fe9f0a63cda61b55b7bbd20edb4e5dc32e73087cc3c57d85264834bd191551d1499185 + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-simple-access@npm:7.22.5" @@ -1653,6 +1881,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9" + dependencies: + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: fdbb5248932198bc26daa6abf0d2ac42cab9c2dbb75b7e9f40d425c8f28f09620b886d40e7f9e4e08ffc7aaa2cefe6fc2c44be7c20e81f7526634702fb615bdc + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.16.7, @babel/helper-split-export-declaration@npm:^7.22.6": version: 7.22.6 resolution: "@babel/helper-split-export-declaration@npm:7.22.6" @@ -1692,6 +1930,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 6435ee0849e101681c1849868278b5aee82686ba2c1e27280e5e8aca6233af6810d39f8e4e693d2f2a44a3728a6ccfd66f72d71826a94105b86b731697cdfa99 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-identifier@npm:7.22.5" @@ -1713,6 +1958,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 5b85918cb1a92a7f3f508ea02699e8d2422fe17ea8e82acd445006c0ef7520fbf48e3dbcdaf7b0a1d571fc3a2715a29719e5226636cb6042e15fe6ed2a590944 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.22.15": version: 7.22.15 resolution: "@babel/helper-validator-option@npm:7.22.15" @@ -1741,6 +1993,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-option@npm:7.25.9" + checksum: 9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-wrap-function@npm:7.22.20" @@ -1795,6 +2054,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/helpers@npm:7.26.9" + dependencies: + "@babel/template": ^7.26.9 + "@babel/types": ^7.26.9 + checksum: 06363f8288a24c1cfda03eccd775ac22f79cba319b533cb0e5d0f2a04a33512881cc3f227a4c46324935504fb92999cc4758b69b5e7b3846107eadcb5ee0abca + languageName: node + linkType: hard + "@babel/highlight@npm:^7.22.13": version: 7.22.13 resolution: "@babel/highlight@npm:7.22.13" @@ -1838,6 +2107,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/parser@npm:7.26.9" + dependencies: + "@babel/types": ^7.26.9 + bin: + parser: ./bin/babel-parser.js + checksum: 2df965dbf3c67d19dc437412ceef23033b4d39b0dbd7cb498d8ab9ad9e1738338656ee72676199773b37d658edf9f4161cf255515234fed30695d74e73be5514 + languageName: node + linkType: hard + "@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.22.11, @babel/parser@npm:^7.22.5": version: 7.22.14 resolution: "@babel/parser@npm:7.22.14" @@ -1919,6 +2199,20 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-private-property-in-object@npm:^7.21.11": + version: 7.21.11 + resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.11" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.21.0 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-private-property-in-object": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1b880543bc5f525b360b53d97dd30807302bb82615cd42bf931968f59003cac75629563d6b104868db50abd22235b3271fdf679fea5db59a267181a99cc0c265 + languageName: node + linkType: hard + "@babel/plugin-syntax-async-generators@npm:^7.8.4": version: 7.8.4 resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" @@ -1996,6 +2290,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-import-assertions@npm:^7.20.0": + version: 7.26.0 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.26.0" + dependencies: + "@babel/helper-plugin-utils": ^7.25.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b58f2306df4a690ca90b763d832ec05202c50af787158ff8b50cdf3354359710bce2e1eb2b5135fcabf284756ac8eadf09ca74764aa7e76d12a5cac5f6b21e67 + languageName: node + linkType: hard + "@babel/plugin-syntax-import-assertions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" @@ -3162,6 +3467,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.21.0": + version: 7.26.9 + resolution: "@babel/runtime@npm:7.26.9" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 838492d8a925092f9ccfbd82ec183a54f430af3a4ce88fb1337a4570629202d5123bad3097a5b8df53822504d12ccb29f45c0f6842e86094f0164f17a51eec92 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.23.2": version: 7.23.5 resolution: "@babel/runtime@npm:7.23.5" @@ -3171,6 +3485,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/template@npm:7.26.9" + dependencies: + "@babel/code-frame": ^7.26.2 + "@babel/parser": ^7.26.9 + "@babel/types": ^7.26.9 + checksum: 32259298c775e543ab994daff0c758b3d6a184349b146d6497aa46cec5907bc47a6bc09e7295a81a5eccfbd023d4811a9777cb5d698d582d09a87cabf5b576e7 + languageName: node + linkType: hard + "@babel/template@npm:^7.22.15": version: 7.22.15 resolution: "@babel/template@npm:7.22.15" @@ -3233,6 +3558,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.5, @babel/traverse@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/traverse@npm:7.26.9" + dependencies: + "@babel/code-frame": ^7.26.2 + "@babel/generator": ^7.26.9 + "@babel/parser": ^7.26.9 + "@babel/template": ^7.26.9 + "@babel/types": ^7.26.9 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: d42d3a5e61422d96467f517447b5e254edbd64e4dbf3e13b630704d1f49beaa5209246dc6f45ba53522293bd4760ff720496d2c1ef189ecce52e9e63d9a59aa8 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.5": version: 7.23.5 resolution: "@babel/traverse@npm:7.23.5" @@ -3326,6 +3666,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/types@npm:7.26.9" + dependencies: + "@babel/helper-string-parser": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + checksum: cc124c149615deb30343a4c81ac5b0e3a68bdb4b1bd61a91a2859ee8e5e5f400f6ff65be4740f407c17bfc09baa9c777e7f8f765dccf3284963956b67ac95a38 + languageName: node + linkType: hard + "@babel/types@npm:^7.17.0, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.11, @babel/types@npm:^7.22.5, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.22.11 resolution: "@babel/types@npm:7.22.11" @@ -3888,6 +4238,43 @@ __metadata: languageName: unknown linkType: soft +"@calcom/console@workspace:apps/console": + version: 0.0.0-use.local + resolution: "@calcom/console@workspace:apps/console" + dependencies: + "@calcom/dayjs": "*" + "@calcom/features": "*" + "@calcom/lib": "*" + "@calcom/tsconfig": "*" + "@calcom/ui": "*" + "@headlessui/react": ^1.5.0 + "@heroicons/react": ^1.0.6 + "@prisma/client": ^5.4.2 + "@tailwindcss/forms": ^0.5.2 + "@types/node": 16.9.1 + "@types/react": 18.0.26 + autoprefixer: ^10.4.12 + chart.js: ^3.7.1 + client-only: ^0.0.1 + eslint: ^8.34.0 + next: ^13.5.4 + next-auth: ^4.22.1 + next-i18next: ^13.2.2 + postcss: ^8.4.18 + prisma: ^5.4.2 + prisma-field-encryption: ^1.4.0 + react: ^18.2.0 + react-chartjs-2: ^4.0.1 + react-dom: ^18.2.0 + react-hook-form: ^7.43.3 + react-live-chat-loader: ^2.8.1 + swr: ^1.2.2 + tailwindcss: ^3.3.3 + typescript: ^4.9.4 + zod: ^3.22.4 + languageName: unknown + linkType: soft + "@calcom/core@*, @calcom/core@workspace:packages/core": version: 0.0.0-use.local resolution: "@calcom/core@workspace:packages/core" @@ -4047,7 +4434,7 @@ __metadata: languageName: unknown linkType: soft -"@calcom/embed-react@workspace:*, @calcom/embed-react@workspace:packages/embeds/embed-react": +"@calcom/embed-react@workspace:*, @calcom/embed-react@workspace:^, @calcom/embed-react@workspace:packages/embeds/embed-react": version: 0.0.0-use.local resolution: "@calcom/embed-react@workspace:packages/embeds/embed-react" dependencies: @@ -5267,6 +5654,118 @@ __metadata: languageName: unknown linkType: soft +"@calcom/website@workspace:apps/website": + version: 0.0.0-use.local + resolution: "@calcom/website@workspace:apps/website" + dependencies: + "@algora/sdk": ^0.1.2 + "@calcom/app-store": "*" + "@calcom/config": "*" + "@calcom/dayjs": "*" + "@calcom/embed-react": "workspace:^" + "@calcom/features": "*" + "@calcom/lib": "*" + "@calcom/prisma": "*" + "@calcom/tsconfig": "*" + "@calcom/ui": "*" + "@datocms/cma-client-node": ^2.0.0 + "@floating-ui/react-dom": ^1.0.0 + "@flodlc/nebula": ^1.0.56 + "@graphql-codegen/cli": ^5.0.0 + "@graphql-codegen/typed-document-node": ^5.0.1 + "@graphql-codegen/typescript": ^4.0.1 + "@graphql-codegen/typescript-operations": ^4.0.1 + "@graphql-typed-document-node/core": ^3.2.0 + "@headlessui/react": ^1.5.0 + "@heroicons/react": ^1.0.6 + "@hookform/resolvers": ^2.9.7 + "@juggle/resize-observer": ^3.4.0 + "@next/bundle-analyzer": ^13.1.6 + "@radix-ui/react-accordion": ^1.0.0 + "@radix-ui/react-avatar": ^1.0.4 + "@radix-ui/react-dropdown-menu": ^2.0.5 + "@radix-ui/react-navigation-menu": ^1.0.0 + "@radix-ui/react-portal": ^1.0.0 + "@radix-ui/react-slider": ^1.0.0 + "@radix-ui/react-tabs": ^1.0.0 + "@radix-ui/react-tooltip": ^1.0.0 + "@stripe/stripe-js": ^1.35.0 + "@tanstack/react-query": ^4.3.9 + "@typeform/embed-react": ^1.2.4 + "@types/bcryptjs": ^2.4.2 + "@types/debounce": ^1.2.1 + "@types/gtag.js": ^0.0.10 + "@types/micro": 7.3.7 + "@types/node": 16.9.1 + "@types/react": 18.0.26 + "@types/react-gtm-module": ^2.0.1 + "@types/xml2js": ^0.4.11 + "@vercel/analytics": ^0.1.6 + "@vercel/edge-functions-ui": ^0.2.1 + "@vercel/og": ^0.5.0 + autoprefixer: ^10.4.12 + bcryptjs: ^2.4.3 + clsx: ^1.2.1 + cobe: ^0.4.1 + concurrently: ^7.6.0 + cross-env: ^7.0.3 + datocms-structured-text-to-plain-text: ^2.0.4 + datocms-structured-text-utils: ^2.0.4 + debounce: ^1.2.1 + dotenv: ^16.3.1 + enquirer: ^2.4.1 + env-cmd: ^10.1.0 + eslint: ^8.34.0 + fathom-client: ^3.5.0 + globby: ^13.1.3 + graphql: ^16.8.0 + graphql-codegen: ^0.4.0 + graphql-request: ^6.1.0 + gray-matter: ^4.0.3 + gsap: ^3.11.0 + i18n-unused: ^0.13.0 + iframe-resizer-react: ^1.1.0 + keen-slider: ^6.8.0 + lucide-react: ^0.171.0 + micro: ^10.0.1 + next: ^14.1.3 + next-auth: ^4.22.1 + next-axiom: ^0.17.0 + next-i18next: ^13.2.2 + next-seo: ^6.0.0 + playwright-core: ^1.38.1 + postcss: ^8.4.18 + prism-react-renderer: ^1.3.5 + react: ^18.2.0 + react-confetti: ^6.0.1 + react-datocms: ^3.1.0 + react-device-detect: ^2.2.2 + react-dom: ^18.2.0 + react-fast-marquee: ^1.6.4 + react-github-btn: ^1.4.0 + react-hook-form: ^7.43.3 + react-hot-toast: ^2.3.0 + react-live-chat-loader: ^2.8.1 + react-markdown: ^9.0.1 + react-merge-refs: 1.1.0 + react-resize-detector: ^9.1.0 + react-twemoji: ^0.3.0 + react-use-measure: ^2.1.1 + react-wrap-balancer: ^1.0.0 + remark: ^14.0.2 + remark-html: ^14.0.1 + remeda: ^1.24.1 + stripe: ^9.16.0 + tailwind-merge: ^1.13.2 + tailwindcss: ^3.3.3 + ts-node: ^10.9.1 + typescript: ^4.9.4 + wait-on: ^7.0.1 + xml2js: ^0.6.0 + zod: ^3.22.2 + languageName: unknown + linkType: soft + "@calcom/whatsapp@workspace:packages/app-store/whatsapp": version: 0.0.0-use.local resolution: "@calcom/whatsapp@workspace:packages/app-store/whatsapp" @@ -5693,6 +6192,38 @@ __metadata: languageName: node linkType: hard +"@datocms/cma-client-node@npm:^2.0.0": + version: 2.2.6 + resolution: "@datocms/cma-client-node@npm:2.2.6" + dependencies: + "@datocms/cma-client": ^2.2.6 + "@datocms/rest-client-utils": ^1.3.3 + got: ^11.8.5 + mime-types: ^2.1.35 + tmp-promise: ^3.0.3 + checksum: d18b568f5a4538abbd824091722f7df95c99cb5e550560bcae701654924e7933559b27d176fc7772056afe214516c99e8bb383319d4e492b053d20d3732df929 + languageName: node + linkType: hard + +"@datocms/cma-client@npm:^2.2.6": + version: 2.2.6 + resolution: "@datocms/cma-client@npm:2.2.6" + dependencies: + "@datocms/rest-client-utils": ^1.3.3 + checksum: 52e65ab5cdc6b09859f6b07f87a5e8700ba2b2f0579894b7f3c6735c1360f83e41941783dfab78bd18d3f9e12bbd50436b953663a332c50fbcd12b167c567ed6 + languageName: node + linkType: hard + +"@datocms/rest-client-utils@npm:^1.3.3": + version: 1.3.3 + resolution: "@datocms/rest-client-utils@npm:1.3.3" + dependencies: + "@whatwg-node/fetch": ^0.5.3 + async-scheduler: ^1.4.4 + checksum: eb746ce41b0c38b0ebef42238046ce8ff2734330580b7198cc11bf7182de394af9de4df021e967419592eb62729feae533c7126d5629ec97e7cc8b3aa30e9eb2 + languageName: node + linkType: hard + "@discoveryjs/json-ext@npm:^0.5.3": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -5860,6 +6391,25 @@ __metadata: languageName: node linkType: hard +"@envelop/core@npm:^5.0.2": + version: 5.0.3 + resolution: "@envelop/core@npm:5.0.3" + dependencies: + "@envelop/types": 5.0.0 + tslib: ^2.5.0 + checksum: 5a5987b57fea6fcae3a0665adb9f11c0d25ecf99f235759e5c9601c464a2b99d8f20aca69437225dd8df6dc97d6fb0d3882979158dff53f9c81cde0a6c27893c + languageName: node + linkType: hard + +"@envelop/types@npm:5.0.0": + version: 5.0.0 + resolution: "@envelop/types@npm:5.0.0" + dependencies: + tslib: ^2.5.0 + checksum: 1098f821981d2b59a453f5f21ac3b2ea9fcf6bd253a564f0317f67b7469ff17e53921ea99a3b528cb5c95e0671460d68b55150836ce635788576500317a8ad9b + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/aix-ppc64@npm:0.21.5" @@ -6460,7 +7010,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^1.3.0": +"@floating-ui/react-dom@npm:^1.0.0, @floating-ui/react-dom@npm:^1.3.0": version: 1.3.0 resolution: "@floating-ui/react-dom@npm:1.3.0" dependencies: @@ -6498,6 +7048,13 @@ __metadata: languageName: node linkType: hard +"@flodlc/nebula@npm:^1.0.56": + version: 1.0.56 + resolution: "@flodlc/nebula@npm:1.0.56" + checksum: 044058423bc8a2c6ea60a0636400593a0912c142fbb6f50cc03288c702ae9c2029f84eb4fbac7e701a7ee1c2a5e33cc1af1b8d94af419c1197f74066b7867d21 + languageName: node + linkType: hard + "@formatjs/ecma402-abstract@npm:1.11.4": version: 1.11.4 resolution: "@formatjs/ecma402-abstract@npm:1.11.4" @@ -6683,6 +7240,607 @@ __metadata: languageName: node linkType: hard +"@graphql-codegen/add@npm:^5.0.3": + version: 5.0.3 + resolution: "@graphql-codegen/add@npm:5.0.3" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.0.3 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 5196b6c64907f03dc1adc0ac4f98ed5fe69abe0eb87027a55dfc14b178cc1fee2bc47f68c8f5a68ce7aa4bc5a4f970f983d1d85b6d7a20489e50657baccd2ad0 + languageName: node + linkType: hard + +"@graphql-codegen/cli@npm:^5.0.0": + version: 5.0.5 + resolution: "@graphql-codegen/cli@npm:5.0.5" + dependencies: + "@babel/generator": ^7.18.13 + "@babel/template": ^7.18.10 + "@babel/types": ^7.18.13 + "@graphql-codegen/client-preset": ^4.6.0 + "@graphql-codegen/core": ^4.0.2 + "@graphql-codegen/plugin-helpers": ^5.0.3 + "@graphql-tools/apollo-engine-loader": ^8.0.0 + "@graphql-tools/code-file-loader": ^8.0.0 + "@graphql-tools/git-loader": ^8.0.0 + "@graphql-tools/github-loader": ^8.0.0 + "@graphql-tools/graphql-file-loader": ^8.0.0 + "@graphql-tools/json-file-loader": ^8.0.0 + "@graphql-tools/load": ^8.0.0 + "@graphql-tools/prisma-loader": ^8.0.0 + "@graphql-tools/url-loader": ^8.0.0 + "@graphql-tools/utils": ^10.0.0 + "@whatwg-node/fetch": ^0.10.0 + chalk: ^4.1.0 + cosmiconfig: ^8.1.3 + debounce: ^1.2.0 + detect-indent: ^6.0.0 + graphql-config: ^5.1.1 + inquirer: ^8.0.0 + is-glob: ^4.0.1 + jiti: ^1.17.1 + json-to-pretty-yaml: ^1.2.2 + listr2: ^4.0.5 + log-symbols: ^4.0.0 + micromatch: ^4.0.5 + shell-quote: ^1.7.3 + string-env-interpolation: ^1.0.1 + ts-log: ^2.2.3 + tslib: ^2.4.0 + yaml: ^2.3.1 + yargs: ^17.0.0 + peerDependencies: + "@parcel/watcher": ^2.1.0 + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + "@parcel/watcher": + optional: true + bin: + gql-gen: cjs/bin.js + graphql-code-generator: cjs/bin.js + graphql-codegen: cjs/bin.js + graphql-codegen-esm: esm/bin.js + checksum: f3ff651b52878da37ff131605600391ff09ff5614b2c17dace14d4c36c16de0e9331b3acbae3631ff97f038483a224f3ba9f4c1631e27d02e9dc01f26960926b + languageName: node + linkType: hard + +"@graphql-codegen/client-preset@npm:^4.6.0": + version: 4.6.2 + resolution: "@graphql-codegen/client-preset@npm:4.6.2" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/template": ^7.20.7 + "@graphql-codegen/add": ^5.0.3 + "@graphql-codegen/gql-tag-operations": 4.0.14 + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-codegen/typed-document-node": ^5.0.13 + "@graphql-codegen/typescript": ^4.1.3 + "@graphql-codegen/typescript-operations": ^4.4.1 + "@graphql-codegen/visitor-plugin-common": ^5.6.1 + "@graphql-tools/documents": ^1.0.0 + "@graphql-tools/utils": ^10.0.0 + "@graphql-typed-document-node/core": 3.2.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: a8f13d1bb5924c98d8e34ddc6c6a3cd9c9c6015a93ec85043db2939da7b872165615d549c346fdc804842c19cce79dd75f289508bde4156207ab380d65ac59ce + languageName: node + linkType: hard + +"@graphql-codegen/core@npm:^4.0.2": + version: 4.0.2 + resolution: "@graphql-codegen/core@npm:4.0.2" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.0.3 + "@graphql-tools/schema": ^10.0.0 + "@graphql-tools/utils": ^10.0.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 5fda4e843174aacd4a481b73b4d259fa2df7ffe4200bd06e95ecd4b3f43aa5969deeaeb51f6cf15a542e99ee5756c3e02a29d9d2ff9891af40e234a8f68ead4d + languageName: node + linkType: hard + +"@graphql-codegen/gql-tag-operations@npm:4.0.14": + version: 4.0.14 + resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.14" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-codegen/visitor-plugin-common": 5.6.1 + "@graphql-tools/utils": ^10.0.0 + auto-bind: ~4.0.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 0d3814904dc02aadb45e149d1839e014374b15b46d3cb0f66b2f806bc676c0cb97a868ce4d2bbda7e4a9c607835f23109f6359624bb1a52be600f2f336c0de32 + languageName: node + linkType: hard + +"@graphql-codegen/plugin-helpers@npm:^5.0.3, @graphql-codegen/plugin-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@graphql-codegen/plugin-helpers@npm:5.1.0" + dependencies: + "@graphql-tools/utils": ^10.0.0 + change-case-all: 1.0.15 + common-tags: 1.8.2 + import-from: 4.0.0 + lodash: ~4.17.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 235762e2e7a55898e71fb1d52ef3093c26787c16e7eb5a3df6e06a7b2003da641b9a9c96833091e9fb11f9cf72da9e7c5f19ce124074d5d7d33a419117d050a9 + languageName: node + linkType: hard + +"@graphql-codegen/schema-ast@npm:^4.0.2": + version: 4.1.0 + resolution: "@graphql-codegen/schema-ast@npm:4.1.0" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.0.3 + "@graphql-tools/utils": ^10.0.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: cddec7723d708990ac8e33eb8935e72545b60ed7b772452ba45b60e577af950d23503de83f0919d1730f7d52dcb970900d3587d9a54202032164ba3c246d4c10 + languageName: node + linkType: hard + +"@graphql-codegen/typed-document-node@npm:^5.0.1, @graphql-codegen/typed-document-node@npm:^5.0.13": + version: 5.0.13 + resolution: "@graphql-codegen/typed-document-node@npm:5.0.13" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-codegen/visitor-plugin-common": 5.6.1 + auto-bind: ~4.0.0 + change-case-all: 1.0.15 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 9a10d8b76ef6c102442a490658e89ebd9e529f81322836b4f2e4ce97b83defba93c022499143c7643c2010b0188842921696dd6223e365a7cb6395e5364db775 + languageName: node + linkType: hard + +"@graphql-codegen/typescript-operations@npm:^4.0.1, @graphql-codegen/typescript-operations@npm:^4.4.1": + version: 4.4.1 + resolution: "@graphql-codegen/typescript-operations@npm:4.4.1" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-codegen/typescript": ^4.1.3 + "@graphql-codegen/visitor-plugin-common": 5.6.1 + auto-bind: ~4.0.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 855de09757dd5b4531afada448f05fe34f59481dba1e4795915de4e2b03f9abd1840e7c3a86b074725359b847d6b09ec94c0580fe2c67214649ed044c9cb75ab + languageName: node + linkType: hard + +"@graphql-codegen/typescript@npm:^4.0.1, @graphql-codegen/typescript@npm:^4.1.3": + version: 4.1.3 + resolution: "@graphql-codegen/typescript@npm:4.1.3" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-codegen/schema-ast": ^4.0.2 + "@graphql-codegen/visitor-plugin-common": 5.6.1 + auto-bind: ~4.0.0 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 218de994c6c702b96aa40ff7ac4ea14d0ca078e60149207967794f4b28fb02c5a5173646ce9a1d0b2feb9181081135e7f9f0d3a9e634730e79c04e50451287ee + languageName: node + linkType: hard + +"@graphql-codegen/visitor-plugin-common@npm:5.6.1, @graphql-codegen/visitor-plugin-common@npm:^5.6.1": + version: 5.6.1 + resolution: "@graphql-codegen/visitor-plugin-common@npm:5.6.1" + dependencies: + "@graphql-codegen/plugin-helpers": ^5.1.0 + "@graphql-tools/optimize": ^2.0.0 + "@graphql-tools/relay-operation-optimizer": ^7.0.0 + "@graphql-tools/utils": ^10.0.0 + auto-bind: ~4.0.0 + change-case-all: 1.0.15 + dependency-graph: ^0.11.0 + graphql-tag: ^2.11.0 + parse-filepath: ^1.0.2 + tslib: ~2.6.0 + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: eff9ee6ed97976b8e4fb5d3b77d3905e98477b55624c17e13e39622f9dadc977084df36406983455d13f10878108826b725d8a6f0c705d3226852ff097821fb9 + languageName: node + linkType: hard + +"@graphql-tools/apollo-engine-loader@npm:^8.0.0": + version: 8.0.15 + resolution: "@graphql-tools/apollo-engine-loader@npm:8.0.15" + dependencies: + "@graphql-tools/utils": ^10.8.1 + "@whatwg-node/fetch": ^0.10.0 + sync-fetch: 0.6.0-2 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 6acf60c6eba895e92c265327ba9fdc818984b38585639ffac57960b4c4235e19b0a22679f588f2d8b26845e8c00cdf8660d5d5c32b4b2fb8498089a169bc83ab + languageName: node + linkType: hard + +"@graphql-tools/batch-execute@npm:^9.0.12": + version: 9.0.12 + resolution: "@graphql-tools/batch-execute@npm:9.0.12" + dependencies: + "@graphql-tools/utils": ^10.8.1 + dataloader: ^2.2.3 + tslib: ^2.8.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 49146231f1ba33959feda6a55cb44b022c1c464841c6f6d8a929796fc8a6728fad6c0f8175a75970369cc86b7c871b5b919b3179f48744f480c4e536c811063e + languageName: node + linkType: hard + +"@graphql-tools/code-file-loader@npm:^8.0.0": + version: 8.1.15 + resolution: "@graphql-tools/code-file-loader@npm:8.1.15" + dependencies: + "@graphql-tools/graphql-tag-pluck": 8.3.14 + "@graphql-tools/utils": ^10.8.1 + globby: ^11.0.3 + tslib: ^2.4.0 + unixify: ^1.0.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 429ae84c1e5be50f6a2d4dc406d8a86282117666ab211e53981cbd71bf368c26326728ecdf219770f3acb89f78d6fca4e2957f2fb5b9e45efef77aace4189e67 + languageName: node + linkType: hard + +"@graphql-tools/delegate@npm:^10.2.13": + version: 10.2.13 + resolution: "@graphql-tools/delegate@npm:10.2.13" + dependencies: + "@graphql-tools/batch-execute": ^9.0.12 + "@graphql-tools/executor": ^1.3.10 + "@graphql-tools/schema": ^10.0.11 + "@graphql-tools/utils": ^10.8.1 + "@repeaterjs/repeater": ^3.0.6 + dataloader: ^2.2.3 + dset: ^3.1.2 + tslib: ^2.8.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 79c0edc5d0420e1eb6033aac06ba882b3256cd33766e7bd8c562e9ba76489929e7884230abc0ceea91262e22291fa6d76bcee5e8552c193c4b5d861af8b640ec + languageName: node + linkType: hard + +"@graphql-tools/documents@npm:^1.0.0": + version: 1.0.1 + resolution: "@graphql-tools/documents@npm:1.0.1" + dependencies: + lodash.sortby: ^4.7.0 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: c6ff859d2673dbf884f19d6319735afacc6bff2df6f8ea3dbf56839d01568f61210f256f0905156f123428cc24962ada26b37903263699fc8cd7efb8849a6508 + languageName: node + linkType: hard + +"@graphql-tools/executor-common@npm:^0.0.2": + version: 0.0.2 + resolution: "@graphql-tools/executor-common@npm:0.0.2" + dependencies: + "@envelop/core": ^5.0.2 + "@graphql-tools/utils": ^10.8.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 5dc1ccd8fb7e091e390038fa6ab1e4a929cecf2608dd6c1ec39b322ba11885dac2462fe1224b8b4451a8d7c1ed50d2903a88785aaddfbcc7f2e11a8237ef468b + languageName: node + linkType: hard + +"@graphql-tools/executor-graphql-ws@npm:^2.0.1": + version: 2.0.2 + resolution: "@graphql-tools/executor-graphql-ws@npm:2.0.2" + dependencies: + "@graphql-tools/executor-common": ^0.0.2 + "@graphql-tools/utils": ^10.8.1 + "@whatwg-node/disposablestack": ^0.0.5 + graphql-ws: ^6.0.3 + isomorphic-ws: ^5.0.0 + tslib: ^2.8.1 + ws: ^8.17.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 69b84e843d9c8b93bc5eaef5d6bab81aabcda6326fdd09158586a698c81ba37889f3c5b2a042e826f9500ebd201c77eb921f99abe1e2076eba3fbeab78041457 + languageName: node + linkType: hard + +"@graphql-tools/executor-http@npm:^1.1.9": + version: 1.2.7 + resolution: "@graphql-tools/executor-http@npm:1.2.7" + dependencies: + "@graphql-tools/executor-common": ^0.0.2 + "@graphql-tools/utils": ^10.8.1 + "@repeaterjs/repeater": ^3.0.4 + "@whatwg-node/disposablestack": ^0.0.5 + "@whatwg-node/fetch": ^0.10.1 + extract-files: ^11.0.0 + meros: ^1.2.1 + tslib: ^2.8.1 + value-or-promise: ^1.0.12 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 3e3544df9ae60c8d0205d062b2581c8391e5edc0f34f006b6df9d3241e13edf13a562978c15cb00ec29eb7a8a4f363ccfdf52dec7162a7d1ab54c4b41547d414 + languageName: node + linkType: hard + +"@graphql-tools/executor-legacy-ws@npm:^1.1.12": + version: 1.1.12 + resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.12" + dependencies: + "@graphql-tools/utils": ^10.8.1 + "@types/ws": ^8.0.0 + isomorphic-ws: ^5.0.0 + tslib: ^2.4.0 + ws: ^8.17.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 6e00e16e03faefb1da6ec1b83ba9dc48b3a0314d5733dcff4495f483a43aa73ec175aee45ddcced193411264a151e892107571bea19e710352ffa38b2e055463 + languageName: node + linkType: hard + +"@graphql-tools/executor@npm:^1.3.10": + version: 1.3.14 + resolution: "@graphql-tools/executor@npm:1.3.14" + dependencies: + "@graphql-tools/utils": ^10.8.1 + "@graphql-typed-document-node/core": ^3.2.0 + "@repeaterjs/repeater": ^3.0.4 + "@whatwg-node/disposablestack": ^0.0.5 + tslib: ^2.4.0 + value-or-promise: ^1.0.12 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 5df270811896e8c6a6f2f7917c9748d5a74c2a5ef45e713f8c5d45ce6411ad266a6f1814922b053be366a49f7b41d08c42df75efe1b8e76a9336f873b107741d + languageName: node + linkType: hard + +"@graphql-tools/git-loader@npm:^8.0.0": + version: 8.0.19 + resolution: "@graphql-tools/git-loader@npm:8.0.19" + dependencies: + "@graphql-tools/graphql-tag-pluck": 8.3.14 + "@graphql-tools/utils": ^10.8.1 + is-glob: 4.0.3 + micromatch: ^4.0.8 + tslib: ^2.4.0 + unixify: ^1.0.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 0105a27a0427ae5019c62340174028ef5c9dbea6601c478bcf7f54129ee0ab6c7a67bc963afd7dddf687e477d7f725caf852670bd1a9895f28a854ce51e186b0 + languageName: node + linkType: hard + +"@graphql-tools/github-loader@npm:^8.0.0": + version: 8.0.15 + resolution: "@graphql-tools/github-loader@npm:8.0.15" + dependencies: + "@graphql-tools/executor-http": ^1.1.9 + "@graphql-tools/graphql-tag-pluck": ^8.3.14 + "@graphql-tools/utils": ^10.8.1 + "@whatwg-node/fetch": ^0.10.0 + sync-fetch: 0.6.0-2 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: d264a6b740cb3099a767796fac39696e1672831731b5bbf7939ade5bc11c6be9db4af8f305a413742a00654fd84823aa5a816512d30198047c8ab7d0980f2169 + languageName: node + linkType: hard + +"@graphql-tools/graphql-file-loader@npm:^8.0.0": + version: 8.0.14 + resolution: "@graphql-tools/graphql-file-loader@npm:8.0.14" + dependencies: + "@graphql-tools/import": 7.0.13 + "@graphql-tools/utils": ^10.8.1 + globby: ^11.0.3 + tslib: ^2.4.0 + unixify: ^1.0.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 38510ec3046e2055f850678ae5acfc63c9767fd92fa0d8896eae403119dc42aa3c13addcf0b8f736e7193e3e82ddca6a767bcb2b80c5fdda894114f6b150dff7 + languageName: node + linkType: hard + +"@graphql-tools/graphql-tag-pluck@npm:8.3.14, @graphql-tools/graphql-tag-pluck@npm:^8.3.14": + version: 8.3.14 + resolution: "@graphql-tools/graphql-tag-pluck@npm:8.3.14" + dependencies: + "@babel/core": ^7.22.9 + "@babel/parser": ^7.16.8 + "@babel/plugin-syntax-import-assertions": ^7.20.0 + "@babel/traverse": ^7.16.8 + "@babel/types": ^7.16.8 + "@graphql-tools/utils": ^10.8.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: d5dc0c1f29b6964487b1cf7c9c4e214da7d39a2cb10eb151347082e73243851aa275075e04170fb39f55e657c1051b30d68b726192543bd8e1861e1b437bc727 + languageName: node + linkType: hard + +"@graphql-tools/import@npm:7.0.13": + version: 7.0.13 + resolution: "@graphql-tools/import@npm:7.0.13" + dependencies: + "@graphql-tools/utils": ^10.8.1 + resolve-from: 5.0.0 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 1a0be36415dab0f6b33c89bf499ed260de94a54e331f0f512c83b4bab1ac417bd7cbe5bd247fbef0301b9dbe329cf7ced642d5d2a06b83ec71aa42a8c7794a74 + languageName: node + linkType: hard + +"@graphql-tools/json-file-loader@npm:^8.0.0": + version: 8.0.13 + resolution: "@graphql-tools/json-file-loader@npm:8.0.13" + dependencies: + "@graphql-tools/utils": ^10.8.1 + globby: ^11.0.3 + tslib: ^2.4.0 + unixify: ^1.0.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: f9683ac092b881da9e3fa64b7a4e502e51603f49c4335bcaba8bc9896ebd6d919941f37f92d51b45edcaf54dfbf6cd637ae3f57a0ab73ae0abe1b4818ff1a9d4 + languageName: node + linkType: hard + +"@graphql-tools/load@npm:^8.0.0": + version: 8.0.14 + resolution: "@graphql-tools/load@npm:8.0.14" + dependencies: + "@graphql-tools/schema": ^10.0.18 + "@graphql-tools/utils": ^10.8.1 + p-limit: 3.1.0 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: d9d8c96fba5bf4ec8b879d6c66c1ccbbdf1ad6b87c9dd3d45be5ddf77cd88cf4e365249bcd24833069c6b30de07ccaf7e4d413376a81ff93a9edad9cfa0fee1d + languageName: node + linkType: hard + +"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.19": + version: 9.0.19 + resolution: "@graphql-tools/merge@npm:9.0.19" + dependencies: + "@graphql-tools/utils": ^10.8.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: dfae329da0ca683d7672d8e08b11161e2299bf78224c61f3673a2ca69c73c0590b67da390c8555c8caf61ee106f2038339da8c7f35555b941ba9b1aefb52e808 + languageName: node + linkType: hard + +"@graphql-tools/optimize@npm:^2.0.0": + version: 2.0.0 + resolution: "@graphql-tools/optimize@npm:2.0.0" + dependencies: + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 7f79c0e1852abc571308e887d27d613da5b797256c8c6eb6c5fe7ca77f09e61524778ae281cebc0b698c51d4fe1074e2b8e0d0627b8e2dcf505aa6ed09b49a2f + languageName: node + linkType: hard + +"@graphql-tools/prisma-loader@npm:^8.0.0": + version: 8.0.17 + resolution: "@graphql-tools/prisma-loader@npm:8.0.17" + dependencies: + "@graphql-tools/url-loader": ^8.0.15 + "@graphql-tools/utils": ^10.5.6 + "@types/js-yaml": ^4.0.0 + "@whatwg-node/fetch": ^0.10.0 + chalk: ^4.1.0 + debug: ^4.3.1 + dotenv: ^16.0.0 + graphql-request: ^6.0.0 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.0 + jose: ^5.0.0 + js-yaml: ^4.0.0 + lodash: ^4.17.20 + scuid: ^1.1.0 + tslib: ^2.4.0 + yaml-ast-parser: ^0.0.43 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: e05be13e5dd152cb5f88acba90ab23982bdea3ed623647956d399196dd6c73a75e47a5c594f9d5a2ef1fc19db178a5a956c9d9cecf75ab0f77609b07808e0b35 + languageName: node + linkType: hard + +"@graphql-tools/relay-operation-optimizer@npm:^7.0.0": + version: 7.0.14 + resolution: "@graphql-tools/relay-operation-optimizer@npm:7.0.14" + dependencies: + "@ardatan/relay-compiler": ^12.0.1 + "@graphql-tools/utils": ^10.8.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 9c792a905f5df60438231028271b9d3a1edaa3bcc88864177e331fbd8daceda7994412fc9312642f0cc04040edf1e54751522710b879cb5e08af6ba380d668f7 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.18": + version: 10.0.18 + resolution: "@graphql-tools/schema@npm:10.0.18" + dependencies: + "@graphql-tools/merge": ^9.0.19 + "@graphql-tools/utils": ^10.8.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 620faa9524c8acf3bac7ca7792ad8b4ac38533fd1f35b1eab53de3c8bdb9c81f9095f977e431a6bbcec14a58454db1449ff3826ff9a23225902f44f1c2a66936 + languageName: node + linkType: hard + +"@graphql-tools/url-loader@npm:^8.0.0, @graphql-tools/url-loader@npm:^8.0.15": + version: 8.0.26 + resolution: "@graphql-tools/url-loader@npm:8.0.26" + dependencies: + "@graphql-tools/executor-graphql-ws": ^2.0.1 + "@graphql-tools/executor-http": ^1.1.9 + "@graphql-tools/executor-legacy-ws": ^1.1.12 + "@graphql-tools/utils": ^10.8.1 + "@graphql-tools/wrap": ^10.0.16 + "@types/ws": ^8.0.0 + "@whatwg-node/fetch": ^0.10.0 + isomorphic-ws: ^5.0.0 + sync-fetch: 0.6.0-2 + tslib: ^2.4.0 + ws: ^8.17.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: dae26f48d8635a31c02fe41342a315afd1b28618b880ca63fee712987c6ca3fd2257690f69d145b394bc630cc024a78fb8b7b000e2e4d9ac58783bc73714c7bc + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.5.6, @graphql-tools/utils@npm:^10.8.1": + version: 10.8.1 + resolution: "@graphql-tools/utils@npm:10.8.1" + dependencies: + "@graphql-typed-document-node/core": ^3.1.1 + cross-inspect: 1.0.1 + dset: ^3.1.4 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 2de6d259a904a053d368134b76bf673c67af43e6d86a7fac5f638af119e61e86b30f3c1c00855ed558915f55b8057c11e732e78748e52d7b9fad491911b51c9c + languageName: node + linkType: hard + +"@graphql-tools/wrap@npm:^10.0.16": + version: 10.0.31 + resolution: "@graphql-tools/wrap@npm:10.0.31" + dependencies: + "@graphql-tools/delegate": ^10.2.13 + "@graphql-tools/schema": ^10.0.11 + "@graphql-tools/utils": ^10.8.1 + tslib: ^2.8.1 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: c25ef1c6c08559c85e656f9db69d63d3c0e4930e315a66b1912268d4b0c0f758b16a8098cb0872dadf06517f06abf69f15df1661c46b333d4d2527b76172727a + languageName: node + linkType: hard + +"@graphql-typed-document-node/core@npm:3.2.0, @graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d + languageName: node + linkType: hard + "@grpc/grpc-js@npm:^1.7.1": version: 1.9.14 resolution: "@grpc/grpc-js@npm:1.9.14" @@ -6707,6 +7865,44 @@ __metadata: languageName: node linkType: hard +"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": + version: 9.3.0 + resolution: "@hapi/hoek@npm:9.3.0" + checksum: 4771c7a776242c3c022b168046af4e324d116a9d2e1d60631ee64f474c6e38d1bb07092d898bf95c7bc5d334c5582798a1456321b2e53ca817d4e7c88bc25b43 + languageName: node + linkType: hard + +"@hapi/topo@npm:^5.1.0": + version: 5.1.0 + resolution: "@hapi/topo@npm:5.1.0" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: 604dfd5dde76d5c334bd03f9001fce69c7ce529883acf92da96f4fe7e51221bf5e5110e964caca287a6a616ba027c071748ab636ff178ad750547fba611d6014 + languageName: node + linkType: hard + +"@headlessui/react@npm:^1.5.0": + version: 1.7.19 + resolution: "@headlessui/react@npm:1.7.19" + dependencies: + "@tanstack/react-virtual": ^3.0.0-beta.60 + client-only: ^0.0.1 + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + checksum: 2a343a5fcf1f45e870cc94613231b89a8da78114001ffafa4751a0eceae7569ff9237aff1f2aedfa6f6e53ee3bb9ba5e5d19ebf1878fee3ff4f3c733fddc1087 + languageName: node + linkType: hard + +"@heroicons/react@npm:^1.0.6": + version: 1.0.6 + resolution: "@heroicons/react@npm:1.0.6" + peerDependencies: + react: ">= 16" + checksum: 372b1eda3ce735ef069777bc96304f70de585ebb71a6d1cedc121bb695f9bca235619112e3ee14e8779e95a03096813cbbe3b755927a54b7580d1ce084fa4096 + languageName: node + linkType: hard + "@hookform/error-message@npm:^2.0.0": version: 2.0.0 resolution: "@hookform/error-message@npm:2.0.0" @@ -7752,7 +8948,7 @@ __metadata: languageName: node linkType: hard -"@juggle/resize-observer@npm:^3.3.1": +"@juggle/resize-observer@npm:^3.3.1, @juggle/resize-observer@npm:^3.4.0": version: 3.4.0 resolution: "@juggle/resize-observer@npm:3.4.0" checksum: 2505028c05cc2e17639fcad06218b1c4b60f932a4ebb4b41ab546ef8c157031ae377e3f560903801f6d01706dbefd4943b6c4704bf19ed86dfa1c62f1473a570 @@ -8563,6 +9759,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:13.5.8": + version: 13.5.8 + resolution: "@next/env@npm:13.5.8" + checksum: 233983c0b6be309b199ba918dec05eca3fb1aff91dd06e076088553f07af9a8bca6336a801302e50a737c8a19ce33e9e5f386f382d5af549b43a6c5f5e2a305e + languageName: node + linkType: hard + "@next/env@npm:14.0.4": version: 14.0.4 resolution: "@next/env@npm:14.0.4" @@ -8595,6 +9798,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-darwin-arm64@npm:13.5.8" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-darwin-arm64@npm:14.0.4" @@ -8609,6 +9819,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-darwin-x64@npm:13.5.8" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-darwin-x64@npm:14.0.4" @@ -8623,6 +9840,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-linux-arm64-gnu@npm:13.5.8" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-arm64-gnu@npm:14.0.4" @@ -8637,6 +9861,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-linux-arm64-musl@npm:13.5.8" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-arm64-musl@npm:14.0.4" @@ -8651,6 +9882,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-linux-x64-gnu@npm:13.5.8" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-x64-gnu@npm:14.0.4" @@ -8665,6 +9903,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-linux-x64-musl@npm:13.5.8" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-x64-musl@npm:14.0.4" @@ -8679,6 +9924,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-win32-arm64-msvc@npm:13.5.8" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-arm64-msvc@npm:14.0.4" @@ -8693,6 +9945,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-ia32-msvc@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-win32-ia32-msvc@npm:13.5.8" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@next/swc-win32-ia32-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-ia32-msvc@npm:14.0.4" @@ -8700,6 +9959,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-ia32-msvc@npm:14.2.24": + version: 14.2.24 + resolution: "@next/swc-win32-ia32-msvc@npm:14.2.24" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:13.5.7": version: 13.5.7 resolution: "@next/swc-win32-x64-msvc@npm:13.5.7" @@ -8707,6 +9973,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:13.5.8": + version: 13.5.8 + resolution: "@next/swc-win32-x64-msvc@npm:13.5.8" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-x64-msvc@npm:14.0.4" @@ -10178,6 +11451,39 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-schema@npm:^2.3.13, @peculiar/asn1-schema@npm:^2.3.8": + version: 2.3.15 + resolution: "@peculiar/asn1-schema@npm:2.3.15" + dependencies: + asn1js: ^3.0.5 + pvtsutils: ^1.3.6 + tslib: ^2.8.1 + checksum: af9a84e3a530f0208561dfeacfc591e1bdbee1a8bb0bc666fc46c7d9e6426c793816cef0eba94d01d289e7b6d7be82767f90553404f3487e4abf54867831d4d7 + languageName: node + linkType: hard + +"@peculiar/json-schema@npm:^1.1.12": + version: 1.1.12 + resolution: "@peculiar/json-schema@npm:1.1.12" + dependencies: + tslib: ^2.0.0 + checksum: b26ececdc23c5ef25837f8be8d1eb5e1c8bb6e9ae7227ac59ffea57fff56bd05137734e7685e9100595d3d88d906dff638ef8d1df54264c388d3eac1b05aa060 + languageName: node + linkType: hard + +"@peculiar/webcrypto@npm:^1.4.0": + version: 1.5.0 + resolution: "@peculiar/webcrypto@npm:1.5.0" + dependencies: + "@peculiar/asn1-schema": ^2.3.8 + "@peculiar/json-schema": ^1.1.12 + pvtsutils: ^1.3.5 + tslib: ^2.6.2 + webcrypto-core: ^1.8.0 + checksum: 9022d7452d564a5a26fbacf477842be34736f2d9139f2f5026adc47fdeda7033193d727491503f2a829d35ab819bbfa61c82a785c49c999eac535ecd69a3a459 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -10343,6 +11649,13 @@ __metadata: languageName: node linkType: hard +"@prisma/debug@npm:5.22.0": + version: 5.22.0 + resolution: "@prisma/debug@npm:5.22.0" + checksum: ee263d933c3ab92e93aee78771e5040a510316d96ce69c64cfd65d21e59646b9c5a047446ce7965651563d001150ef763485474bd43ca8a6544ab7ce604d2ffa + languageName: node + linkType: hard + "@prisma/debug@npm:5.3.1": version: 5.3.1 resolution: "@prisma/debug@npm:5.3.1" @@ -10513,6 +11826,15 @@ __metadata: languageName: node linkType: hard +"@prisma/generator-helper@npm:^5.9.1": + version: 5.22.0 + resolution: "@prisma/generator-helper@npm:5.22.0" + dependencies: + "@prisma/debug": 5.22.0 + checksum: 5c942d95b0e31c5b80eecb29185ff2561ca9a5eb86badb0d6dfd96c4e8b8e65e726c8682366bd55c332d9682deda08bc90d8f8dd7c0fc0652c9254d0d983bf47 + languageName: node + linkType: hard + "@prisma/generator-helper@npm:~3.8.1": version: 3.8.1 resolution: "@prisma/generator-helper@npm:3.8.1" @@ -10883,6 +12205,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-collapsible@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-collapsible@npm:1.1.3" + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1 + "@radix-ui/react-context": 1.1.1 + "@radix-ui/react-id": 1.1.0 + "@radix-ui/react-presence": 1.1.2 + "@radix-ui/react-primitive": 2.0.2 + "@radix-ui/react-use-controllable-state": 1.1.0 + "@radix-ui/react-use-layout-effect": 1.1.0 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 3689dd1393ed983869e1f2aa4ccfe349af4788a4eb8ee36805ff89aaf6df5ed9743823302388691f9d4527ecee6ed558e95a198fdbe5b86bd2d81b40d0bc2a8c + languageName: node + linkType: hard + "@radix-ui/react-collapsible@npm:^1.0.0": version: 1.0.3 resolution: "@radix-ui/react-collapsible@npm:1.0.3" @@ -11265,6 +12613,29 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-dismissable-layer@npm:1.1.5": + version: 1.1.5 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.5" + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1 + "@radix-ui/react-primitive": 2.0.2 + "@radix-ui/react-use-callback-ref": 1.1.0 + "@radix-ui/react-use-escape-keydown": 1.1.0 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 6f8c219df5033e98b5337e5351e0add8706aab6d10a6f2c7f028f7f36202300f648805530e788a8c8cc3014a7fb9d8e9824ea02100da510e70778457ffb3e4c0 + languageName: node + linkType: hard + "@radix-ui/react-dropdown-menu@npm:^2.0.5": version: 2.0.5 resolution: "@radix-ui/react-dropdown-menu@npm:2.0.5" @@ -11436,6 +12807,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-id@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-id@npm:1.1.0" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.0 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6fbc9d1739b3b082412da10359e63967b4f3a60383ebda4c9e56b07a722d29bee53b203b3b1418f88854a29315a7715867133bb149e6e22a027a048cdd20d970 + languageName: node + linkType: hard + "@radix-ui/react-label@npm:0.1.5": version: 0.1.5 resolution: "@radix-ui/react-label@npm:0.1.5" @@ -11488,6 +12874,38 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-navigation-menu@npm:^1.0.0": + version: 1.2.5 + resolution: "@radix-ui/react-navigation-menu@npm:1.2.5" + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-collection": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.1 + "@radix-ui/react-context": 1.1.1 + "@radix-ui/react-direction": 1.1.0 + "@radix-ui/react-dismissable-layer": 1.1.5 + "@radix-ui/react-id": 1.1.0 + "@radix-ui/react-presence": 1.1.2 + "@radix-ui/react-primitive": 2.0.2 + "@radix-ui/react-use-callback-ref": 1.1.0 + "@radix-ui/react-use-controllable-state": 1.1.0 + "@radix-ui/react-use-layout-effect": 1.1.0 + "@radix-ui/react-use-previous": 1.1.0 + "@radix-ui/react-visually-hidden": 1.1.2 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: b5790c9f2ec06fd10fb2b7226f62777555a7691003ecd085cd8d6beef59ee1e224023e31b260775278da6d7be8d5996e8ad6e8ec5ab00805d018752c8988a134 + languageName: node + linkType: hard + "@radix-ui/react-popover@npm:^1.0.2": version: 1.0.6 resolution: "@radix-ui/react-popover@npm:1.0.6" @@ -11682,6 +13100,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-presence@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-presence@npm:1.1.2" + dependencies: + "@radix-ui/react-compose-refs": 1.1.1 + "@radix-ui/react-use-layout-effect": 1.1.0 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 0345bc8d3e1ddcbf4b864025833c71f3d76e4801ce16ad126a98aed816be6e819c4fe01097c6c1320771b947f5a14929cc610d18e7a1438cfb5573289fa4d4a6 + languageName: node + linkType: hard + "@radix-ui/react-primitive@npm:0.1.4": version: 0.1.4 resolution: "@radix-ui/react-primitive@npm:0.1.4" @@ -11822,6 +13260,33 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-roving-focus@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-roving-focus@npm:1.1.2" + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-collection": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.1 + "@radix-ui/react-context": 1.1.1 + "@radix-ui/react-direction": 1.1.0 + "@radix-ui/react-id": 1.1.0 + "@radix-ui/react-primitive": 2.0.2 + "@radix-ui/react-use-callback-ref": 1.1.0 + "@radix-ui/react-use-controllable-state": 1.1.0 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 4a57f5af51387ae7621a6d0c7e3c743f16b06afda9311e6e5a39c023157d6a314931170dd794f901b59c0c768f0c3a121b8af59f7f80e58eb87512fb4540b037 + languageName: node + linkType: hard + "@radix-ui/react-select@npm:^0.1.1": version: 0.1.1 resolution: "@radix-ui/react-select@npm:0.1.1" @@ -12092,6 +13557,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-tabs@npm:^1.0.0": + version: 1.1.3 + resolution: "@radix-ui/react-tabs@npm:1.1.3" + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-context": 1.1.1 + "@radix-ui/react-direction": 1.1.0 + "@radix-ui/react-id": 1.1.0 + "@radix-ui/react-presence": 1.1.2 + "@radix-ui/react-primitive": 2.0.2 + "@radix-ui/react-roving-focus": 1.1.2 + "@radix-ui/react-use-controllable-state": 1.1.0 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: abfba997219743ac75840281df5577f0be154c4813588cb4bb4aac16fc8bd4bcd230170663162a805bb31bdc393adfd2ee942daa3f15484ab4f82aa210e532bb + languageName: node + linkType: hard + "@radix-ui/react-toast@npm:^1.1.5": version: 1.1.5 resolution: "@radix-ui/react-toast@npm:1.1.5" @@ -12385,6 +13876,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-escape-keydown@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.0" + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.0 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 9bf88ea272b32ea0f292afd336780a59c5646f795036b7e6105df2d224d73c54399ee5265f61d571eb545d28382491a8b02dc436e3088de8dae415d58b959b71 + languageName: node + linkType: hard + "@radix-ui/react-use-layout-effect@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-use-layout-effect@npm:0.1.0" @@ -12553,6 +14059,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-visually-hidden@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-visually-hidden@npm:1.1.2" + dependencies: + "@radix-ui/react-primitive": 2.0.2 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 87dc45ffb32b652bde629bb5c3b216adb82fd1ec68c484fec260980b31508cbc515a73327798d0f47d558dd22245b25f51bb06296b1762693af9f27e23c1cb1f + languageName: node + linkType: hard + "@radix-ui/rect@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/rect@npm:1.0.1" @@ -12652,6 +14177,13 @@ __metadata: languageName: node linkType: hard +"@repeaterjs/repeater@npm:^3.0.4, @repeaterjs/repeater@npm:^3.0.6": + version: 3.0.6 + resolution: "@repeaterjs/repeater@npm:3.0.6" + checksum: aae878b953162bec77c94b45f2236ddfc01a65308267c7cb30220fa2f8511654a302c0d32aad228c58241d685607d7bb35b6d528b2879355e6636ff08fddb266 + languageName: node + linkType: hard + "@replexica/sdk@npm:^0.7.0": version: 0.7.2 resolution: "@replexica/sdk@npm:0.7.2" @@ -12684,6 +14216,13 @@ __metadata: languageName: node linkType: hard +"@resvg/resvg-wasm@npm:2.6.0": + version: 2.6.0 + resolution: "@resvg/resvg-wasm@npm:2.6.0" + checksum: 730ababcab7d11be84129ab68aa9b86bb393b820b5a0a223fb53232462c33bc777eb875170691f54cd5d29c6aa36498f5b7dcd7fca000b56818e3e312be01804 + languageName: node + linkType: hard + "@rollup/plugin-commonjs@npm:24.0.0": version: 24.0.0 resolution: "@rollup/plugin-commonjs@npm:24.0.0" @@ -13828,6 +15367,29 @@ __metadata: languageName: node linkType: hard +"@sideway/address@npm:^4.1.5": + version: 4.1.5 + resolution: "@sideway/address@npm:4.1.5" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: 3e3ea0f00b4765d86509282290368a4a5fd39a7995fdc6de42116ca19a96120858e56c2c995081def06e1c53e1f8bccc7d013f6326602bec9d56b72ee2772b9d + languageName: node + linkType: hard + +"@sideway/formula@npm:^3.0.1": + version: 3.0.1 + resolution: "@sideway/formula@npm:3.0.1" + checksum: e4beeebc9dbe2ff4ef0def15cec0165e00d1612e3d7cea0bc9ce5175c3263fc2c818b679bd558957f49400ee7be9d4e5ac90487e1625b4932e15c4aa7919c57a + languageName: node + linkType: hard + +"@sideway/pinpoint@npm:^2.0.0": + version: 2.0.0 + resolution: "@sideway/pinpoint@npm:2.0.0" + checksum: 0f4491e5897fcf5bf02c46f5c359c56a314e90ba243f42f0c100437935daa2488f20482f0f77186bd6bf43345095a95d8143ecf8b1f4d876a7bc0806aba9c3d2 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.25.16": version: 0.25.24 resolution: "@sinclair/typebox@npm:0.25.24" @@ -13842,7 +15404,7 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^4": +"@sindresorhus/is@npm:^4, @sindresorhus/is@npm:^4.0.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" checksum: 83839f13da2c29d55c97abc3bc2c55b250d33a0447554997a85c539e058e57b8da092da396e252b11ec24a0279a0bed1f537fa26302209327060643e327f81d2 @@ -14375,13 +15937,27 @@ __metadata: languageName: node linkType: hard -"@stablelib/base64@npm:^1.0.0": +"@stablelib/base64@npm:^1.0.0, @stablelib/base64@npm:^1.0.1": version: 1.0.1 resolution: "@stablelib/base64@npm:1.0.1" checksum: 3ef4466d1d6889ac3fc67407bc21aa079953981c322eeca3b29f426d05506c63011faab1bfc042d7406e0677a94de6c9d2db2ce079afdd1eccae90031bfb5859 languageName: node linkType: hard +"@stablelib/hex@npm:^1.0.1": + version: 1.0.1 + resolution: "@stablelib/hex@npm:1.0.1" + checksum: 557f1c5d6b42963deee7627d4be1ae3542607851c5561e9419c42682d09562ebd3a06e2d92e088c52213a71ed121ec38221abfc5acd9e65707a77ecee3c96915 + languageName: node + linkType: hard + +"@stablelib/utf8@npm:^1.0.1": + version: 1.0.2 + resolution: "@stablelib/utf8@npm:1.0.2" + checksum: 3ab01baa4eb36eece44a310bf6b2e4313e8d585fc04dbcf8a5dc2d239f06071f34038b85aad6fbd98e9969f0b3b0584fcb541fe5e512c8f0cc0982b9fe1290a3 + languageName: node + linkType: hard + "@storybook/addon-actions@npm:7.6.3, @storybook/addon-actions@npm:^7.6.3": version: 7.6.3 resolution: "@storybook/addon-actions@npm:7.6.3" @@ -15540,6 +17116,16 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:0.5.5": + version: 0.5.5 + resolution: "@swc/helpers@npm:0.5.5" + dependencies: + "@swc/counter": ^0.1.3 + tslib: ^2.4.0 + checksum: d4f207b191e54b29460804ddf2984ba6ece1d679a0b2f6a9c765dcf27bba92c5769e7965668a4546fb9f1021eaf0ff9be4bf5c235ce12adcd65acdfe77187d11 + languageName: node + linkType: hard + "@swc/types@npm:^0.1.5": version: 0.1.5 resolution: "@swc/types@npm:0.1.5" @@ -15556,6 +17142,15 @@ __metadata: languageName: node linkType: hard +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: ^2.0.0 + checksum: c29df3bcec6fc3bdec2b17981d89d9c9fc9bd7d0c9bcfe92821dc533f4440bc890ccde79971838b4ceed1921d456973c4180d7175ee1d0023ad0562240a58d95 + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^5.0.1": version: 5.0.1 resolution: "@szmarczak/http-timer@npm:5.0.1" @@ -15598,6 +17193,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:4.36.1": + version: 4.36.1 + resolution: "@tanstack/query-core@npm:4.36.1" + checksum: 47672094da20d89402d9fe03bb7b0462be73a76ff9ca715169738bc600a719d064d106d083a8eedae22a2c22de22f87d5eb5d31ef447aba771d9190f2117ed10 + languageName: node + linkType: hard + "@tanstack/query-core@npm:5.17.19": version: 5.17.19 resolution: "@tanstack/query-core@npm:5.17.19" @@ -15605,6 +17207,25 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-query@npm:^4.3.9": + version: 4.36.1 + resolution: "@tanstack/react-query@npm:4.36.1" + dependencies: + "@tanstack/query-core": 4.36.1 + use-sync-external-store: ^1.2.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 1aff0a476859386f8d32253fa0d0bde7b81769a6d4d4d9cbd78778f0f955459a3bdb7ee27a0d2ee7373090f12998b45df80db0b5b313bd0a7a39d36c6e8e51c5 + languageName: node + linkType: hard + "@tanstack/react-query@npm:^5.17.15": version: 5.17.19 resolution: "@tanstack/react-query@npm:5.17.19" @@ -15628,6 +17249,18 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-virtual@npm:^3.0.0-beta.60": + version: 3.13.0 + resolution: "@tanstack/react-virtual@npm:3.13.0" + dependencies: + "@tanstack/virtual-core": 3.13.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 0cc6fcc63d68af698d79b455fa6a44115ee3abfb41bd2a52fc96094cb4760743989e593871b3b872021f966c7ecc90eb45e85ccfc70446fff44ce8e6296cc76f + languageName: node + linkType: hard + "@tanstack/react-virtual@npm:^3.10.9": version: 3.10.9 resolution: "@tanstack/react-virtual@npm:3.10.9" @@ -15654,6 +17287,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/virtual-core@npm:3.13.0": + version: 3.13.0 + resolution: "@tanstack/virtual-core@npm:3.13.0" + checksum: 0cbead3350002bea1f8353e091d3d8d3d9ba7815f4a0eb359bc927b7b7f39c9530c1dfa15f8d75fe5f47621c8f55be57643133b8fe728af09f7ea0578016f78d + languageName: node + linkType: hard + "@tediousjs/connection-string@npm:^0.5.0": version: 0.5.0 resolution: "@tediousjs/connection-string@npm:0.5.0" @@ -15849,6 +17489,15 @@ __metadata: languageName: node linkType: hard +"@trpc/client@npm:^10.0.0": + version: 10.45.2 + resolution: "@trpc/client@npm:10.45.2" + peerDependencies: + "@trpc/server": 10.45.2 + checksum: d1eaa8e0059a371265065dafb48372be8456bc5bbc68f63c92401b12258cf15efb3f9f3790ef18ec6a0b7b73daa362bbd371f98db67c0610f2aee284f12cf09a + languageName: node + linkType: hard + "@trpc/next@npm:11.0.0-next-beta.222": version: 11.0.0-next-beta.222 resolution: "@trpc/next@npm:11.0.0-next-beta.222" @@ -15889,6 +17538,13 @@ __metadata: languageName: node linkType: hard +"@trpc/server@npm:^10.0.0": + version: 10.45.2 + resolution: "@trpc/server@npm:10.45.2" + checksum: 30b92853c45747a376bbbd5c4eef71fea17a2b22e83ba7e694fb13cc99b15d1f24a17aa9124346074618fb5cee8d13434aa16cdf24af82f5e8acabdecfee0ca2 + languageName: node + linkType: hard + "@tryvital/vital-node@npm:^1.4.6": version: 1.4.6 resolution: "@tryvital/vital-node@npm:1.4.6" @@ -15942,6 +17598,25 @@ __metadata: languageName: node linkType: hard +"@typeform/embed-react@npm:^1.2.4": + version: 1.21.0 + resolution: "@typeform/embed-react@npm:1.21.0" + dependencies: + "@typeform/embed": 1.38.0 + fast-deep-equal: ^3.1.3 + peerDependencies: + react: ">=16.8.0" + checksum: 1d91cb797dfe7b27e08798f7a571f34724a8f56bf9c89a8ed2a454820efce2de4db44fd4a18f446573784c28f79478f269c1719500e1b332e7ea34ea77ffa185 + languageName: node + linkType: hard + +"@typeform/embed@npm:1.38.0": + version: 1.38.0 + resolution: "@typeform/embed@npm:1.38.0" + checksum: 41115134e5cee28f5e031181b4525c7885d23707699cf183921110262717c7544bfd101428267410b812914c235687783e373ce98e37bdc447d72f8177663597 + languageName: node + linkType: hard + "@types/accept-language-parser@npm:1.5.2": version: 1.5.2 resolution: "@types/accept-language-parser@npm:1.5.2" @@ -16053,6 +17728,18 @@ __metadata: languageName: node linkType: hard +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "*" + "@types/keyv": ^3.1.4 + "@types/node": "*" + "@types/responselike": ^1.0.0 + checksum: d9b26403fe65ce6b0cb3720b7030104c352bcb37e4fac2a7089a25a97de59c355fa08940658751f2f347a8512aa9d18fdb66ab3ade835975b2f454f2d5befbd9 + languageName: node + linkType: hard + "@types/cli-progress@npm:^3.11.0": version: 3.11.4 resolution: "@types/cli-progress@npm:3.11.4" @@ -16225,6 +17912,13 @@ __metadata: languageName: node linkType: hard +"@types/debounce@npm:^1.2.1": + version: 1.2.4 + resolution: "@types/debounce@npm:1.2.4" + checksum: decef3eee65d681556d50f7fac346f1b33134f6b21f806d41326f9dfb362fa66b0282ff0640ae6791b690694c9dc3dad4e146e909e707e6f96650f3aa325b9da + languageName: node + linkType: hard + "@types/debug@npm:4.1.7": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" @@ -16252,6 +17946,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.0.0": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "*" + checksum: 47876a852de8240bfdaf7481357af2b88cb660d30c72e73789abf00c499d6bc7cd5e52f41c915d1b9cd8ec9fef5b05688d7b7aef17f7f272c2d04679508d1053 + languageName: node + linkType: hard + "@types/detect-port@npm:^1.3.0": version: 1.3.5 resolution: "@types/detect-port@npm:1.3.5" @@ -16330,6 +18033,15 @@ __metadata: languageName: node linkType: hard +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree-jsx@npm:1.0.5" + dependencies: + "@types/estree": "*" + checksum: a028ab0cd7b2950168a05c6a86026eb3a36a54a4adfae57f13911d7b49dffe573d9c2b28421b2d029b49b3d02fcd686611be2622dc3dad6d9791166c083f6008 + languageName: node + linkType: hard + "@types/estree@npm:*, @types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" @@ -16457,6 +18169,13 @@ __metadata: languageName: node linkType: hard +"@types/gtag.js@npm:^0.0.10": + version: 0.0.10 + resolution: "@types/gtag.js@npm:0.0.10" + checksum: 5c18ffdc64418887763ec1a564e73c9fbf222ff3eece1fbc35a182fdd884e7884bb7708f67e6e4939f157bb9f2cb7a4aff42be7834527e35c5aac4f98783164c + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.4 resolution: "@types/hast@npm:2.3.4" @@ -16466,6 +18185,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "*" + checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 + languageName: node + linkType: hard + "@types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1": version: 3.3.1 resolution: "@types/hoist-non-react-statics@npm:3.3.1" @@ -16490,7 +18218,7 @@ __metadata: languageName: node linkType: hard -"@types/http-cache-semantics@npm:^4.0.2": +"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.2": version: 4.0.4 resolution: "@types/http-cache-semantics@npm:4.0.4" checksum: 7f4dd832e618bc1e271be49717d7b4066d77c2d4eed5b81198eb987e532bb3e1c7e02f45d77918185bad936f884b700c10cebe06305f50400f382ab75055f9e8 @@ -16581,6 +18309,13 @@ __metadata: languageName: node linkType: hard +"@types/js-yaml@npm:^4.0.0": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: e5e5e49b5789a29fdb1f7d204f82de11cb9e8f6cb24ab064c616da5d6e1b3ccfbf95aa5d1498a9fbd3b9e745564e69b4a20b6c530b5a8bbb2d4eb830cda9bc69 + languageName: node + linkType: hard + "@types/jsdom@npm:^21.1.3": version: 21.1.4 resolution: "@types/jsdom@npm:21.1.4" @@ -16656,6 +18391,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "*" + checksum: e009a2bfb50e90ca9b7c6e8f648f8464067271fd99116f881073fa6fa76dc8d0133181dd65e6614d5fb1220d671d67b0124aef7d97dc02d7e342ab143a47779d + languageName: node + linkType: hard + "@types/koa-compose@npm:*": version: 3.2.8 resolution: "@types/koa-compose@npm:3.2.8" @@ -16751,6 +18495,24 @@ __metadata: languageName: node linkType: hard +"@types/mdast@npm:^3.0.0": + version: 3.0.15 + resolution: "@types/mdast@npm:3.0.15" + dependencies: + "@types/unist": ^2 + checksum: af85042a4e3af3f879bde4059fa9e76c71cb552dffc896cdcc6cf9dc1fd38e37035c2dbd6245cfa6535b433f1f0478f5549696234ccace47a64055a10c656530 + languageName: node + linkType: hard + +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "*" + checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee + languageName: node + linkType: hard + "@types/mdurl@npm:*": version: 1.0.2 resolution: "@types/mdurl@npm:1.0.2" @@ -16758,6 +18520,13 @@ __metadata: languageName: node linkType: hard +"@types/mdurl@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/mdurl@npm:1.0.5" + checksum: e8e872e8da8f517a9c748b06cec61c947cb73fd3069e8aeb0926670ec5dfac5d30549b3d0f1634950401633e812f9b7263f2d5dbe7e98fce12bcb2c659aa4b21 + languageName: node + linkType: hard + "@types/mdx@npm:^2.0.0": version: 2.0.10 resolution: "@types/mdx@npm:2.0.10" @@ -16897,6 +18666,13 @@ __metadata: languageName: node linkType: hard +"@types/parse5@npm:^6.0.0": + version: 6.0.3 + resolution: "@types/parse5@npm:6.0.3" + checksum: ddb59ee4144af5dfcc508a8dcf32f37879d11e12559561e65788756b95b33e6f03ea027d88e1f5408f9b7bfb656bf630ace31a2169edf44151daaf8dd58df1b7 + languageName: node + linkType: hard + "@types/passport-jwt@npm:^3.0.13": version: 3.0.13 resolution: "@types/passport-jwt@npm:3.0.13" @@ -17033,6 +18809,13 @@ __metadata: languageName: node linkType: hard +"@types/react-gtm-module@npm:^2.0.1": + version: 2.0.4 + resolution: "@types/react-gtm-module@npm:2.0.4" + checksum: 635699a2d85f958ba92126cda851f9a1b46df75d3846bdc89ce2345524f73d889a266110d00492f6c23e9e42e70ec2246449a93c9cc2c6367bd643a7d2f0e88a + languageName: node + linkType: hard + "@types/react-phone-number-input@npm:^3.0.14": version: 3.0.14 resolution: "@types/react-phone-number-input@npm:3.0.14" @@ -17095,6 +18878,15 @@ __metadata: languageName: node linkType: hard +"@types/responselike@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/responselike@npm:1.0.3" + dependencies: + "@types/node": "*" + checksum: 6ac4b35723429b11b117e813c7acc42c3af8b5554caaf1fc750404c1ae59f9b7376bc69b9e9e194a5a97357a597c2228b7173d317320f0360d617b6425212f58 + languageName: node + linkType: hard + "@types/retry@npm:0.12.0": version: 0.12.0 resolution: "@types/retry@npm:0.12.0" @@ -17292,6 +19084,20 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:^2": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 6d436e832bc35c6dde9f056ac515ebf2b3384a1d7f63679d12358766f9b313368077402e9c1126a14d827f10370a5485e628bf61aa91117cf4fc882423191a4e + languageName: node + linkType: hard + +"@types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a + languageName: node + linkType: hard + "@types/uuid@npm:8.3.1": version: 8.3.1 resolution: "@types/uuid@npm:8.3.1" @@ -17338,6 +19144,24 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^8.0.0": + version: 8.5.14 + resolution: "@types/ws@npm:8.5.14" + dependencies: + "@types/node": "*" + checksum: b63d25146a0d2ebb9cb35e4b68c5a01d81b8f4657d62b0a0d9470df6a357798349a44064b2f84b8f98a553ba84d9a5ee302d3053feef02c7771010c55d290ca6 + languageName: node + linkType: hard + +"@types/xml2js@npm:^0.4.11": + version: 0.4.14 + resolution: "@types/xml2js@npm:0.4.14" + dependencies: + "@types/node": "*" + checksum: df9f106b9953dcdec7ba3304ebc56d6c2f61d49bf556d600bed439f94a1733f73ca0bf2d0f64330b402191622862d9d6058bab9d7e3dcb5b0fe51ebdc4372aac + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -17689,6 +19513,15 @@ __metadata: languageName: node linkType: hard +"@vercel/analytics@npm:^0.1.6": + version: 0.1.11 + resolution: "@vercel/analytics@npm:0.1.11" + peerDependencies: + react: ^16.8||^17||^18 + checksum: 05b8180ac6e23ebe7c09d74c43f8ee78c408cd0b6546e676389cbf4fba44dfeeae3648c9b52e2421be64fe3aeee8b026e6ea4bdfc0589fb5780670f2b090a167 + languageName: node + linkType: hard + "@vercel/edge-config@npm:^0.1.1": version: 0.1.1 resolution: "@vercel/edge-config@npm:0.1.1" @@ -17722,6 +19555,17 @@ __metadata: languageName: node linkType: hard +"@vercel/og@npm:^0.5.0": + version: 0.5.20 + resolution: "@vercel/og@npm:0.5.20" + dependencies: + "@resvg/resvg-wasm": 2.6.0 + satori: 0.10.9 + yoga-wasm-web: 0.3.3 + checksum: b6e08f3a9b2a0ef7c2cfe074d7e2699e7d4b1ab78cab370846ebea92dcb1becfcfef50309e1f905c1c37f37a64484fc789b0a364a5ce9496b8b4ab25048e7aaa + languageName: node + linkType: hard + "@vercel/og@npm:^0.6.3": version: 0.6.3 resolution: "@vercel/og@npm:0.6.3" @@ -18203,6 +20047,52 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/disposablestack@npm:^0.0.5": + version: 0.0.5 + resolution: "@whatwg-node/disposablestack@npm:0.0.5" + dependencies: + tslib: ^2.6.3 + checksum: d2df8ba46c567c3cc07767821fcedc8f4c9247f1f5488c7dadc42f86ca6429bace4451e48506ac2de15afe8e5baa1cd0951a7aa6419f091d48c671844e399931 + languageName: node + linkType: hard + +"@whatwg-node/fetch@npm:^0.10.0, @whatwg-node/fetch@npm:^0.10.1": + version: 0.10.3 + resolution: "@whatwg-node/fetch@npm:0.10.3" + dependencies: + "@whatwg-node/node-fetch": ^0.7.7 + urlpattern-polyfill: ^10.0.0 + checksum: 9fb7072cb048d1b747dfb6cf9b9a4e5240071bd290095b0b5552644b68626fa7116c76817462c74461dc0e0c57eb6bd2ba73e2dafa5f1d8d178e0366bea75bbb + languageName: node + linkType: hard + +"@whatwg-node/fetch@npm:^0.5.3": + version: 0.5.4 + resolution: "@whatwg-node/fetch@npm:0.5.4" + dependencies: + "@peculiar/webcrypto": ^1.4.0 + abort-controller: ^3.0.0 + busboy: ^1.6.0 + form-data-encoder: ^1.7.1 + formdata-node: ^4.3.1 + node-fetch: ^2.6.7 + undici: ^5.12.0 + web-streams-polyfill: ^3.2.0 + checksum: 6fb6c6a582cb78fc438beee11f1d931eabc0ac8b5b660b68ea30a42c2068f4d3126d2b07e21770a4d6f391e70979bae527ca892898da9857e73dc3cc7adb8da9 + languageName: node + linkType: hard + +"@whatwg-node/node-fetch@npm:^0.7.7": + version: 0.7.9 + resolution: "@whatwg-node/node-fetch@npm:0.7.9" + dependencies: + "@whatwg-node/disposablestack": ^0.0.5 + busboy: ^1.6.0 + tslib: ^2.6.3 + checksum: e7b6e5300eb522bc365c90e92a7aed33aa7197dd41d102302db83b819d3c62eedcd4d802cf0cf7a9998acf7465430afea5ad23bb6391ccc9609532a19dedb436 + languageName: node + linkType: hard + "@wojtekmaj/date-utils@npm:^1.0.2, @wojtekmaj/date-utils@npm:^1.0.3": version: 1.0.3 resolution: "@wojtekmaj/date-utils@npm:1.0.3" @@ -18978,6 +20868,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:^3.0.0": + version: 3.0.0 + resolution: "array-flatten@npm:3.0.0" + checksum: ad00c51ca70cf837501fb6da823ba39bc6a86b43d0b76d840daa02fe0f8e68e94ad5bc2d0d038053118b879aaca8ea6168c32c7387a2fa5b118ad28db4f1f863 + languageName: node + linkType: hard + "array-includes@npm:^3.1.4": version: 3.1.4 resolution: "array-includes@npm:3.1.4" @@ -19279,6 +21176,17 @@ __metadata: languageName: node linkType: hard +"asn1js@npm:^3.0.5": + version: 3.0.5 + resolution: "asn1js@npm:3.0.5" + dependencies: + pvtsutils: ^1.3.2 + pvutils: ^1.1.3 + tslib: ^2.4.0 + checksum: 3b6af1bbadd5762ef8ead5daf2f6bda1bc9e23bc825c4dcc996aa1f9521ad7390a64028565d95d98090d69c8431f004c71cccb866004759169d7c203cf9075eb + languageName: node + linkType: hard + "assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": version: 1.0.0 resolution: "assert-plus@npm:1.0.0" @@ -19370,6 +21278,13 @@ __metadata: languageName: node linkType: hard +"async-scheduler@npm:^1.4.4": + version: 1.4.4 + resolution: "async-scheduler@npm:1.4.4" + checksum: 080310e642bc4309aa83d625b21f9f0f1291bd0a292361cf6c0ebc86646ca719888bebc3d519f8ed177130b623b0f20640dad7f24fd8c2ede31d6d6f976968a4 + languageName: node + linkType: hard + "async@npm:^3.2.3, async@npm:^3.2.4": version: 3.2.4 resolution: "async@npm:3.2.4" @@ -19422,7 +21337,7 @@ __metadata: languageName: node linkType: hard -"auto-bind@npm:4.0.0": +"auto-bind@npm:4.0.0, auto-bind@npm:~4.0.0": version: 4.0.0 resolution: "auto-bind@npm:4.0.0" checksum: 00cad71cce5742faccb7dd65c1b55ebc4f45add4b0c9a1547b10b05bab22813230133b0c892c67ba3eb969a4524710c5e43cc45c72898ec84e56f3a596e7a04f @@ -19606,6 +21521,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.6.1": + version: 1.7.9 + resolution: "axios@npm:1.7.9" + dependencies: + follow-redirects: ^1.15.6 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: cb8ce291818effda09240cb60f114d5625909b345e10f389a945320e06acf0bc949d0f8422d25720f5dd421362abee302c99f5e97edec4c156c8939814b23d19 + languageName: node + linkType: hard + "axios@npm:^1.6.7": version: 1.6.8 resolution: "axios@npm:1.6.8" @@ -19818,6 +21744,23 @@ __metadata: languageName: node linkType: hard +"babel-runtime@npm:^6.11.6": + version: 6.26.0 + resolution: "babel-runtime@npm:6.26.0" + dependencies: + core-js: ^2.4.0 + regenerator-runtime: ^0.11.0 + checksum: 8aeade94665e67a73c1ccc10f6fd42ba0c689b980032b70929de7a6d9a12eb87ef51902733f8fefede35afea7a5c3ef7e916a64d503446c1eedc9e3284bd3d50 + languageName: node + linkType: hard + +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: aab4e8ccdc8d762bf3fdfce8e706601695620c0c2eda256dd85088dc0be3cfd7ff126f6e99c2bee1f24f5d418414aacf09d7f9702f16d6963df2fa488cda8824 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -20315,6 +22258,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.24.0": + version: 4.24.4 + resolution: "browserslist@npm:4.24.4" + dependencies: + caniuse-lite: ^1.0.30001688 + electron-to-chromium: ^1.5.73 + node-releases: ^2.0.19 + update-browserslist-db: ^1.1.1 + bin: + browserslist: cli.js + checksum: 64074bf6cf0a9ae3094d753270e3eae9cf925149db45d646f0bc67bacc2e46d7ded64a4e835b95f5fdcf0350f63a83c3755b32f80831f643a47f0886deb8a065 + languageName: node + linkType: hard + "bs-logger@npm:0.x": version: 0.2.6 resolution: "bs-logger@npm:0.2.6" @@ -20463,7 +22420,7 @@ __metadata: languageName: node linkType: hard -"busboy@npm:1.6.0, busboy@npm:^1.0.0": +"busboy@npm:1.6.0, busboy@npm:^1.0.0, busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -20568,6 +22525,13 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: 763e02cf9196bc9afccacd8c418d942fc2677f22261969a4c2c2e760fa44a2351a81557bd908291c3921fe9beb10b976ba8fa50c5ca837c5a0dd945f16468f2d + languageName: node + linkType: hard + "cacheable-lookup@npm:^7.0.0": version: 7.0.0 resolution: "cacheable-lookup@npm:7.0.0" @@ -20590,6 +22554,21 @@ __metadata: languageName: node linkType: hard +"cacheable-request@npm:^7.0.2": + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" + dependencies: + clone-response: ^1.0.2 + get-stream: ^5.1.0 + http-cache-semantics: ^4.0.0 + keyv: ^4.0.0 + lowercase-keys: ^2.0.0 + normalize-url: ^6.0.1 + responselike: ^2.0.0 + checksum: 0de9df773fd4e7dd9bd118959878f8f2163867e2e1ab3575ffbecbe6e75e80513dd0c68ba30005e5e5a7b377cc6162bbc00ab1db019bb4e9cb3c2f3f7a6f1ee4 + languageName: node + linkType: hard + "calcom-monorepo@workspace:.": version: 0.0.0-use.local resolution: "calcom-monorepo@workspace:." @@ -20691,6 +22670,16 @@ __metadata: languageName: node linkType: hard +"camel-case@npm:^3.0.0": + version: 3.0.0 + resolution: "camel-case@npm:3.0.0" + dependencies: + no-case: ^2.2.0 + upper-case: ^1.1.1 + checksum: 4190ed6ab8acf4f3f6e1a78ad4d0f3f15ce717b6bfa1b5686d58e4bcd29960f6e312dd746b5fa259c6d452f1413caef25aee2e10c9b9a580ac83e516533a961a + languageName: node + linkType: hard + "camel-case@npm:^4.1.2": version: 4.1.2 resolution: "camel-case@npm:4.1.2" @@ -20719,6 +22708,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^3.0.0": + version: 3.0.0 + resolution: "camelcase@npm:3.0.0" + checksum: ae4fe1c17c8442a3a345a6b7d2393f028ab7a7601af0c352ad15d1ab97ca75112e19e29c942b2a214898e160194829b68923bce30e018d62149c6d84187f1673 + languageName: node + linkType: hard + "camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" @@ -20789,6 +22785,24 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001688": + version: 1.0.30001699 + resolution: "caniuse-lite@npm:1.0.30001699" + checksum: 697172065537b0f33c428fe8561f4cba6796428dc8e3e56f78eee28404edfcbea70d48bb109ab6c6536de6da90e331058a2cb8ef3a15c58b2226c96ee558bc07 + languageName: node + linkType: hard + +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: ^3.0.4 + tslib: ^2.0.3 + upper-case-first: ^2.0.2 + checksum: 41fa8fa87f6d24d0835a2b4a9341a3eaecb64ac29cd7c5391f35d6175a0fa98ab044e7f2602e1ec3afc886231462ed71b5b80c590b8b41af903ec2c15e5c5931 + languageName: node + linkType: hard + "cardinal@npm:^2.1.1": version: 2.1.1 resolution: "cardinal@npm:2.1.1" @@ -20815,6 +22829,13 @@ __metadata: languageName: node linkType: hard +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 + languageName: node + linkType: hard + "chai@npm:^5.1.1": version: 5.1.1 resolution: "chai@npm:5.1.1" @@ -20902,6 +22923,24 @@ __metadata: languageName: node linkType: hard +"change-case-all@npm:1.0.15": + version: 1.0.15 + resolution: "change-case-all@npm:1.0.15" + dependencies: + change-case: ^4.1.2 + is-lower-case: ^2.0.2 + is-upper-case: ^2.0.2 + lower-case: ^2.0.2 + lower-case-first: ^2.0.2 + sponge-case: ^1.0.1 + swap-case: ^2.0.2 + title-case: ^3.0.3 + upper-case: ^2.0.2 + upper-case-first: ^2.0.2 + checksum: e1dabdcd8447a3690f3faf15f92979dfbc113109b50916976e1d5e518e6cfdebee4f05f54d0ca24fb79a4bf835185b59ae25e967bb3dc10bd236a775b19ecc52 + languageName: node + linkType: hard + "change-case@npm:^2.3.0": version: 2.3.1 resolution: "change-case@npm:2.3.1" @@ -20926,6 +22965,52 @@ __metadata: languageName: node linkType: hard +"change-case@npm:^3.0.0": + version: 3.1.0 + resolution: "change-case@npm:3.1.0" + dependencies: + camel-case: ^3.0.0 + constant-case: ^2.0.0 + dot-case: ^2.1.0 + header-case: ^1.0.0 + is-lower-case: ^1.1.0 + is-upper-case: ^1.1.0 + lower-case: ^1.1.1 + lower-case-first: ^1.0.0 + no-case: ^2.3.2 + param-case: ^2.1.0 + pascal-case: ^2.0.0 + path-case: ^2.1.0 + sentence-case: ^2.1.0 + snake-case: ^2.1.0 + swap-case: ^1.1.0 + title-case: ^2.1.0 + upper-case: ^1.1.1 + upper-case-first: ^1.1.0 + checksum: d6f9f90a5f1d2a98294e06ea62f913fa0d7cfc289f188bf05662344da6128f5710b5c99ece83682c6a848db8d996b7348e09b2235dc3363afb6ae7142e7978e1 + languageName: node + linkType: hard + +"change-case@npm:^4.1.2": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: ^4.1.2 + capital-case: ^1.0.4 + constant-case: ^3.0.4 + dot-case: ^3.0.4 + header-case: ^2.0.4 + no-case: ^3.0.4 + param-case: ^3.0.4 + pascal-case: ^3.1.2 + path-case: ^3.0.4 + sentence-case: ^3.0.4 + snake-case: ^3.0.4 + tslib: ^2.0.3 + checksum: e4bc4a093a1f7cce8b33896665cf9e456e3bc3cc0def2ad7691b1994cfca99b3188d0a513b16855b01a6bd20692fcde12a7d4d87a5615c4c515bbbf0e651f116 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -20933,6 +23018,13 @@ __metadata: languageName: node linkType: hard +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d + languageName: node + linkType: hard + "character-entities-legacy@npm:^1.0.0": version: 1.1.4 resolution: "character-entities-legacy@npm:1.1.4" @@ -20940,6 +23032,13 @@ __metadata: languageName: node linkType: hard +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + "character-entities@npm:^1.0.0": version: 1.2.4 resolution: "character-entities@npm:1.2.4" @@ -20947,6 +23046,13 @@ __metadata: languageName: node linkType: hard +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: cf1643814023697f725e47328fcec17923b8f1799102a8a79c1514e894815651794a2bffd84bb1b3a4b124b050154e4529ed6e81f7c8068a734aecf07a6d3def + languageName: node + linkType: hard + "character-reference-invalid@npm:^1.0.0": version: 1.1.4 resolution: "character-reference-invalid@npm:1.1.4" @@ -20954,6 +23060,13 @@ __metadata: languageName: node linkType: hard +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 98d3b1a52ae510b7329e6ee7f6210df14f1e318c5415975d4c9e7ee0ef4c07875d47c6e74230c64551f12f556b4a8ccc24d9f3691a2aa197019e72a95e9297ee + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -20968,6 +23081,13 @@ __metadata: languageName: node linkType: hard +"chart.js@npm:^3.7.1": + version: 3.9.1 + resolution: "chart.js@npm:3.9.1" + checksum: 9ab0c0ac01215af0b3f020f2e313030fd6e347b48ed17d5484ee9c4e8ead45e78ae71bea16c397621c386b409ce0b14bf17f9f6c2492cd15b56c0f433efdfff6 + languageName: node + linkType: hard + "check-error@npm:^2.1.1": version: 2.1.1 resolution: "check-error@npm:2.1.1" @@ -21371,13 +23491,24 @@ __metadata: languageName: node linkType: hard -"client-only@npm:0.0.1": +"client-only@npm:0.0.1, client-only@npm:^0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" checksum: 0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8 languageName: node linkType: hard +"cliui@npm:^3.2.0": + version: 3.2.0 + resolution: "cliui@npm:3.2.0" + dependencies: + string-width: ^1.0.1 + strip-ansi: ^3.0.1 + wrap-ansi: ^2.0.0 + checksum: c68d1dbc3e347bfe79ed19cc7f48007d5edd6cd8438342e32073e0b4e311e3c44e1f4f19221462bc6590de56c2df520e427533a9dde95dee25710bec322746ad + languageName: node + linkType: hard + "cliui@npm:^6.0.0": version: 6.0.0 resolution: "cliui@npm:6.0.0" @@ -21422,6 +23553,15 @@ __metadata: languageName: node linkType: hard +"clone-response@npm:^1.0.2": + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" + dependencies: + mimic-response: ^1.0.0 + checksum: 4e671cac39b11c60aa8ba0a450657194a5d6504df51bca3fac5b3bd0145c4f8e8464898f87c8406b83232e3bc5cca555f51c1f9c8ac023969ebfbf7f6bdabb2e + languageName: node + linkType: hard + "clone@npm:^1.0.2": version: 1.0.4 resolution: "clone@npm:1.0.4" @@ -21450,6 +23590,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^1.2.1": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 + languageName: node + linkType: hard + "clsx@npm:^2.0.0": version: 2.1.0 resolution: "clsx@npm:2.1.0" @@ -21507,6 +23654,15 @@ __metadata: languageName: node linkType: hard +"cobe@npm:^0.4.1": + version: 0.4.2 + resolution: "cobe@npm:0.4.2" + dependencies: + phenomenon: ^1.6.0 + checksum: 4c11dd8cf3c6614a2ff2f6eacca5283278c6db06c2e441011aa90f58c66d045e66ef98a5e4b9d0282d58ee22f5b87d965538aa4c6064ed97f436f3e4cd9ee3ed + languageName: node + linkType: hard + "code-block-writer@npm:^11.0.0": version: 11.0.0 resolution: "code-block-writer@npm:11.0.0" @@ -21525,6 +23681,13 @@ __metadata: languageName: node linkType: hard +"code-point-at@npm:^1.0.0": + version: 1.1.0 + resolution: "code-point-at@npm:1.1.0" + checksum: 17d5666611f9b16d64fdf48176d9b7fb1c7d1c1607a189f7e600040a11a6616982876af148230336adb7d8fe728a559f743a4e29db3747e3b1a32fa7f4529681 + languageName: node + linkType: hard + "coffeescript@npm:^1.10.0": version: 1.12.7 resolution: "coffeescript@npm:1.12.7" @@ -21660,6 +23823,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d + languageName: node + linkType: hard + "command-score@npm:0.1.2, command-score@npm:^0.1.2": version: 0.1.2 resolution: "command-score@npm:0.1.2" @@ -21767,6 +23937,13 @@ __metadata: languageName: node linkType: hard +"common-tags@npm:1.8.2": + version: 1.8.2 + resolution: "common-tags@npm:1.8.2" + checksum: 767a6255a84bbc47df49a60ab583053bb29a7d9687066a18500a516188a062c4e4cd52de341f22de0b07062e699b1b8fe3cfa1cb55b241cb9301aeb4f45b4dff + languageName: node + linkType: hard + "commondir@npm:^1.0.1": version: 1.0.1 resolution: "commondir@npm:1.0.1" @@ -21862,6 +24039,26 @@ __metadata: languageName: node linkType: hard +"concurrently@npm:^7.6.0": + version: 7.6.0 + resolution: "concurrently@npm:7.6.0" + dependencies: + chalk: ^4.1.0 + date-fns: ^2.29.1 + lodash: ^4.17.21 + rxjs: ^7.0.0 + shell-quote: ^1.7.3 + spawn-command: ^0.0.2-1 + supports-color: ^8.1.0 + tree-kill: ^1.2.2 + yargs: ^17.3.1 + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: f705c9a7960f1b16559ca64958043faeeef6385c0bf30a03d1375e15ab2d96dba4f8166f1bbbb1c85e8da35ca0ce3c353875d71dff2aa132b2357bb533b3332e + languageName: node + linkType: hard + "conf@npm:10.2.0": version: 10.2.0 resolution: "conf@npm:10.2.0" @@ -21946,6 +24143,27 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^2.0.0": + version: 2.0.0 + resolution: "constant-case@npm:2.0.0" + dependencies: + snake-case: ^2.1.0 + upper-case: ^1.1.1 + checksum: 893c793a425ebcd0744061c7f12650c655aae259b89d5654fb8eda42d22c3690716a4988ed03f2abe370b1ee7bfec44f8e4395e76e2f1458a8921982b15410ba + languageName: node + linkType: hard + +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: ^3.0.4 + tslib: ^2.0.3 + upper-case: ^2.0.2 + checksum: 6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -22133,6 +24351,13 @@ __metadata: languageName: node linkType: hard +"core-js@npm:^2.4.0": + version: 2.6.12 + resolution: "core-js@npm:2.6.12" + checksum: 44fa9934a85f8c78d61e0c8b7b22436330471ffe59ec5076fe7f324d6e8cf7f824b14b1c81ca73608b13bdb0fef035bd820989bf059767ad6fa13123bb8bd016 + languageName: node + linkType: hard + "core-js@npm:^3": version: 3.21.1 resolution: "core-js@npm:3.21.1" @@ -22214,7 +24439,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.1.0, cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -22350,6 +24575,18 @@ __metadata: languageName: node linkType: hard +"cross-env@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-env@npm:7.0.3" + dependencies: + cross-spawn: ^7.0.1 + bin: + cross-env: src/bin/cross-env.js + cross-env-shell: src/bin/cross-env-shell.js + checksum: 26f2f3ea2ab32617f57effb70d329c2070d2f5630adc800985d8b30b56e8bf7f5f439dd3a0358b79cee6f930afc23cf8e23515f17ccfb30092c6b62c6b630a79 + languageName: node + linkType: hard + "cross-fetch@npm:3.1.5, cross-fetch@npm:^3.1.5": version: 3.1.5 resolution: "cross-fetch@npm:3.1.5" @@ -22368,6 +24605,15 @@ __metadata: languageName: node linkType: hard +"cross-inspect@npm:1.0.1": + version: 1.0.1 + resolution: "cross-inspect@npm:1.0.1" + dependencies: + tslib: ^2.4.0 + checksum: 7c1e02e0a9670b62416a3ea1df7ae880fdad3aa0a857de8932c4e5f8acd71298c7e3db9da8e9da603f5692cd1879938f5e72e34a9f5d1345987bef656d117fc1 + languageName: node + linkType: hard + "cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -22875,6 +25121,15 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.29.1": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": ^7.21.0 + checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4 + languageName: node + linkType: hard + "date-fns@npm:^3.6.0": version: 3.6.0 resolution: "date-fns@npm:3.6.0" @@ -22882,6 +25137,43 @@ __metadata: languageName: node linkType: hard +"datocms-listen@npm:^0.1.9": + version: 0.1.15 + resolution: "datocms-listen@npm:0.1.15" + dependencies: + "@0no-co/graphql.web": ^1.0.1 + checksum: 243fec6f8c07d35f8d8d206ded45c4b8cfeb22907bba23d2180af6284903e4af1146c89e08ad0f68977193d42530323c53d0d906f7cf9f1ba92490ed11386e8c + languageName: node + linkType: hard + +"datocms-structured-text-generic-html-renderer@npm:^2.0.1, datocms-structured-text-generic-html-renderer@npm:^2.1.12": + version: 2.1.12 + resolution: "datocms-structured-text-generic-html-renderer@npm:2.1.12" + dependencies: + datocms-structured-text-utils: ^2.1.12 + checksum: ef9e29b15903ce69be42faa4f4a4364bb7ba0c6565008cb2f557e8865c0806edb938a27f9bee7aa682f7af84fe89489b688b9713ea48501c139d204fea1f18c0 + languageName: node + linkType: hard + +"datocms-structured-text-to-plain-text@npm:^2.0.4": + version: 2.1.12 + resolution: "datocms-structured-text-to-plain-text@npm:2.1.12" + dependencies: + datocms-structured-text-generic-html-renderer: ^2.1.12 + datocms-structured-text-utils: ^2.1.12 + checksum: 8d436ca14379650d66257b5a2f36523e1ebaee41584caf021c863155404a292dbde7408c6cb0e0185638c2eb7508087239f862e78e8647b806ececd4e24683fd + languageName: node + linkType: hard + +"datocms-structured-text-utils@npm:^2.0.1, datocms-structured-text-utils@npm:^2.0.4, datocms-structured-text-utils@npm:^2.1.12": + version: 2.1.12 + resolution: "datocms-structured-text-utils@npm:2.1.12" + dependencies: + array-flatten: ^3.0.0 + checksum: 68d5be9cb711fb630866cca2c0e44b333b390d9672b2ea680d5870c26e8ccadcc6bd77d02a95ccbb913c160466a3046cbd934c41712f9d064f98a73009e0b97a + languageName: node + linkType: hard + "dayjs@npm:1.11.4": version: 1.11.4 resolution: "dayjs@npm:1.11.4" @@ -22929,6 +25221,13 @@ __metadata: languageName: node linkType: hard +"debounce@npm:^1.2.0, debounce@npm:^1.2.1": + version: 1.2.1 + resolution: "debounce@npm:1.2.1" + checksum: 682a89506d9e54fb109526f4da255c5546102fbb8e3ae75eef3b04effaf5d4853756aee97475cd4650641869794e44f410eeb20ace2b18ea592287ab2038519e + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.6.0, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -22959,6 +25258,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.0.0": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479 + languageName: node + linkType: hard + "debug@npm:^4.3.6, debug@npm:^4.3.7": version: 4.3.7 resolution: "debug@npm:4.3.7" @@ -22981,7 +25292,7 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0": +"decamelize@npm:^1.1.0, decamelize@npm:^1.1.1, decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa @@ -23002,6 +25313,15 @@ __metadata: languageName: node linkType: hard +"decode-named-character-reference@npm:^1.0.0": + version: 1.0.2 + resolution: "decode-named-character-reference@npm:1.0.2" + dependencies: + character-entities: ^2.0.0 + checksum: f4c71d3b93105f20076052f9cb1523a22a9c796b8296cd35eef1ca54239c78d182c136a848b83ff8da2071e3ae2b1d300bf29d00650a6d6e675438cc31b11d78 + languageName: node + linkType: hard + "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -23127,7 +25447,7 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.1": +"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": version: 2.0.1 resolution: "defer-to-connect@npm:2.0.1" checksum: 8a9b50d2f25446c0bfefb55a48e90afd58f85b21bcf78e9207cd7b804354f6409032a1705c2491686e202e64fc05f147aa5aa45f9aa82627563f045937f5791b @@ -23278,6 +25598,13 @@ __metadata: languageName: node linkType: hard +"dependency-graph@npm:^0.11.0": + version: 0.11.0 + resolution: "dependency-graph@npm:0.11.0" + checksum: 477204beaa9be69e642bc31ffe7a8c383d0cf48fa27acbc91c5df01431ab913e65c154213d2ef83d034c98d77280743ec85e5da018a97a18dd43d3c0b78b28cd + languageName: node + linkType: hard + "deprecation@npm:^2.0.0": version: 2.3.1 resolution: "deprecation@npm:2.3.1" @@ -23285,7 +25612,7 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.2, dequal@npm:^2.0.3": +"dequal@npm:^2.0.0, dequal@npm:^2.0.2, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 @@ -23383,6 +25710,15 @@ __metadata: languageName: node linkType: hard +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + "dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -23421,6 +25757,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.0.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 12b63ca9c36c72bafa3effa77121f0581b4015df18bc16bac1f8e263597735649f1a173c26f7eba17fb4162b073fee61788abe49610e6c70a2641fe1895443fd + languageName: node + linkType: hard + "diffie-hellman@npm:^5.0.0": version: 5.0.3 resolution: "diffie-hellman@npm:5.0.3" @@ -23642,6 +25985,15 @@ __metadata: languageName: node linkType: hard +"dot-case@npm:^2.1.0": + version: 2.1.1 + resolution: "dot-case@npm:2.1.1" + dependencies: + no-case: ^2.2.0 + checksum: 5c9d937245ff810a7ae788602e40c62e38cb515146ddf9b11c7f60cb02aae84859588761f1e8769d9e713609fae3c78dc99c8da9e0ee8e4d8b5c09a2fdf70328 + languageName: node + linkType: hard + "dot-case@npm:^3.0.4": version: 3.0.4 resolution: "dot-case@npm:3.0.4" @@ -23726,6 +26078,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: f412c5fe8c24fbe313d302d2500e247ba8a1946492db405a4de4d30dd0eb186a88a43f13c958c5a7de303938949c4231c56994f97d05c4bc1f22478d631b4005 + languageName: node + linkType: hard + "dotenv@npm:^16.0.0": version: 16.0.1 resolution: "dotenv@npm:16.0.1" @@ -23867,6 +26226,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.73": + version: 1.5.101 + resolution: "electron-to-chromium@npm:1.5.101" + checksum: e8358a7c5638514b8f9483ae45cc33dbbad60ed103a35cf179a823483abf28a75195086de1ec942fa33473156839605e642979ac8adbe6c19da3c3b31acba9cc + languageName: node + linkType: hard + "elliptic@npm:^6.5.3": version: 6.5.4 resolution: "elliptic@npm:6.5.4" @@ -23954,7 +26320,7 @@ __metadata: languageName: node linkType: hard -"encoding@npm:0.1.13, encoding@npm:^0.1.13": +"encoding@npm:0.1.13, encoding@npm:^0.1.11, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" dependencies: @@ -24032,6 +26398,16 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.4.1": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: ^4.1.1 + strip-ansi: ^6.0.1 + checksum: f080f11a74209647dbf347a7c6a83c8a47ae1ebf1e75073a808bc1088eb780aa54075bfecd1bcdb3e3c724520edb8e6ee05da031529436b421b71066fcc48cb5 + languageName: node + linkType: hard + "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -24106,7 +26482,7 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.3.1": +"error-ex@npm:^1.2.0, error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" dependencies: @@ -24795,6 +27171,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 47b029c83de01b0d17ad99ed766347b974b0d628e848de404018f3abee728e987da0d2d370ad4574aa3d5b5bfc368754fd085d69a30f8e75903486ec4b5b709e + languageName: node + linkType: hard + "escape-goat@npm:^4.0.0": version: 4.0.0 resolution: "escape-goat@npm:4.0.0" @@ -25415,6 +27798,13 @@ __metadata: languageName: node linkType: hard +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: ea3909f0188ea164af0aadeca87c087e3e5da78d76da5ae9c7954ff1340ea3e4679c4653bbf4299ffb70caa9b322218cc1128db2541f3d2976eb9704f9857787 + languageName: node + linkType: hard + "estree-walker@npm:^1.0.1": version: 1.0.1 resolution: "estree-walker@npm:1.0.1" @@ -25715,6 +28105,13 @@ __metadata: languageName: node linkType: hard +"extract-files@npm:^11.0.0": + version: 11.0.0 + resolution: "extract-files@npm:11.0.0" + checksum: 39ebd92772e9a1e30d1e3112fb7db85d353c8243640635668b615ac1d605ceb79fbb13d17829dd308993ef37bb189ad99817f79ab164ae95c9bb3df9f440bd16 + languageName: node + linkType: hard + "extract-zip@npm:^1.6.6": version: 1.7.0 resolution: "extract-zip@npm:1.7.0" @@ -25930,6 +28327,13 @@ __metadata: languageName: node linkType: hard +"fathom-client@npm:^3.5.0": + version: 3.7.2 + resolution: "fathom-client@npm:3.7.2" + checksum: 2f6859cc8e2995334b67b1e9efe3e1084e0b55cfd8832f4fd9badf37abf80d2213a5892313ec57a3dfccd7b7a62bb3949617dc8310dd7af2af0781e534398cbc + languageName: node + linkType: hard + "fault@npm:^1.0.0": version: 1.0.4 resolution: "fault@npm:1.0.4" @@ -25971,6 +28375,28 @@ __metadata: languageName: node linkType: hard +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: 72baf6d22c45b75109118b4daecb6c8016d4c83c8c0f23f683f22e9d7c21f32fff6201d288df46eb561e3c7d4bb4489b8ad140b7f56444c453ba407e8bd28511 + languageName: node + linkType: hard + +"fbjs@npm:^3.0.0": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: ^3.1.5 + fbjs-css-vars: ^1.0.0 + loose-envify: ^1.0.0 + object-assign: ^4.1.0 + promise: ^7.1.1 + setimmediate: ^1.0.5 + ua-parser-js: ^1.0.35 + checksum: e609b5b64686bc96495a5c67728ed9b2710b9b3d695c5759c5f5e47c9483d1c323543ac777a86459e3694efc5712c6ce7212e944feb19752867d699568bb0e54 + languageName: node + linkType: hard + "fd-slicer@npm:~1.1.0": version: 1.1.0 resolution: "fd-slicer@npm:1.1.0" @@ -26241,6 +28667,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^1.0.0": + version: 1.1.2 + resolution: "find-up@npm:1.1.2" + dependencies: + path-exists: ^2.0.0 + pinkie-promise: ^2.0.0 + checksum: a2cb9f4c9f06ee3a1e92ed71d5aed41ac8ae30aefa568132f6c556fac7678a5035126153b59eaec68da78ac409eef02503b2b059706bdbf232668d7245e3240a + languageName: node + linkType: hard + "find-up@npm:^2.1.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -26477,6 +28913,13 @@ __metadata: languageName: node linkType: hard +"form-data-encoder@npm:^1.7.1": + version: 1.9.0 + resolution: "form-data-encoder@npm:1.9.0" + checksum: a73f617976f91b594dbd777ec5147abdb0c52d707475130f8cefc8ae9102ccf51be154b929f7c18323729c2763ac25b16055f5034bc188834e9febeb0d971d7f + languageName: node + linkType: hard + "form-data-encoder@npm:^2.1.2": version: 2.1.4 resolution: "form-data-encoder@npm:2.1.4" @@ -26556,6 +28999,16 @@ __metadata: languageName: node linkType: hard +"formdata-node@npm:^4.3.1": + version: 4.4.1 + resolution: "formdata-node@npm:4.4.1" + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + checksum: d91d4f667cfed74827fc281594102c0dabddd03c9f8b426fc97123eedbf73f5060ee43205d89284d6854e2fc5827e030cd352ef68b93beda8decc2d72128c576 + languageName: node + linkType: hard + "formdata-polyfill@npm:^4.0.10": version: 4.0.10 resolution: "formdata-polyfill@npm:4.0.10" @@ -26698,7 +29151,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^8.1.0": +"fs-extra@npm:^8.0.1, fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" dependencies: @@ -26964,6 +29417,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^1.0.1": + version: 1.0.3 + resolution: "get-caller-file@npm:1.0.3" + checksum: 2b90a7f848896abcebcdc0acc627a435bcf05b9cd280599bc980ebfcdc222416c3df12c24c4845f69adc4346728e8966f70b758f9369f3534182791dfbc25c05 + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -27202,6 +29662,13 @@ __metadata: languageName: node linkType: hard +"github-buttons@npm:^2.22.0": + version: 2.29.1 + resolution: "github-buttons@npm:2.29.1" + checksum: adde1324247df85730e0056206d751894c02ce8dcad5e96234bbc9f2f15caaa12a6431608b7e04434e29df83b6900f4fc36d2d4581605f435d97dc29dcfd1a42 + languageName: node + linkType: hard + "github-from-package@npm:0.0.0": version: 0.0.0 resolution: "github-from-package@npm:0.0.0" @@ -27419,7 +29886,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:11.1.0, globby@npm:^11.0.0, globby@npm:^11.0.1, globby@npm:^11.0.2, globby@npm:^11.1.0": +"globby@npm:11.1.0, globby@npm:^11.0.0, globby@npm:^11.0.1, globby@npm:^11.0.2, globby@npm:^11.0.3, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -27473,6 +29940,19 @@ __metadata: languageName: node linkType: hard +"globby@npm:^13.1.3": + version: 13.2.2 + resolution: "globby@npm:13.2.2" + dependencies: + dir-glob: ^3.0.1 + fast-glob: ^3.3.0 + ignore: ^5.2.4 + merge2: ^1.4.1 + slash: ^4.0.0 + checksum: f3d84ced58a901b4fcc29c846983108c426631fe47e94872868b65565495f7bee7b3defd68923bd480582771fd4bbe819217803a164a618ad76f1d22f666f41e + languageName: node + linkType: hard + "globrex@npm:^0.1.2": version: 0.1.2 resolution: "globrex@npm:0.1.2" @@ -27602,6 +30082,25 @@ __metadata: languageName: node linkType: hard +"got@npm:^11.8.5": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": ^4.0.0 + "@szmarczak/http-timer": ^4.0.5 + "@types/cacheable-request": ^6.0.1 + "@types/responselike": ^1.0.0 + cacheable-lookup: ^5.0.3 + cacheable-request: ^7.0.2 + decompress-response: ^6.0.0 + http2-wrapper: ^1.0.0-beta.5.2 + lowercase-keys: ^2.0.0 + p-cancelable: ^2.0.0 + responselike: ^2.0.0 + checksum: bbc783578a8d5030c8164ef7f57ce41b5ad7db2ed13371e1944bef157eeca5a7475530e07c0aaa71610d7085474d0d96222c9f4268d41db333a17e39b463f45d + languageName: node + linkType: hard + "got@npm:^12.1.0": version: 12.6.1 resolution: "got@npm:12.6.1" @@ -27685,6 +30184,13 @@ __metadata: languageName: node linkType: hard +"gsap@npm:^3.11.0": + version: 3.12.7 + resolution: "gsap@npm:3.12.7" + checksum: 15f5da56d02f04be8eb4b58cb733fbd71619647608c0854717697da8b1922a5ce4ea5487cba0c66f428aa645247e143e462bec885f8470725c2c9ab393247e25 + languageName: node + linkType: hard + "gtoken@npm:^5.0.4": version: 5.3.2 resolution: "gtoken@npm:5.3.2" @@ -27955,6 +30461,21 @@ __metadata: languageName: node linkType: hard +"hast-util-from-parse5@npm:^7.0.0": + version: 7.1.2 + resolution: "hast-util-from-parse5@npm:7.1.2" + dependencies: + "@types/hast": ^2.0.0 + "@types/unist": ^2.0.0 + hastscript: ^7.0.0 + property-information: ^6.0.0 + vfile: ^5.0.0 + vfile-location: ^4.0.0 + web-namespaces: ^2.0.0 + checksum: 7b4ed5b508b1352127c6719f7b0c0880190cf9859fe54ccaf7c9228ecf623d36cef3097910b3874d2fe1aac6bf4cf45d3cc2303daac3135a05e9ade6534ddddb + languageName: node + linkType: hard + "hast-util-parse-selector@npm:^2.0.0": version: 2.2.5 resolution: "hast-util-parse-selector@npm:2.2.5" @@ -27962,6 +30483,115 @@ __metadata: languageName: node linkType: hard +"hast-util-parse-selector@npm:^3.0.0": + version: 3.1.1 + resolution: "hast-util-parse-selector@npm:3.1.1" + dependencies: + "@types/hast": ^2.0.0 + checksum: 511d373465f60dd65e924f88bf0954085f4fb6e3a2b062a4b5ac43b93cbfd36a8dce6234b5d1e3e63499d936375687e83fc5da55628b22bd6b581b5ee167d1c4 + languageName: node + linkType: hard + +"hast-util-raw@npm:^7.0.0": + version: 7.2.3 + resolution: "hast-util-raw@npm:7.2.3" + dependencies: + "@types/hast": ^2.0.0 + "@types/parse5": ^6.0.0 + hast-util-from-parse5: ^7.0.0 + hast-util-to-parse5: ^7.0.0 + html-void-elements: ^2.0.0 + parse5: ^6.0.0 + unist-util-position: ^4.0.0 + unist-util-visit: ^4.0.0 + vfile: ^5.0.0 + web-namespaces: ^2.0.0 + zwitch: ^2.0.0 + checksum: 21857eea3ffb8fd92d2d9be7793b56d0b2c40db03c4cfa14828855ae41d7c584917aa83efb7157220b2e41e25e95f81f24679ac342c35145e5f1c1d39015f81f + languageName: node + linkType: hard + +"hast-util-sanitize@npm:^4.0.0": + version: 4.1.0 + resolution: "hast-util-sanitize@npm:4.1.0" + dependencies: + "@types/hast": ^2.0.0 + checksum: 4f1786d6556bae6485a657a3e77e7e71b573fd20e4e2d70678e0f445eb8fe3dc6c4441cda6d18b89a79b53e2c03b6232eb6c470ecd478737050724ea09398603 + languageName: node + linkType: hard + +"hast-util-to-html@npm:^8.0.0": + version: 8.0.4 + resolution: "hast-util-to-html@npm:8.0.4" + dependencies: + "@types/hast": ^2.0.0 + "@types/unist": ^2.0.0 + ccount: ^2.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-raw: ^7.0.0 + hast-util-whitespace: ^2.0.0 + html-void-elements: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + stringify-entities: ^4.0.0 + zwitch: ^2.0.4 + checksum: 8f2ae071df2ced5afb4f19f76af8fd3a2f837dc47bcc1c0e0c1578d29dafcd28738f9617505d13c4a2adf13d70e043143e2ad8f130d5554ab4fc11bfa8f74094 + languageName: node + linkType: hard + +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.2 + resolution: "hast-util-to-jsx-runtime@npm:2.3.2" + dependencies: + "@types/estree": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + comma-separated-tokens: ^2.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 + hast-util-whitespace: ^3.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + style-to-object: ^1.0.0 + unist-util-position: ^5.0.0 + vfile-message: ^4.0.0 + checksum: 223cc3e2ea622d14529e2aa070bd88f6ca7255084bd5e6e28015dad435cda22b1ddd98064bba6a4753d546d882dcd3f8067af1ea27c253986f6f303869544075 + languageName: node + linkType: hard + +"hast-util-to-parse5@npm:^7.0.0": + version: 7.1.0 + resolution: "hast-util-to-parse5@npm:7.1.0" + dependencies: + "@types/hast": ^2.0.0 + comma-separated-tokens: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + web-namespaces: ^2.0.0 + zwitch: ^2.0.0 + checksum: 3a7f2175a3db599bbae7e49ba73d3e5e688e5efca7590ff50130ba108ad649f728402815d47db49146f6b94c14c934bf119915da9f6964e38802c122bcc8af6b + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "hast-util-whitespace@npm:2.0.1" + checksum: 431be6b2f35472f951615540d7a53f69f39461e5e080c0190268bdeb2be9ab9b1dddfd1f467dd26c1de7e7952df67beb1307b6ee940baf78b24a71b5e0663868 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + "hastscript@npm:^6.0.0": version: 6.0.0 resolution: "hastscript@npm:6.0.0" @@ -27975,6 +30605,19 @@ __metadata: languageName: node linkType: hard +"hastscript@npm:^7.0.0": + version: 7.2.0 + resolution: "hastscript@npm:7.2.0" + dependencies: + "@types/hast": ^2.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-parse-selector: ^3.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + checksum: 928a21576ff7b9a8c945e7940bcbf2d27f770edb4279d4d04b33dc90753e26ca35c1172d626f54afebd377b2afa32331e399feb3eb0f7b91a399dca5927078ae + languageName: node + linkType: hard + "he@npm:1.2.0, he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -27984,6 +30627,26 @@ __metadata: languageName: node linkType: hard +"header-case@npm:^1.0.0": + version: 1.0.1 + resolution: "header-case@npm:1.0.1" + dependencies: + no-case: ^2.2.0 + upper-case: ^1.1.3 + checksum: fe1cc9a555ec9aabc2de80f4dd961a81c534fc23951694fef34297e59b0dd60f26647148731bf0dd3fdb3a1c688089d3cd147d7038db850e25be7c0a5fabb022 + languageName: node + linkType: hard + +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: ^1.0.4 + tslib: ^2.0.3 + checksum: 571c83eeb25e8130d172218712f807c0b96d62b020981400bccc1503a7cf14b09b8b10498a962d2739eccf231d950e3848ba7d420b58a6acd2f9283439546cd9 + languageName: node + linkType: hard + "headers-polyfill@npm:^3.0.4": version: 3.0.7 resolution: "headers-polyfill@npm:3.0.7" @@ -28144,6 +30807,20 @@ __metadata: languageName: node linkType: hard +"html-url-attributes@npm:^3.0.0": + version: 3.0.1 + resolution: "html-url-attributes@npm:3.0.1" + checksum: 1ecbf9cae0c438d2802386710177b7bbf7e30cc61327e9f125eb32fca7302cd1e3ab45c441859cb1e7646109be322fc1163592ad4dfde9b14d09416d101a6573 + languageName: node + linkType: hard + +"html-void-elements@npm:^2.0.0": + version: 2.0.1 + resolution: "html-void-elements@npm:2.0.1" + checksum: 06d41f13b9d5d6e0f39861c4bec9a9196fa4906d56cd5cf6cf54ad2e52a85bf960cca2bf9600026bde16c8331db171bedba5e5a35e2e43630c8f1d497b2fb658 + languageName: node + linkType: hard + "html-webpack-plugin@npm:^5.5.0": version: 5.5.4 resolution: "html-webpack-plugin@npm:5.5.4" @@ -28183,7 +30860,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 @@ -28337,6 +31014,16 @@ __metadata: languageName: node linkType: hard +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: ^5.1.1 + resolve-alpn: ^1.0.0 + checksum: 74160b862ec699e3f859739101ff592d52ce1cb207b7950295bf7962e4aa1597ef709b4292c673bece9c9b300efad0559fc86c71b1409c7a1e02b7229456003e + languageName: node + linkType: hard + "http2-wrapper@npm:^2.1.10": version: 2.2.1 resolution: "http2-wrapper@npm:2.2.1" @@ -28594,6 +31281,28 @@ __metadata: languageName: node linkType: hard +"iframe-resizer-react@npm:^1.1.0": + version: 1.1.1 + resolution: "iframe-resizer-react@npm:1.1.1" + dependencies: + "@babel/plugin-proposal-private-property-in-object": ^7.21.11 + iframe-resizer: ^4.4.4 + warning: ^4.0.3 + peerDependencies: + prop-types: ^15.7.2 + react: ^16.13.1 || ^18.0.0 + react-dom: ^16.13.1 || ^18.0.0 + checksum: fd3db2dfd3e1455e2f8150ed0cc4068b8970389eca478d13e2ca6408e1fe0a85425f7b2cb004e5a8eee0e7d4ed304774cad4b1b40d9a7467c2db3ed4851c4572 + languageName: node + linkType: hard + +"iframe-resizer@npm:^4.4.4": + version: 4.4.5 + resolution: "iframe-resizer@npm:4.4.5" + checksum: fa2493daba2df7578866aeb5fceabcf2129da9327abd7d26b4f16e9e7109eddcb97a8ba7ea6e94b043705f13bcbe6ae307e67cc48c24f7bd9d948d491a150163 + languageName: node + linkType: hard + "ignore-walk@npm:^5.0.1": version: 5.0.1 resolution: "ignore-walk@npm:5.0.1" @@ -28637,6 +31346,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^10.0.3": + version: 10.1.1 + resolution: "immer@npm:10.1.1" + checksum: 07c67970b7d22aded73607193d84861bf786f07d47f7d7c98bb10016c7a88f6654ad78ae1e220b3c623695b133aabbf24f5eb8d9e8060cff11e89ccd81c9c10b + languageName: node + linkType: hard + "immutable@npm:^3.8.2, immutable@npm:^3.x.x": version: 3.8.2 resolution: "immutable@npm:3.8.2" @@ -28644,6 +31360,13 @@ __metadata: languageName: node linkType: hard +"immutable@npm:~3.7.6": + version: 3.7.6 + resolution: "immutable@npm:3.7.6" + checksum: 8cccfb22d3ecf14fe0c474612e96d6bb5d117493e7639fe6642fb81e78c9ac4b698dd8a322c105001a709ad873ffc90e30bad7db5d9a3ef0b54a6e1db0258e8e + languageName: node + linkType: hard + "import-fresh@npm:^3.0.0, import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -28654,6 +31377,13 @@ __metadata: languageName: node linkType: hard +"import-from@npm:4.0.0": + version: 4.0.0 + resolution: "import-from@npm:4.0.0" + checksum: 1fa29c05b048da18914e91d9a529e5d9b91774bebbfab10e53f59bcc1667917672b971cf102fee857f142e5e433ce69fa1f0a596e1c7d82f9947a5ec352694b9 + languageName: node + linkType: hard + "import-in-the-middle@npm:1.4.2": version: 1.4.2 resolution: "import-in-the-middle@npm:1.4.2" @@ -28749,6 +31479,13 @@ __metadata: languageName: node linkType: hard +"inflected@npm:^1.1.7": + version: 1.1.7 + resolution: "inflected@npm:1.1.7" + checksum: eac857f10871586006d629dc72bab05058f1967c10fae9d7391d627534566a8e5be0a1f690d2590102d890e4ef5647b73b7827380dc7bcf50faac4154f0ab084 + languageName: node + linkType: hard + "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -28844,6 +31581,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.2.4": + version: 0.2.4 + resolution: "inline-style-parser@npm:0.2.4" + checksum: 5df20a21dd8d67104faaae29774bb50dc9690c75bc5c45dac107559670a5530104ead72c4cf54f390026e617e7014c65b3d68fb0bb573a37c4d1f94e9c36e1ca + languageName: node + linkType: hard + "inline-style-prefixer@npm:^7.0.0": version: 7.0.0 resolution: "inline-style-prefixer@npm:7.0.0" @@ -28885,7 +31629,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:8.2.6": +"inquirer@npm:8.2.6, inquirer@npm:^8.0.0": version: 8.2.6 resolution: "inquirer@npm:8.2.6" dependencies: @@ -29045,6 +31789,13 @@ __metadata: languageName: node linkType: hard +"invert-kv@npm:^1.0.0": + version: 1.0.0 + resolution: "invert-kv@npm:1.0.0" + checksum: aebeee31dda3b3d25ffd242e9a050926e7fe5df642d60953ab183aca1a7d1ffb39922eb2618affb0e850cf2923116f0da1345367759d88d097df5da1f1e1590e + languageName: node + linkType: hard + "ioredis@npm:^5.3.2": version: 5.3.2 resolution: "ioredis@npm:5.3.2" @@ -29093,6 +31844,16 @@ __metadata: languageName: node linkType: hard +"is-absolute@npm:^1.0.0": + version: 1.0.0 + resolution: "is-absolute@npm:1.0.0" + dependencies: + is-relative: ^1.0.0 + is-windows: ^1.0.1 + checksum: 9d16b2605eda3f3ce755410f1d423e327ad3a898bcb86c9354cf63970ed3f91ba85e9828aa56f5d6a952b9fae43d0477770f78d37409ae8ecc31e59ebc279b27 + languageName: node + linkType: hard + "is-alphabetical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphabetical@npm:1.0.4" @@ -29100,6 +31861,13 @@ __metadata: languageName: node linkType: hard +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 56207db8d9de0850f0cd30f4966bf731eb82cedfe496cbc2e97e7c3bacaf66fc54a972d2d08c0d93bb679cb84976a05d24c5ad63de56fabbfc60aadae312edaa + languageName: node + linkType: hard + "is-alphanumerical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphanumerical@npm:1.0.4" @@ -29110,6 +31878,16 @@ __metadata: languageName: node linkType: hard +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: ^2.0.0 + is-decimal: ^2.0.0 + checksum: 87acc068008d4c9c4e9f5bd5e251041d42e7a50995c77b1499cf6ed248f971aadeddb11f239cabf09f7975ee58cac7a48ffc170b7890076d8d227b24a68663c9 + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -29314,6 +32092,13 @@ __metadata: languageName: node linkType: hard +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 97132de7acdce77caa7b797632970a2ecd649a88e715db0e4dbc00ab0708b5e7574ba5903962c860cd4894a14fd12b100c0c4ac8aed445cf6f55c6cf747a4158 + languageName: node + linkType: hard + "is-deflate@npm:^1.0.0": version: 1.0.0 resolution: "is-deflate@npm:1.0.0" @@ -29409,7 +32194,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": +"is-glob@npm:4.0.3, is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -29432,6 +32217,13 @@ __metadata: languageName: node linkType: hard +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 66a2ea85994c622858f063f23eda506db29d92b52580709eb6f4c19550552d4dcf3fb81952e52f7cf972097237959e00adc7bb8c9400cd12886e15bf06145321 + languageName: node + linkType: hard + "is-in-ci@npm:^0.1.0": version: 0.1.0 resolution: "is-in-ci@npm:0.1.0" @@ -29492,6 +32284,15 @@ __metadata: languageName: node linkType: hard +"is-lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "is-lower-case@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: ba57dd1201e15fd9b590654736afccf1b3b68e919f40c23ef13b00ebcc639b1d9c2f81fe86415bff3e8eccffec459786c9ac9dc8f3a19cfa4484206c411c1d7d + languageName: node + linkType: hard + "is-map@npm:^2.0.2": version: 2.0.2 resolution: "is-map@npm:2.0.2" @@ -29630,7 +32431,7 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^4.1.0": +"is-plain-obj@npm:^4.0.0, is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" checksum: 6dc45da70d04a81f35c9310971e78a6a3c7a63547ef782e3a07ee3674695081b6ca4e977fbb8efc48dae3375e0b34558d2bcd722aec9bddfa2d7db5b041be8ce @@ -29693,6 +32494,15 @@ __metadata: languageName: node linkType: hard +"is-relative@npm:^1.0.0": + version: 1.0.0 + resolution: "is-relative@npm:1.0.0" + dependencies: + is-unc-path: ^1.0.0 + checksum: 3271a0df109302ef5e14a29dcd5d23d9788e15ade91a40b942b035827ffbb59f7ce9ff82d036ea798541a52913cbf9d2d0b66456340887b51f3542d57b5a4c05 + languageName: node + linkType: hard + "is-retry-allowed@npm:^1.1.0": version: 1.2.0 resolution: "is-retry-allowed@npm:1.2.0" @@ -29748,6 +32558,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^1.0.1": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 063c6bec9d5647aa6d42108d4c59723d2bd4ae42135a2d4db6eadbd49b7ea05b750fd69d279e5c7c45cf9da753ad2c00d8978be354d65aa9f6bb434969c6a2ae + languageName: node + linkType: hard + "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -29821,6 +32638,15 @@ __metadata: languageName: node linkType: hard +"is-unc-path@npm:^1.0.0": + version: 1.0.0 + resolution: "is-unc-path@npm:1.0.0" + dependencies: + unc-path-regex: ^0.1.2 + checksum: e8abfde203f7409f5b03a5f1f8636e3a41e78b983702ef49d9343eb608cdfe691429398e8815157519b987b739bcfbc73ae7cf4c8582b0ab66add5171088eab6 + languageName: node + linkType: hard + "is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" @@ -29851,6 +32677,22 @@ __metadata: languageName: node linkType: hard +"is-upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "is-upper-case@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: cf4fd43c00c2e72cd5cff911923070b89f0933b464941bd782e2315385f80b5a5acd772db3b796542e5e3cfed735f4dffd88c54d62db1ebfc5c3daa7b1af2bc6 + languageName: node + linkType: hard + +"is-utf8@npm:^0.2.0": + version: 0.2.1 + resolution: "is-utf8@npm:0.2.1" + checksum: 167ccd2be869fc228cc62c1a28df4b78c6b5485d15a29027d3b5dceb09b383e86a3522008b56dcac14b592b22f0a224388718c2505027a994fd8471465de54b3 + languageName: node + linkType: hard + "is-weakmap@npm:^2.0.2": version: 2.0.2 resolution: "is-weakmap@npm:2.0.2" @@ -29891,7 +32733,7 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:1.0.2, is-windows@npm:^1.0.0": +"is-windows@npm:1.0.2, is-windows@npm:^1.0.0, is-windows@npm:^1.0.1": version: 1.0.2 resolution: "is-windows@npm:1.0.2" checksum: 438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 @@ -29986,6 +32828,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -30081,6 +32932,13 @@ __metadata: languageName: node linkType: hard +"iterall@npm:1.0.2": + version: 1.0.2 + resolution: "iterall@npm:1.0.2" + checksum: f87cbb8e22e3681f4d7ba634e4ce7bd419a78b47ff7b01f57f90cf761d187cb845fcac0d4d7e4c7dfcbfb80467c8ea42a9f3691bb3aa909e3e3707a25735ed70 + languageName: node + linkType: hard + "iterare@npm:1.2.1": version: 1.2.1 resolution: "iterare@npm:1.2.1" @@ -30726,6 +33584,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^1.17.1, jiti@npm:^1.21.6": + version: 1.21.7 + resolution: "jiti@npm:1.21.7" + bin: + jiti: bin/jiti.js + checksum: 9cd20dabf82e3a4cceecb746a69381da7acda93d34eed0cdb9c9bdff3bce07e4f2f4a016ca89924392c935297d9aedc58ff9f7d3281bc5293319ad244926e0b7 + languageName: node + linkType: hard + "jiti@npm:^1.18.2": version: 1.20.0 resolution: "jiti@npm:1.20.0" @@ -30751,6 +33618,19 @@ __metadata: languageName: node linkType: hard +"joi@npm:^17.11.0": + version: 17.13.3 + resolution: "joi@npm:17.13.3" + dependencies: + "@hapi/hoek": ^9.3.0 + "@hapi/topo": ^5.1.0 + "@sideway/address": ^4.1.5 + "@sideway/formula": ^3.0.1 + "@sideway/pinpoint": ^2.0.0 + checksum: 66ed454fee3d8e8da1ce21657fd2c7d565d98f3e539d2c5c028767e5f38cbd6297ce54df8312d1d094e62eb38f9452ebb43da4ce87321df66cf5e3f128cbc400 + languageName: node + linkType: hard + "jose@npm:5.2.1": version: 5.2.1 resolution: "jose@npm:5.2.1" @@ -30779,6 +33659,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.0.0": + version: 5.9.6 + resolution: "jose@npm:5.9.6" + checksum: 4b536da0201858ed4c4582e8bb479081f11e0c63dd0f5e473adde16fc539785e1f2f0409bc1fc7cbbb5b68026776c960b4952da3a06f6fdfff0b9764c9127ae0 + languageName: node + linkType: hard + "jpeg-js@npm:0.4.2": version: 0.4.2 resolution: "jpeg-js@npm:0.4.2" @@ -30828,7 +33715,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.0, js-yaml@npm:=4.1.0, js-yaml@npm:^4.1.0": +"js-yaml@npm:4.1.0, js-yaml@npm:=4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -30986,6 +33873,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 19c94095ea026725540c0d29da33ab03144f6bcf2d4159e4833d534976e99e0c09c38cefa9a575279a51fc36b31166f8d6d05c9fe2645d5f15851d690b41f17f + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -31108,6 +34004,16 @@ __metadata: languageName: node linkType: hard +"json-to-pretty-yaml@npm:^1.2.2": + version: 1.2.2 + resolution: "json-to-pretty-yaml@npm:1.2.2" + dependencies: + remedial: ^1.0.7 + remove-trailing-spaces: ^1.0.6 + checksum: 4b78480f426e176e5fdac073e05877683bb026f1175deb52d0941b992f9c91a58a812c020f00aa67ba1fc7cadb220539a264146f222e48a48c8bb2a0931cac9b + languageName: node + linkType: hard + "json5@npm:^1.0.1, json5@npm:^1.0.2": version: 1.0.2 resolution: "json5@npm:1.0.2" @@ -31163,6 +34069,19 @@ __metadata: languageName: node linkType: hard +"jsonfile@npm:^5.0.0": + version: 5.0.0 + resolution: "jsonfile@npm:5.0.0" + dependencies: + graceful-fs: ^4.1.6 + universalify: ^0.1.2 + dependenciesMeta: + graceful-fs: + optional: true + checksum: e0ecff572dba34153a66e3a3bc5c6cb01a2c1d2cf4a2c19b6728dcfcab39d94be9cca4a0fc86a17ff2c815f2aeb43768ac75545780dbea4009433fdc32aa14d1 + languageName: node + linkType: hard + "jsonfile@npm:^6.0.1": version: 6.1.0 resolution: "jsonfile@npm:6.1.0" @@ -31336,6 +34255,13 @@ __metadata: languageName: node linkType: hard +"keen-slider@npm:^6.8.0": + version: 6.8.6 + resolution: "keen-slider@npm:6.8.6" + checksum: f87e65d72e3b2e73cbd52b1908c1458b253ec5a2a2d3e1e34bd1a831172d18568649188cf3e4ad679c7988568929195ae91d4b7a1c0bd3d6da592a453be3723a + languageName: node + linkType: hard + "keypress@npm:~0.2.1": version: 0.2.1 resolution: "keypress@npm:0.2.1" @@ -31359,7 +34285,7 @@ __metadata: languageName: node linkType: hard -"kleur@npm:4.1.5, kleur@npm:^4.1.5": +"kleur@npm:4.1.5, kleur@npm:^4.0.3, kleur@npm:^4.1.5": version: 4.1.5 resolution: "kleur@npm:4.1.5" checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 @@ -31462,6 +34388,15 @@ __metadata: languageName: node linkType: hard +"lcid@npm:^1.0.0": + version: 1.0.0 + resolution: "lcid@npm:1.0.0" + dependencies: + invert-kv: ^1.0.0 + checksum: e8c7a4db07663068c5c44b650938a2bc41aa992037eebb69376214320f202c1250e70b50c32f939e28345fd30c2d35b8e8cd9a19d5932c398246a864ce54843d + languageName: node + linkType: hard + "level-blobs@npm:^0.1.7": version: 0.1.7 resolution: "level-blobs@npm:0.1.7" @@ -31748,6 +34683,19 @@ __metadata: languageName: node linkType: hard +"load-json-file@npm:^1.0.0": + version: 1.1.0 + resolution: "load-json-file@npm:1.1.0" + dependencies: + graceful-fs: ^4.1.2 + parse-json: ^2.2.0 + pify: ^2.0.0 + pinkie-promise: ^2.0.0 + strip-bom: ^2.0.0 + checksum: 0e4e4f380d897e13aa236246a917527ea5a14e4fc34d49e01ce4e7e2a1e08e2740ee463a03fb021c04f594f29a178f4adb994087549d7c1c5315fcd29bf9934b + languageName: node + linkType: hard + "load-json-file@npm:^4.0.0": version: 4.0.0 resolution: "load-json-file@npm:4.0.0" @@ -31875,6 +34823,13 @@ __metadata: languageName: node linkType: hard +"lodash.assign@npm:^4.1.0, lodash.assign@npm:^4.2.0": + version: 4.2.0 + resolution: "lodash.assign@npm:4.2.0" + checksum: 75bbc6733c9f577c448031b4051f990f068802708891f94be9d4c2faffd6a9ec67a2c49671dafc908a068d35687765464853282842b4560b662e6c903d11cc90 + languageName: node + linkType: hard + "lodash.camelcase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.camelcase@npm:4.3.0" @@ -32036,6 +34991,13 @@ __metadata: languageName: node linkType: hard +"lodash.sortby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.sortby@npm:4.7.0" + checksum: db170c9396d29d11fe9a9f25668c4993e0c1331bcb941ddbd48fb76f492e732add7f2a47cfdf8e9d740fa59ac41bbfaf931d268bc72aab3ab49e9f89354d718c + languageName: node + linkType: hard + "lodash.startcase@npm:^4.4.0": version: 4.4.0 resolution: "lodash.startcase@npm:4.4.0" @@ -32071,14 +35033,14 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.15": +"lodash@npm:4.17.21, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 languageName: node linkType: hard -"log-symbols@npm:4.1.0, log-symbols@npm:^4.1.0": +"log-symbols@npm:4.1.0, log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -32159,6 +35121,13 @@ __metadata: languageName: node linkType: hard +"longest-streak@npm:^3.0.0": + version: 3.1.0 + resolution: "longest-streak@npm:3.1.0" + checksum: d7f952ed004cbdb5c8bcfc4f7f5c3d65449e6c5a9e9be4505a656e3df5a57ee125f284286b4bf8ecea0c21a7b3bf2b8f9001ad506c319b9815ad6a63a47d0fd0 + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -32214,6 +35183,15 @@ __metadata: languageName: node linkType: hard +"lower-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case-first@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: 33e3da1098ddda219ce125d4ab7a78a944972c0ee8872e95b6ccc35df8ad405284ab233b0ba4d72315ad1a06fe2f0d418ee4cba9ec1ef1c386dea78899fc8958 + languageName: node + linkType: hard + "lower-case@npm:^1.1.0, lower-case@npm:^1.1.1, lower-case@npm:^1.1.2": version: 1.1.4 resolution: "lower-case@npm:1.1.4" @@ -32230,6 +35208,13 @@ __metadata: languageName: node linkType: hard +"lowercase-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "lowercase-keys@npm:2.0.0" + checksum: 24d7ebd56ccdf15ff529ca9e08863f3c54b0b9d1edb97a3ae1af34940ae666c01a1e6d200707bce730a8ef76cb57cc10e65f245ecaaf7e6bc8639f2fb460ac23 + languageName: node + linkType: hard + "lowercase-keys@npm:^3.0.0": version: 3.0.0 resolution: "lowercase-keys@npm:3.0.0" @@ -32344,6 +35329,15 @@ __metadata: languageName: node linkType: hard +"lucide-react@npm:^0.171.0": + version: 0.171.0 + resolution: "lucide-react@npm:0.171.0" + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + checksum: 768ffe368c52a518ee339203d86ff4479989ab4d79c0716f721900c4bb7392ef6ff7a14807f6a685abd74d27f4c1778170bff77a0ab4c3e06c17944b557d8300 + languageName: node + linkType: hard + "lucide-static@npm:^0.424.0": version: 0.424.0 resolution: "lucide-static@npm:0.424.0" @@ -32574,6 +35568,13 @@ __metadata: languageName: node linkType: hard +"map-cache@npm:^0.2.0": + version: 0.2.2 + resolution: "map-cache@npm:0.2.2" + checksum: 3067cea54285c43848bb4539f978a15dedc63c03022abeec6ef05c8cb6829f920f13b94bcaf04142fc6a088318e564c4785704072910d120d55dbc2e0c421969 + languageName: node + linkType: hard + "map-obj@npm:^1.0.0": version: 1.0.1 resolution: "map-obj@npm:1.0.1" @@ -32678,6 +35679,192 @@ __metadata: languageName: node linkType: hard +"mdast-util-definitions@npm:^5.0.0": + version: 5.1.2 + resolution: "mdast-util-definitions@npm:5.1.2" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + unist-util-visit: ^4.0.0 + checksum: 2544daccab744ea1ede76045c2577ae4f1cc1b9eb1ea51ab273fe1dca8db5a8d6f50f87759c0ce6484975914b144b7f40316f805cb9c86223a78db8de0b77bae + languageName: node + linkType: hard + +"mdast-util-from-markdown@npm:^1.0.0": + version: 1.3.1 + resolution: "mdast-util-from-markdown@npm:1.3.1" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + decode-named-character-reference: ^1.0.0 + mdast-util-to-string: ^3.1.0 + micromark: ^3.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-decode-string: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + unist-util-stringify-position: ^3.0.0 + uvu: ^0.5.0 + checksum: c2fac225167e248d394332a4ea39596e04cbde07d8cdb3889e91e48972c4c3462a02b39fda3855345d90231eb17a90ac6e082fb4f012a77c1d0ddfb9c7446940 + languageName: node + linkType: hard + +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.2 + resolution: "mdast-util-from-markdown@npm:2.0.2" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + mdast-util-to-string: ^4.0.0 + micromark: ^4.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 1ad19f48b30ac6e0cb756070c210c78ad93c26876edfb3f75127783bc6df8b9402016d8f3e9964f3d1d5430503138ec65c145e869438727e1aa7f3cebf228fba + languageName: node + linkType: hard + +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdx-expression@npm:2.0.1" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 6af56b06bde3ab971129db9855dcf0d31806c70b3b052d7a90a5499a366b57ffd0c2efca67d281c448c557298ba7e3e61bd07133733b735440840dd339b28e19 + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + ccount: ^2.0.0 + devlop: ^1.1.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + parse-entities: ^4.0.0 + stringify-entities: ^4.0.0 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 224f5f6ad247f0f2622ee36c82ac7a4c6a60c31850de4056bf95f531bd2f7ec8943ef34dfe8a8375851f65c07e4913c4f33045d703df4ff4d11b2de5a088f7f9 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 1f9dad04d31d59005332e9157ea9510dc1d03092aadbc607a10475c7eec1c158b475aa0601a3a4f74e13097ca735deb8c2d9d37928ddef25d3029fd7c9e14dc3 + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^3.0.0": + version: 3.0.1 + resolution: "mdast-util-phrasing@npm:3.0.1" + dependencies: + "@types/mdast": ^3.0.0 + unist-util-is: ^5.0.0 + checksum: c5b616d9b1eb76a6b351d195d94318494722525a12a89d9c8a3b091af7db3dd1fc55d294f9d29266d8159a8267b0df4a7a133bda8a3909d5331c383e1e1ff328 + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" + dependencies: + "@types/mdast": ^4.0.0 + unist-util-is: ^6.0.0 + checksum: 3a97533e8ad104a422f8bebb34b3dde4f17167b8ed3a721cf9263c7416bd3447d2364e6d012a594aada40cac9e949db28a060bb71a982231693609034ed5324e + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^11.0.0": + version: 11.3.0 + resolution: "mdast-util-to-hast@npm:11.3.0" + dependencies: + "@types/hast": ^2.0.0 + "@types/mdast": ^3.0.0 + "@types/mdurl": ^1.0.0 + mdast-util-definitions: ^5.0.0 + mdurl: ^1.0.0 + unist-builder: ^3.0.0 + unist-util-generated: ^2.0.0 + unist-util-position: ^4.0.0 + unist-util-visit: ^4.0.0 + checksum: a968d034613aa5cfb44b9c03d8e61a08bb563bfde3a233fb3d83a28857357e2beef56b6767bab2867d3c3796dc5dd796af4d03fb83e3133aeb7f4187b5cc9327 + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.0 + resolution: "mdast-util-to-hast@npm:13.2.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 + trim-lines: ^3.0.0 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 7e5231ff3d4e35e1421908437577fd5098141f64918ff5cc8a0f7a8a76c5407f7a3ee88d75f7a1f7afb763989c9f357475fa0ba8296c00aaff1e940098fe86a6 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^1.0.0": + version: 1.5.0 + resolution: "mdast-util-to-markdown@npm:1.5.0" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + longest-streak: ^3.0.0 + mdast-util-phrasing: ^3.0.0 + mdast-util-to-string: ^3.0.0 + micromark-util-decode-string: ^1.0.0 + unist-util-visit: ^4.0.0 + zwitch: ^2.0.0 + checksum: 64338eb33e49bb0aea417591fd986f72fdd39205052563bb7ce9eb9ecc160824509bfacd740086a05af355c6d5c36353aafe95cab9e6927d674478757cee6259 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^2.0.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + longest-streak: ^3.0.0 + mdast-util-phrasing: ^4.0.0 + mdast-util-to-string: ^4.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + unist-util-visit: ^5.0.0 + zwitch: ^2.0.0 + checksum: 288d152bd50c00632e6e01c610bb904a220d1e226c8086c40627877959746f83ab0b872f4150cb7d910198953b1bf756e384ac3fee3e7b0ddb4517f9084c5803 + languageName: node + linkType: hard + "mdast-util-to-string@npm:^1.0.0": version: 1.1.0 resolution: "mdast-util-to-string@npm:1.1.0" @@ -32685,6 +35872,24 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": + version: 3.2.0 + resolution: "mdast-util-to-string@npm:3.2.0" + dependencies: + "@types/mdast": ^3.0.0 + checksum: dc40b544d54339878ae2c9f2b3198c029e1e07291d2126bd00ca28272ee6616d0d2194eb1c9828a7c34d412a79a7e73b26512a734698d891c710a1e73db1e848 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": ^4.0.0 + checksum: 35489fb5710d58cbc2d6c8b6547df161a3f81e0f28f320dfb3548a9393555daf07c310c0c497708e67ed4dfea4a06e5655799e7d631ca91420c288b4525d6c29 + languageName: node + linkType: hard + "mdn-data@npm:2.0.14": version: 2.0.14 resolution: "mdn-data@npm:2.0.14" @@ -32692,7 +35897,7 @@ __metadata: languageName: node linkType: hard -"mdurl@npm:^1.0.1": +"mdurl@npm:^1.0.0, mdurl@npm:^1.0.1": version: 1.0.1 resolution: "mdurl@npm:1.0.1" checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b @@ -32826,6 +36031,18 @@ __metadata: languageName: node linkType: hard +"meros@npm:^1.2.1": + version: 1.3.0 + resolution: "meros@npm:1.3.0" + peerDependencies: + "@types/node": ">=13" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: ea86c83fe9357d3eb2f5bad20909e12642c7bc8c10340d9bd0968b48f69ec453de14f7e5032d138ad04cb10d79b8c9fb3c9601bb515e8fbdf9bec4eed62994ad + languageName: node + linkType: hard + "methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -32846,6 +36063,478 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^1.0.1": + version: 1.1.0 + resolution: "micromark-core-commonmark@npm:1.1.0" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-factory-destination: ^1.0.0 + micromark-factory-label: ^1.0.0 + micromark-factory-space: ^1.0.0 + micromark-factory-title: ^1.0.0 + micromark-factory-whitespace: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-chunked: ^1.0.0 + micromark-util-classify-character: ^1.0.0 + micromark-util-html-tag-name: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-resolve-all: ^1.0.0 + micromark-util-subtokenize: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.1 + uvu: ^0.5.0 + checksum: c6dfedc95889cc73411cb222fc2330b9eda6d849c09c9fd9eb3cd3398af246167e9d3cdb0ae3ce9ae59dd34a14624c8330e380255d41279ad7350cf6c6be6c5b + languageName: node + linkType: hard + +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-core-commonmark@npm:2.0.2" + dependencies: + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-factory-destination: ^2.0.0 + micromark-factory-label: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-factory-title: ^2.0.0 + micromark-factory-whitespace: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-html-tag-name: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e49d78429baf72533a02d06ae83e5a24d4d547bc832173547ffbae93c0960a7dbf0d8896058301498fa4297f280070a5a66891e0e6160040d6c5ef9bc5d9cd51 + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-destination@npm:1.1.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 9c4baa9ca2ed43c061bbf40ddd3d85154c2a0f1f485de9dea41d7dd2ad994ebb02034a003b2c1dbe228ba83a0576d591f0e90e0bf978713f84ee7d7f3aa98320 + languageName: node + linkType: hard + +"micromark-factory-label@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-label@npm:1.1.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 + languageName: node + linkType: hard + +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: bd03f5a75f27cdbf03b894ddc5c4480fc0763061fecf9eb927d6429233c930394f223969a99472df142d570c831236134de3dc23245d23d9f046f9d0b623b5c2 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-space@npm:1.1.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 1bd68a017c1a66f4787506660c1e1c5019169aac3b1cb075d49ac5e360e0b2065e984d4e1d6e9e52a9d44000f2fa1c98e66a743d7aae78b4b05616bf3242ed71 + languageName: node + linkType: hard + +"micromark-factory-title@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-title@npm:1.1.0" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 + languageName: node + linkType: hard + +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: b4d2e4850a8ba0dff25ce54e55a3eb0d43dda88a16293f53953153288f9d84bcdfa8ca4606b2cfbb4f132ea79587bbb478a73092a349f893f5264fbcdbce2ee1 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-whitespace@npm:1.1.0" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 67b3944d012a42fee9e10e99178254a04d48af762b54c10a50fcab988688799993efb038daf9f5dbc04001a97b9c1b673fc6f00e6a56997877ab25449f0c8650 + languageName: node + linkType: hard + +"micromark-util-character@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-character@npm:1.2.0" + dependencies: + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 089e79162a19b4a28731736246579ab7e9482ac93cd681c2bfca9983dcff659212ef158a66a5957e9d4b1dba957d1b87b565d85418a5b009f0294f1f07f2aaac + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-chunked@npm:1.1.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: f8cb2a67bcefe4bd2846d838c97b777101f0043b9f1de4f69baf3e26bb1f9885948444e3c3aec66db7595cad8173bd4567a000eb933576c233d54631f6323fe4 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-classify-character@npm:1.1.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 4d8bbe3a6dbf69ac0fc43516866b5bab019fe3f4568edc525d4feaaaf78423fa54e6b6732b5bccbeed924455279a3758ffc9556954aafb903982598a95a02704 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-combine-extensions@npm:1.1.0" + dependencies: + micromark-util-chunked: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 5d22fb9ee37e8143adfe128a72b50fa09568c2cc553b3c76160486c96dbbb298c5802a177a10a215144a604b381796071b5d35be1f2c2b2ee17995eda92f0c8e + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: 4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: ee11c8bde51e250e302050474c4a2adca094bca05c69f6cdd241af12df285c48c88d19ee6e022b9728281c280be16328904adca994605680c43af56019f4b0b6 + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-string@npm:1.1.0" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-symbol: ^1.0.0 + checksum: f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: e9546ae53f9b5a4f9aa6aaf3e750087100d3429485ca80dbacec99ff2bb15a406fa7d93784a0fc2fe05ad7296b9295e75160ef71faec9e90110b7be2ae66241a + languageName: node + linkType: hard + +"micromark-util-encode@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-encode@npm:1.1.0" + checksum: 4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-html-tag-name@npm:1.2.0" + checksum: ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: dea365f5ad28ad74ff29fcb581f7b74fc1f80271c5141b3b2bc91c454cbb6dfca753f28ae03730d657874fcbd89d0494d0e3965dfdca06d9855f467c576afa9d + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-normalize-identifier@npm:1.1.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: 8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: 1eb9a289d7da067323df9fdc78bfa90ca3207ad8fd893ca02f3133e973adcb3743b233393d23d95c84ccaf5d220ae7f5a28402a644f135dcd4b8cfa60a7b5f84 + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-resolve-all@npm:1.1.0" + dependencies: + micromark-util-types: ^1.0.0 + checksum: 1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: ^2.0.0 + checksum: 9275f3ddb6c26f254dd2158e66215d050454b279707a7d9ce5a3cd0eba23201021cedcb78ae1a746c1b23227dcc418ee40dd074ade195359506797a5493550cc + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-sanitize-uri@npm:1.2.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-encode: ^1.0.0 + micromark-util-symbol: ^1.0.0 + checksum: 6663f365c4fe3961d622a580f4a61e34867450697f6806f027f21cf63c92989494895fcebe2345d52e249fe58a35be56e223a9776d084c9287818b40c779acc1 + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-subtokenize@npm:1.1.0" + dependencies: + micromark-util-chunked: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: 4a9d780c4d62910e196ea4fd886dc4079d8e424e5d625c0820016da0ed399a281daff39c50f9288045cc4bcd90ab47647e5396aba500f0853105d70dc8b1fc45 + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.0.4 + resolution: "micromark-util-subtokenize@npm:2.0.4" + dependencies: + devlop: ^1.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: a084e626773f0750747770226c017a2d31f30fa5bd7c7e9df62755681395fea7d8693eeb81b5f41a15a05db941f2491c37af5c23f05e50d2d9777e4036a2b6bb + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-symbol@npm:1.1.0" + checksum: 02414a753b79f67ff3276b517eeac87913aea6c028f3e668a19ea0fc09d98aea9f93d6222a76ca783d20299af9e4b8e7c797fe516b766185dcc6e93290f11f88 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 + languageName: node + linkType: hard + +"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": + version: 1.1.0 + resolution: "micromark-util-types@npm:1.1.0" + checksum: b0ef2b4b9589f15aec2666690477a6a185536927ceb7aa55a0f46475852e012d75a1ab945187e5c7841969a842892164b15d58ff8316b8e0d6cc920cabd5ede7 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-types@npm:2.0.1" + checksum: 630aac466628a360962f478f69421599c53ff8b3080765201b7be3b3a4be7f4c5b73632b9a6dd426b9e06035353c18acccee637d6c43d9b0bf1c31111bbb88a7 + languageName: node + linkType: hard + +"micromark@npm:^3.0.0": + version: 3.2.0 + resolution: "micromark@npm:3.2.0" + dependencies: + "@types/debug": ^4.0.0 + debug: ^4.0.0 + decode-named-character-reference: ^1.0.0 + micromark-core-commonmark: ^1.0.1 + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-chunked: ^1.0.0 + micromark-util-combine-extensions: ^1.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-encode: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-resolve-all: ^1.0.0 + micromark-util-sanitize-uri: ^1.0.0 + micromark-util-subtokenize: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.1 + uvu: ^0.5.0 + checksum: 56c15851ad3eb8301aede65603473443e50c92a54849cac1dadd57e4ec33ab03a0a77f3df03de47133e6e8f695dae83b759b514586193269e98c0bf319ecd5e4 + languageName: node + linkType: hard + +"micromark@npm:^4.0.0": + version: 4.0.1 + resolution: "micromark@npm:4.0.1" + dependencies: + "@types/debug": ^4.0.0 + debug: ^4.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 83ea084e8bf84442cc70c1207e916df11f0fde0ebd9daf978c895a1466c47a1dd4ed42b21b6e65bcc0d268fcbec24b4b1b28bc59c548940fe690929b8e0e7732 + languageName: node + linkType: hard + "micromatch@npm:^4.0.0, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": version: 4.0.5 resolution: "micromatch@npm:4.0.5" @@ -32923,6 +36612,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^1.0.0": + version: 1.0.1 + resolution: "mimic-response@npm:1.0.1" + checksum: 034c78753b0e622bc03c983663b1cdf66d03861050e0c8606563d149bc2b02d63f62ce4d32be4ab50d0553ae0ffe647fc34d1f5281184c6e1e8cf4d85e8d9823 + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -33012,7 +36708,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -33032,7 +36728,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.1.0, minimist@npm:^1.2.3": +"minimist@npm:^1.1.0, minimist@npm:^1.2.3, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 @@ -33398,7 +37094,7 @@ __metadata: languageName: node linkType: hard -"mri@npm:^1.2.0": +"mri@npm:^1.1.0, mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" checksum: 83f515abbcff60150873e424894a2f65d68037e5a7fcde8a9e2b285ee9c13ac581b63cfc1e6826c4732de3aeb84902f7c1e16b7aff46cd3f897a0f757a894e85 @@ -34085,6 +37781,64 @@ __metadata: languageName: node linkType: hard +"next@npm:^14.1.3": + version: 14.2.24 + resolution: "next@npm:14.2.24" + dependencies: + "@next/env": 14.2.24 + "@next/swc-darwin-arm64": 14.2.24 + "@next/swc-darwin-x64": 14.2.24 + "@next/swc-linux-arm64-gnu": 14.2.24 + "@next/swc-linux-arm64-musl": 14.2.24 + "@next/swc-linux-x64-gnu": 14.2.24 + "@next/swc-linux-x64-musl": 14.2.24 + "@next/swc-win32-arm64-msvc": 14.2.24 + "@next/swc-win32-ia32-msvc": 14.2.24 + "@next/swc-win32-x64-msvc": 14.2.24 + "@swc/helpers": 0.5.5 + busboy: 1.6.0 + caniuse-lite: ^1.0.30001579 + graceful-fs: ^4.2.11 + postcss: 8.4.31 + styled-jsx: 5.1.1 + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-ia32-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: b85053104c932b0e1b248623e2b40c78a6597ca3acf9bdad6040a9a2748b4badf59af7d0095ad54405f1b74d106f89797b4cbd072ad49d732f54b6df2e6fd8b7 + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -34092,6 +37846,15 @@ __metadata: languageName: node linkType: hard +"no-case@npm:^2.2.0, no-case@npm:^2.3.2": + version: 2.3.2 + resolution: "no-case@npm:2.3.2" + dependencies: + lower-case: ^1.1.1 + checksum: 856487731936fef44377ca74fdc5076464aba2e0734b56a4aa2b2a23d5b154806b591b9b2465faa59bb982e2b5c9391e3685400957fb4eeb38f480525adcf3dd + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -34226,7 +37989,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:3.3.2": +"node-fetch@npm:3.3.2, node-fetch@npm:^3.3.2": version: 3.3.2 resolution: "node-fetch@npm:3.3.2" dependencies: @@ -34237,6 +38000,16 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^1.5.3": + version: 1.7.3 + resolution: "node-fetch@npm:1.7.3" + dependencies: + encoding: ^0.1.11 + is-stream: ^1.0.1 + checksum: 3bb0528c05d541316ebe52770d71ee25a6dce334df4231fd55df41a644143e07f068637488c18a5b0c43f05041dbd3346752f9e19b50df50569a802484544d5b + languageName: node + linkType: hard + "node-forge@npm:1.3.1, node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" @@ -34429,6 +38202,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 917dbced519f48c6289a44830a0ca6dc944c3ee9243c468ebd8515a41c97c8b2c256edb7f3f750416bc37952cc9608684e6483c7b6c6f39f6bd8d86c52cfe658 + languageName: node + linkType: hard + "node-releases@npm:^2.0.6": version: 2.0.6 resolution: "node-releases@npm:2.0.6" @@ -34535,6 +38315,15 @@ __metadata: languageName: node linkType: hard +"normalize-path@npm:^2.1.1": + version: 2.1.1 + resolution: "normalize-path@npm:2.1.1" + dependencies: + remove-trailing-separator: ^1.0.1 + checksum: 7e9cbdcf7f5b8da7aa191fbfe33daf290cdcd8c038f422faf1b8a83c972bf7a6d94c5be34c4326cb00fb63bc0fd97d9fbcfaf2e5d6142332c2cd36d2e1b86cea + languageName: node + linkType: hard + "normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" @@ -34549,6 +38338,13 @@ __metadata: languageName: node linkType: hard +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 4a4944631173e7d521d6b80e4c85ccaeceb2870f315584fa30121f505a6dfd86439c5e3fdd8cd9e0e291290c41d0c3599f0cb12ab356722ed242584c30348e50 + languageName: node + linkType: hard + "normalize-url@npm:^8.0.0": version: 8.0.1 resolution: "normalize-url@npm:8.0.1" @@ -34683,6 +38479,13 @@ __metadata: languageName: node linkType: hard +"nullthrows@npm:^1.1.1": + version: 1.1.1 + resolution: "nullthrows@npm:1.1.1" + checksum: 10806b92121253eb1b08ecf707d92480f5331ba8ae5b23fa3eb0548ad24196eb797ed47606153006568a5733ea9e528a3579f21421f7828e09e7756f4bdd386f + languageName: node + linkType: hard + "number-allocator@npm:^1.0.9": version: 1.0.14 resolution: "number-allocator@npm:1.0.14" @@ -34816,6 +38619,13 @@ __metadata: languageName: node linkType: hard +"object-path@npm:^0.11.8": + version: 0.11.8 + resolution: "object-path@npm:0.11.8" + checksum: 684ccf0fb6b82f067dc81e2763481606692b8485bec03eb2a64e086a44dbea122b2b9ef44423a08e09041348fe4b4b67bd59985598f1652f67df95f0618f5968 + languageName: node + linkType: hard + "object-treeify@npm:^1.1.33": version: 1.1.33 resolution: "object-treeify@npm:1.1.33" @@ -35318,6 +39128,15 @@ __metadata: languageName: node linkType: hard +"os-locale@npm:^1.4.0": + version: 1.4.0 + resolution: "os-locale@npm:1.4.0" + dependencies: + lcid: ^1.0.0 + checksum: 0161a1b6b5a8492f99f4b47fe465df9fc521c55ba5414fce6444c45e2500487b8ed5b40a47a98a2363fe83ff04ab033785300ed8df717255ec4c3b625e55b1fb + languageName: node + linkType: hard + "os-name@npm:5.1.0": version: 5.1.0 resolution: "os-name@npm:5.1.0" @@ -35367,6 +39186,13 @@ __metadata: languageName: node linkType: hard +"p-cancelable@npm:^2.0.0": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 3dba12b4fb4a1e3e34524535c7858fc82381bbbd0f247cc32dedc4018592a3950ce66b106d0880b4ec4c2d8d6576f98ca885dc1d7d0f274d1370be20e9523ddf + languageName: node + linkType: hard + "p-cancelable@npm:^3.0.0": version: 3.0.0 resolution: "p-cancelable@npm:3.0.0" @@ -35390,6 +39216,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:3.1.0, p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: ^0.1.0 + checksum: 7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 + languageName: node + linkType: hard + "p-limit@npm:^1.1.0": version: 1.3.0 resolution: "p-limit@npm:1.3.0" @@ -35408,15 +39243,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: ^0.1.0 - checksum: 7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 - languageName: node - linkType: hard - "p-limit@npm:^4.0.0": version: 4.0.0 resolution: "p-limit@npm:4.0.0" @@ -35605,6 +39431,15 @@ __metadata: languageName: node linkType: hard +"param-case@npm:^2.1.0": + version: 2.1.1 + resolution: "param-case@npm:2.1.1" + dependencies: + no-case: ^2.2.0 + checksum: 3a63dcb8d8dc7995a612de061afdc7bb6fe7bd0e6db994db8d4cae999ed879859fd24389090e1a0d93f4c9207ebf8c048c870f468a3f4767161753e03cb9ab58 + languageName: node + linkType: hard + "param-case@npm:^3.0.4": version: 3.0.4 resolution: "param-case@npm:3.0.4" @@ -35692,6 +39527,32 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": ^2.0.0 + character-entities-legacy: ^3.0.0 + character-reference-invalid: ^2.0.0 + decode-named-character-reference: ^1.0.0 + is-alphanumerical: ^2.0.0 + is-decimal: ^2.0.0 + is-hexadecimal: ^2.0.0 + checksum: db22b46da1a62af00409c929ac49fbd306b5ebf0dbacf4646d2ae2b58616ef90a40eedc282568a3cf740fac2a7928bc97146973a628f6977ca274dedc2ad6edc + languageName: node + linkType: hard + +"parse-filepath@npm:^1.0.2": + version: 1.0.2 + resolution: "parse-filepath@npm:1.0.2" + dependencies: + is-absolute: ^1.0.0 + map-cache: ^0.2.0 + path-root: ^0.1.1 + checksum: 6794c3f38d3921f0f7cc63fb1fb0c4d04cd463356ad389c8ce6726d3c50793b9005971f4138975a6d7025526058d5e65e9bfe634d0765e84c4e2571152665a69 + languageName: node + linkType: hard + "parse-headers@npm:^2.0.0": version: 2.0.5 resolution: "parse-headers@npm:2.0.5" @@ -35699,6 +39560,15 @@ __metadata: languageName: node linkType: hard +"parse-json@npm:^2.2.0": + version: 2.2.0 + resolution: "parse-json@npm:2.2.0" + dependencies: + error-ex: ^1.2.0 + checksum: dda78a63e57a47b713a038630868538f718a7ca0cd172a36887b0392ccf544ed0374902eb28f8bf3409e8b71d62b79d17062f8543afccf2745f9b0b2d2bb80ca + languageName: node + linkType: hard + "parse-json@npm:^4.0.0": version: 4.0.0 resolution: "parse-json@npm:4.0.0" @@ -35769,7 +39639,7 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^6.0.1": +"parse5@npm:^6.0.0, parse5@npm:^6.0.1": version: 6.0.1 resolution: "parse5@npm:6.0.1" checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd @@ -35802,6 +39672,16 @@ __metadata: languageName: node linkType: hard +"pascal-case@npm:^2.0.0": + version: 2.0.1 + resolution: "pascal-case@npm:2.0.1" + dependencies: + camel-case: ^3.0.0 + upper-case-first: ^1.1.0 + checksum: 4c539bf556572812f64a02fc6b544f3d2b51db12aed484e5162ed7f8ac2b366775d15e536091c890d71d82bdf9153128321f21574721b3a984bd85df9e519a35 + languageName: node + linkType: hard + "pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2" @@ -35873,6 +39753,34 @@ __metadata: languageName: node linkType: hard +"path-case@npm:^2.1.0": + version: 2.1.1 + resolution: "path-case@npm:2.1.1" + dependencies: + no-case: ^2.2.0 + checksum: eb1da508c28378715cbe4ce054ee5f83a570c5010f041f4cfb439c811f7a78e36c46f26a8d59b2594c3882b53db06ef26195519c27f86523dc5d19c2e29f306d + languageName: node + linkType: hard + +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 61de0526222629f65038a66f63330dd22d5b54014ded6636283e1d15364da38b3cf29e4433aa3f9d8b0dba407ae2b059c23b0104a34ee789944b1bc1c5c7e06d + languageName: node + linkType: hard + +"path-exists@npm:^2.0.0": + version: 2.1.0 + resolution: "path-exists@npm:2.1.0" + dependencies: + pinkie-promise: ^2.0.0 + checksum: fdb734f1d00f225f7a0033ce6d73bff6a7f76ea08936abf0e5196fa6e54a645103538cd8aedcb90d6d8c3fa3705ded0c58a4da5948ae92aa8834892c1ab44a84 + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -35929,6 +39837,22 @@ __metadata: languageName: node linkType: hard +"path-root-regex@npm:^0.1.0": + version: 0.1.2 + resolution: "path-root-regex@npm:0.1.2" + checksum: dcd75d1f8e93faabe35a58e875b0f636839b3658ff2ad8c289463c40bc1a844debe0dab73c3398ef9dc8f6ec6c319720aff390cf4633763ddcf3cf4b1bbf7e8b + languageName: node + linkType: hard + +"path-root@npm:^0.1.1": + version: 0.1.1 + resolution: "path-root@npm:0.1.1" + dependencies: + path-root-regex: ^0.1.0 + checksum: ff88aebfc1c59ace510cc06703d67692a11530989920427625e52b66a303ca9b3d4059b0b7d0b2a73248d1ad29bcb342b8b786ec00592f3101d38a45fd3b2e08 + languageName: node + linkType: hard + "path-scurry@npm:^1.10.0, path-scurry@npm:^1.10.1, path-scurry@npm:^1.6.1": version: 1.10.1 resolution: "path-scurry@npm:1.10.1" @@ -36187,6 +40111,13 @@ __metadata: languageName: node linkType: hard +"phenomenon@npm:^1.6.0": + version: 1.6.0 + resolution: "phenomenon@npm:1.6.0" + checksum: e05ca8223a9df42c5cee02c082103ef96a349424fec18a8478d2171060a63095e21bef394529263c64d8082aff43204a0fa1355211fd7ac2a338c2839fffbca3 + languageName: node + linkType: hard + "phin@npm:^2.9.1": version: 2.9.3 resolution: "phin@npm:2.9.3" @@ -36254,7 +40185,7 @@ __metadata: languageName: node linkType: hard -"pify@npm:^2.3.0": +"pify@npm:^2.0.0, pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" checksum: 9503aaeaf4577acc58642ad1d25c45c6d90288596238fb68f82811c08104c800e5a7870398e9f015d82b44ecbcbef3dc3d4251a1cbb582f6e5959fe09884b2ba @@ -36284,6 +40215,15 @@ __metadata: languageName: node linkType: hard +"pinkie-promise@npm:^2.0.0": + version: 2.0.1 + resolution: "pinkie-promise@npm:2.0.1" + dependencies: + pinkie: ^2.0.0 + checksum: b53a4a2e73bf56b6f421eef711e7bdcb693d6abb474d57c5c413b809f654ba5ee750c6a96dd7225052d4b96c4d053cdcb34b708a86fceed4663303abee52fcca + languageName: node + linkType: hard + "pinkie@npm:^1.0.0": version: 1.0.0 resolution: "pinkie@npm:1.0.0" @@ -36291,6 +40231,13 @@ __metadata: languageName: node linkType: hard +"pinkie@npm:^2.0.0": + version: 2.0.4 + resolution: "pinkie@npm:2.0.4" + checksum: b12b10afea1177595aab036fc220785488f67b4b0fc49e7a27979472592e971614fa1c728e63ad3e7eb748b4ec3c3dbd780819331dad6f7d635c77c10537b9db + languageName: node + linkType: hard + "pirates@npm:^4.0.1, pirates@npm:^4.0.5": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -36370,6 +40317,15 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:^1.38.1": + version: 1.50.1 + resolution: "playwright-core@npm:1.50.1" + bin: + playwright-core: cli.js + checksum: 9c9c3115e8c39c9a2f4e0dc5b2bc1d76925fb0244051ace2bcffc7b2b66cf6fe1b96d9162f81bc7d9b7811b8c6f9df5aa93052d1284bfd6d30203bf4f7b58868 + languageName: node + linkType: hard + "playwright@npm:1.45.3": version: 1.45.3 resolution: "playwright@npm:1.45.3" @@ -37065,6 +41021,33 @@ __metadata: languageName: node linkType: hard +"prism-react-renderer@npm:^1.3.5": + version: 1.3.5 + resolution: "prism-react-renderer@npm:1.3.5" + peerDependencies: + react: ">=0.14.9" + checksum: c18806dcbc4c0b4fd6fd15bd06b4f7c0a6da98d93af235c3e970854994eb9b59e23315abb6cfc29e69da26d36709a47e25da85ab27fed81b6812f0a52caf6dfa + languageName: node + linkType: hard + +"prisma-field-encryption@npm:^1.4.0": + version: 1.6.0 + resolution: "prisma-field-encryption@npm:1.6.0" + dependencies: + "@47ng/cloak": ^1.2.0 + "@prisma/generator-helper": ^5.9.1 + debug: ^4.3.4 + immer: ^10.0.3 + object-path: ^0.11.8 + zod: ^3.22.4 + peerDependencies: + "@prisma/client": ">= 4.7" + bin: + prisma-field-encryption: dist/generator/main.js + checksum: 70a63e6ba316b17982288f01ce69a268ed924d6ca4b25082297ceedc75bc1aacbef20703d0e6356518a599107bc7a61ecec7964cb698a93e8076044291f31890 + languageName: node + linkType: hard + "prisma-kysely@npm:^1.7.1": version: 1.8.0 resolution: "prisma-kysely@npm:1.8.0" @@ -37250,6 +41233,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^6.0.0": + version: 6.5.0 + resolution: "property-information@npm:6.5.0" + checksum: 6e55664e2f64083b715011e5bafaa1e694faf36986c235b0907e95d09259cc37c38382e3cc94a4c3f56366e05336443db12c8a0f0968a8c0a1b1416eebfc8f53 + languageName: node + linkType: hard + "proto-list@npm:~1.2.1": version: 1.2.4 resolution: "proto-list@npm:1.2.4" @@ -37468,6 +41458,22 @@ __metadata: languageName: node linkType: hard +"pvtsutils@npm:^1.3.2, pvtsutils@npm:^1.3.5, pvtsutils@npm:^1.3.6": + version: 1.3.6 + resolution: "pvtsutils@npm:1.3.6" + dependencies: + tslib: ^2.8.1 + checksum: 97b023b46d7b95bff004f8340efc465c1d995f35d7e97a2ef2e28d5e160f5ca47b48f42463b6be92b4341452a6b8c555feb2b1eb59ee90b97bd5d6fc86ffb186 + languageName: node + linkType: hard + +"pvutils@npm:^1.1.3": + version: 1.1.3 + resolution: "pvutils@npm:1.1.3" + checksum: 2ee26a9e5176c348977d6ec00d8ee80bff62f51743b1c5fe8abeeb4c5d29d9959cdfe0ce146707a9e6801bce88190fed3002d720b072dc87d031c692820b44c9 + languageName: node + linkType: hard + "q@npm:2.0.x": version: 2.0.3 resolution: "q@npm:2.0.3" @@ -37760,6 +41766,16 @@ __metadata: languageName: node linkType: hard +"react-chartjs-2@npm:^4.0.1": + version: 4.3.1 + resolution: "react-chartjs-2@npm:4.3.1" + peerDependencies: + chart.js: ^3.5.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 574d12cc43b9b4a0f1e04cc692982e16ef7083c03da2a8a9fc2180fe9bcadc793008f81d8f4eec5465925eff84c95d7c523cb74376858b363ae75a83bb3c2a5d + languageName: node + linkType: hard + "react-colorful@npm:^5.1.2": version: 5.6.1 resolution: "react-colorful@npm:5.6.1" @@ -37780,6 +41796,17 @@ __metadata: languageName: node linkType: hard +"react-confetti@npm:^6.0.1": + version: 6.2.2 + resolution: "react-confetti@npm:6.2.2" + dependencies: + tween-functions: ^1.2.0 + peerDependencies: + react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 + checksum: df470c7fae00c1db6b50562469916f900f1beaf3fcd3c735146e5c55cc3e79bb142851d10b8b9b493e347a016c94156b50564ca9ec4f9864ea02d2eae5fb9559 + languageName: node + linkType: hard + "react-copy-to-clipboard@npm:5.0.4": version: 5.0.4 resolution: "react-copy-to-clipboard@npm:5.0.4" @@ -37813,6 +41840,23 @@ __metadata: languageName: node linkType: hard +"react-datocms@npm:^3.1.0": + version: 3.1.4 + resolution: "react-datocms@npm:3.1.4" + dependencies: + datocms-listen: ^0.1.9 + datocms-structured-text-generic-html-renderer: ^2.0.1 + datocms-structured-text-utils: ^2.0.1 + react-intersection-observer: ^8.33.1 + react-string-replace: ^1.1.0 + universal-base64: ^2.1.0 + use-deep-compare-effect: ^1.6.1 + peerDependencies: + react: ">= 16.12.0" + checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3 + languageName: node + linkType: hard + "react-day-picker@npm:^8.10.1": version: 8.10.1 resolution: "react-day-picker@npm:8.10.1" @@ -37835,6 +41879,18 @@ __metadata: languageName: node linkType: hard +"react-device-detect@npm:^2.2.2": + version: 2.2.3 + resolution: "react-device-detect@npm:2.2.3" + dependencies: + ua-parser-js: ^1.0.33 + peerDependencies: + react: ">= 0.14.0" + react-dom: ">= 0.14.0" + checksum: 42d9b3182b9d2495bf0d7914c9f370da51d8bdb853a3eba2acaf433894ae760386a075ba103185be825b33d42f50d85ef462087f261656d433f4c74dab23861f + languageName: node + linkType: hard + "react-devtools-core@npm:^4.19.1": version: 4.24.6 resolution: "react-devtools-core@npm:4.24.6" @@ -37929,6 +41985,16 @@ __metadata: languageName: node linkType: hard +"react-fast-marquee@npm:^1.6.4": + version: 1.6.5 + resolution: "react-fast-marquee@npm:1.6.5" + peerDependencies: + react: ">= 16.8.0 || ^18.0.0" + react-dom: ">= 16.8.0 || ^18.0.0" + checksum: 5213b10983f47a2dc27c3206144f96b37dad2f08457b19ecb9e393e4bd3c8db8c9cccf1758ceb5c4ca6d697e3715f3f90c5d806e4cb62bbbad9ece7f0e7b3caf + languageName: node + linkType: hard + "react-fit@npm:^1.4.0": version: 1.4.0 resolution: "react-fit@npm:1.4.0" @@ -37952,6 +42018,17 @@ __metadata: languageName: node linkType: hard +"react-github-btn@npm:^1.4.0": + version: 1.4.0 + resolution: "react-github-btn@npm:1.4.0" + dependencies: + github-buttons: ^2.22.0 + peerDependencies: + react: ">=16.3.0" + checksum: 33a416ad76ab4cc9238ac5cf5cfcab636bb2127c48fb30805385350fd3a3c2aa0aaeb78f6c726c52a0d3d133ca469be35d4b3d188c8e40c735c7ff458d2c8c3c + languageName: node + linkType: hard + "react-hook-form@npm:^7.43.3": version: 7.43.3 resolution: "react-hook-form@npm:7.43.3" @@ -38025,6 +42102,15 @@ __metadata: languageName: node linkType: hard +"react-intersection-observer@npm:^8.33.1": + version: 8.34.0 + resolution: "react-intersection-observer@npm:8.34.0" + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0 + checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5 + languageName: node + linkType: hard + "react-intl@npm:^5.25.1": version: 5.25.1 resolution: "react-intl@npm:5.25.1" @@ -38093,6 +42179,34 @@ __metadata: languageName: node linkType: hard +"react-markdown@npm:^9.0.1": + version: 9.0.3 + resolution: "react-markdown@npm:9.0.3" + dependencies: + "@types/hast": ^3.0.0 + devlop: ^1.0.0 + hast-util-to-jsx-runtime: ^2.0.0 + html-url-attributes: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + remark-parse: ^11.0.0 + remark-rehype: ^11.0.0 + unified: ^11.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: 7ebb01b295f7c9acddcd305308a8531c58c582c24fb8d6a4897f16b21ba0bd7e9e20ddae4a9024767e910310d22db0003489b61478cdb491a3d802343cf3a931 + languageName: node + linkType: hard + +"react-merge-refs@npm:1.1.0": + version: 1.1.0 + resolution: "react-merge-refs@npm:1.1.0" + checksum: 90884352999002d868ab9f1bcfe3222fb0f2178ed629f1da7e98e5a9b02a2c96b4aa72800db92aabd69d2483211b4be57a2088e89a11a0b660e7ada744d4ddf7 + languageName: node + linkType: hard + "react-multi-email@npm:^0.5.3": version: 0.5.3 resolution: "react-multi-email@npm:0.5.3" @@ -38294,6 +42408,18 @@ __metadata: languageName: node linkType: hard +"react-resize-detector@npm:^9.1.0": + version: 9.1.1 + resolution: "react-resize-detector@npm:9.1.1" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 0612b4416212962e5762f25e20c7016bdce3107d745a399b44bc099a04488a704228908357845c2e3fa49bf9902077f0eeebc99cf76d42ea479a8aa79f4c5b9d + languageName: node + linkType: hard + "react-schemaorg@npm:^2.0.0": version: 2.0.0 resolution: "react-schemaorg@npm:2.0.0" @@ -38368,6 +42494,13 @@ __metadata: languageName: node linkType: hard +"react-string-replace@npm:^1.1.0": + version: 1.1.1 + resolution: "react-string-replace@npm:1.1.1" + checksum: fd5058bbb3953f4bb2daa7012630a154c6109e3e848f93925e146710cebf527647a7c1496c89ca0b5d9e4b4f8ffd5c55ca631539095039faaba0a7ba3787e7cb + languageName: node + linkType: hard + "react-style-singleton@npm:^2.1.0": version: 2.1.1 resolution: "react-style-singleton@npm:2.1.1" @@ -38486,6 +42619,20 @@ __metadata: languageName: node linkType: hard +"react-twemoji@npm:^0.3.0": + version: 0.3.0 + resolution: "react-twemoji@npm:0.3.0" + dependencies: + lodash.isequal: ^4.5.0 + prop-types: ^15.7.2 + twemoji: ^13.0.1 + peerDependencies: + react: ^16.4.2 + react-dom: ^16.4.2 + checksum: d4e56477c28c0ad65b98a062a2e9a1777b808a16c05dfa35d77d84fea26f7d1e884d2e30f95a8f14c94c31330d3d4f53a4fd0bfce917bc4be9fb21d34a05db8a + languageName: node + linkType: hard + "react-universal-interface@npm:^0.6.2": version: 0.6.2 resolution: "react-universal-interface@npm:0.6.2" @@ -38506,6 +42653,19 @@ __metadata: languageName: node linkType: hard +"react-use-measure@npm:^2.1.1": + version: 2.1.7 + resolution: "react-use-measure@npm:2.1.7" + peerDependencies: + react: ">=16.13" + react-dom: ">=16.13" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 5f00c14cf50b0710cdbd27b63a005be20283099d2fa2723a97f3a1cf0b2daedddd67249520c21e49e95348f56428689f3229c343dcb9ed37da58f9c227d29bee + languageName: node + linkType: hard + "react-use@npm:^17.4.2": version: 17.5.0 resolution: "react-use@npm:17.5.0" @@ -38542,6 +42702,15 @@ __metadata: languageName: node linkType: hard +"react-wrap-balancer@npm:^1.0.0": + version: 1.1.1 + resolution: "react-wrap-balancer@npm:1.1.1" + peerDependencies: + react: ">=16.8.0 || ^17.0.0 || ^18" + checksum: 68d9417efa751eced4a8c9f7e3f1f43d59fbf9ecf42dfd90b7b4c33f5af855766bb21bf967c9fc8a0f09f6ca4a58071c1dd47af2c813b33b3813ebe7efcb32f2 + languageName: node + linkType: hard + "react@npm:^18, react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" @@ -38571,6 +42740,27 @@ __metadata: languageName: node linkType: hard +"read-pkg-up@npm:^1.0.1": + version: 1.0.1 + resolution: "read-pkg-up@npm:1.0.1" + dependencies: + find-up: ^1.0.0 + read-pkg: ^1.0.0 + checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 + languageName: node + linkType: hard + +"read-pkg@npm:^1.0.0": + version: 1.1.0 + resolution: "read-pkg@npm:1.1.0" + dependencies: + load-json-file: ^1.0.0 + normalize-package-data: ^2.3.2 + path-type: ^1.0.0 + checksum: a0f5d5e32227ec8e6a028dd5c5134eab229768dcb7a5d9a41a284ed28ad4b9284fecc47383dc1593b5694f4de603a7ffaee84b738956b9b77e0999567485a366 + languageName: node + linkType: hard + "read-pkg@npm:^3.0.0": version: 3.0.0 resolution: "read-pkg@npm:3.0.0" @@ -38936,6 +43126,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.11.0": + version: 0.11.1 + resolution: "regenerator-runtime@npm:0.11.1" + checksum: 3c97bd2c7b2b3247e6f8e2147a002eb78c995323732dad5dc70fac8d8d0b758d0295e7015b90d3d444446ae77cbd24b9f9123ec3a77018e81d8999818301b4f4 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.13.3": version: 0.13.9 resolution: "regenerator-runtime@npm:0.13.9" @@ -39081,6 +43278,17 @@ __metadata: languageName: node linkType: hard +"relay-runtime@npm:12.0.0": + version: 12.0.0 + resolution: "relay-runtime@npm:12.0.0" + dependencies: + "@babel/runtime": ^7.0.0 + fbjs: ^3.0.0 + invariant: ^2.2.4 + checksum: 51cdc8a5e04188982452ae4e7c6ac7d6375ee769130d24ce8e8f9cdd45aa7e11ecd68670f56e30dcee1b4974585e88ecce19e69a9868b80cda0db7678c3b8f0a + languageName: node + linkType: hard + "release-it@npm:^17.1.1": version: 17.1.1 resolution: "release-it@npm:17.1.1" @@ -39131,6 +43339,55 @@ __metadata: languageName: node linkType: hard +"remark-html@npm:^14.0.1": + version: 14.0.1 + resolution: "remark-html@npm:14.0.1" + dependencies: + "@types/mdast": ^3.0.0 + hast-util-sanitize: ^4.0.0 + hast-util-to-html: ^8.0.0 + mdast-util-to-hast: ^11.0.0 + unified: ^10.0.0 + checksum: 5d689b05dc6b4f24e08ece07aabca98685949198a8b6ceacb2fbf7fba8f45f55cea5623caddfd92aaf6e6f082bc1c93797dfb82dc48a6f6e1c5263947a4779c9 + languageName: node + linkType: hard + +"remark-parse@npm:^10.0.0": + version: 10.0.2 + resolution: "remark-parse@npm:10.0.2" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-from-markdown: ^1.0.0 + unified: ^10.0.0 + checksum: 5041b4b44725f377e69986e02f8f072ae2222db5e7d3b6c80829756b842e811343ffc2069cae1f958a96bfa36104ab91a57d7d7e2f0cef521e210ab8c614d5c7 + languageName: node + linkType: hard + +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + micromark-util-types: ^2.0.0 + unified: ^11.0.0 + checksum: d83d245290fa84bb04fb3e78111f09c74f7417e7c012a64dd8dc04fccc3699036d828fbd8eeec8944f774b6c30cc1d925c98f8c46495ebcee7c595496342ab7f + languageName: node + linkType: hard + +"remark-rehype@npm:^11.0.0": + version: 11.1.1 + resolution: "remark-rehype@npm:11.1.1" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + mdast-util-to-hast: ^13.0.0 + unified: ^11.0.0 + vfile: ^6.0.0 + checksum: e199dff098ae6a560e13dd1778dec9c61440f979cc931c4ca4dcde0d58e51c0723243a901c1842379b189083cf4d74f224f57833738095ffa9535179d7cf3f01 + languageName: node + linkType: hard + "remark-slug@npm:^6.0.0": version: 6.1.0 resolution: "remark-slug@npm:6.1.0" @@ -39142,6 +43399,29 @@ __metadata: languageName: node linkType: hard +"remark-stringify@npm:^10.0.0": + version: 10.0.3 + resolution: "remark-stringify@npm:10.0.3" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-to-markdown: ^1.0.0 + unified: ^10.0.0 + checksum: 6004e204fba672ee322c3cf0bef090e95802feedf7ef875f88b120c5e6208f1eb09c014486d5ca42a1e199c0a17ce0ed165fb248c66608458afed4bdca51dd3a + languageName: node + linkType: hard + +"remark@npm:^14.0.2": + version: 14.0.3 + resolution: "remark@npm:14.0.3" + dependencies: + "@types/mdast": ^3.0.0 + remark-parse: ^10.0.0 + remark-stringify: ^10.0.0 + unified: ^10.0.0 + checksum: 36eec9668c5f5e497507fa5d396c79183265a5f7dd204a608e7f031a4f61b48f7bb5cfaec212f5614ccd1266cc4a9f8d7a59a45e95aed9876986b4c453b191be + languageName: node + linkType: hard + "remarkable@npm:^2.0.1": version: 2.0.1 resolution: "remarkable@npm:2.0.1" @@ -39154,6 +43434,20 @@ __metadata: languageName: node linkType: hard +"remeda@npm:^1.24.1": + version: 1.61.0 + resolution: "remeda@npm:1.61.0" + checksum: c080da71953ccc178334ca774f0da5c3f4664ef25ff7bc1e1f09a921ff9c264bbf8be8e0ac876c4a3f851a4d3e017cd454319a0e24610fd744bd8f9177dd4c7b + languageName: node + linkType: hard + +"remedial@npm:^1.0.7": + version: 1.0.8 + resolution: "remedial@npm:1.0.8" + checksum: 12df7c55eb92501d7f33cfe5f5ad12be13bb6ac0c53f494aaa9963d5a5155bb8be2143e8d5e17afa1a500ef5dc71d13642920d35350f2a31b65a9778afab6869 + languageName: node + linkType: hard + "remove-markdown@npm:^0.5.0": version: 0.5.0 resolution: "remove-markdown@npm:0.5.0" @@ -39161,6 +43455,20 @@ __metadata: languageName: node linkType: hard +"remove-trailing-separator@npm:^1.0.1": + version: 1.1.0 + resolution: "remove-trailing-separator@npm:1.1.0" + checksum: d3c20b5a2d987db13e1cca9385d56ecfa1641bae143b620835ac02a6b70ab88f68f117a0021838db826c57b31373d609d52e4f31aca75fc490c862732d595419 + languageName: node + linkType: hard + +"remove-trailing-spaces@npm:^1.0.6": + version: 1.0.9 + resolution: "remove-trailing-spaces@npm:1.0.9" + checksum: a1a839ca5f17298fe37236f0515f51f04ea2777882227b1c2e4856f65f0e061c5e44c2f57ffa4bf5f901ebb6b17d7873f475fa02aeafbbd52f77595872adf3d2 + languageName: node + linkType: hard + "renderkid@npm:^3.0.0": version: 3.0.0 resolution: "renderkid@npm:3.0.0" @@ -39241,6 +43549,13 @@ __metadata: languageName: node linkType: hard +"require-main-filename@npm:^1.0.1": + version: 1.0.1 + resolution: "require-main-filename@npm:1.0.1" + checksum: 1fef30754da961f4e13c450c3eb60c7ae898a529c6ad6fa708a70bd2eed01564ceb299187b2899f5562804d797a059f39a5789884d0ac7b7ae1defc68fba4abf + languageName: node + linkType: hard + "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -39269,7 +43584,7 @@ __metadata: languageName: node linkType: hard -"resolve-alpn@npm:^1.2.0": +"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" checksum: f558071fcb2c60b04054c99aebd572a2af97ef64128d59bef7ab73bd50d896a222a056de40ffc545b633d99b304c259ea9d0c06830d5c867c34f0bfa60b8eae0 @@ -39285,6 +43600,13 @@ __metadata: languageName: node linkType: hard +"resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -39292,13 +43614,6 @@ __metadata: languageName: node linkType: hard -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf - languageName: node - linkType: hard - "resolve-url-loader@npm:^5.0.0": version: 5.0.0 resolution: "resolve-url-loader@npm:5.0.0" @@ -39489,6 +43804,15 @@ __metadata: languageName: node linkType: hard +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: ^2.0.0 + checksum: b122535466e9c97b55e69c7f18e2be0ce3823c5d47ee8de0d9c0b114aa55741c6db8bfbfce3766a94d1272e61bfb1ebf0a15e9310ac5629fbb7446a861b4fd3a + languageName: node + linkType: hard + "responselike@npm:^3.0.0": version: 3.0.0 resolution: "responselike@npm:3.0.0" @@ -39927,7 +44251,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1, rxjs@npm:^7.8.1": +"rxjs@npm:7.8.1, rxjs@npm:^7.0.0, rxjs@npm:^7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -39945,6 +44269,22 @@ __metadata: languageName: node linkType: hard +"s-ago@npm:^2.2.0": + version: 2.2.0 + resolution: "s-ago@npm:2.2.0" + checksum: f665fef44d9d88322ce5a798ca3c49b40f96231ddc7bd46dc23c883e98215675aa422985760d45d3779faa3c0bc94edb2a50630bf15f54c239d11963e53d998c + languageName: node + linkType: hard + +"sade@npm:^1.7.3": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: ^1.1.0 + checksum: 0756e5b04c51ccdc8221ebffd1548d0ce5a783a44a0fa9017a026659b97d632913e78f7dca59f2496aa996a0be0b0c322afd87ca72ccd909406f49dbffa0f45d + languageName: node + linkType: hard + "safe-array-concat@npm:^1.0.1": version: 1.1.0 resolution: "safe-array-concat@npm:1.1.0" @@ -40179,6 +44519,13 @@ __metadata: languageName: node linkType: hard +"scuid@npm:^1.1.0": + version: 1.1.0 + resolution: "scuid@npm:1.1.0" + checksum: cd094ac3718b0070a222f9a499b280c698fdea10268cc163fa244421099544c1766dd893fdee0e2a8eba5d53ab9d0bcb11067bedff166665030fa6fda25a096b + languageName: node + linkType: hard + "section-matter@npm:^1.0.0": version: 1.0.0 resolution: "section-matter@npm:1.0.0" @@ -40317,6 +44664,27 @@ __metadata: languageName: node linkType: hard +"sentence-case@npm:^2.1.0": + version: 2.1.1 + resolution: "sentence-case@npm:2.1.1" + dependencies: + no-case: ^2.2.0 + upper-case-first: ^1.1.2 + checksum: ce5ca48804051e056a6956ad75a1a7d833e5d8f5021a015d380a22d3cf04496d5238de2e5c876d9701a9218633052c3a65911ca1b6460d36a41ecad46e81d139 + languageName: node + linkType: hard + +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: ^3.0.4 + tslib: ^2.0.3 + upper-case-first: ^2.0.2 + checksum: 3cfe6c0143e649132365695706702d7f729f484fa7b25f43435876efe7af2478243eefb052bacbcce10babf9319fd6b5b6bc59b94c80a1c819bcbb40651465d5 + languageName: node + linkType: hard + "seq-queue@npm:^0.0.5": version: 0.0.5 resolution: "seq-queue@npm:0.0.5" @@ -40533,6 +44901,13 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.7.3": + version: 1.8.2 + resolution: "shell-quote@npm:1.8.2" + checksum: 1e97b62ced1c4c5135015978ebf273bed1f425a68cf84163e83fbb0f34b3ff9471e656720dab2b7cbb4ae0f58998e686d17d166c28dfb3662acd009e8bd7faed + languageName: node + linkType: hard + "shelljs@npm:0.8.5": version: 0.8.5 resolution: "shelljs@npm:0.8.5" @@ -40607,6 +44982,13 @@ __metadata: languageName: node linkType: hard +"signedsource@npm:^1.0.0": + version: 1.0.0 + resolution: "signedsource@npm:1.0.0" + checksum: 64b2c8d7a48de9009cfd3aff62bb7c88abf3b8e0421f17ebb1d7f5ca9cc9c3ad10f5a1e3ae6cd804e4e6121c87b668202ae9057065f058ddfbf34ea65f63945d + languageName: node + linkType: hard + "simple-concat@npm:^1.0.0": version: 1.0.1 resolution: "simple-concat@npm:1.0.1" @@ -40764,6 +45146,25 @@ __metadata: languageName: node linkType: hard +"snake-case@npm:^2.1.0": + version: 2.1.0 + resolution: "snake-case@npm:2.1.0" + dependencies: + no-case: ^2.2.0 + checksum: 7e42b4841103be4dd050b2f57f5cb423d5164524c1cb3d81efda9809265a82a2d02ddf44361beae37d75a239308e6414be85fe441dc48cd70c708cb975387d10 + languageName: node + linkType: hard + +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "socks-proxy-agent@npm:^7.0.0": version: 7.0.0 resolution: "socks-proxy-agent@npm:7.0.0" @@ -40934,6 +45335,13 @@ __metadata: languageName: node linkType: hard +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 + languageName: node + linkType: hard + "spacetime@npm:^7.1.4": version: 7.1.4 resolution: "spacetime@npm:7.1.4" @@ -40950,6 +45358,13 @@ __metadata: languageName: node linkType: hard +"spawn-command@npm:^0.0.2-1": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: e35c5d28177b4d461d33c88cc11f6f3a5079e2b132c11e1746453bbb7a0c0b8a634f07541a2a234fa4758239d88203b758def509161b651e81958894c0b4b64b + languageName: node + linkType: hard + "spawndamnit@npm:^2.0.0": version: 2.0.0 resolution: "spawndamnit@npm:2.0.0" @@ -41026,6 +45441,15 @@ __metadata: languageName: node linkType: hard +"sponge-case@npm:^1.0.1": + version: 1.0.1 + resolution: "sponge-case@npm:1.0.1" + dependencies: + tslib: ^2.0.3 + checksum: 64f53d930f63c5a9e59d4cae487c1ffa87d25eab682833b01d572cc885e7e3fdbad4f03409a41f03ecb27f1f8959432253eb48332c7007c3388efddb24ba2792 + languageName: node + linkType: hard + "sprintf-js@npm:^1.1.2": version: 1.1.2 resolution: "sprintf-js@npm:1.1.2" @@ -41406,6 +45830,13 @@ __metadata: languageName: node linkType: hard +"string-env-interpolation@npm:^1.0.1": + version: 1.0.1 + resolution: "string-env-interpolation@npm:1.0.1" + checksum: d126329587f635bee65300e4451e7352b9b67e03daeb62f006ca84244cac12a1f6e45176b018653ba0c3ec3b5d980f9ca59d2eeed99cf799501cdaa7f871dc6f + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -41434,6 +45865,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^1.0.1, string-width@npm:^1.0.2": + version: 1.0.2 + resolution: "string-width@npm:1.0.2" + dependencies: + code-point-at: ^1.0.0 + is-fullwidth-code-point: ^1.0.0 + strip-ansi: ^3.0.0 + checksum: 5c79439e95bc3bd7233a332c5f5926ab2ee90b23816ed4faa380ce3b2576d7800b0a5bb15ae88ed28737acc7ea06a518c2eef39142dd727adad0e45c776cd37e + languageName: node + linkType: hard + "string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" @@ -41646,6 +46088,16 @@ __metadata: languageName: node linkType: hard +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: ^2.0.0 + character-entities-legacy: ^3.0.0 + checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 + languageName: node + linkType: hard + "stringify-object@npm:^3.3.0": version: 3.3.0 resolution: "stringify-object@npm:3.3.0" @@ -41666,7 +46118,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^3.0.0": +"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" dependencies: @@ -41700,6 +46152,15 @@ __metadata: languageName: node linkType: hard +"strip-bom@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-bom@npm:2.0.0" + dependencies: + is-utf8: ^0.2.0 + checksum: 08efb746bc67b10814cd03d79eb31bac633393a782e3f35efbc1b61b5165d3806d03332a97f362822cf0d4dd14ba2e12707fcff44fe1c870c48a063a0c9e4944 + languageName: node + linkType: hard + "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -41822,6 +46283,15 @@ __metadata: languageName: node linkType: hard +"style-to-object@npm:^1.0.0": + version: 1.0.8 + resolution: "style-to-object@npm:1.0.8" + dependencies: + inline-style-parser: 0.2.4 + checksum: 80ca4773fc728d7919edc552eb46bab11aa8cdd0b426528ee8b817ba6872ea7b9d38fbb97b6443fd2d4895a4c4b02ec32765387466a302d0b4d1b91deab1e1a0 + languageName: node + linkType: hard + "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" @@ -41916,6 +46386,15 @@ __metadata: languageName: node linkType: hard +"superjson@npm:^1.9.1": + version: 1.13.3 + resolution: "superjson@npm:1.13.3" + dependencies: + copy-anything: ^3.0.2 + checksum: f5aeb010f24163cb871a4bc402d9164112201c059afc247a75b03131c274aea6eec9cf08be9e4a9465fe4961689009a011584528531d52f7cc91c077e07e5c75 + languageName: node + linkType: hard + "supertest@npm:^6.3.3": version: 6.3.4 resolution: "supertest@npm:6.3.4" @@ -41951,7 +46430,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.0, supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -42134,6 +46613,15 @@ __metadata: languageName: node linkType: hard +"swap-case@npm:^2.0.2": + version: 2.0.2 + resolution: "swap-case@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: 6e21c9e1b3cd5735eb2af679a99ec3efc78a14e3d4d5e3fd594e254b91cfd37185b3d1c6e41b22f53a2cdf5d1b963ce30c0fe8b78337e3fd43d0137084670a5f + languageName: node + linkType: hard + "swc-loader@npm:^0.2.3": version: 0.2.3 resolution: "swc-loader@npm:0.2.3" @@ -42144,6 +46632,15 @@ __metadata: languageName: node linkType: hard +"swr@npm:^1.2.2": + version: 1.3.0 + resolution: "swr@npm:1.3.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + checksum: e7a184f0d560e9c8be85c023cc8e65e56a88a6ed46f9394b301b07f838edca23d2e303685319a4fcd620b81d447a7bcb489c7fa0a752c259f91764903c690cdb + languageName: node + linkType: hard + "symbol-observable@npm:4.0.0": version: 4.0.0 resolution: "symbol-observable@npm:4.0.0" @@ -42158,6 +46655,17 @@ __metadata: languageName: node linkType: hard +"sync-fetch@npm:0.6.0-2": + version: 0.6.0-2 + resolution: "sync-fetch@npm:0.6.0-2" + dependencies: + node-fetch: ^3.3.2 + timeout-signal: ^2.0.0 + whatwg-mimetype: ^4.0.0 + checksum: 325988e32633affdc7c6dcababd5a6ed5a1e825f30eca55af4a44f877d4154ae5a9783351f82fd793de85d3105213a3f60a12411a4db25e7ae4be08581367252 + languageName: node + linkType: hard + "synchronous-promise@npm:^2.0.15": version: 2.0.15 resolution: "synchronous-promise@npm:2.0.15" @@ -42639,6 +47147,13 @@ __metadata: languageName: node linkType: hard +"timeout-signal@npm:^2.0.0": + version: 2.0.0 + resolution: "timeout-signal@npm:2.0.0" + checksum: 0e3a6fb7ce4912e890475fe268888dd31068814ed096b264b3748d57f5a63a323fa1a5c4ba3d7da4529fb5031ed1a69bf8edfe5fb709bd3eec79d72f9ee51cc9 + languageName: node + linkType: hard + "timers-browserify@npm:^2.0.12, timers-browserify@npm:^2.0.4": version: 2.0.12 resolution: "timers-browserify@npm:2.0.12" @@ -42786,6 +47301,25 @@ __metadata: languageName: node linkType: hard +"title-case@npm:^2.1.0": + version: 2.1.1 + resolution: "title-case@npm:2.1.1" + dependencies: + no-case: ^2.2.0 + upper-case: ^1.0.3 + checksum: e88ddfc4608a7fb18ed440139d9c42a5f8a50f916e07062be2aef5e2038720746ed51c4fdf9e7190d24a8cc10e6dec9773027fc44450b3a4a5e5c49b4a931fb1 + languageName: node + linkType: hard + +"title-case@npm:^3.0.3": + version: 3.0.3 + resolution: "title-case@npm:3.0.3" + dependencies: + tslib: ^2.0.3 + checksum: e8b7ea006b53cf3208d278455d9f1e22c409459d7f9878da324fa3b18cc0aef8560924c19c744e870394a5d9cddfdbe029ebae9875909ee7f4fc562e7cbfc53e + languageName: node + linkType: hard + "tldts-core@npm:^6.1.61": version: 6.1.61 resolution: "tldts-core@npm:6.1.61" @@ -42804,6 +47338,15 @@ __metadata: languageName: node linkType: hard +"tmp-promise@npm:^3.0.3": + version: 3.0.3 + resolution: "tmp-promise@npm:3.0.3" + dependencies: + tmp: ^0.2.0 + checksum: f854f5307dcee6455927ec3da9398f139897faf715c5c6dcee6d9471ae85136983ea06662eba2edf2533bdcb0fca66d16648e79e14381e30c7fb20be9c1aa62c + languageName: node + linkType: hard + "tmp@npm:0.2.1": version: 0.2.1 resolution: "tmp@npm:0.2.1" @@ -42822,6 +47365,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.0": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 73b5c96b6e52da7e104d9d44afb5d106bb1e16d9fa7d00dbeb9e6522e61b571fbdb165c756c62164be9a3bbe192b9b268c236d370a2a0955c7689cd2ae377b95 + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -42969,7 +47519,7 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:1.2.2": +"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" bin: @@ -42978,6 +47528,13 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed + languageName: node + linkType: hard + "trim-newlines@npm:^3.0.0": version: 3.0.1 resolution: "trim-newlines@npm:3.0.1" @@ -42992,6 +47549,13 @@ __metadata: languageName: node linkType: hard +"trough@npm:^2.0.0": + version: 2.2.0 + resolution: "trough@npm:2.2.0" + checksum: 6097df63169aca1f9b08c263b1b501a9b878387f46e161dde93f6d0bba7febba93c95f876a293c5ea370f6cb03bcb687b2488c8955c3cfb66c2c0161ea8c00f6 + languageName: node + linkType: hard + "ts-api-utils@npm:^1.0.1": version: 1.0.3 resolution: "ts-api-utils@npm:1.0.3" @@ -43140,6 +47704,13 @@ __metadata: languageName: node linkType: hard +"ts-log@npm:^2.2.3": + version: 2.2.7 + resolution: "ts-log@npm:2.2.7" + checksum: c423a5eb54abb9471578902953814d3d0c88b3f237db016998f8998ecf982cb0f748bb8ebf93670eeba9b836ff0ce407d8065a340f3ab218ea7b9442c255b3d4 + languageName: node + linkType: hard + "ts-morph@npm:^13.0.2": version: 13.0.3 resolution: "ts-morph@npm:13.0.3" @@ -43485,6 +48056,13 @@ __metadata: languageName: node linkType: hard +"tween-functions@npm:^1.2.0": + version: 1.2.0 + resolution: "tween-functions@npm:1.2.0" + checksum: 880708d680eff5c347ddcb9f922ad121703a91c78ce308ed309073e73a794b633eb0b80589a839365803f150515ad34c9646809ae8a0e90f09e62686eefb1ab6 + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" @@ -43492,6 +48070,25 @@ __metadata: languageName: node linkType: hard +"twemoji-parser@npm:13.1.0": + version: 13.1.0 + resolution: "twemoji-parser@npm:13.1.0" + checksum: 8046ce003c03dd92d68c2648cfbfa39c659fca4f05c10da8d14957985dc3c0c680f3ecf2de8245dc1ddffedc5b2a675f2032053e1e77cc7474301a88fe192ad3 + languageName: node + linkType: hard + +"twemoji@npm:^13.0.1": + version: 13.1.1 + resolution: "twemoji@npm:13.1.1" + dependencies: + fs-extra: ^8.0.1 + jsonfile: ^5.0.0 + twemoji-parser: 13.1.0 + universalify: ^0.1.2 + checksum: f60a8785ad6eb1a673c4f1bccb00a852ead13639db9f763c0e3e9dee6e3e67d88f1d2b481bcee34c35570ab060918b30b6ee68aa65ea1042ad35cd83212a102a + languageName: node + linkType: hard + "twilio@npm:^3.80.1": version: 3.80.1 resolution: "twilio@npm:3.80.1" @@ -43931,6 +48528,15 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.33, ua-parser-js@npm:^1.0.35": + version: 1.0.40 + resolution: "ua-parser-js@npm:1.0.40" + bin: + ua-parser-js: script/cli.js + checksum: ae555a33dc9395dd877e295d6adbf5634e047aad7c3358328830218f3ca3a6233e35848cd355465a7612f269860e8029984389282940c7a27c9af4dfcdbba8c3 + languageName: node + linkType: hard + "uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": version: 1.0.6 resolution: "uc.micro@npm:1.0.6" @@ -44037,6 +48643,36 @@ __metadata: languageName: node linkType: hard +"unified@npm:^10.0.0": + version: 10.1.2 + resolution: "unified@npm:10.1.2" + dependencies: + "@types/unist": ^2.0.0 + bail: ^2.0.0 + extend: ^3.0.0 + is-buffer: ^2.0.0 + is-plain-obj: ^4.0.0 + trough: ^2.0.0 + vfile: ^5.0.0 + checksum: 053e7c65ede644607f87bd625a299e4b709869d2f76ec8138569e6e886903b6988b21cd9699e471eda42bee189527be0a9dac05936f1d069a5e65d0125d5d756 + languageName: node + linkType: hard + +"unified@npm:^11.0.0": + version: 11.0.5 + resolution: "unified@npm:11.0.5" + dependencies: + "@types/unist": ^3.0.0 + bail: ^2.0.0 + devlop: ^1.0.0 + extend: ^3.0.0 + is-plain-obj: ^4.0.0 + trough: ^2.0.0 + vfile: ^6.0.0 + checksum: b3bf7fd6f568cc261e074dae21188483b0f2a8ab858d62e6e85b75b96cc655f59532906ae3c64d56a9b257408722d71f1d4135292b3d7ee02907c8b592fb3cf0 + languageName: node + linkType: hard + "unique-filename@npm:^2.0.0": version: 2.0.1 resolution: "unique-filename@npm:2.0.1" @@ -44091,6 +48727,22 @@ __metadata: languageName: node linkType: hard +"unist-builder@npm:^3.0.0": + version: 3.0.1 + resolution: "unist-builder@npm:3.0.1" + dependencies: + "@types/unist": ^2.0.0 + checksum: d8c42fe69aa55a3e9aed3c581007ec5371349bf9885bfa8b0b787634f8d12fa5081f066b205ded379b6d0aeaa884039bae9ebb65a3e71784005fb110aef30d0f + languageName: node + linkType: hard + +"unist-util-generated@npm:^2.0.0": + version: 2.0.1 + resolution: "unist-util-generated@npm:2.0.1" + checksum: 6221ad0571dcc9c8964d6b054f39ef6571ed59cc0ce3e88ae97ea1c70afe76b46412a5ffaa91f96814644ac8477e23fb1b477d71f8d70e625728c5258f5c0d99 + languageName: node + linkType: hard + "unist-util-is@npm:^4.0.0": version: 4.1.0 resolution: "unist-util-is@npm:4.1.0" @@ -44098,6 +48750,60 @@ __metadata: languageName: node linkType: hard +"unist-util-is@npm:^5.0.0": + version: 5.2.1 + resolution: "unist-util-is@npm:5.2.1" + dependencies: + "@types/unist": ^2.0.0 + checksum: ae76fdc3d35352cd92f1bedc3a0d407c3b9c42599a52ab9141fe89bdd786b51f0ec5a2ab68b93fb532e239457cae62f7e39eaa80229e1cb94875da2eafcbe5c4 + languageName: node + linkType: hard + +"unist-util-is@npm:^6.0.0": + version: 6.0.0 + resolution: "unist-util-is@npm:6.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f630a925126594af9993b091cf807b86811371e465b5049a6283e08537d3e6ba0f7e248e1e7dab52cfe33f9002606acef093441137181b327f6fe504884b20e2 + languageName: node + linkType: hard + +"unist-util-position@npm:^4.0.0": + version: 4.0.4 + resolution: "unist-util-position@npm:4.0.4" + dependencies: + "@types/unist": ^2.0.0 + checksum: e7487b6cec9365299695e3379ded270a1717074fa11fd2407c9b934fb08db6fe1d9077ddeaf877ecf1813665f8ccded5171693d3d9a7a01a125ec5cdd5e88691 + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^3.0.0": + version: 3.0.3 + resolution: "unist-util-stringify-position@npm:3.0.3" + dependencies: + "@types/unist": ^2.0.0 + checksum: dbd66c15183607ca942a2b1b7a9f6a5996f91c0d30cf8966fb88955a02349d9eefd3974e9010ee67e71175d784c5a9fea915b0aa0b0df99dcb921b95c4c9e124 + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 + languageName: node + linkType: hard + "unist-util-visit-parents@npm:^3.0.0": version: 3.1.1 resolution: "unist-util-visit-parents@npm:3.1.1" @@ -44108,6 +48814,26 @@ __metadata: languageName: node linkType: hard +"unist-util-visit-parents@npm:^5.1.1": + version: 5.1.3 + resolution: "unist-util-visit-parents@npm:5.1.3" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^5.0.0 + checksum: 8ecada5978994f846b64658cf13b4092cd78dea39e1ba2f5090a5de842ba4852712c02351a8ae95250c64f864635e7b02aedf3b4a093552bb30cf1bd160efbaa + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-visit-parents@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: 08927647c579f63b91aafcbec9966dc4a7d0af1e5e26fc69f4e3e6a01215084835a2321b06f3cbe7bf7914a852830fc1439f0fc3d7153d8804ac3ef851ddfa20 + languageName: node + linkType: hard + "unist-util-visit@npm:^2.0.0": version: 2.0.3 resolution: "unist-util-visit@npm:2.0.3" @@ -44119,6 +48845,35 @@ __metadata: languageName: node linkType: hard +"unist-util-visit@npm:^4.0.0": + version: 4.1.2 + resolution: "unist-util-visit@npm:4.1.2" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^5.0.0 + unist-util-visit-parents: ^5.1.1 + checksum: 95a34e3f7b5b2d4b68fd722b6229972099eb97b6df18913eda44a5c11df8b1e27efe7206dd7b88c4ed244a48c474a5b2e2629ab79558ff9eb936840295549cee + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 + languageName: node + linkType: hard + +"universal-base64@npm:^2.1.0": + version: 2.1.0 + resolution: "universal-base64@npm:2.1.0" + checksum: 03bc6f7de04aee83038c26038cd2639f470fd9665f99b3613934c4ccde5d59047d45e34ea4c843ac582da83ea1b050bf8defba8eb390e566f0be314646ddbc9b + languageName: node + linkType: hard + "universal-user-agent@npm:^6.0.0": version: 6.0.1 resolution: "universal-user-agent@npm:6.0.1" @@ -44126,7 +48881,7 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^0.1.0": +"universalify@npm:^0.1.0, universalify@npm:^0.1.2": version: 0.1.2 resolution: "universalify@npm:0.1.2" checksum: 40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff @@ -44147,6 +48902,15 @@ __metadata: languageName: node linkType: hard +"unixify@npm:^1.0.0": + version: 1.0.0 + resolution: "unixify@npm:1.0.0" + dependencies: + normalize-path: ^2.1.1 + checksum: 3be30e48579fc6c7390bd59b4ab9e745fede0c164dfb7351cf710bd1dbef8484b1441186205af6bcb13b731c0c88caf9b33459f7bf8c89e79c046e656ae433f0 + languageName: node + linkType: hard + "unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" @@ -44227,6 +48991,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.1": + version: 1.1.2 + resolution: "update-browserslist-db@npm:1.1.2" + dependencies: + escalade: ^3.2.0 + picocolors: ^1.1.1 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 088d2bad8ddeaeccd82d87d3f6d736d5256d697b725ffaa2b601dfd0ec16ba5fad20db8dcdccf55396e1a36194236feb69e3f5cce772e5be15a5e4261ff2815d + languageName: node + linkType: hard + "update-input-width@npm:^1.2.2": version: 1.4.2 resolution: "update-input-width@npm:1.4.2" @@ -44254,7 +49032,7 @@ __metadata: languageName: node linkType: hard -"upper-case-first@npm:^1.1.0": +"upper-case-first@npm:^1.1.0, upper-case-first@npm:^1.1.2": version: 1.1.2 resolution: "upper-case-first@npm:1.1.2" dependencies: @@ -44263,13 +49041,31 @@ __metadata: languageName: node linkType: hard -"upper-case@npm:^1.0.3, upper-case@npm:^1.1.0, upper-case@npm:^1.1.1": +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: 4487db4701effe3b54ced4b3e4aa4d9ab06c548f97244d04aafb642eedf96a76d5a03cf5f38f10f415531d5792d1ac6e1b50f2a76984dc6964ad530f12876409 + languageName: node + linkType: hard + +"upper-case@npm:^1.0.3, upper-case@npm:^1.1.0, upper-case@npm:^1.1.1, upper-case@npm:^1.1.3": version: 1.1.3 resolution: "upper-case@npm:1.1.3" checksum: 991c845de75fa56e5ad983f15e58494dd77b77cadd79d273cc11e8da400067e9881ae1a52b312aed79b3d754496e2e0712e08d22eae799e35c7f9ba6f3d8a85d languageName: node linkType: hard +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: 508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -44323,6 +49119,13 @@ __metadata: languageName: node linkType: hard +"urlpattern-polyfill@npm:^10.0.0": + version: 10.0.0 + resolution: "urlpattern-polyfill@npm:10.0.0" + checksum: 61d890f151ea4ecf34a3dcab32c65ad1f3cda857c9d154af198260c6e5b2ad96d024593409baaa6d4428dd1ab206c14799bf37fe011117ac93a6a44913ac5aa4 + languageName: node + linkType: hard + "use-callback-ref@npm:^1.2.3": version: 1.2.5 resolution: "use-callback-ref@npm:1.2.5" @@ -44351,6 +49154,18 @@ __metadata: languageName: node linkType: hard +"use-deep-compare-effect@npm:^1.6.1": + version: 1.8.1 + resolution: "use-deep-compare-effect@npm:1.8.1" + dependencies: + "@babel/runtime": ^7.12.5 + dequal: ^2.0.2 + peerDependencies: + react: ">=16.13" + checksum: 2b9b6291df3f772f44d259b352e5d998963ccee0db2efeb76bb05525d928064aeeb69bb0dee5a5e428fea7cf3db67c097a770ebd30caa080662b565f6ef02b2e + languageName: node + linkType: hard + "use-isomorphic-layout-effect@npm:^1.1.2": version: 1.1.2 resolution: "use-isomorphic-layout-effect@npm:1.1.2" @@ -44412,6 +49227,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.2.0": + version: 1.4.0 + resolution: "use-sync-external-store@npm:1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: dc3843a1b59ac8bd01417bd79498d4c688d5df8bf4801be50008ef4bfaacb349058c0b1605b5b43c828e0a2d62722d7e861573b3f31cea77a7f23e8b0fc2f7e3 + languageName: node + linkType: hard + "utif@npm:^2.0.1": version: 2.0.1 resolution: "utif@npm:2.0.1" @@ -44491,6 +49315,20 @@ __metadata: languageName: node linkType: hard +"uvu@npm:^0.5.0": + version: 0.5.6 + resolution: "uvu@npm:0.5.6" + dependencies: + dequal: ^2.0.0 + diff: ^5.0.0 + kleur: ^4.0.3 + sade: ^1.7.3 + bin: + uvu: bin.js + checksum: 09460a37975627de9fcad396e5078fb844d01aaf64a6399ebfcfd9e55f1c2037539b47611e8631f89be07656962af0cf48c334993db82b9ae9c3d25ce3862168 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -44526,6 +49364,13 @@ __metadata: languageName: node linkType: hard +"value-or-promise@npm:^1.0.12": + version: 1.0.12 + resolution: "value-or-promise@npm:1.0.12" + checksum: f53a66c75b7447c90bbaf946a757ca09c094629cb80ba742f59c980ec3a69be0a385a0e75505dedb4e757862f1a994ca4beaf083a831f24d3ffb3d4bb18cd1e1 + languageName: node + linkType: hard + "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -44544,6 +49389,58 @@ __metadata: languageName: node linkType: hard +"vfile-location@npm:^4.0.0": + version: 4.1.0 + resolution: "vfile-location@npm:4.1.0" + dependencies: + "@types/unist": ^2.0.0 + vfile: ^5.0.0 + checksum: c894e8e5224170d1f85288f4a1d1ebcee0780823ea2b49d881648ab360ebf01b37ecb09b1c4439a75f9a51f31a9f9742cd045e987763e367c352a1ef7c50d446 + languageName: node + linkType: hard + +"vfile-message@npm:^3.0.0": + version: 3.1.4 + resolution: "vfile-message@npm:3.1.4" + dependencies: + "@types/unist": ^2.0.0 + unist-util-stringify-position: ^3.0.0 + checksum: d0ee7da1973ad76513c274e7912adbed4d08d180eaa34e6bd40bc82459f4b7bc50fcaff41556135e3339995575eac5f6f709aba9332b80f775618ea4880a1367 + languageName: node + linkType: hard + +"vfile-message@npm:^4.0.0": + version: 4.0.2 + resolution: "vfile-message@npm:4.0.2" + dependencies: + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 964e7e119f4c0e0270fc269119c41c96da20afa01acb7c9809a88365c8e0c64aa692fafbd952669382b978002ecd7ad31ef4446d85e8a22cdb62f6df20186c2d + languageName: node + linkType: hard + +"vfile@npm:^5.0.0": + version: 5.3.7 + resolution: "vfile@npm:5.3.7" + dependencies: + "@types/unist": ^2.0.0 + is-buffer: ^2.0.0 + unist-util-stringify-position: ^3.0.0 + vfile-message: ^3.0.0 + checksum: 642cce703afc186dbe7cabf698dc954c70146e853491086f5da39e1ce850676fc96b169fcf7898aa3ff245e9313aeec40da93acd1e1fcc0c146dc4f6308b4ef9 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": ^3.0.0 + vfile-message: ^4.0.0 + checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af + languageName: node + linkType: hard + "victory-vendor@npm:^36.6.8": version: 36.9.2 resolution: "victory-vendor@npm:36.9.2" @@ -45000,6 +49897,21 @@ __metadata: languageName: node linkType: hard +"wait-on@npm:^7.0.1": + version: 7.2.0 + resolution: "wait-on@npm:7.2.0" + dependencies: + axios: ^1.6.1 + joi: ^17.11.0 + lodash: ^4.17.21 + minimist: ^1.2.8 + rxjs: ^7.8.1 + bin: + wait-on: bin/wait-on + checksum: 69ec1432bb4479363fdd71f2f3f501a98aa356a562781108a4a89ef8fdf1e3d5fd0c2fd56c4cc5902abbb662065f1f22d4e436a1e6fc9331ce8b575eb023325e + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -45009,6 +49921,15 @@ __metadata: languageName: node linkType: hard +"warning@npm:^4.0.3": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: ^1.0.0 + checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c + languageName: node + linkType: hard + "watchpack@npm:2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" @@ -45045,6 +49966,13 @@ __metadata: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: b6d9f02f1a43d0ef0848a812d89c83801d5bbad57d8bb61f02eb6d7eb794c3736f6cc2e1191664bb26136594c8218ac609f4069722c6f56d9fc2d808fa9271c6 + languageName: node + linkType: hard + "web-push@npm:^3.6.7": version: 3.6.7 resolution: "web-push@npm:3.6.7" @@ -45067,7 +49995,14 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:^3.0.3": +"web-streams-polyfill@npm:4.0.0-beta.3": + version: 4.0.0-beta.3 + resolution: "web-streams-polyfill@npm:4.0.0-beta.3" + checksum: dfec1fbf52b9140e4183a941e380487b6c3d5d3838dd1259be81506c1c9f2abfcf5aeb670aeeecfd9dff4271a6d8fef931b193c7bedfb42542a3b05ff36c0d16 + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.0.3, web-streams-polyfill@npm:^3.2.0": version: 3.3.3 resolution: "web-streams-polyfill@npm:3.3.3" checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb @@ -45081,6 +50016,19 @@ __metadata: languageName: node linkType: hard +"webcrypto-core@npm:^1.8.0": + version: 1.8.1 + resolution: "webcrypto-core@npm:1.8.1" + dependencies: + "@peculiar/asn1-schema": ^2.3.13 + "@peculiar/json-schema": ^1.1.12 + asn1js: ^3.0.5 + pvtsutils: ^1.3.5 + tslib: ^2.7.0 + checksum: 5f8d862991bf9b36bfec53a317ceec7de95010c6d16092d2ba62b988acdfca9adfd254ef388036e610c66d0aad506d3f344574f62e06501a8c72ef961c8996b2 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -45387,6 +50335,13 @@ __metadata: languageName: node linkType: hard +"which-module@npm:^1.0.0": + version: 1.0.0 + resolution: "which-module@npm:1.0.0" + checksum: 98434f7deb36350cb543c1f15612188541737e1f12d39b23b1c371dff5cf4aa4746210f2bdec202d5fe9da8682adaf8e3f7c44c520687d30948cfc59d5534edb + languageName: node + linkType: hard + "which-module@npm:^2.0.0": version: 2.0.1 resolution: "which-module@npm:2.0.1" @@ -45509,6 +50464,15 @@ __metadata: languageName: node linkType: hard +"window-size@npm:^0.2.0": + version: 0.2.0 + resolution: "window-size@npm:0.2.0" + bin: + window-size: cli.js + checksum: a85e2acf156cfa194301294809867bdadd8a48ee5d972d9fa8e3e1b3420a1d0201b13ac8eb0348a0d14bbf2c3316565b6a749749c2384c5d286caf8a064c4f90 + languageName: node + linkType: hard + "windows-release@npm:^5.0.1": version: 5.1.1 resolution: "windows-release@npm:5.1.1" @@ -45584,6 +50548,16 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^2.0.0": + version: 2.1.0 + resolution: "wrap-ansi@npm:2.1.0" + dependencies: + string-width: ^1.0.1 + strip-ansi: ^3.0.1 + checksum: 2dacd4b3636f7a53ee13d4d0fe7fa2ed9ad81e9967e17231924ea88a286ec4619a78288de8d41881ee483f4449ab2c0287cde8154ba1bd0126c10271101b2ee3 + languageName: node + linkType: hard + "wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" @@ -45685,7 +50659,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.0": +"ws@npm:^8.17.1, ws@npm:^8.18.0": version: 8.18.0 resolution: "ws@npm:8.18.0" peerDependencies: @@ -45782,7 +50756,7 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:0.6.2, xml2js@npm:^0.6.2": +"xml2js@npm:0.6.2, xml2js@npm:^0.6.0, xml2js@npm:^0.6.2": version: 0.6.2 resolution: "xml2js@npm:0.6.2" dependencies: @@ -45901,6 +50875,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^3.2.1": + version: 3.2.2 + resolution: "y18n@npm:3.2.2" + checksum: 6154fd7544f8bbf5b18cdf77692ed88d389be49c87238ecb4e0d6a5276446cd2a5c29cc4bdbdddfc7e4e498b08df9d7e38df4a1453cf75eecfead392246ea74a + languageName: node + linkType: hard + "y18n@npm:^4.0.0": version: 4.0.3 resolution: "y18n@npm:4.0.3" @@ -45936,6 +50917,13 @@ __metadata: languageName: node linkType: hard +"yaml-ast-parser@npm:^0.0.43": + version: 0.0.43 + resolution: "yaml-ast-parser@npm:0.0.43" + checksum: fb5df4c067b6ccbd00953a46faf6ff27f0e290d623c712dc41f330251118f110e22cfd184bbff498bd969cbcda3cd27e0f9d0adb9e6d90eb60ccafc0d8e28077 + languageName: node + linkType: hard + "yaml@npm:2.0.0-1": version: 2.0.0-1 resolution: "yaml@npm:2.0.0-1" @@ -45981,6 +50969,16 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "yargs-parser@npm:3.2.0" + dependencies: + camelcase: ^3.0.0 + lodash.assign: ^4.1.0 + checksum: d86fd69816a28a617f4cad21f7bfe7f7c931b16d063c73449cd914db409b8d5981a0f29f9e2a05014a7229164aa47336adf5a8856fa9870ab892346d29138e9a + languageName: node + linkType: hard + "yargs@npm:^15.1.0, yargs@npm:^15.3.1": version: 15.4.1 resolution: "yargs@npm:15.4.1" @@ -46015,7 +51013,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.1, yargs@npm:^17.7.2": +"yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -46030,6 +51028,28 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^5.0.0": + version: 5.0.0 + resolution: "yargs@npm:5.0.0" + dependencies: + cliui: ^3.2.0 + decamelize: ^1.1.1 + get-caller-file: ^1.0.1 + lodash.assign: ^4.2.0 + os-locale: ^1.4.0 + read-pkg-up: ^1.0.1 + require-directory: ^2.1.1 + require-main-filename: ^1.0.1 + set-blocking: ^2.0.0 + string-width: ^1.0.2 + which-module: ^1.0.0 + window-size: ^0.2.0 + y18n: ^3.2.1 + yargs-parser: ^3.2.0 + checksum: 478e9c8562c5cf5b9c2efc7c917e0b00c489cc50153d5d911ab68767c2cfddaf0b5bd4e04d6fefc00cb1d8f6263eab1d73df79a24d650ba95f5d45c819a1585a + languageName: node + linkType: hard + "yarn@npm:^1.22.18": version: 1.22.19 resolution: "yarn@npm:1.22.19" @@ -46230,3 +51250,10 @@ __metadata: checksum: 160052a7faaefbaad1071e890a06e5d7a04f6ff6985def30a7b4471f4ddbdd1d30bb05b3688a2777cd0b717d1f0d98dad24883a5caa3deeb3afb4d83b6dabc55 languageName: node linkType: hard + +"zwitch@npm:^2.0.0, zwitch@npm:^2.0.4": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 + languageName: node + linkType: hard