refactor: platform org event types (#15961)

* chore: platform-libraries export updateNewTeamMemberEventTypes

* chore: platform-libraries export updateNewTeamMemberEventTypes

* chore: v2 api bump platform-libraries to 0.0.22

* fix: add created managed user to assignAllMembers: true team event-types

* fix: add created organizations user to assignAllMembers: true team event-types

* refactor: organization teams chose if team creator is auto-accepted

* refactor: managed users have only 2 default event types with video

* refactor: lowercase schedulingType for creating team event-types

* refactor: lowercase schedulingType for creating team event-types

* feat: separate platform teams by OAuth client

* swagger regenerate

* refactor: delete platform team when oauth client deleted

* revert: keep 4 default event-types

* fix: tests && merge of main into this branch

* fix: typecheck

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
Lauris Skraucis
2024-07-30 22:23:31 +03:00
committed by GitHub
co-authored by Morgan
parent 2f946fc5da
commit 41e565fcf8
45 changed files with 1186 additions and 296 deletions
+1 -1
View File
@@ -26,7 +26,7 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-libraries-0.0.21": "npm:@calcom/platform-libraries@0.0.21",
"@calcom/platform-libraries-0.0.22": "npm:@calcom/platform-libraries@0.0.22",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
"@calcom/prisma": "*",
@@ -19,7 +19,7 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { handleNewBooking } from "@calcom/platform-libraries-0.0.21";
import { handleNewBooking } from "@calcom/platform-libraries-0.0.22";
import { ApiSuccessResponse, ApiResponse } from "@calcom/platform-types";
describe("Bookings Endpoints", () => {
@@ -46,7 +46,7 @@ import {
getBookingInfo,
handleCancelBooking,
getBookingForReschedule,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { GetBookingsInput, CancelBookingInput, Status } from "@calcom/platform-types";
import { ApiResponse } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@@ -1,7 +1,7 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { IsBoolean, IsNumber, IsOptional } from "class-validator";
import type { AppsStatus } from "@calcom/platform-libraries-0.0.21";
import type { AppsStatus } from "@calcom/platform-libraries-0.0.22";
export class CreateRecurringBookingInput extends CreateBookingInput {
@IsBoolean()
@@ -5,7 +5,7 @@ import { BadRequestException, UnauthorizedException } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
import { SUCCESS_STATUS, APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
import { symmetricEncrypt, CalendarService } from "@calcom/platform-libraries-0.0.21";
import { symmetricEncrypt, CalendarService } from "@calcom/platform-libraries-0.0.22";
@Injectable()
export class AppleCalendarService implements CredentialSyncCalendarApp {
@@ -18,7 +18,7 @@ import { User } from "@prisma/client";
import { DateTime } from "luxon";
import { z } from "zod";
import { getConnectedDestinationCalendars, getBusyCalendarTimes } from "@calcom/platform-libraries-0.0.21";
import { getConnectedDestinationCalendars, getBusyCalendarTimes } from "@calcom/platform-libraries-0.0.22";
import { Calendar } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@@ -35,7 +35,7 @@ import {
EventTypesPublic,
eventTypeBookingFields,
eventTypeLocations,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { ApiSuccessResponse } from "@calcom/platform-types";
describe("Event types Endpoints", () => {
@@ -4,7 +4,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import { getEventTypeById } from "@calcom/platform-libraries-0.0.21";
import { getEventTypeById } from "@calcom/platform-libraries-0.0.22";
import type { PrismaClient } from "@calcom/prisma";
@Injectable()
@@ -14,7 +14,7 @@ import {
updateEventType,
EventTypesPublic,
getEventTypesPublic,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { EventType } from "@calcom/prisma/client";
@Injectable()
@@ -7,7 +7,7 @@ import {
getEventTypeById,
transformApiEventTypeBookingFields,
transformApiEventTypeLocations,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
import type { PrismaClient } from "@calcom/prisma";
@@ -10,9 +10,9 @@ import { UsersService } from "@/modules/users/services/users.service";
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { createEventType, updateEventType } from "@calcom/platform-libraries-0.0.21";
import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-libraries-0.0.21";
import { dynamicEvent } from "@calcom/platform-libraries-0.0.21";
import { createEventType, updateEventType } from "@calcom/platform-libraries-0.0.22";
import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-libraries-0.0.22";
import { dynamicEvent } from "@calcom/platform-libraries-0.0.22";
import {
CreateEventTypeInput_2024_06_14,
UpdateEventTypeInput_2024_06_14,
@@ -3,7 +3,7 @@ import { Injectable } from "@nestjs/common";
import {
transformApiEventTypeBookingFields,
transformApiEventTypeLocations,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
@Injectable()
@@ -9,7 +9,7 @@ import {
parseRecurringEvent,
TransformedLocationsSchema,
BookingFieldsSchema,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
type EventTypeRelations = { users: User[]; schedule: Schedule | null };
type DatabaseEventType = EventType & EventTypeRelations;
@@ -3,7 +3,7 @@ import { Injectable } from "@nestjs/common";
import {
transformApiScheduleOverrides,
transformApiScheduleAvailability,
} from "@calcom/platform-libraries-0.0.21";
} from "@calcom/platform-libraries-0.0.22";
import { CreateScheduleInput_2024_06_11, ScheduleAvailabilityInput_2024_06_11 } from "@calcom/platform-types";
import { ScheduleOverrideInput_2024_06_11 } from "@calcom/platform-types";
@@ -2,7 +2,7 @@ import { ArgumentsHost, Catch, ExceptionFilter, Logger } from "@nestjs/common";
import { Request } from "express";
import { ERROR_STATUS } from "@calcom/platform-constants";
import { TRPCError } from "@calcom/platform-libraries-0.0.21";
import { TRPCError } from "@calcom/platform-libraries-0.0.22";
import { Response } from "@calcom/platform-types";
@Catch(TRPCError)
@@ -1,6 +1,6 @@
import { Injectable } from "@nestjs/common";
import { sendSignupToOrganizationEmail, getTranslation } from "@calcom/platform-libraries-0.0.21";
import { sendSignupToOrganizationEmail, getTranslation } from "@calcom/platform-libraries-0.0.22";
@Injectable()
export class EmailService {
@@ -14,7 +14,7 @@ import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { PlatformOAuthClient, Team, User } from "@prisma/client";
import { PlatformOAuthClient, Team, User, EventType } from "@prisma/client";
import * as request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
@@ -204,10 +204,16 @@ describe("OAuth Client Users Endpoints", () => {
// note(Lauris): to determine count see default event types created in EventTypesService.createUserDefaultEventTypes
expect(defaultEventTypes?.length).toEqual(4);
expect(
defaultEventTypes?.find((eventType) => eventType.length === DEFAULT_EVENT_TYPES.thirtyMinutes.length)
defaultEventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutes.slug)
).toBeTruthy();
expect(
defaultEventTypes?.find((eventType) => eventType.length === DEFAULT_EVENT_TYPES.sixtyMinutes.length)
defaultEventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutes.slug)
).toBeTruthy();
expect(
defaultEventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutesVideo.slug)
).toBeTruthy();
expect(
defaultEventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutesVideo.slug)
).toBeTruthy();
}
@@ -301,4 +307,243 @@ describe("OAuth Client Users Endpoints", () => {
await app.close();
});
});
describe("User team even-types", () => {
let app: INestApplication;
let oAuthClient1: PlatformOAuthClient;
let oAuthClient2: PlatformOAuthClient;
let organization: Team;
let team1: Team;
let team2: Team;
let owner: User;
let managedEventType1: EventType;
let userRepositoryFixture: UserRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let profileRepositoryFixture: ProfileRepositoryFixture;
let postResponseData: CreateUserResponse;
const userEmail = "oauth-client-users-user@gmail.com";
const userTimeZone = "Europe/Rome";
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule],
}).compile();
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
organization = await teamRepositoryFixture.create({
name: "Testy Organization",
isOrganization: true,
});
owner = await userRepositoryFixture.create({
email: userEmail,
username: userEmail,
organization: { connect: { id: organization.id } },
});
await profileRepositoryFixture.create({
uid: `usr-${owner.id}`,
username: userEmail,
organization: {
connect: {
id: organization.id,
},
},
user: {
connect: {
id: owner.id,
},
},
});
oAuthClient1 = await createOAuthClient(organization.id);
oAuthClient2 = await createOAuthClient(organization.id);
team1 = await teamRepositoryFixture.create({
name: "Testy org team",
isOrganization: false,
parent: { connect: { id: organization.id } },
createdByOAuthClient: { connect: { id: oAuthClient1.id } },
});
team2 = await teamRepositoryFixture.create({
name: "Testy org team 2",
isOrganization: false,
parent: { connect: { id: organization.id } },
createdByOAuthClient: { connect: { id: oAuthClient2.id } },
});
// note(Lauris): team1 team event-types
await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team1.id },
},
title: "Collective Event Type",
slug: "collective-event-type",
length: 30,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
managedEventType1 = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "MANAGED",
team: {
connect: { id: team1.id },
},
title: "Managed Event Type",
slug: "managed-event-type",
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
// note(Lauris): team2 team event-types
await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team2.id },
},
title: "Collective Event Type team 2",
slug: "collective-event-type-team-2",
length: 30,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "MANAGED",
team: {
connect: { id: team2.id },
},
title: "Managed Event Type team 2",
slug: "managed-event-type-team-2",
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: [CLIENT_REDIRECT_URI],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should be defined", () => {
expect(oauthClientRepositoryFixture).toBeDefined();
expect(userRepositoryFixture).toBeDefined();
expect(oAuthClient1).toBeDefined();
});
it(`should create managed user and update team event-types of OAuthClient marked as assignAllTeamMembers: true`, async () => {
const requestBody: CreateManagedUserInput = {
email: userEmail,
timeZone: userTimeZone,
weekStart: "Monday",
timeFormat: 24,
locale: Locales.FR,
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${oAuthClient1.id}/users`)
.set("x-cal-secret-key", oAuthClient1.secret)
.send(requestBody)
.expect(201);
const responseBody: ApiSuccessResponse<{
user: Omit<User, "password">;
accessToken: string;
refreshToken: string;
}> = response.body;
postResponseData = responseBody.data;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
await userHasCorrectEventTypes(responseBody.data.user.id);
await teamHasCorrectEventTypes(team1.id);
});
async function userHasCorrectEventTypes(userId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
expect(eventTypes?.length).toEqual(5);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
// note(Lauris): default event types
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutes.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutes.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutesVideo.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutesVideo.slug)
).toBeTruthy();
}
async function teamHasCorrectEventTypes(teamId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
expect(eventTypes?.length).toEqual(2);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
expect(collective).toBeTruthy();
expect(collective?.hosts).toBeDefined();
expect(collective?.hosts?.length).toEqual(1);
expect(collective?.hosts[0].userId).toEqual(postResponseData.user.id);
}
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient1.id);
await teamRepositoryFixture.delete(organization.id);
await userRepositoryFixture.delete(owner.id);
try {
await userRepositoryFixture.delete(postResponseData.user.id);
} catch (e) {
// User might have been deleted by the test
}
await app.close();
});
});
});
@@ -78,6 +78,7 @@ export class OAuthClientUsersController {
`Creating user with data: ${JSON.stringify(body, null, 2)} for OAuth Client with ID ${oAuthClientId}`
);
const client = await this.oauthRepository.getOAuthClient(oAuthClientId);
console.log("asap createUser client", JSON.stringify(client, null, 2));
const isPlatformManaged = true;
const { user, tokens } = await this.oAuthClientUsersService.createOauthClientUser(
@@ -10,6 +10,7 @@ import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repo
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { OrganizationsModule } from "@/modules/organizations/organizations.module";
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { StripeModule } from "@/modules/stripe/stripe.module";
@@ -33,7 +34,13 @@ import { Global, Module } from "@nestjs/common";
BillingModule,
SchedulesModule_2024_04_15,
],
providers: [OAuthClientRepository, TokensRepository, OAuthFlowService, OAuthClientUsersService],
providers: [
OAuthClientRepository,
TokensRepository,
OAuthFlowService,
OAuthClientUsersService,
OrganizationsTeamsService,
],
controllers: [OAuthClientUsersController, OAuthClientsController, OAuthFlowController],
exports: [OAuthClientRepository],
})
@@ -1,5 +1,6 @@
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
@@ -7,7 +8,7 @@ import { UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, Injectable } from "@nestjs/common";
import { User } from "@prisma/client";
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries-0.0.21";
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries-0.0.22";
@Injectable()
export class OAuthClientUsersService {
@@ -15,7 +16,8 @@ export class OAuthClientUsersService {
private readonly userRepository: UsersRepository,
private readonly tokensRepository: TokensRepository,
private readonly eventTypesService: EventTypesService_2024_04_15,
private readonly schedulesService: SchedulesService_2024_04_15
private readonly schedulesService: SchedulesService_2024_04_15,
private readonly organizationsTeamsService: OrganizationsTeamsService
) {}
async createOauthClientUser(
@@ -78,6 +80,8 @@ export class OAuthClientUsersService {
user.defaultScheduleId = defaultSchedule.id;
}
await this.organizationsTeamsService.addUserToPlatformTeamEvents(user.id, organizationId, oAuthClientId);
return {
user,
tokens: {
@@ -248,7 +248,9 @@ describe("Organizations Event Types Endpoints", () => {
options: ["javascript", "python", "cobol"],
},
],
schedulingType: "COLLECTIVE",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
schedulingType: "collective",
hosts: [
{
userId: teammate1.id,
@@ -270,6 +272,7 @@ describe("Organizations Event Types Endpoints", () => {
const data = responseBody.data;
expect(data.title).toEqual(body.title);
expect(data.hosts.length).toEqual(2);
expect(data.schedulingType).toEqual("COLLECTIVE");
evaluateHost(body.hosts[0], data.hosts[0]);
evaluateHost(body.hosts[1], data.hosts[1]);
@@ -385,7 +388,6 @@ describe("Organizations Event Types Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data = responseBody.data;
console.log("asap responseBody.data", JSON.stringify(responseBody.data, null, 2));
expect(data.length).toEqual(2);
const eventTypeCollective = data.find((eventType) => eventType.schedulingType === "COLLECTIVE");
@@ -11,20 +11,20 @@ import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import * as request from "supertest";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { SUCCESS_STATUS, X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
import { ApiSuccessResponse } from "@calcom/platform-types";
import { Team } from "@calcom/prisma/client";
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
describe("Organizations Team Endpoints", () => {
describe("User Authentication - User is Org Admin", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: TeamRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
@@ -32,6 +32,7 @@ describe("Organizations Team Endpoints", () => {
let team: Team;
let team2: Team;
let teamCreatedViaApi: Team;
let teamCreatedViaApi2: Team;
const userEmail = "org-admin-teams-controller-e2e@api.com";
let user: User;
@@ -45,7 +46,6 @@ describe("Organizations Team Endpoints", () => {
).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
@@ -54,7 +54,7 @@ describe("Organizations Team Endpoints", () => {
username: userEmail,
});
org = await organizationsRepositoryFixture.create({
org = await teamsRepositoryFixture.create({
name: "Test Organization",
isOrganization: true,
});
@@ -85,7 +85,7 @@ describe("Organizations Team Endpoints", () => {
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(organizationsRepositoryFixture).toBeDefined();
expect(teamsRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
expect(org).toBeDefined();
});
@@ -147,6 +147,7 @@ describe("Organizations Team Endpoints", () => {
teamCreatedViaApi.id
);
expect(membership?.role ?? "").toEqual("OWNER");
expect(membership?.accepted).toEqual(true);
});
});
@@ -199,11 +200,35 @@ describe("Organizations Team Endpoints", () => {
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/123132145`).expect(404);
});
it("should create the team of the org without auto-accepting creator", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams`)
.send({
name: "Team II created via API",
autoAcceptCreator: false,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
teamCreatedViaApi2 = responseBody.data;
expect(teamCreatedViaApi2.name).toEqual("Team II created via API");
expect(teamCreatedViaApi2.parentId).toEqual(org.id);
const membership = await membershipsRepositoryFixture.getUserMembershipByTeamId(
user.id,
teamCreatedViaApi2.id
);
expect(membership?.role ?? "").toEqual("OWNER");
expect(membership?.accepted).toEqual(false);
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await teamsRepositoryFixture.delete(team.id);
await teamsRepositoryFixture.delete(team2.id);
await organizationsRepositoryFixture.delete(org.id);
await teamsRepositoryFixture.delete(teamCreatedViaApi2.id);
await teamsRepositoryFixture.delete(org.id);
await app.close();
});
});
@@ -214,7 +239,6 @@ describe("Organizations Team Endpoints", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: TeamRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
@@ -234,7 +258,6 @@ describe("Organizations Team Endpoints", () => {
).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
@@ -243,7 +266,7 @@ describe("Organizations Team Endpoints", () => {
username: userEmail,
});
org = await organizationsRepositoryFixture.create({
org = await teamsRepositoryFixture.create({
name: "Test Organization",
isOrganization: true,
});
@@ -274,7 +297,7 @@ describe("Organizations Team Endpoints", () => {
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(organizationsRepositoryFixture).toBeDefined();
expect(teamsRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
expect(org).toBeDefined();
});
@@ -317,7 +340,7 @@ describe("Organizations Team Endpoints", () => {
await userRepositoryFixture.deleteByEmail(user.email);
await teamsRepositoryFixture.delete(team.id);
await teamsRepositoryFixture.delete(team2.id);
await organizationsRepositoryFixture.delete(org.id);
await teamsRepositoryFixture.delete(org.id);
await app.close();
});
});
@@ -448,3 +471,158 @@ describe("Organizations Team Endpoints", () => {
});
});
});
describe("Organizations Team Endpoints", () => {
describe("Platform teams", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let oAuthClient1: PlatformOAuthClient;
let oAuthClient2: PlatformOAuthClient;
let org: Team;
let team1: Team;
let team2: Team;
const userEmail = "platform-org-member-teams-owner-controller-e2e@api.com";
let user: User;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
})
).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
user = await userRepositoryFixture.create({
email: userEmail,
username: userEmail,
});
org = await teamsRepositoryFixture.create({
name: "Platform Test Organization",
isOrganization: true,
});
await membershipsRepositoryFixture.create({
role: "ADMIN",
user: { connect: { id: user.id } },
team: { connect: { id: org.id } },
});
oAuthClient1 = await createOAuthClient(org.id);
oAuthClient2 = await createOAuthClient(org.id);
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["redirect-uri"],
permissions: 32,
};
const secret = "secret";
return await oauthClientRepositoryFixture.create(organizationId, data, secret);
}
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(teamsRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
expect(org).toBeDefined();
});
it("should create first oAuth client team", async () => {
const teamName = "Platform team created via API";
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams`)
.set(X_CAL_CLIENT_ID, oAuthClient1.id)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
team1 = responseBody.data;
expect(team1.name).toEqual(teamName);
expect(team1.parentId).toEqual(org.id);
const membership = await membershipsRepositoryFixture.getUserMembershipByTeamId(user.id, team1.id);
expect(membership?.role ?? "").toEqual("OWNER");
expect(membership?.accepted).toEqual(true);
});
});
it("should create second oAuth client team", async () => {
const teamName = "Platform team II created via API";
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams`)
.set(X_CAL_CLIENT_ID, oAuthClient2.id)
.set(X_CAL_SECRET_KEY, oAuthClient2.secret)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
team2 = responseBody.data;
expect(team2.name).toEqual(teamName);
expect(team2.parentId).toEqual(org.id);
const membership = await membershipsRepositoryFixture.getUserMembershipByTeamId(user.id, team2.id);
expect(membership?.role ?? "").toEqual("OWNER");
expect(membership?.accepted).toEqual(true);
});
});
it("should get all the platform teams correctly tied to OAuth clients", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams`)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team[]> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data[0].id).toEqual(team1.id);
expect(responseBody.data[1].id).toEqual(team2.id);
const oAuthClientTeams = await teamsRepositoryFixture.getPlatformOrgTeams(org.id, oAuthClient1.id);
expect(oAuthClientTeams.length).toEqual(1);
const oAuthClientTeam = oAuthClientTeams[0];
expect(oAuthClientTeam.id).toEqual(team1.id);
expect(oAuthClientTeam.name).toEqual(team1.name);
const oAuthClient2Teams = await teamsRepositoryFixture.getPlatformOrgTeams(org.id, oAuthClient2.id);
expect(oAuthClient2Teams.length).toEqual(1);
const oAuthClientTeam2 = oAuthClient2Teams[0];
expect(oAuthClientTeam2.id).toEqual(team2.id);
expect(oAuthClientTeam2.name).toEqual(team2.name);
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await teamsRepositoryFixture.delete(team1.id);
await teamsRepositoryFixture.delete(org.id);
await app.close();
});
});
});
@@ -9,6 +9,7 @@ import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
import { CreateOrgTeamDto } from "@/modules/organizations/inputs/create-organization-team.input";
import { UpdateOrgTeamDto } from "@/modules/organizations/inputs/update-organization-team.input";
import {
OrgMeTeamOutputDto,
OrgMeTeamsOutputResponseDto,
@@ -28,11 +29,12 @@ import {
Patch,
Post,
Body,
Headers,
} from "@nestjs/common";
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { SUCCESS_STATUS, X_CAL_CLIENT_ID } from "@calcom/platform-constants";
import { OrgTeamOutputDto } from "@calcom/platform-types";
import { SkipTakePagination } from "@calcom/platform-types";
import { Team } from "@calcom/prisma/client";
@@ -126,7 +128,7 @@ export class OrganizationsTeamsController {
async updateTeam(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body() body: CreateOrgTeamDto
@Body() body: UpdateOrgTeamDto
): Promise<OrgTeamOutputResponseDto> {
const team = await this.organizationsTeamsService.updateOrgTeam(orgId, teamId, body);
return {
@@ -142,9 +144,13 @@ export class OrganizationsTeamsController {
async createTeam(
@Param("orgId", ParseIntPipe) orgId: number,
@Body() body: CreateOrgTeamDto,
@GetUser() user: UserWithProfile
@GetUser() user: UserWithProfile,
@Headers(X_CAL_CLIENT_ID) oAuthClientId?: string
): Promise<OrgTeamOutputResponseDto> {
const team = await this.organizationsTeamsService.createOrgTeam(orgId, body, user);
const team = oAuthClientId
? await this.organizationsTeamsService.createPlatformOrgTeam(orgId, oAuthClientId, body, user)
: await this.organizationsTeamsService.createOrgTeam(orgId, body, user);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamOutputDto, team, { strategy: "excludeAll" }),
@@ -8,6 +8,7 @@ import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import * as request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
@@ -15,7 +16,7 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { User, Team, Membership } from "@calcom/prisma/client";
import { User, Team, EventType } from "@calcom/prisma/client";
describe("Organizations Users Endpoints", () => {
describe("Member role", () => {
@@ -29,7 +30,6 @@ describe("Organizations Users Endpoints", () => {
const userEmail = "member1@org.com";
let user: User;
let org: Team;
let membership: Membership;
beforeAll(async () => {
const moduleRef = await withApiAuth(
@@ -70,7 +70,7 @@ describe("Organizations Users Endpoints", () => {
},
});
membership = await membershipFixtures.addUserToOrg(user, org, "MEMBER", true);
await membershipFixtures.addUserToOrg(user, org, "MEMBER", true);
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
@@ -344,4 +344,165 @@ describe("Organizations Users Endpoints", () => {
await app.close();
});
});
describe("Member event-types", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: TeamRepositoryFixture;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let membershipFixtures: MembershipRepositoryFixture;
let profileRepositoryFixture: ProfileRepositoryFixture;
const authEmail = "auth@org.com";
let user: User;
let org: Team;
let team: Team;
let managedEventType: EventType;
let createdUser: User;
beforeAll(async () => {
const moduleRef = await withApiAuth(
authEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
})
).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
org = await organizationsRepositoryFixture.create({
name: "Test org 4",
isOrganization: true,
});
team = await organizationsRepositoryFixture.create({
name: "Test org 4 team",
isOrganization: false,
parent: { connect: { id: org.id } },
});
user = await userRepositoryFixture.create({
email: authEmail,
username: authEmail,
organization: { connect: { id: org.id } },
});
await profileRepositoryFixture.create({
uid: `usr-${user.id}`,
username: authEmail,
organization: {
connect: {
id: org.id,
},
},
user: {
connect: {
id: user.id,
},
},
});
await membershipFixtures.addUserToOrg(user, org, "ADMIN", true);
await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team.id },
},
title: "Collective Event Type",
slug: "collective-event-type",
length: 30,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
managedEventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "MANAGED",
team: {
connect: { id: team.id },
},
title: "Managed Event Type",
slug: "managed-event-type",
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(organizationsRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
expect(org).toBeDefined();
});
it("should create a new org user with team event-types", async () => {
const newOrgUser = {
email: "new-org-member-d@org.com",
organizationRole: "MEMBER",
autoAccept: true,
};
const { body } = await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/users`)
.send({
email: newOrgUser.email,
})
.set("Content-Type", "application/json")
.set("Accept", "application/json");
const userData = body.data;
expect(body.status).toBe(SUCCESS_STATUS);
userHasCorrectEventTypes(userData.id);
createdUser = userData;
teamHasCorrectEventTypes(team.id);
});
async function userHasCorrectEventTypes(userId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
expect(eventTypes?.length).toEqual(1);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
}
async function teamHasCorrectEventTypes(teamId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
expect(eventTypes?.length).toEqual(2);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
expect(collective).toBeTruthy();
expect(collective?.hosts).toBeDefined();
expect(collective?.hosts?.length).toEqual(1);
expect(collective?.hosts[0].userId).toEqual(createdUser.id);
}
afterAll(async () => {
// await membershipFixtures.delete(membership.id);
await userRepositoryFixture.deleteByEmail(user.email);
await userRepositoryFixture.deleteByEmail(createdUser.email);
await organizationsRepositoryFixture.delete(org.id);
await app.close();
await app.close();
});
});
});
@@ -72,4 +72,8 @@ export class CreateOrgTeamDto {
@IsOptional()
@IsString()
readonly weekStart?: string = "Sunday";
@IsOptional()
@IsBoolean()
readonly autoAcceptCreator?: boolean = true;
}
@@ -0,0 +1,75 @@
import { IsBoolean, IsOptional, IsString, IsUrl, Length } from "class-validator";
export class UpdateOrgTeamDto {
@IsString()
@Length(1)
readonly name?: string;
@IsOptional()
@IsString()
readonly slug?: string;
@IsOptional()
@IsUrl()
readonly logoUrl?: string;
@IsOptional()
@IsUrl()
readonly calVideoLogo?: string;
@IsOptional()
@IsUrl()
readonly appLogo?: string;
@IsOptional()
@IsUrl()
readonly appIconLogo?: string;
@IsOptional()
@IsString()
readonly bio?: string;
@IsOptional()
@IsBoolean()
readonly hideBranding?: boolean = false;
@IsOptional()
@IsBoolean()
readonly isPrivate?: boolean;
@IsOptional()
@IsBoolean()
readonly hideBookATeamMember?: boolean;
@IsOptional()
@IsString()
readonly metadata?: string; // Assuming metadata is a JSON string. Adjust accordingly if it's a nested object.
@IsOptional()
@IsString()
readonly theme?: string;
@IsOptional()
@IsString()
readonly brandColor?: string;
@IsOptional()
@IsString()
readonly darkBrandColor?: string;
@IsOptional()
@IsUrl()
readonly bannerUrl?: string;
@IsOptional()
@IsString()
readonly timeFormat?: number;
@IsOptional()
@IsString()
readonly timeZone?: string = "Europe/London";
@IsOptional()
@IsString()
readonly weekStart?: string = "Sunday";
}
@@ -1,4 +1,5 @@
import { CreateOrgTeamDto } from "@/modules/organizations/inputs/create-organization-team.input";
import { UpdateOrgTeamDto } from "@/modules/organizations/inputs/update-organization-team.input";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { Injectable } from "@nestjs/common";
@@ -16,6 +17,14 @@ export class OrganizationsTeamsRepository {
});
}
async findOrgTeams(organizationId: number) {
return this.dbRead.prisma.team.findMany({
where: {
parentId: organizationId,
},
});
}
async deleteOrgTeam(organizationId: number, teamId: number) {
return this.dbRead.prisma.team.delete({
where: {
@@ -32,7 +41,26 @@ export class OrganizationsTeamsRepository {
});
}
async updateOrgTeam(organizationId: number, teamId: number, data: CreateOrgTeamDto) {
async createPlatformOrgTeam(organizationId: number, oAuthClientId: string, data: CreateOrgTeamDto) {
return this.dbRead.prisma.team.create({
data: {
...data,
parentId: organizationId,
createdByOAuthClientId: oAuthClientId,
},
});
}
async getPlatformOrgTeams(organizationId: number, oAuthClientId: string) {
return this.dbRead.prisma.team.findMany({
where: {
parentId: organizationId,
createdByOAuthClientId: oAuthClientId,
},
});
}
async updateOrgTeam(organizationId: number, teamId: number, data: UpdateOrgTeamDto) {
return this.dbRead.prisma.team.update({
data: { ...data },
where: { id: teamId, parentId: organizationId, isOrganization: false },
@@ -9,7 +9,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { createEventType, updateEventType } from "@calcom/platform-libraries-0.0.21";
import { createEventType, updateEventType } from "@calcom/platform-libraries-0.0.22";
import {
CreateTeamEventTypeInput_2024_06_14,
UpdateTeamEventTypeInput_2024_06_14,
@@ -1,9 +1,12 @@
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { CreateOrgTeamDto } from "@/modules/organizations/inputs/create-organization-team.input";
import { UpdateOrgTeamDto } from "@/modules/organizations/inputs/update-organization-team.input";
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries-0.0.22";
@Injectable()
export class OrganizationsTeamsService {
constructor(
@@ -31,16 +34,58 @@ export class OrganizationsTeamsService {
return team;
}
async updateOrgTeam(organizationId: number, teamId: number, data: CreateOrgTeamDto) {
async updateOrgTeam(organizationId: number, teamId: number, data: UpdateOrgTeamDto) {
const team = await this.organizationsTeamRepository.updateOrgTeam(organizationId, teamId, data);
return team;
}
async createOrgTeam(organizationId: number, data: CreateOrgTeamDto, user: UserWithProfile) {
const team = await this.organizationsTeamRepository.createOrgTeam(organizationId, data);
const { autoAcceptCreator, ...rest } = data;
const team = await this.organizationsTeamRepository.createOrgTeam(organizationId, rest);
if (user.role !== "ADMIN") {
await this.membershipsRepository.createMembership(team.id, user.id, "OWNER", true);
await this.membershipsRepository.createMembership(team.id, user.id, "OWNER", !!autoAcceptCreator);
}
return team;
}
async createPlatformOrgTeam(
organizationId: number,
oAuthClientId: string,
data: CreateOrgTeamDto,
user: UserWithProfile
) {
const { autoAcceptCreator, ...rest } = data;
const team = await this.organizationsTeamRepository.createPlatformOrgTeam(
organizationId,
oAuthClientId,
rest
);
if (user.role !== "ADMIN") {
await this.membershipsRepository.createMembership(team.id, user.id, "OWNER", !!autoAcceptCreator);
}
return team;
}
async addUserToTeamEvents(userId: number, organizationId: number) {
const orgTeams = await this.organizationsTeamRepository.findOrgTeams(organizationId);
for (const team of orgTeams) {
await updateNewTeamMemberEventTypes(userId, team.id);
}
}
async addUserToPlatformTeamEvents(userId: number, organizationId: number, oAuthClientId: string) {
const oAuthClientTeams = await this.organizationsTeamRepository.getPlatformOrgTeams(
organizationId,
oAuthClientId
);
for (const team of oAuthClientTeams) {
await updateNewTeamMemberEventTypes(userId, team.id);
}
}
}
@@ -2,17 +2,19 @@ import { EmailService } from "@/modules/email/email.service";
import { CreateOrganizationUserInput } from "@/modules/organizations/inputs/create-organization-user.input";
import { UpdateOrganizationUserInput } from "@/modules/organizations/inputs/update-organization-user.input";
import { OrganizationsUsersRepository } from "@/modules/organizations/repositories/organizations-users.repository";
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
import { Injectable, ConflictException } from "@nestjs/common";
import { plainToInstance } from "class-transformer";
import { createNewUsersConnectToOrgIfExists } from "@calcom/platform-libraries-0.0.21";
import { createNewUsersConnectToOrgIfExists } from "@calcom/platform-libraries-0.0.22";
import { Team } from "@calcom/prisma/client";
@Injectable()
export class OrganizationsUsersService {
constructor(
private readonly organizationsUsersRepository: OrganizationsUsersRepository,
private readonly organizationsTeamsService: OrganizationsTeamsService,
private readonly emailService: EmailService
) {}
@@ -77,6 +79,8 @@ export class OrganizationsUsersService {
updateUserBody
);
await this.organizationsTeamsService.addUserToTeamEvents(user.id, org.id);
// Need to send email to new user to create password
await this.emailService.sendSignupToOrganizationEmail({
usernameOrEmail,
@@ -5,8 +5,8 @@ import { ApiTags as DocsTags } from "@nestjs/swagger";
import { Response as ExpressResponse, Request as ExpressRequest } from "express";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { getAvailableSlots } from "@calcom/platform-libraries-0.0.21";
import type { AvailableSlotsType } from "@calcom/platform-libraries-0.0.21";
import { getAvailableSlots } from "@calcom/platform-libraries-0.0.22";
import type { AvailableSlotsType } from "@calcom/platform-libraries-0.0.22";
import { RemoveSelectedSlotInput, ReserveSlotInput } from "@calcom/platform-types";
import { ApiResponse, GetAvailableSlotsInput } from "@calcom/platform-types";
@@ -3,7 +3,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { DateTime } from "luxon";
import { MINUTES_TO_BOOK } from "@calcom/platform-libraries-0.0.21";
import { MINUTES_TO_BOOK } from "@calcom/platform-libraries-0.0.22";
import { ReserveSlotInput } from "@calcom/platform-types";
@Injectable()
@@ -4,7 +4,7 @@ import { Controller, Get } from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { CityTimezones } from "@calcom/platform-libraries-0.0.21";
import type { CityTimezones } from "@calcom/platform-libraries-0.0.22";
import { ApiResponse } from "@calcom/platform-types";
@Controller({
@@ -1,8 +1,8 @@
import { RedisService } from "@/modules/redis/redis.service";
import { Injectable } from "@nestjs/common";
import { cityTimezonesHandler } from "@calcom/platform-libraries-0.0.21";
import type { CityTimezones } from "@calcom/platform-libraries-0.0.21";
import { cityTimezonesHandler } from "@calcom/platform-libraries-0.0.22";
import type { CityTimezones } from "@calcom/platform-libraries-0.0.22";
@Injectable()
export class TimezonesService {
+76 -1
View File
@@ -962,6 +962,14 @@
"schema": {
"type": "number"
}
},
{
"name": "x-cal-client-id",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
}
],
"requestBody": {
@@ -1126,7 +1134,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrgTeamDto"
"$ref": "#/components/schemas/UpdateOrgTeamDto"
}
}
}
@@ -5224,6 +5232,69 @@
"data"
]
},
"UpdateOrgTeamDto": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"slug": {
"type": "string"
},
"logoUrl": {
"type": "string"
},
"calVideoLogo": {
"type": "string"
},
"appLogo": {
"type": "string"
},
"appIconLogo": {
"type": "string"
},
"bio": {
"type": "string"
},
"hideBranding": {
"type": "boolean",
"default": false
},
"isPrivate": {
"type": "boolean"
},
"hideBookATeamMember": {
"type": "boolean"
},
"metadata": {
"type": "string"
},
"theme": {
"type": "string"
},
"brandColor": {
"type": "string"
},
"darkBrandColor": {
"type": "string"
},
"bannerUrl": {
"type": "string"
},
"timeFormat": {
"type": "number"
},
"timeZone": {
"type": "string",
"default": "Europe/London"
},
"weekStart": {
"type": "string",
"default": "Sunday"
}
}
},
"CreateOrgTeamDto": {
"type": "object",
"properties": {
@@ -5284,6 +5355,10 @@
"weekStart": {
"type": "string",
"default": "Sunday"
},
"autoAcceptCreator": {
"type": "boolean",
"default": true
}
},
"required": [
@@ -4,6 +4,8 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { TestingModule } from "@nestjs/testing";
import { EventType } from "@prisma/client";
import { Prisma } from "@calcom/prisma/client";
export class EventTypesRepositoryFixture {
private primaReadClient: PrismaReadService["prisma"];
private prismaWriteClient: PrismaWriteService["prisma"];
@@ -26,6 +28,9 @@ export class EventTypesRepositoryFixture {
where: {
teamId,
},
include: {
hosts: true,
},
});
}
@@ -41,6 +46,10 @@ export class EventTypesRepositoryFixture {
});
}
async createTeamEventType(data: Prisma.EventTypeCreateInput) {
return this.prismaWriteClient.eventType.create({ data });
}
async delete(eventTypeId: EventType["id"]) {
return this.prismaWriteClient.eventType.delete({ where: { id: eventTypeId } });
}
@@ -23,4 +23,13 @@ export class TeamRepositoryFixture {
async delete(teamId: Team["id"]) {
return this.prismaWriteClient.team.delete({ where: { id: teamId } });
}
async getPlatformOrgTeams(organizationId: number, oAuthClientId: string) {
return this.primaReadClient.team.findMany({
where: {
parentId: organizationId,
createdByOAuthClientId: oAuthClientId,
},
});
}
}
+2
View File
@@ -1574,6 +1574,7 @@ async function createOrg(
metadata: {
...(data.metadata || {}),
},
createdByOAuthClient: undefined,
},
});
}
@@ -1591,6 +1592,7 @@ async function createTeamOutsideOrg(
...(data.metadata || {}),
isOrganization: false,
},
createdByOAuthClient: undefined,
},
});
}
+4
View File
@@ -1,3 +1,7 @@
## 0.0.22
Export `updateNewTeamMemberEventTypes` from `"@calcom/lib/server/queries"` so that we can assign newly created organizations
teams members to event-types that have been marked as "assign all team members"
## 0.0.20
In event-types create handler (packages/trpc/server/routers/viewer/eventTypes/create.handler.ts) enable passing scheduleId so that when an event type is created it can be connected
to a specific schedule.
+2
View File
@@ -109,3 +109,5 @@ export { CalendarService };
export { getCalendar };
export { getTranslation };
export { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
@@ -1,5 +1,5 @@
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { Type, Transform } from "class-transformer";
import {
IsString,
IsInt,
@@ -148,6 +148,18 @@ export class CreateTeamEventTypeInput_2024_06_14 {
@DocsProperty()
afterEventBuffer?: number;
@Transform(({ value }) => {
if (value === "collective") {
return SchedulingType.COLLECTIVE;
}
if (value === "roundRobin") {
return SchedulingType.ROUND_ROBIN;
}
if (value === "managed") {
return SchedulingType.MANAGED;
}
return value;
})
@IsEnum(SchedulingType)
@DocsProperty()
schedulingType!: keyof typeof SchedulingType;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Team" ADD COLUMN "createdByOAuthClientId" TEXT;
-- AddForeignKey
ALTER TABLE "Team" ADD CONSTRAINT "Team_createdByOAuthClientId_fkey" FOREIGN KEY ("createdByOAuthClientId") REFERENCES "PlatformOAuthClient"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "Team" DROP CONSTRAINT "Team_createdByOAuthClientId_fkey";
-- AddForeignKey
ALTER TABLE "Team" ADD CONSTRAINT "Team_createdByOAuthClientId_fkey" FOREIGN KEY ("createdByOAuthClientId") REFERENCES "PlatformOAuthClient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+51 -46
View File
@@ -359,55 +359,59 @@ model Profile {
}
model Team {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
/// @zod.min(1)
name String
name String
/// @zod.min(1)
slug String?
logoUrl String?
calVideoLogo String?
appLogo String?
appIconLogo String?
bio String?
hideBranding Boolean @default(false)
isPrivate Boolean @default(false)
hideBookATeamMember Boolean @default(false)
members Membership[]
eventTypes EventType[]
workflows Workflow[]
createdAt DateTime @default(now())
slug String?
logoUrl String?
calVideoLogo String?
appLogo String?
appIconLogo String?
bio String?
hideBranding Boolean @default(false)
isPrivate Boolean @default(false)
hideBookATeamMember Boolean @default(false)
members Membership[]
eventTypes EventType[]
workflows Workflow[]
createdAt DateTime @default(now())
/// @zod.custom(imports.teamMetadataSchema)
metadata Json?
theme String?
brandColor String?
darkBrandColor String?
verifiedNumbers VerifiedNumber[]
verifiedEmails VerifiedEmail[]
bannerUrl String?
parentId Int?
parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade)
children Team[] @relation("organization")
orgUsers User[] @relation("scope")
inviteTokens VerificationToken[]
webhooks Webhook[]
timeFormat Int?
timeZone String @default("Europe/London")
weekStart String @default("Sunday")
routingForms App_RoutingForms_Form[]
apiKeys ApiKey[]
credentials Credential[]
accessCodes AccessCode[]
isOrganization Boolean @default(false)
organizationSettings OrganizationSettings?
instantMeetingTokens InstantMeetingToken[]
orgProfiles Profile[]
pendingPayment Boolean @default(false)
dsyncTeamGroupMapping DSyncTeamGroupMapping[]
isPlatform Boolean @default(false)
platformOAuthClient PlatformOAuthClient[]
smsLockState SMSLockState @default(UNLOCKED)
platformBilling PlatformBilling?
activeOrgWorkflows WorkflowsOnTeams[]
metadata Json?
theme String?
brandColor String?
darkBrandColor String?
verifiedNumbers VerifiedNumber[]
verifiedEmails VerifiedEmail[]
bannerUrl String?
parentId Int?
parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade)
children Team[] @relation("organization")
orgUsers User[] @relation("scope")
inviteTokens VerificationToken[]
webhooks Webhook[]
timeFormat Int?
timeZone String @default("Europe/London")
weekStart String @default("Sunday")
routingForms App_RoutingForms_Form[]
apiKeys ApiKey[]
credentials Credential[]
accessCodes AccessCode[]
isOrganization Boolean @default(false)
organizationSettings OrganizationSettings?
instantMeetingTokens InstantMeetingToken[]
orgProfiles Profile[]
pendingPayment Boolean @default(false)
dsyncTeamGroupMapping DSyncTeamGroupMapping[]
isPlatform Boolean @default(false)
// Organization's OAuth clients. Organization has them but a team does not.
platformOAuthClient PlatformOAuthClient[]
// OAuth client used to create team of an organization. Team has it but organization does not.
createdByOAuthClient PlatformOAuthClient? @relation("CreatedByOAuthClient", fields: [createdByOAuthClientId], references: [id], onDelete: Cascade)
createdByOAuthClientId String?
smsLockState SMSLockState @default(UNLOCKED)
platformBilling PlatformBilling?
activeOrgWorkflows WorkflowsOnTeams[]
@@unique([slug, parentId])
@@index([parentId])
@@ -1287,6 +1291,7 @@ model PlatformOAuthClient {
redirectUris String[]
organizationId Int
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
teams Team[] @relation("CreatedByOAuthClient")
accessTokens AccessToken[]
refreshToken RefreshToken[]
+193 -191
View File
@@ -4066,12 +4066,13 @@ __metadata:
dependencies:
"@calcom/platform-constants": "*"
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2"
"@calcom/platform-libraries-0.0.20": "npm:@calcom/platform-libraries@0.0.20"
"@calcom/platform-libraries-0.0.22": "npm:@calcom/platform-libraries@0.0.22"
"@calcom/platform-types": "*"
"@calcom/platform-utils": "*"
"@calcom/prisma": "*"
"@golevelup/ts-jest": ^0.4.0
"@microsoft/microsoft-graph-types-beta": ^0.42.0-preview
"@nestjs/bull": ^10.1.1
"@nestjs/cli": ^10.0.0
"@nestjs/common": ^10.0.0
"@nestjs/config": ^3.1.1
@@ -4093,6 +4094,7 @@ __metadata:
"@types/passport-jwt": ^3.0.13
"@types/supertest": ^2.0.12
body-parser: ^1.20.2
bull: ^4.12.4
class-transformer: ^0.5.1
class-validator: ^0.14.0
cookie-parser: ^1.4.6
@@ -4216,7 +4218,7 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/atoms@*, @calcom/atoms@workspace:^, @calcom/atoms@workspace:packages/platform/atoms":
"@calcom/atoms@*, @calcom/atoms@workspace:packages/platform/atoms":
version: 0.0.0-use.local
resolution: "@calcom/atoms@workspace:packages/platform/atoms"
dependencies:
@@ -5025,14 +5027,14 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/platform-libraries-0.0.20@npm:@calcom/platform-libraries@0.0.20":
version: 0.0.20
resolution: "@calcom/platform-libraries@npm:0.0.20"
"@calcom/platform-libraries-0.0.22@npm:@calcom/platform-libraries@0.0.22":
version: 0.0.22
resolution: "@calcom/platform-libraries@npm:0.0.22"
dependencies:
"@calcom/core": "*"
"@calcom/features": "*"
"@calcom/lib": "*"
checksum: f17e9bd51429a1a70240944b8f3836be41790a8c0eba34ddeaa7336c4f98503c0ff8c70c4d79b15809895c3af8462cbf10cff14ca4ba0453cc751febf4dd86cb
checksum: 137238600e8c19e5dcd77f340addb5e1284c5dee46b4dfb20c8fb37808251d1752a6c870120f394093c3d503987820c20a10b9a6f2bd4221fbf9109f71a08d87
languageName: node
linkType: hard
@@ -5673,7 +5675,6 @@ __metadata:
dependencies:
"@algora/sdk": ^0.1.2
"@calcom/app-store": "*"
"@calcom/atoms": "workspace:^"
"@calcom/config": "*"
"@calcom/dayjs": "*"
"@calcom/embed-react": "workspace:^"
@@ -5719,7 +5720,6 @@ __metadata:
"@vercel/og": ^0.5.0
autoprefixer: ^10.4.12
bcryptjs: ^2.4.3
class-variance-authority: ^0.7.0
clsx: ^1.2.1
cobe: ^0.4.1
concurrently: ^7.6.0
@@ -5732,7 +5732,6 @@ __metadata:
env-cmd: ^10.1.0
eslint: ^8.34.0
fathom-client: ^3.5.0
framer-motion: ^11.0.25
globby: ^13.1.3
graphql: ^16.8.0
graphql-codegen: ^0.4.0
@@ -5742,7 +5741,7 @@ __metadata:
i18n-unused: ^0.13.0
iframe-resizer-react: ^1.1.0
keen-slider: ^6.8.0
lucide-react: ^0.364.0
lucide-react: ^0.171.0
micro: ^10.0.1
next: ^14.1.3
next-auth: ^4.22.1
@@ -5754,7 +5753,7 @@ __metadata:
prism-react-renderer: ^1.3.5
react: ^18.2.0
react-confetti: ^6.0.1
react-datocms: ^5.0.3
react-datocms: ^3.1.0
react-device-detect: ^2.2.2
react-dom: ^18.2.0
react-fast-marquee: ^1.6.4
@@ -5764,10 +5763,8 @@ __metadata:
react-live-chat-loader: ^2.8.1
react-markdown: ^9.0.1
react-merge-refs: 1.1.0
react-parallax-tilt: ^1.7.226
react-resize-detector: ^9.1.0
react-twemoji: ^0.3.0
react-twitter-embed: ^4.0.4
react-use-measure: ^2.1.1
react-wrap-balancer: ^1.0.0
remark: ^14.0.2
@@ -9497,6 +9494,48 @@ __metadata:
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3":
version: 3.0.3
resolution: "@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@mswjs/cookies@npm:^0.2.0":
version: 0.2.1
resolution: "@mswjs/cookies@npm:0.2.1"
@@ -9521,59 +9560,6 @@ __metadata:
languageName: node
linkType: hard
"@mux/mux-player-react@npm:*":
version: 2.8.0
resolution: "@mux/mux-player-react@npm:2.8.0"
dependencies:
"@mux/mux-player": 2.8.0
"@mux/playback-core": 0.25.0
prop-types: ^15.7.2
peerDependencies:
"@types/react": ^17.0.0 || ^18
react: ^17.0.2 || ^18
react-dom: ^17.0.2 || ^18
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: b4891a151ff92bbee3b9d2e0411c21be07b976c30f6e13c30b30cda97eeb5d4a88bb3d78fd58dac447b05bb12563234a9a5fbba3bdf79e0c274575cf9bb020cd
languageName: node
linkType: hard
"@mux/mux-player@npm:2.8.0":
version: 2.8.0
resolution: "@mux/mux-player@npm:2.8.0"
dependencies:
"@mux/mux-video": 0.20.0
"@mux/playback-core": 0.25.0
media-chrome: ~3.2.3
checksum: 6a8e410456a325c2e59ef3ebb4f3efba69596a4df3e38e9ccb332af23fe16c571a8271c51e97ea5f003352f09f63fdb311ff548c0b2f613b7ca4c47fa68fcade
languageName: node
linkType: hard
"@mux/mux-video@npm:0.20.0":
version: 0.20.0
resolution: "@mux/mux-video@npm:0.20.0"
dependencies:
"@mux/playback-core": 0.25.0
castable-video: ~1.0.9
custom-media-element: ~1.3.1
media-tracks: ~0.3.2
checksum: 2f316a6697ff63dbcc5428216600ced1db14e22c195567c69e826b29b2752b9aeee785f073bc2eef71d7422b696370608d9d081b8e24b5332dfeb57bf0839791
languageName: node
linkType: hard
"@mux/playback-core@npm:0.25.0":
version: 0.25.0
resolution: "@mux/playback-core@npm:0.25.0"
dependencies:
hls.js: ~1.5.11
mux-embed: ~5.2.0
checksum: 37175985f7ec7df0b2f24e9b6e3df9c39f94da81b0f1e59b989dd9d04d9451dae2ea6e06d4208a673e532ea2351c39c2d2ec0e149cde60a9a09b9dbd00ea2866
languageName: node
linkType: hard
"@ndelangen/get-tarball@npm:^3.0.7":
version: 3.0.9
resolution: "@ndelangen/get-tarball@npm:3.0.9"
@@ -9585,6 +9571,32 @@ __metadata:
languageName: node
linkType: hard
"@nestjs/bull-shared@npm:^10.1.1":
version: 10.1.1
resolution: "@nestjs/bull-shared@npm:10.1.1"
dependencies:
tslib: 2.6.2
peerDependencies:
"@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0
"@nestjs/core": ^8.0.0 || ^9.0.0 || ^10.0.0
checksum: 492c6bfd8d77ded163913aa8fced3c6a52f8b6b40ae2e35b722cde98d89d3a0136497233ea8d60436cc842107cb61fc1f2a5ddc3fa01dd85aa68f08a350f8c47
languageName: node
linkType: hard
"@nestjs/bull@npm:^10.1.1":
version: 10.1.1
resolution: "@nestjs/bull@npm:10.1.1"
dependencies:
"@nestjs/bull-shared": ^10.1.1
tslib: 2.6.2
peerDependencies:
"@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0
"@nestjs/core": ^8.0.0 || ^9.0.0 || ^10.0.0
bull: ^3.3 || ^4.0.0
checksum: 3f3cf1a119324f2e7721fedc3d1484a60ffb55381a2d291040953770e0aded825cd14ee2a2526e68929f79fee56053ae9ac0858f8a427cbb11dbffb73191be36
languageName: node
linkType: hard
"@nestjs/cli@npm:^10.0.0":
version: 10.3.2
resolution: "@nestjs/cli@npm:10.3.2"
@@ -21178,6 +21190,21 @@ __metadata:
languageName: node
linkType: hard
"bull@npm:^4.12.4":
version: 4.15.1
resolution: "bull@npm:4.15.1"
dependencies:
cron-parser: ^4.2.1
get-port: ^5.1.1
ioredis: ^5.3.2
lodash: ^4.17.21
msgpackr: ^1.10.1
semver: ^7.5.2
uuid: ^8.3.0
checksum: 9ac743e32bd68d6f52ec0dcb3696ad4d81ecd994faddc1b6fe48ceb5047ffaae4e45da1211b3b68f6c1dfd719c84405cc132c611fccd0a92ef8c44217b81a82f
languageName: node
linkType: hard
"bundle-name@npm:^4.1.0":
version: 4.1.0
resolution: "bundle-name@npm:4.1.0"
@@ -21335,6 +21362,7 @@ __metadata:
husky: ^8.0.0
i18n-unused: ^0.13.0
jest-diff: ^29.5.0
jest-summarizing-reporter: ^1.1.4
jsdom: ^22.0.0
lint-staged: ^12.5.0
mailhog: ^4.16.0
@@ -21569,15 +21597,6 @@ __metadata:
languageName: node
linkType: hard
"castable-video@npm:~1.0.9":
version: 1.0.10
resolution: "castable-video@npm:1.0.10"
dependencies:
custom-media-element: ~1.3.2
checksum: 5b7a27aacf305f40c6867e96e773f4290260f6eb8b01fe1f209506f8f6dec4cd099f3e35ee57de098829e4bcc688ccdc2cbb2fc86c509712dd573eadf66d42cc
languageName: node
linkType: hard
"ccount@npm:^2.0.0":
version: 2.0.1
resolution: "ccount@npm:2.0.1"
@@ -22042,15 +22061,6 @@ __metadata:
languageName: node
linkType: hard
"class-variance-authority@npm:^0.7.0":
version: 0.7.0
resolution: "class-variance-authority@npm:0.7.0"
dependencies:
clsx: 2.0.0
checksum: e7fd1fab433ef06f52a1b7b241b70b4a185864deef199d3b0a2c3412f1cc179517288264c383f3b971a00d76811625fc8f7ffe709e6170219e88cd7368f08a20
languageName: node
linkType: hard
"classnames@npm:^2.2.5, classnames@npm:^2.2.6":
version: 2.3.2
resolution: "classnames@npm:2.3.2"
@@ -22323,13 +22333,6 @@ __metadata:
languageName: node
linkType: hard
"clsx@npm:2.0.0":
version: 2.0.0
resolution: "clsx@npm:2.0.0"
checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e
languageName: node
linkType: hard
"clsx@npm:^1.1.1":
version: 1.1.1
resolution: "clsx@npm:1.1.1"
@@ -23289,6 +23292,15 @@ __metadata:
languageName: node
linkType: hard
"cron-parser@npm:^4.2.1":
version: 4.9.0
resolution: "cron-parser@npm:4.9.0"
dependencies:
luxon: ^3.2.1
checksum: 3cf248fc5cae6c19ec7124962b1cd84b76f02b9bc4f58976b3bd07624db3ef10aaf1548efcc2d2dcdab0dad4f12029d640a55ecce05ea5e1596af9db585502cf
languageName: node
linkType: hard
"cross-env@npm:^7.0.3":
version: 7.0.3
resolution: "cross-env@npm:7.0.3"
@@ -23635,13 +23647,6 @@ __metadata:
languageName: node
linkType: hard
"custom-media-element@npm:~1.3.1, custom-media-element@npm:~1.3.2":
version: 1.3.2
resolution: "custom-media-element@npm:1.3.2"
checksum: 9ec2ff88c3c5ba7d20b9484c0359b4cfe08ad6804f3116934ffbae2f2e0e04d2a832beb6f0bec56027a969ab366727d217406f750890613c988de6de5330cb4f
languageName: node
linkType: hard
"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6":
version: 3.2.3
resolution: "d3-array@npm:3.2.3"
@@ -24334,6 +24339,13 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.1":
version: 2.0.3
resolution: "detect-libc@npm:2.0.3"
checksum: 2ba6a939ae55f189aea996ac67afceb650413c7a34726ee92c40fb0deb2400d57ef94631a8a3f052055eea7efb0f99a9b5e6ce923415daa3e68221f963cfc27d
languageName: node
linkType: hard
"detect-newline@npm:^3.0.0":
version: 3.1.0
resolution: "detect-newline@npm:3.1.0"
@@ -27689,26 +27701,6 @@ __metadata:
languageName: node
linkType: hard
"framer-motion@npm:^11.0.25":
version: 11.3.12
resolution: "framer-motion@npm:11.3.12"
dependencies:
tslib: ^2.4.0
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: d13fa5fd2a21e73846a1171b8e6cad0be214b48f0c9b91070397975977758a98c66ea55d3e8dc027d959831027d76d0d8044cf41c49942cc3362a7b0cce7d4a0
languageName: node
linkType: hard
"fresh@npm:0.5.2, fresh@npm:^0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
@@ -29381,13 +29373,6 @@ __metadata:
languageName: node
linkType: hard
"hls.js@npm:~1.5.11":
version: 1.5.13
resolution: "hls.js@npm:1.5.13"
checksum: 534b48b18638fefe7788fd31d92b0b4e3820271b470fbd18c29cd46d51e6381b6f011d732f6196d4cda3148c81d8cb7740e1d1d0f2da0031e808b468198142f3
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
@@ -32080,6 +32065,13 @@ __metadata:
languageName: node
linkType: hard
"jest-summarizing-reporter@npm:^1.1.4":
version: 1.1.4
resolution: "jest-summarizing-reporter@npm:1.1.4"
checksum: 46be3e78146a86ba2b75b3e85f17b854f0da42587bf90e664b35d1b56cd51607c3a431da2c3b6a0a1fe5b3d3429ea493aedb51d2d7ce615919643e7e7dea3cbe
languageName: node
linkType: hard
"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0":
version: 29.7.0
resolution: "jest-util@npm:29.7.0"
@@ -34208,6 +34200,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-react@npm:^0.364.0":
version: 0.364.0
resolution: "lucide-react@npm:0.364.0"
@@ -34238,7 +34239,7 @@ __metadata:
languageName: node
linkType: hard
"luxon@npm:^3.4.4":
"luxon@npm:^3.2.1, luxon@npm:^3.4.4":
version: 3.4.4
resolution: "luxon@npm:3.4.4"
checksum: 36c1f99c4796ee4bfddf7dc94fa87815add43ebc44c8934c924946260a58512f0fd2743a629302885df7f35ccbd2d13f178c15df046d0e3b6eb71db178f1c60c
@@ -34777,20 +34778,6 @@ __metadata:
languageName: node
linkType: hard
"media-chrome@npm:~3.2.3":
version: 3.2.4
resolution: "media-chrome@npm:3.2.4"
checksum: 6401f509f4075cd7d02f8a479e81bcb084cd06104aa17fd7ebe6623bd171eaa52e1e1333c1895ad84c1f794ea3889cca13db52dd49b64129d67dd8be558d6eb2
languageName: node
linkType: hard
"media-tracks@npm:~0.3.2":
version: 0.3.3
resolution: "media-tracks@npm:0.3.3"
checksum: 4795af3f171d7ad3a68d1ac1c1e8166a735244fe57d3fc0ec53b1c7410799e524756fc0bfb389632aebb148436b03baade36ca34a3f5377776c3968f6d2cc580
languageName: node
linkType: hard
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@@ -36062,6 +36049,49 @@ __metadata:
languageName: node
linkType: hard
"msgpackr-extract@npm:^3.0.2":
version: 3.0.3
resolution: "msgpackr-extract@npm:3.0.3"
dependencies:
"@msgpackr-extract/msgpackr-extract-darwin-arm64": 3.0.3
"@msgpackr-extract/msgpackr-extract-darwin-x64": 3.0.3
"@msgpackr-extract/msgpackr-extract-linux-arm": 3.0.3
"@msgpackr-extract/msgpackr-extract-linux-arm64": 3.0.3
"@msgpackr-extract/msgpackr-extract-linux-x64": 3.0.3
"@msgpackr-extract/msgpackr-extract-win32-x64": 3.0.3
node-gyp: latest
node-gyp-build-optional-packages: 5.2.2
dependenciesMeta:
"@msgpackr-extract/msgpackr-extract-darwin-arm64":
optional: true
"@msgpackr-extract/msgpackr-extract-darwin-x64":
optional: true
"@msgpackr-extract/msgpackr-extract-linux-arm":
optional: true
"@msgpackr-extract/msgpackr-extract-linux-arm64":
optional: true
"@msgpackr-extract/msgpackr-extract-linux-x64":
optional: true
"@msgpackr-extract/msgpackr-extract-win32-x64":
optional: true
bin:
download-msgpackr-prebuilds: bin/download-prebuilds.js
checksum: 3b5ae152821feff843380f0b091afbebd80bd224e644f4410abd33d05da3159eb8b0d45c7dcf7d5226ce1d5c71cd68052f066788f46ea7a3cd8791a1c740a079
languageName: node
linkType: hard
"msgpackr@npm:^1.10.1":
version: 1.11.0
resolution: "msgpackr@npm:1.11.0"
dependencies:
msgpackr-extract: ^3.0.2
dependenciesMeta:
msgpackr-extract:
optional: true
checksum: 20a1941e1a281c10a1d43d04349ae7a79bef1cbd0644f1e5acef7f98f6a6e6cd83388ee82689799f9e65623591b11f849ef2f86c8eac83492449bee5a2b055b2
languageName: node
linkType: hard
"mssql@npm:10.0.2":
version: 10.0.2
resolution: "mssql@npm:10.0.2"
@@ -36168,13 +36198,6 @@ __metadata:
languageName: node
linkType: hard
"mux-embed@npm:~5.2.0":
version: 5.2.1
resolution: "mux-embed@npm:5.2.1"
checksum: ec34d3e003e2520be6d1386ca2383daa198a1fa3a469ca3f06c11a742d6ab136eef4944c37e8f2dcaa0585e1b0a6dfc119e3cf2302c7eb0e7f2ebc7c1fc95fbd
languageName: node
linkType: hard
"mysql2@npm:3.9.1":
version: 3.9.1
resolution: "mysql2@npm:3.9.1"
@@ -36867,6 +36890,19 @@ __metadata:
languageName: node
linkType: hard
"node-gyp-build-optional-packages@npm:5.2.2":
version: 5.2.2
resolution: "node-gyp-build-optional-packages@npm:5.2.2"
dependencies:
detect-libc: ^2.0.1
bin:
node-gyp-build-optional-packages: bin.js
node-gyp-build-optional-packages-optional: optional.js
node-gyp-build-optional-packages-test: build-test.js
checksum: 3c10d7380901ab5febcd153d2632917fe7507edb15a3405e9ef19801834a4c2162459a67b9944887f737f8718baeb4aaf0002c829a8214011930f2de80e4b42f
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 9.3.1
resolution: "node-gyp@npm:9.3.1"
@@ -40483,21 +40519,20 @@ __metadata:
languageName: node
linkType: hard
"react-datocms@npm:^5.0.3":
version: 5.0.3
resolution: "react-datocms@npm:5.0.3"
"react-datocms@npm:^3.1.0":
version: 3.1.4
resolution: "react-datocms@npm:3.1.4"
dependencies:
"@mux/mux-player-react": "*"
datocms-listen: ^0.1.9
datocms-structured-text-generic-html-renderer: ^2.0.1
datocms-structured-text-utils: ^2.0.1
react-intersection-observer: ^9.4.3
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: 22c20152afb54424acfe967a2c8c525cd9f132a33374f2aba0231f16ea64dade389b096e2dac8de9ffded612bc32e9891d725609ee947639fe1cef907cb143f5
checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3
languageName: node
linkType: hard
@@ -40749,16 +40784,12 @@ __metadata:
languageName: node
linkType: hard
"react-intersection-observer@npm:^9.4.3":
version: 9.13.0
resolution: "react-intersection-observer@npm:9.13.0"
"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 || ^19.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
react-dom:
optional: true
checksum: ee2a163b078923c1556814834f83d7c3ea3e8a9d3ceef974351fd52afe12163518ec57cb2eb0f6544ac255ac4b64d2e7652bc5d60c255159c0f25bda57a565b6
react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0
checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5
languageName: node
linkType: hard
@@ -40868,16 +40899,6 @@ __metadata:
languageName: node
linkType: hard
"react-parallax-tilt@npm:^1.7.226":
version: 1.7.232
resolution: "react-parallax-tilt@npm:1.7.232"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 8c866c9c06315aeaf1eddd4e5265db706e010d788667c337be552cbf8786e8b39f19cafc529be6c69d925e25b31705c52398e90bd89a805bf8914bbb46a8ebdb
languageName: node
linkType: hard
"react-phone-input-2@npm:^2.15.1":
version: 2.15.1
resolution: "react-phone-input-2@npm:2.15.1"
@@ -41292,18 +41313,6 @@ __metadata:
languageName: node
linkType: hard
"react-twitter-embed@npm:^4.0.4":
version: 4.0.4
resolution: "react-twitter-embed@npm:4.0.4"
dependencies:
scriptjs: ^2.5.9
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: cdb3c5bd04c4da0efa767476be47c0a3865fb6335f2a1b9e242170167b51615c38164223278cef60c77143c4bac27ba582cbea054d0af3f138104fa5ec537c4c
languageName: node
linkType: hard
"react-universal-interface@npm:^0.6.2":
version: 0.6.2
resolution: "react-universal-interface@npm:0.6.2"
@@ -43128,13 +43137,6 @@ __metadata:
languageName: node
linkType: hard
"scriptjs@npm:^2.5.9":
version: 2.5.9
resolution: "scriptjs@npm:2.5.9"
checksum: fc84cb6b60b6fb9aa6f1b3bc59fc94b233bd5241ed3a04233579014382b5eb60640269c87d8657902acc09f9b785ee33230c218627cea00e653564bda8f5acb6
languageName: node
linkType: hard
"scuid@npm:^1.1.0":
version: 1.1.0
resolution: "scuid@npm:1.1.0"