Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f6454df40 | ||
|
|
3da9fc5500 | ||
|
|
540bf868b5 | ||
|
|
1674ecc585 | ||
|
|
4920abdaf7 | ||
|
|
a6b98bd35d | ||
|
|
34d4f394fc | ||
|
|
a4ef8d4200 | ||
|
|
9d9662687e | ||
|
|
2d41c719c4 | ||
|
|
b69bd966a6 | ||
|
|
09a49dd0fd | ||
|
|
3dd3372c35 | ||
|
|
0a60864310 | ||
|
|
cb24e5d0aa | ||
|
|
8c5aebdca1 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@calcom/atoms": patch
|
||||
---
|
||||
|
||||
fix saving event type settings
|
||||
Vendored
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
@@ -37,7 +37,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.222",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.223",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -81,5 +81,9 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
if (process?.env?.API_GLOBAL_PREFIX) {
|
||||
app.setGlobalPrefix(process?.env?.API_GLOBAL_PREFIX);
|
||||
}
|
||||
|
||||
return app;
|
||||
};
|
||||
|
||||
@@ -267,7 +267,14 @@ export class BookingsController_2024_08_13 {
|
||||
@ApiOperation({
|
||||
summary: "Cancel a booking",
|
||||
description: `:bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.
|
||||
For seated bookings to cancel one individual booking provide :bookingUid and :seatUid in the request body. For recurring seated bookings it is not possible to cancel all of them with 1 call
|
||||
|
||||
\nCancelling seated bookings:
|
||||
It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.
|
||||
\n1. As an attendee - provide :bookingUid in the request URL \`/bookings/:bookingUid/cancel\` and seatUid in the request body \`{"seatUid": "123-123-123"}\` . This will remove this particular attendance from the booking.
|
||||
\n2. As the host - host can cancel booking for all attendees aka for every seat. Provide :bookingUid in the request URL \`/bookings/:bookingUid/cancel\` and cancellationReason in the request body \`{"cancellationReason": "Will travel"}\` and \`Authorization: Bearer token\` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.
|
||||
|
||||
\nCancelling recurring seated bookings:
|
||||
For recurring seated bookings it is not possible to cancel all of them with 1 call
|
||||
like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.`,
|
||||
})
|
||||
@ApiBody({
|
||||
|
||||
+190
-78
@@ -14,29 +14,27 @@ import { Test } from "@nestjs/testing";
|
||||
import { User } from "@prisma/client";
|
||||
import { DateTime } from "luxon";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.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 { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
CancelBookingInput_2024_08_13,
|
||||
CancelSeatedBookingInput_2024_08_13,
|
||||
CreateRecurringSeatedBookingOutput_2024_08_13,
|
||||
CreateSeatedBookingOutput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetRecurringSeatedBookingOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RescheduleSeatedBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import {
|
||||
CreateBookingInput_2024_08_13,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
@@ -51,11 +49,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let schedulesService: SchedulesService_2024_04_15;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let bookingSeatRepositoryFixture: BookingSeatRepositoryFixture;
|
||||
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let apiKeyString: string;
|
||||
|
||||
let seatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
@@ -63,6 +63,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdSeatedBooking2: CreateSeatedBookingOutput_2024_08_13;
|
||||
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
@@ -70,12 +71,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
|
||||
})
|
||||
)
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
|
||||
})
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
@@ -87,22 +85,21 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
bookingSeatRepositoryFixture = new BookingSeatRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClient.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = `cal_test_${keyString}`;
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
@@ -118,6 +115,14 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
metadata: {
|
||||
disableStandardEmails: {
|
||||
all: {
|
||||
attendee: true,
|
||||
host: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -129,19 +134,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
await app.init();
|
||||
});
|
||||
|
||||
async function createOAuthClient(organizationId: number) {
|
||||
const data = {
|
||||
logo: "logo-url",
|
||||
name: "name",
|
||||
redirectUris: ["http://localhost:5555"],
|
||||
permissions: 32,
|
||||
};
|
||||
const secret = "secret";
|
||||
|
||||
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
|
||||
return client;
|
||||
}
|
||||
|
||||
it("should book an event type with seats for the first time", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
@@ -333,6 +325,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
return request(app.getHttpServer())
|
||||
.get("/v2/bookings?sortCreated=asc")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
@@ -409,67 +402,186 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should cancel seated booking", async () => {
|
||||
const body: CancelSeatedBookingInput_2024_08_13 = {
|
||||
seatUid: createdSeatedBooking.seatUid,
|
||||
};
|
||||
describe("cancel seated booking", () => {
|
||||
describe("cancel seated booking as attendee", () => {
|
||||
it("should cancel seated booking", async () => {
|
||||
const body: CancelSeatedBookingInput_2024_08_13 = {
|
||||
seatUid: createdSeatedBooking.seatUid,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${createdSeatedBooking.uid}/cancel`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsGetSeatedBooking(responseBody.data)).toBe(true);
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${createdSeatedBooking.uid}/cancel`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsGetSeatedBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsGetSeatedBooking(responseBody.data)) {
|
||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.status).toEqual("cancelled");
|
||||
expect(data.start).toEqual(createdSeatedBooking.start);
|
||||
expect(data.end).toEqual(createdSeatedBooking.end);
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedEventSlug,
|
||||
if (responseDataIsGetSeatedBooking(responseBody.data)) {
|
||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.status).toEqual("cancelled");
|
||||
expect(data.start).toEqual(createdSeatedBooking.start);
|
||||
expect(data.end).toEqual(createdSeatedBooking.end);
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
expect(data.absentHost).toEqual(false);
|
||||
} else {
|
||||
throw new Error("Invalid response data - expected booking but received array response");
|
||||
}
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
expect(data.absentHost).toEqual(false);
|
||||
} else {
|
||||
throw new Error("Invalid response data - expected booking but received array response");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancel seated booking as host", () => {
|
||||
it("should book an event type with seats", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString(),
|
||||
eventTypeId: seatedEventTypeId,
|
||||
attendee: {
|
||||
name: nameAttendeeOne,
|
||||
email: emailAttendeeOne,
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
bookingFieldsResponses: {
|
||||
codingLanguage: "TypeScript",
|
||||
},
|
||||
metadata: {
|
||||
userId: "100",
|
||||
},
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsCreateSeatedBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsCreateSeatedBooking(responseBody.data)) {
|
||||
const data: CreateSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.seatUid).toBeDefined();
|
||||
const seatUid = data.seatUid;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(
|
||||
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
|
||||
);
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
seatUid,
|
||||
bookingFieldsResponses: {
|
||||
name: body.attendee.name,
|
||||
...body.bookingFieldsResponses,
|
||||
},
|
||||
metadata: body.metadata,
|
||||
});
|
||||
expect(data.location).toBeDefined();
|
||||
expect(data.absentHost).toEqual(false);
|
||||
createdSeatedBooking2 = data;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected recurring booking but received non array response"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be able to cancel without cancellation reason", async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${createdSeatedBooking2.uid}/cancel`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.message).toEqual("Cancellation reason is required when you are the host");
|
||||
});
|
||||
|
||||
it("should cancel seated booking", async () => {
|
||||
const body: CancelBookingInput_2024_08_13 = {
|
||||
cancellationReason: "I will be travelling without internet",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${createdSeatedBooking2.uid}/cancel`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsGetSeatedBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsGetSeatedBooking(responseBody.data)) {
|
||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.status).toEqual("cancelled");
|
||||
expect(data.start).toEqual(createdSeatedBooking2.start);
|
||||
expect(data.end).toEqual(createdSeatedBooking2.end);
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
expect(data.absentHost).toEqual(false);
|
||||
expect(data.cancellationReason).toEqual("I will be travelling without internet");
|
||||
|
||||
const seats = await bookingSeatRepositoryFixture.findAllByBookingId(data.id);
|
||||
expect(seats.length).toEqual(0);
|
||||
} else {
|
||||
throw new Error("Invalid response data - expected booking but received array response");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function responseDataIsCreateSeatedBooking(data: any): data is CreateSeatedBookingOutput_2024_08_13 {
|
||||
return data.hasOwnProperty("seatUid");
|
||||
}
|
||||
|
||||
function responseDataIsCreateRecurringSeatedBooking(
|
||||
data: any
|
||||
): data is CreateRecurringSeatedBookingOutput_2024_08_13[] {
|
||||
return Array.isArray(data);
|
||||
}
|
||||
|
||||
function responseDataIsGetSeatedBooking(data: any): data is GetSeatedBookingOutput_2024_08_13 {
|
||||
return data?.attendees?.every((attendee: any) => attendee?.hasOwnProperty("seatUid"));
|
||||
}
|
||||
|
||||
function responseDataIsGetRecurringSeatedBooking(
|
||||
data: any
|
||||
): data is GetRecurringSeatedBookingOutput_2024_08_13[] {
|
||||
return Array.isArray(data);
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getRecordingsOfCalVideoByRoomName,
|
||||
getAllTranscriptsAccessLinkFromRoomName,
|
||||
getDownloadLinkOfCalVideoByRecordingId,
|
||||
} from "@calcom/platform-libraries";
|
||||
} from "@calcom/platform-libraries/conferencing";
|
||||
|
||||
@Injectable()
|
||||
export class CalVideoService {
|
||||
|
||||
@@ -266,6 +266,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
description: databaseBooking.description,
|
||||
hosts: [this.getHost(databaseBooking.user)],
|
||||
status: databaseBooking.status.toLowerCase(),
|
||||
cancellationReason: databaseBooking.cancellationReason || undefined,
|
||||
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
||||
start: databaseBooking.startTime,
|
||||
end: databaseBooking.endTime,
|
||||
|
||||
+93
@@ -34,6 +34,7 @@ import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
GuestsDefaultFieldOutput_2024_06_14,
|
||||
NameDefaultFieldInput_2024_06_14,
|
||||
NotesDefaultFieldInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
@@ -535,6 +536,7 @@ describe("Event types Endpoints", () => {
|
||||
body.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(createdEventType.color).toEqual(body.color);
|
||||
expect(createdEventType.disableGuests).toEqual(false);
|
||||
|
||||
const expectedBookingFields = [
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
@@ -1041,6 +1043,7 @@ describe("Event types Endpoints", () => {
|
||||
expect(updatedEventType.description).toEqual(eventType.description);
|
||||
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(updatedEventType.locations).toEqual(eventType.locations);
|
||||
expect(updatedEventType.disableGuests).toEqual(false);
|
||||
|
||||
const expectedBookingFields = [
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
@@ -1887,6 +1890,96 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggle disable guests", () => {
|
||||
let eventTypeWithGuestsDisabledId: number;
|
||||
|
||||
it("should create an event type with guests disabled", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class guests disabled",
|
||||
slug: "coding-class-guests-disabled",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
disableGuests: true,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const createdEventType = responseBody.data;
|
||||
expect(createdEventType).toHaveProperty("id");
|
||||
expect(createdEventType.title).toEqual(body.title);
|
||||
expect(createdEventType.description).toEqual(body.description);
|
||||
expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes);
|
||||
|
||||
expect(createdEventType.disableGuests).toEqual(true);
|
||||
const guestsBookingField = createdEventType.bookingFields.find(
|
||||
(field) => field.slug === "guests"
|
||||
) as GuestsDefaultFieldOutput_2024_06_14 | undefined;
|
||||
expect(guestsBookingField?.hidden).toEqual(true);
|
||||
|
||||
eventTypeWithGuestsDisabledId = createdEventType.id;
|
||||
});
|
||||
});
|
||||
|
||||
it("should update an event type with guests enabled", async () => {
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
disableGuests: false,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventTypeWithGuestsDisabledId}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const updatedEventType = responseBody.data;
|
||||
|
||||
expect(updatedEventType.disableGuests).toEqual(false);
|
||||
const guestsBookingField = updatedEventType.bookingFields.find(
|
||||
(field) => field.slug === "guests"
|
||||
) as GuestsDefaultFieldOutput_2024_06_14 | undefined;
|
||||
expect(guestsBookingField?.hidden).toEqual(false);
|
||||
|
||||
eventTypeWithGuestsDisabledId = updatedEventType.id;
|
||||
});
|
||||
});
|
||||
|
||||
it("should update an event type with guests disabled", async () => {
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
disableGuests: true,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventTypeWithGuestsDisabledId}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const updatedEventType = responseBody.data;
|
||||
|
||||
expect(updatedEventType.disableGuests).toEqual(true);
|
||||
const guestsBookingField = updatedEventType.bookingFields.find(
|
||||
(field) => field.slug === "guests"
|
||||
) as GuestsDefaultFieldOutput_2024_06_14 | undefined;
|
||||
expect(guestsBookingField?.hidden).toEqual(true);
|
||||
|
||||
eventTypeWithGuestsDisabledId = updatedEventType.id;
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (eventTypeWithGuestsDisabledId) {
|
||||
await eventTypesRepositoryFixture.delete(eventTypeWithGuestsDisabledId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
|
||||
+39
-2
@@ -32,6 +32,7 @@ import { validateCustomEventName, EventTypeMetaDataSchema } from "@calcom/platfo
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
DestinationCalendar_2024_06_14,
|
||||
InputBookingField_2024_06_14,
|
||||
OutputUnknownLocation_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
@@ -117,16 +118,23 @@ export class InputEventTypesService_2024_06_14 {
|
||||
seats,
|
||||
customName,
|
||||
useDestinationCalendarEmail,
|
||||
disableGuests,
|
||||
...rest
|
||||
} = inputEventType;
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
|
||||
const locationsTransformed = locations?.length ? this.transformInputLocations(locations) : undefined;
|
||||
|
||||
const effectiveBookingFields =
|
||||
disableGuests !== undefined
|
||||
? this.getBookingFieldsWithGuestsToggled(bookingFields, disableGuests)
|
||||
: bookingFields;
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: locationsTransformed,
|
||||
bookingFields: this.transformInputBookingFields(bookingFields),
|
||||
bookingFields: this.transformInputBookingFields(effectiveBookingFields),
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
@@ -167,6 +175,7 @@ export class InputEventTypesService_2024_06_14 {
|
||||
seats,
|
||||
customName,
|
||||
useDestinationCalendarEmail,
|
||||
disableGuests,
|
||||
...rest
|
||||
} = inputEventType;
|
||||
const eventTypeDb = await this.eventTypesRepository.getEventTypeWithMetaData(eventTypeId);
|
||||
@@ -176,11 +185,18 @@ export class InputEventTypesService_2024_06_14 {
|
||||
|
||||
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
|
||||
|
||||
const effectiveBookingFields =
|
||||
disableGuests !== undefined
|
||||
? this.getBookingFieldsWithGuestsToggled(bookingFields, disableGuests)
|
||||
: bookingFields;
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: locations ? this.transformInputLocations(locations) : undefined,
|
||||
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
|
||||
bookingFields: effectiveBookingFields
|
||||
? this.transformInputBookingFields(effectiveBookingFields)
|
||||
: undefined,
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
@@ -206,6 +222,27 @@ export class InputEventTypesService_2024_06_14 {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
getBookingFieldsWithGuestsToggled(
|
||||
bookingFields: InputBookingField_2024_06_14[] | undefined,
|
||||
hideGuests: boolean
|
||||
) {
|
||||
const toggledGuestsBookingField: InputBookingField_2024_06_14 = { slug: "guests", hidden: hideGuests };
|
||||
if (!bookingFields) {
|
||||
return [toggledGuestsBookingField];
|
||||
}
|
||||
|
||||
const bookingFieldsCopy = [...bookingFields];
|
||||
|
||||
const guestsBookingField = bookingFieldsCopy.find((field) => "slug" in field && field.slug === "guests");
|
||||
if (guestsBookingField) {
|
||||
Object.assign(guestsBookingField, { hidden: hideGuests });
|
||||
return bookingFieldsCopy;
|
||||
}
|
||||
|
||||
bookingFieldsCopy.push(toggledGuestsBookingField);
|
||||
return bookingFieldsCopy;
|
||||
}
|
||||
|
||||
transformInputLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
|
||||
return transformLocationsApiToInternal(inputLocations);
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ export class CalUnifiedCalendarsController {
|
||||
})
|
||||
@ApiParam({
|
||||
name: "eventUid",
|
||||
description: "The Google Calendar event ID",
|
||||
description:
|
||||
"The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
|
||||
type: String,
|
||||
})
|
||||
@Get("/:calendar/event/:eventUid")
|
||||
|
||||
@@ -7653,7 +7653,7 @@
|
||||
"post": {
|
||||
"operationId": "BookingsController_2024_08_13_cancelBooking",
|
||||
"summary": "Cancel a booking",
|
||||
"description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n For seated bookings to cancel one individual booking provide :bookingUid and :seatUid in the request body. For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.",
|
||||
"description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n \n \nCancelling seated bookings:\n It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.\n \n1. As an attendee - provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and seatUid in the request body `{\"seatUid\": \"123-123-123\"}` . This will remove this particular attendance from the booking.\n \n2. As the host - host can cancel booking for all attendees aka for every seat. Provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}` and `Authorization: Bearer token` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.\n \n \nCancelling recurring seated bookings:\n For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "cal-api-version",
|
||||
@@ -8142,7 +8142,7 @@
|
||||
"name": "eventUid",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "The Google Calendar event ID",
|
||||
"description": "The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -10695,6 +10695,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bookingUidToReschedule",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.",
|
||||
"example": "abc123def456",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
@@ -25447,8 +25457,7 @@
|
||||
"startTs",
|
||||
"status",
|
||||
"duration",
|
||||
"shareToken",
|
||||
"downloadLink"
|
||||
"shareToken"
|
||||
]
|
||||
},
|
||||
"GetBookingRecordingsOutput": {
|
||||
|
||||
@@ -16,4 +16,8 @@ export class BookingSeatRepositoryFixture {
|
||||
async create(bookingSeat: Prisma.BookingSeatCreateInput) {
|
||||
return this.prismaWriteClient.bookingSeat.create({ data: bookingSeat });
|
||||
}
|
||||
|
||||
async findAllByBookingId(bookingId: number) {
|
||||
return this.prismaReadClient.bookingSeat.findMany({ where: { bookingId } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"@calcom/platform-libraries/emails": ["../../../packages/platform/libraries/emails.ts"],
|
||||
"@calcom/platform-libraries/schedules": ["../../../packages/platform/libraries/schedules.ts"],
|
||||
"@calcom/platform-libraries/app-store": ["../../../packages/platform/libraries/app-store.ts"],
|
||||
"@calcom/platform-libraries/workflows": ["../../../packages/platform/libraries/workflows.ts"]
|
||||
"@calcom/platform-libraries/workflows": ["../../../packages/platform/libraries/workflows.ts"],
|
||||
"@calcom/platform-libraries/conferencing": ["../../../packages/platform/libraries/conferencing.ts"]
|
||||
},
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
+14
-5
@@ -3,6 +3,7 @@ import { _generateMetadata } from "app/_utils";
|
||||
import { unstable_cache } from "next/cache";
|
||||
|
||||
import { AttributeRepository } from "@calcom/lib/server/repository/attribute";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { viewerOrganizationsRouter } from "@calcom/trpc/server/routers/viewer/organizations/_router";
|
||||
|
||||
import { MembersView } from "~/members/members-view";
|
||||
@@ -26,12 +27,20 @@ const getCachedAttributes = unstable_cache(
|
||||
|
||||
const Page = async () => {
|
||||
const orgCaller = await createRouterCaller(viewerOrganizationsRouter);
|
||||
const [org, teams, facetedTeamValues] = await Promise.all([
|
||||
orgCaller.listCurrent(),
|
||||
orgCaller.getTeams(),
|
||||
orgCaller.getFacetedValues(),
|
||||
]);
|
||||
const [org, teams] = await Promise.all([orgCaller.listCurrent(), orgCaller.getTeams()]);
|
||||
const attributes = await getCachedAttributes(org.id);
|
||||
const facetedTeamValues = {
|
||||
roles: [MembershipRole.OWNER, MembershipRole.ADMIN, MembershipRole.MEMBER],
|
||||
teams,
|
||||
attributes: attributes.map((attribute) => ({
|
||||
id: attribute.id,
|
||||
name: attribute.name,
|
||||
options: Array.from(new Set(attribute.options.map((option) => option.value))).map((value) => ({
|
||||
value,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<MembersView org={org} teams={teams} facetedTeamValues={facetedTeamValues} attributes={attributes} />
|
||||
);
|
||||
|
||||
@@ -134,7 +134,7 @@ const getEventTypes = async (userId: number, teamIds?: number[]) => {
|
||||
.sort((eventTypeA, eventTypeB) => eventTypeB.position - eventTypeA.position),
|
||||
}));
|
||||
} else {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
|
||||
@@ -157,7 +157,7 @@ export const handleSeatsEventTypeOnBooking = async (
|
||||
} | null;
|
||||
let seatAttendee: seatAttendee = null;
|
||||
if (seatReferenceUid) {
|
||||
seatAttendee = await prisma.bookingSeat.findFirst({
|
||||
seatAttendee = await prisma.bookingSeat.findUnique({
|
||||
where: {
|
||||
referenceUid: seatReferenceUid,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "5.3.13",
|
||||
"version": "5.4.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -768,6 +768,13 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
||||
* @deprecated use apiLogin instead
|
||||
*/
|
||||
login: async () => login({ ...(await self()), password: user.username }, store.page),
|
||||
loginOnNewBrowser: async (browser: Browser) => {
|
||||
const newContext = await browser.newContext();
|
||||
const newPage = await newContext.newPage();
|
||||
await login({ ...(await self()), password: user.username }, newPage);
|
||||
// Don't forget to: newContext.close();
|
||||
return [newContext, newPage] as const;
|
||||
},
|
||||
logout: async () => {
|
||||
await page.goto("/auth/logout");
|
||||
},
|
||||
@@ -1074,9 +1081,11 @@ export async function apiLogin(
|
||||
json: "true",
|
||||
csrfToken,
|
||||
};
|
||||
return page.context().request.post("/api/auth/callback/credentials", {
|
||||
const response = await page.context().request.post("/api/auth/callback/credentials", {
|
||||
data,
|
||||
});
|
||||
expect(response.status()).toBe(200);
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function setupEventWithPrice(eventType: Pick<Prisma.EventType, "id">, slug: string, page: Page) {
|
||||
|
||||
@@ -65,10 +65,12 @@ export async function moveUserToOrg({
|
||||
);
|
||||
}
|
||||
|
||||
const userWithSameUsernameInOrg = await prisma.user.findFirst({
|
||||
const userWithSameUsernameInOrg = await prisma.user.findUnique({
|
||||
where: {
|
||||
username: targetOrgUsername,
|
||||
organizationId: targetOrgId,
|
||||
username_organizationId: {
|
||||
username: targetOrgUsername,
|
||||
organizationId: targetOrgId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ test.describe("2FA Tests", async () => {
|
||||
test.afterEach(async ({ users }) => {
|
||||
await users.deleteAll();
|
||||
});
|
||||
test("should allow a user to enable 2FA and login using 2FA", async ({ page, users }) => {
|
||||
test("should allow a user to enable 2FA and login using 2FA", async ({ page, users, browser }) => {
|
||||
// log in trail user
|
||||
const user = await test.step("Enable 2FA", async () => {
|
||||
const user = await users.create();
|
||||
@@ -60,7 +60,7 @@ test.describe("2FA Tests", async () => {
|
||||
});
|
||||
|
||||
await test.step("Login with 2FA enabled", async () => {
|
||||
await user.login();
|
||||
const [secondContext, secondPage] = await user.loginOnNewBrowser(browser);
|
||||
const userWith2FaSecret = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: user.id,
|
||||
@@ -74,13 +74,14 @@ test.describe("2FA Tests", async () => {
|
||||
process.env.CALENDSO_ENCRYPTION_KEY!
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await fillOtp({ page, secret: secret! });
|
||||
await fillOtp({ page: secondPage, secret: secret! });
|
||||
await Promise.all([
|
||||
page.press('input[name="2fa6"]', "Enter"),
|
||||
page.waitForResponse("**/api/auth/callback/credentials**"),
|
||||
secondPage.press('input[name="2fa6"]', "Enter"),
|
||||
secondPage.waitForResponse("**/api/auth/callback/credentials**"),
|
||||
]);
|
||||
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
|
||||
const shellLocator = secondPage.locator(`[data-testid=dashboard-shell]`);
|
||||
await expect(shellLocator).toBeVisible();
|
||||
await secondContext.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -368,10 +368,11 @@ test.describe("Bookings", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("Round robin event type with confirmation required handles cancellation and rebooking correctly with the same details and slot", async ({
|
||||
test("Round robin event type with confirmation required handles rejection and rebooking correctly with the same details and slot", async ({
|
||||
page,
|
||||
users,
|
||||
orgs,
|
||||
browser,
|
||||
}) => {
|
||||
const org = await orgs.create({
|
||||
name: "TestOrg",
|
||||
@@ -421,38 +422,41 @@ test.describe("Bookings", () => {
|
||||
const bookingUid = page.url().split("/booking/")[1];
|
||||
expect(bookingUid).not.toBeNull();
|
||||
|
||||
// Login as the host to cancel the booking
|
||||
await page.goto("/auth/logout");
|
||||
const allUsers = await users.get();
|
||||
|
||||
const allUsers = users.get();
|
||||
const hostUser = allUsers.find((mate) => mate.name === firstHost);
|
||||
if (!hostUser) throw new Error("Host not found");
|
||||
|
||||
await hostUser.apiLogin();
|
||||
const [secondContext, secondPage] = await hostUser.apiLoginOnNewBrowser(browser);
|
||||
|
||||
// Cancel the booking
|
||||
await page.goto(`/booking/${bookingUid}`);
|
||||
await page.getByTestId("cancel").click();
|
||||
await page.getByTestId("confirm_cancel").click();
|
||||
await page.waitForResponse((response) => response.url().includes("/api/cancel"));
|
||||
// Reject the booking
|
||||
await secondPage.goto("/bookings/upcoming");
|
||||
await secondPage.click('[data-testid="reject"]');
|
||||
await submitAndWaitForResponse(secondPage, "/api/trpc/bookings/confirm?batch=1", {
|
||||
action: () => secondPage.click('[data-testid="rejection-confirm"]'),
|
||||
});
|
||||
|
||||
// Logout and go back to booking page
|
||||
await page.goto("/auth/logout");
|
||||
await page.goto(`/org/${org.slug}/${team.slug}/${teamEvent.slug}`);
|
||||
await secondPage.goto("/auth/logout");
|
||||
await secondPage.goto(`/org/${org.slug}/${team.slug}/${teamEvent.slug}`);
|
||||
|
||||
// Rebook with the same details
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
await bookTimeSlot(page);
|
||||
await expect(page.getByTestId("success-page")).toBeVisible();
|
||||
await selectFirstAvailableTimeSlotNextMonth(secondPage);
|
||||
await bookTimeSlot(secondPage);
|
||||
await expect(secondPage.getByTestId("success-page")).toBeVisible();
|
||||
|
||||
// Verify a new host is assigned
|
||||
const newHost = await page.getByTestId("booking-host-name").textContent();
|
||||
const newHost = await secondPage.getByTestId("booking-host-name").textContent();
|
||||
expect(newHost).not.toBeNull();
|
||||
expect(newHost).toBe(firstHost);
|
||||
|
||||
// Verify the booking was successful by checking the new booking UID
|
||||
const newBookingUid = page.url().split("/booking/")[1];
|
||||
const newBookingUid = secondPage.url().split("/booking/")[1];
|
||||
expect(newBookingUid).not.toBeNull();
|
||||
expect(newBookingUid).not.toBe(bookingUid);
|
||||
await secondContext.close();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -602,7 +602,7 @@ async function addWebhooks(webhooks: InputWebhook[]) {
|
||||
async function addWorkflowsToDb(workflows: InputWorkflow[]) {
|
||||
await Promise.all(
|
||||
workflows.map(async (workflow) => {
|
||||
const team = await prismock.team.findFirst({
|
||||
const team = await prismock.team.findUnique({
|
||||
where: {
|
||||
id: workflow.teamId ?? 0,
|
||||
},
|
||||
|
||||
@@ -7321,7 +7321,7 @@
|
||||
"post": {
|
||||
"operationId": "BookingsController_2024_08_13_cancelBooking",
|
||||
"summary": "Cancel a booking",
|
||||
"description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n For seated bookings to cancel one individual booking provide :bookingUid and :seatUid in the request body. For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.",
|
||||
"description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n \n \nCancelling seated bookings:\n It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.\n \n1. As an attendee - provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and seatUid in the request body `{\"seatUid\": \"123-123-123\"}` . This will remove this particular attendance from the booking.\n \n2. As the host - host can cancel booking for all attendees aka for every seat. Provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}` and `Authorization: Bearer token` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.\n \n \nCancelling recurring seated bookings:\n For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "cal-api-version",
|
||||
@@ -7792,7 +7792,7 @@
|
||||
"name": "eventUid",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "The Google Calendar event ID",
|
||||
"description": "The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -10205,6 +10205,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bookingUidToReschedule",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.",
|
||||
"example": "abc123def456",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
@@ -23342,7 +23352,7 @@
|
||||
"example": "Error message"
|
||||
}
|
||||
},
|
||||
"required": ["id", "roomName", "startTs", "status", "duration", "shareToken", "downloadLink"]
|
||||
"required": ["id", "roomName", "startTs", "status", "duration", "shareToken"]
|
||||
},
|
||||
"GetBookingRecordingsOutput": {
|
||||
"type": "object",
|
||||
|
||||
Binary file not shown.
@@ -31,7 +31,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
bookingId: Booking["id"]
|
||||
) {
|
||||
try {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
select: {
|
||||
uid: true,
|
||||
title: true,
|
||||
|
||||
@@ -41,7 +41,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
bookerEmail: string
|
||||
) {
|
||||
try {
|
||||
const booking: PaidBooking | null = await prisma.booking.findFirst({
|
||||
const booking: PaidBooking | null = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
bookingId: Booking["id"]
|
||||
) {
|
||||
try {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
select: {
|
||||
uid: true,
|
||||
title: true,
|
||||
@@ -71,7 +71,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
paymentOption: PaymentOption
|
||||
): Promise<Payment> {
|
||||
try {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
select: {
|
||||
uid: true,
|
||||
title: true,
|
||||
|
||||
@@ -37,7 +37,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
bookingId: Booking["id"]
|
||||
) {
|
||||
try {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
select: {
|
||||
uid: true,
|
||||
title: true,
|
||||
@@ -113,7 +113,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
throw new Error("Payment option is not compatible with create method");
|
||||
}
|
||||
try {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
select: {
|
||||
uid: true,
|
||||
title: true,
|
||||
|
||||
@@ -26,7 +26,7 @@ export const getServerSideProps = async function getServerSideProps(
|
||||
|
||||
const isEmbed = appPages[1] === "embed";
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
const form = await prisma.app_RoutingForms_Form.findUnique({
|
||||
where: {
|
||||
id: formId,
|
||||
},
|
||||
|
||||
@@ -235,7 +235,7 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
|
||||
inputFields: InputFields
|
||||
) {
|
||||
for (const [, connectedForm] of Object.entries(serializedForm.connectedForms)) {
|
||||
const connectedFormDb = await prisma.app_RoutingForms_Form.findFirst({
|
||||
const connectedFormDb = await prisma.app_RoutingForms_Form.findUnique({
|
||||
where: {
|
||||
id: connectedForm.id,
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ const getInCompleteBookingSettingsHandler = async (options: GetIncompleteBooking
|
||||
formId: input.formId,
|
||||
},
|
||||
}),
|
||||
prisma.app_RoutingForms_Form.findFirst({
|
||||
prisma.app_RoutingForms_Form.findUnique({
|
||||
where: {
|
||||
id: input.formId,
|
||||
},
|
||||
@@ -51,7 +51,7 @@ const getInCompleteBookingSettingsHandler = async (options: GetIncompleteBooking
|
||||
|
||||
if (teamId) {
|
||||
// Need to get the credentials for the team and org
|
||||
const orgQuery = await prisma.team.findFirst({
|
||||
const orgQuery = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
|
||||
@@ -1356,7 +1356,7 @@ export default class SalesforceCRMService implements CRM {
|
||||
if (!organizerEmail) {
|
||||
this.log.warn(`No organizer email found for bookingUid ${bookingUid}`);
|
||||
}
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: { uid: bookingUid },
|
||||
select: { createdAt: true },
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function assignmentReasonHandler({
|
||||
}
|
||||
|
||||
async function handleAccountLookupFieldReason(routingFormResponseId: number, teamMemberEmail: string) {
|
||||
const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findFirst({
|
||||
const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
id: routingFormResponseId,
|
||||
},
|
||||
|
||||
@@ -67,7 +67,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
|
||||
|
||||
if (bookingToReschedule && bookingToReschedule.eventTypeId && bookingToReschedule.user) {
|
||||
const userOwner = bookingToReschedule.user;
|
||||
const event = await prisma.eventType.findFirstOrThrow({
|
||||
const event = await prisma.eventType.findUniqueOrThrow({
|
||||
select: {
|
||||
title: true,
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
|
||||
|
||||
if (bookingToReschedule && bookingToReschedule.eventTypeId && bookingToReschedule.user) {
|
||||
const userOwner = bookingToReschedule.user;
|
||||
const event = await prisma.eventType.findFirstOrThrow({
|
||||
const event = await prisma.eventType.findUniqueOrThrow({
|
||||
select: {
|
||||
title: true,
|
||||
schedulingType: true,
|
||||
|
||||
@@ -24,7 +24,6 @@ export const AttendeeRequestEmail = (props: React.ComponentProps<typeof Attendee
|
||||
title: props.calEvent.title,
|
||||
date,
|
||||
})}
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,8 @@ export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail {
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("guests_added_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
|
||||
@@ -26,7 +26,8 @@ export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail
|
||||
from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`,
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("download_recording_subject", {
|
||||
title: this.calEvent.title,
|
||||
|
||||
@@ -38,7 +38,8 @@ export default class OrganizerDailyVideoDownloadTranscriptEmail extends BaseEmai
|
||||
from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`,
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("download_transcript_email_subject", {
|
||||
title: this.calEvent.title,
|
||||
|
||||
@@ -19,7 +19,8 @@ export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmai
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("location_changed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
|
||||
@@ -24,7 +24,8 @@ export default class OrganizerRequestEmail extends OrganizerScheduledEmail {
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("awaiting_approval")}: ${this.calEvent.title}`,
|
||||
html: await this.getHtmlRequestEmail(template, this.calEvent, this.calEvent.organizer),
|
||||
|
||||
@@ -13,7 +13,8 @@ export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.t("event_awaiting_approval_subject", {
|
||||
title: this.calEvent.title,
|
||||
|
||||
@@ -19,7 +19,8 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.calEvent.organizer.language.translate("event_type_has_been_rescheduled_on_time_date", {
|
||||
title: this.calEvent.title,
|
||||
|
||||
@@ -52,7 +52,8 @@ export default class OrganizerScheduledEmail extends BaseEmail {
|
||||
to: toAddresses.join(","),
|
||||
...getReplyToHeader(
|
||||
this.calEvent,
|
||||
this.calEvent.attendees.map(({ email }) => email)
|
||||
this.calEvent.attendees.map(({ email }) => email),
|
||||
true
|
||||
),
|
||||
subject: `${this.newSeat ? `${this.t("new_attendee")}: ` : ""}${this.calEvent.title}`,
|
||||
html: await this.getHtml(
|
||||
|
||||
@@ -438,7 +438,7 @@ export const getOptions = ({
|
||||
encode: async ({ token, maxAge, secret }) => {
|
||||
log.debug("jwt:encode", safeStringify({ token, maxAge }));
|
||||
if (token?.sub && isNumber(token.sub)) {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: Number(token.sub) },
|
||||
select: { metadata: true },
|
||||
});
|
||||
@@ -489,7 +489,7 @@ export const getOptions = ({
|
||||
} as JWT;
|
||||
}
|
||||
const autoMergeIdentities = async () => {
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
where: { email: token.email! },
|
||||
select: {
|
||||
@@ -855,7 +855,7 @@ export const getOptions = ({
|
||||
// If the email address doesn't match, check if an account already exists
|
||||
// with the new email address. If it does, for now we return an error. If
|
||||
// not, update the email of their account and log them in.
|
||||
const userWithNewEmail = await prisma.user.findFirst({
|
||||
const userWithNewEmail = await prisma.user.findUnique({
|
||||
where: { email: user.email },
|
||||
});
|
||||
|
||||
@@ -875,12 +875,9 @@ export const getOptions = ({
|
||||
// a new account. If an account already exists with the incoming email
|
||||
// address return an error for now.
|
||||
|
||||
const existingUserWithEmail = await prisma.user.findFirst({
|
||||
const existingUserWithEmail = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: {
|
||||
equals: user.email,
|
||||
mode: "insensitive",
|
||||
},
|
||||
email: user.email,
|
||||
},
|
||||
include: {
|
||||
password: true,
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function isAuthorized(token: string, requiredScopes: string
|
||||
}
|
||||
|
||||
if (decodedToken.userId) {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: decodedToken.userId,
|
||||
},
|
||||
@@ -35,7 +35,7 @@ export default async function isAuthorized(token: string, requiredScopes: string
|
||||
}
|
||||
|
||||
if (decodedToken.teamId) {
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: decodedToken.teamId,
|
||||
},
|
||||
|
||||
@@ -83,11 +83,12 @@ const usernameCheck = async (usernameRaw: string) => {
|
||||
|
||||
const username = slugify(usernameRaw);
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
username,
|
||||
// Simply remove it when we drop organizationId column
|
||||
organizationId: null,
|
||||
username_organizationId: {
|
||||
username,
|
||||
organizationId: null,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
username: true,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const prefillAvatar = async ({ email }: IPrefillAvatar) => {
|
||||
if (!base64Image) return;
|
||||
|
||||
const avatar = await resizeBase64Image(base64Image);
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: email },
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { validateAndGetCorrectedUsernameInTeam } from "@calcom/lib/validateUsern
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
export async function findTokenByToken({ token }: { token: string }) {
|
||||
const foundToken = await prisma.verificationToken.findFirst({
|
||||
const foundToken = await prisma.verificationToken.findUnique({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ function getResponsesFromOldBooking(
|
||||
}
|
||||
|
||||
async function getBooking(prisma: PrismaClient, uid: string, isSeatedEvent?: boolean) {
|
||||
const rawBooking = await prisma.booking.findFirst({
|
||||
const rawBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
@@ -125,7 +125,7 @@ export default getBooking;
|
||||
|
||||
export const getBookingForReschedule = async (uid: string, userId?: number) => {
|
||||
let rescheduleUid: string | null = null;
|
||||
const theBooking = await prisma.booking.findFirst({
|
||||
const theBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
@@ -226,7 +226,7 @@ export const getBookingForReschedule = async (uid: string, userId?: number) => {
|
||||
* @returns booking with masked attendee emails
|
||||
*/
|
||||
export const getBookingForSeatedEvent = async (uid: string) => {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
@@ -253,7 +253,7 @@ export const getBookingForSeatedEvent = async (uid: string) => {
|
||||
if (!booking || booking.eventTypeId === null) return null;
|
||||
|
||||
// Validate booking event type has seats enabled
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: booking.eventTypeId,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const getUserBooking = async (uid: string) => {
|
||||
const bookingInfo = await prisma.booking.findFirst({
|
||||
const bookingInfo = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: uid,
|
||||
},
|
||||
|
||||
@@ -154,7 +154,7 @@ async function handler(input: CancelBookingInput) {
|
||||
|
||||
const webhooks = await getWebhooks(subscriberOptions);
|
||||
|
||||
const organizer = await prisma.user.findFirstOrThrow({
|
||||
const organizer = await prisma.user.findUniqueOrThrow({
|
||||
where: {
|
||||
id: bookingToDelete.userId,
|
||||
},
|
||||
|
||||
@@ -500,7 +500,7 @@ export async function handleConfirmation(args: {
|
||||
orgId,
|
||||
oAuthClientId: platformClientParams?.platformClientId,
|
||||
});
|
||||
const bookingWithPayment = await prisma.booking.findFirst({
|
||||
const bookingWithPayment = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
|
||||
@@ -136,7 +136,7 @@ const handleDeleteCredential = async ({
|
||||
credential.app?.categories.includes(AppCategories.calendar) &&
|
||||
eventType.destinationCalendar?.credential?.appId === credential.appId
|
||||
) {
|
||||
const destinationCalendar = await prisma.destinationCalendar.findFirst({
|
||||
const destinationCalendar = await prisma.destinationCalendar.findUnique({
|
||||
where: {
|
||||
id: eventType.destinationCalendar?.id,
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ async function checkLicense(
|
||||
let licenseKey = process.env.CALCOM_LICENSE_KEY;
|
||||
if (!licenseKey) {
|
||||
/** We try to check on DB only if env is undefined */
|
||||
const deployment = await prisma.deployment.findFirst({ where: { id: 1 } });
|
||||
const deployment = await prisma.deployment.findUnique({ where: { id: 1 } });
|
||||
licenseKey = deployment?.licenseKey ?? undefined;
|
||||
}
|
||||
if (!licenseKey) return false;
|
||||
|
||||
@@ -33,7 +33,7 @@ async function syncCustomAttributesToUser({
|
||||
};
|
||||
directoryId: string;
|
||||
}) {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: userEmail,
|
||||
},
|
||||
@@ -62,7 +62,7 @@ const handleUserEvents = async (event: DirectorySyncEvent, organizationId: numbe
|
||||
const eventData = event.data as User;
|
||||
const userEmail = eventData.email;
|
||||
// Check if user exists in DB
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: userEmail,
|
||||
},
|
||||
|
||||
@@ -25,10 +25,12 @@ export const getServerSideProps = async ({ req }: GetServerSidePropsContext) =>
|
||||
}
|
||||
|
||||
// Check if logged in user has OWNER/ADMIN role in organization
|
||||
const membership = await prisma.membership.findFirst({
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId: session?.user.id,
|
||||
teamId: session?.user.profile.organizationId,
|
||||
userId_teamId: {
|
||||
userId: session?.user.id,
|
||||
teamId: session?.user.profile.organizationId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
role: true,
|
||||
|
||||
@@ -20,7 +20,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
const { uid } = querySchema.parse(context.query);
|
||||
const { currentOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const rawPayment = await prisma.payment.findFirst({
|
||||
const rawPayment = await prisma.payment.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class AssignmentReasonRecorder {
|
||||
teamId: number;
|
||||
}) {
|
||||
// Get the routing form data
|
||||
const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findFirst({
|
||||
const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
id: routingFormResponseId,
|
||||
},
|
||||
@@ -186,7 +186,7 @@ export default class AssignmentReasonRecorder {
|
||||
reassignReason?: string;
|
||||
reassignmentType: RRReassignmentType;
|
||||
}) {
|
||||
const reassignedBy = await prisma.user.findFirst({
|
||||
const reassignedBy = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: reassignById,
|
||||
},
|
||||
|
||||
@@ -239,7 +239,7 @@ export const getMessageBody = async (referenceId: string) => {
|
||||
|
||||
async function isLockedForSMSSending(userId?: number | null, teamId?: number | null) {
|
||||
if (teamId) {
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
@@ -269,7 +269,7 @@ async function isLockedForSMSSending(userId?: number | null, teamId?: number | n
|
||||
return true;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
|
||||
@@ -119,7 +119,7 @@ const processWorkflowStep = async (
|
||||
const limitGuestsDate = new Date("2025-01-13");
|
||||
|
||||
if (workflow.userId) {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: workflow.userId,
|
||||
},
|
||||
|
||||
@@ -339,58 +339,54 @@ const Locations: React.FC<LocationsProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isCalVideo && (
|
||||
{isCalVideo && !isPlatform && (
|
||||
<div className="bg-muted mt-2 space-y-2 rounded-lg p-4">
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
{!isPlatform && (
|
||||
<Controller
|
||||
name="calVideoSettings.disableRecordingForGuests"
|
||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForGuests}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("disable_recording_for_guests")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isPlatform && (
|
||||
<Controller
|
||||
name="calVideoSettings.disableRecordingForOrganizer"
|
||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForOrganizer}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("disable_recording_for_organizer")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isPlatform && (
|
||||
<Controller
|
||||
name="calVideoSettings.enableAutomaticTranscription"
|
||||
defaultValue={!!eventType.calVideoSettings?.enableAutomaticTranscription}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("enable_automatic_transcription")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
name="calVideoSettings.disableRecordingForGuests"
|
||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForGuests}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("disable_recording_for_guests")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="calVideoSettings.disableRecordingForOrganizer"
|
||||
defaultValue={!!eventType.calVideoSettings?.disableRecordingForOrganizer}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("disable_recording_for_organizer")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="calVideoSettings.enableAutomaticTranscription"
|
||||
defaultValue={!!eventType.calVideoSettings?.enableAutomaticTranscription}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
title={t("enable_automatic_transcription")}
|
||||
labelClassName="text-sm"
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={t("enter_redirect_url_on_exit_description")}
|
||||
|
||||
@@ -910,6 +910,8 @@ export const EventAdvancedTab = ({
|
||||
disabled={seatsLocked.disabled}
|
||||
//For old events if value > MAX_SEATS_PER_TIME_SLOT
|
||||
value={value > MAX_SEATS_PER_TIME_SLOT ? MAX_SEATS_PER_TIME_SLOT : value ?? 1}
|
||||
step={1}
|
||||
placeholder="1"
|
||||
min={1}
|
||||
max={MAX_SEATS_PER_TIME_SLOT}
|
||||
containerClassName={classNames(
|
||||
@@ -921,8 +923,7 @@ export const EventAdvancedTab = ({
|
||||
labelClassName={customClassNames?.seatsOptions?.seatsInput?.label}
|
||||
addOnSuffix={t("seats")}
|
||||
onChange={(e) => {
|
||||
let enteredValue = Number(e.target.value);
|
||||
if (enteredValue < 1) enteredValue = 1;
|
||||
const enteredValue = parseInt(e.target.value);
|
||||
onChange(Math.min(enteredValue, MAX_SEATS_PER_TIME_SLOT));
|
||||
}}
|
||||
data-testid="seats-per-time-slot"
|
||||
|
||||
@@ -170,10 +170,12 @@ export class FeaturesRepository implements IFeaturesRepository {
|
||||
async checkIfTeamHasFeature(teamId: number, featureId: keyof AppFlags): Promise<boolean> {
|
||||
try {
|
||||
// Early return if team has feature directly assigned
|
||||
const teamHasFeature = await db.teamFeatures.findFirst({
|
||||
const teamHasFeature = await db.teamFeatures.findUnique({
|
||||
where: {
|
||||
teamId,
|
||||
featureId,
|
||||
teamId_featureId: {
|
||||
teamId,
|
||||
featureId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (teamHasFeature) return true;
|
||||
|
||||
@@ -625,10 +625,12 @@ class EventsInsights {
|
||||
sessionUserId: number;
|
||||
teamId: number;
|
||||
}) => {
|
||||
const isOwnerAdminOfTeam = await prisma.membership.findFirst({
|
||||
const isOwnerAdminOfTeam = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId: sessionUserId,
|
||||
teamId,
|
||||
userId_teamId: {
|
||||
userId: sessionUserId,
|
||||
teamId,
|
||||
},
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["OWNER", "ADMIN"],
|
||||
@@ -659,10 +661,12 @@ class EventsInsights {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isOwnerAdminOfParentTeam = await prisma.membership.findFirst({
|
||||
const isOwnerAdminOfParentTeam = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId: sessionUserId,
|
||||
teamId: team.parentId,
|
||||
userId_teamId: {
|
||||
userId: sessionUserId,
|
||||
teamId: team.parentId,
|
||||
},
|
||||
accepted: true,
|
||||
role: {
|
||||
in: ["OWNER", "ADMIN"],
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function handleSendingAttendeeNoShowDataToApps(
|
||||
attendees: NoShowAttendees
|
||||
) {
|
||||
// Get event type metadata
|
||||
const eventTypeQuery = await prisma.booking.findFirst({
|
||||
const eventTypeQuery = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: bookingUid,
|
||||
},
|
||||
@@ -62,7 +62,7 @@ async function handleCRMNoShow(
|
||||
) {
|
||||
// Handle checking if no she in CrmService
|
||||
|
||||
const credential = await prisma.credential.findFirst({
|
||||
const credential = await prisma.credential.findUnique({
|
||||
where: {
|
||||
id: appData.credentialId,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type Prisma } from "@prisma/client";
|
||||
import db from "@calcom/prisma";
|
||||
|
||||
import { type TaskTypes } from "./tasker";
|
||||
import { scanWorkflowBodySchema } from "./tasks/scanWorkflowBody";
|
||||
|
||||
const whereSucceeded: Prisma.TaskWhereInput = {
|
||||
succeededAt: { not: null },
|
||||
@@ -187,4 +188,24 @@ export class Task {
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
static async hasNewerScanTaskForStepId(workflowStepId: number, createdAt: string) {
|
||||
const tasks = await db.$queryRaw<{ payload: string }[]>`
|
||||
SELECT "payload"
|
||||
FROM "Task"
|
||||
WHERE "type" = 'scanWorkflowBody'
|
||||
AND "succeededAt" IS NULL
|
||||
AND (payload::jsonb ->> 'workflowStepId')::int = ${workflowStepId}
|
||||
`;
|
||||
|
||||
return tasks.some((task) => {
|
||||
try {
|
||||
const parsed = scanWorkflowBodySchema.parse(JSON.parse(task.payload));
|
||||
if (!parsed.createdAt) return false;
|
||||
return new Date(parsed.createdAt) > new Date(createdAt);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import prisma from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
const buildCalendarEvent: (bookingUid: string) => Promise<CalendarEvent> = async (bookingUid: string) => {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: bookingUid,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import z from "zod";
|
||||
|
||||
import { getTemplateBodyForAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
|
||||
import compareReminderBodyToTemplate from "@calcom/features/ee/workflows/lib/compareReminderBodyToTemplate";
|
||||
import { Task } from "@calcom/features/tasker/repository";
|
||||
import { lockUser, LockReason } from "@calcom/lib/autoLock";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
@@ -11,18 +12,39 @@ import { scheduleWorkflowNotifications } from "@calcom/trpc/server/routers/viewe
|
||||
|
||||
export const scanWorkflowBodySchema = z.object({
|
||||
userId: z.number(),
|
||||
workflowStepIds: z.array(z.number()),
|
||||
// deprecated: use workflowStepId instead
|
||||
workflowStepIds: z.array(z.number()).optional(),
|
||||
workflowStepId: z.number().optional(),
|
||||
createdAt: z.string().optional(),
|
||||
});
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[tasker] scanWorkflowBody"] });
|
||||
|
||||
export async function scanWorkflowBody(payload: string) {
|
||||
const { workflowStepIds, userId } = scanWorkflowBodySchema.parse(JSON.parse(payload));
|
||||
const { workflowStepIds, userId, createdAt, workflowStepId } = scanWorkflowBodySchema.parse(
|
||||
JSON.parse(payload)
|
||||
);
|
||||
|
||||
const stepIdsToScan: number[] = workflowStepIds ? workflowStepIds : [];
|
||||
|
||||
if (workflowStepId && !workflowStepIds) {
|
||||
if (createdAt) {
|
||||
const hasNewerTask = await Task.hasNewerScanTaskForStepId(workflowStepId, createdAt);
|
||||
|
||||
if (hasNewerTask) return;
|
||||
|
||||
stepIdsToScan.push(workflowStepId);
|
||||
} else {
|
||||
stepIdsToScan.push(workflowStepId);
|
||||
}
|
||||
}
|
||||
|
||||
if (stepIdsToScan.length === 0) return;
|
||||
|
||||
const workflowSteps = await prisma.workflowStep.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: workflowStepIds,
|
||||
in: stepIdsToScan,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
@@ -116,7 +138,7 @@ export async function scanWorkflowBody(payload: string) {
|
||||
await prisma.workflowStep.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: workflowStepIds,
|
||||
in: stepIdsToScan,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
@@ -130,7 +152,7 @@ export async function scanWorkflowBody(payload: string) {
|
||||
steps: {
|
||||
some: {
|
||||
id: {
|
||||
in: workflowStepIds,
|
||||
in: stepIdsToScan,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -142,7 +164,7 @@ export async function scanWorkflowBody(payload: string) {
|
||||
});
|
||||
|
||||
if (!workflow) {
|
||||
log.warn(`Workflow with steps ${workflowStepIds} not found`);
|
||||
log.warn(`Workflow with steps ${stepIdsToScan} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from "@calcom/lib/csvUtils";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
@@ -109,8 +110,18 @@ function reducer(state: UserTableState, action: UserTableAction): UserTableState
|
||||
export type UserListTableProps = {
|
||||
org: RouterOutputs["viewer"]["organizations"]["listCurrent"];
|
||||
teams: RouterOutputs["viewer"]["organizations"]["getTeams"];
|
||||
facetedTeamValues?: RouterOutputs["viewer"]["organizations"]["getFacetedValues"];
|
||||
attributes?: RouterOutputs["viewer"]["attributes"]["list"];
|
||||
facetedTeamValues?: {
|
||||
roles: MembershipRole[];
|
||||
teams: RouterOutputs["viewer"]["organizations"]["getTeams"];
|
||||
attributes: {
|
||||
id: string;
|
||||
name: string;
|
||||
options: {
|
||||
value: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export function UserListTable(props: UserListTableProps) {
|
||||
|
||||
@@ -186,7 +186,7 @@ export default class EventManager {
|
||||
// Fallback to cal video if no location is set
|
||||
if (!evt.location) {
|
||||
// See if cal video is enabled & has keys
|
||||
const calVideo = await prisma.app.findFirst({
|
||||
const calVideo = await prisma.app.findUnique({
|
||||
where: {
|
||||
slug: "daily-video",
|
||||
},
|
||||
@@ -461,7 +461,7 @@ export default class EventManager {
|
||||
}
|
||||
|
||||
// Get details of existing booking.
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: rescheduleUid,
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ export const bulkUpdateTeamEventsToDefaultLocation = async ({
|
||||
teamId: number;
|
||||
prisma: PrismaClient;
|
||||
}) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: { id: teamId },
|
||||
select: { metadata: true },
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ function getResponsesFromOldBooking(
|
||||
}
|
||||
|
||||
async function getBooking(prisma: PrismaClient, uid: string) {
|
||||
const rawBooking = await prisma.booking.findFirst({
|
||||
const rawBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,17 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
/**
|
||||
* Returns the reply-to email address to use, with fallback to organizer's email
|
||||
*/
|
||||
export const getReplyToEmail = (calEvent: Pick<CalendarEvent, "customReplyToEmail" | "organizer">) => {
|
||||
return calEvent.customReplyToEmail || calEvent.organizer.email;
|
||||
export const getReplyToEmail = (
|
||||
calEvent: Pick<CalendarEvent, "customReplyToEmail" | "organizer">,
|
||||
excludeOrganizerEmail?: boolean
|
||||
) => {
|
||||
if (calEvent.customReplyToEmail) {
|
||||
return calEvent.customReplyToEmail;
|
||||
}
|
||||
|
||||
if (excludeOrganizerEmail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return calEvent.organizer.email;
|
||||
};
|
||||
|
||||
@@ -2,15 +2,32 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { getReplyToEmail } from "./getReplyToEmail";
|
||||
|
||||
export function getReplyToHeader(calEvent: CalendarEvent, additionalEmails?: string | string[]) {
|
||||
export function getReplyToHeader(
|
||||
calEvent: CalendarEvent,
|
||||
additionalEmails?: string | string[],
|
||||
excludeOrganizerEmail?: boolean
|
||||
) {
|
||||
if (calEvent.hideOrganizerEmail) return {};
|
||||
|
||||
const replyToEmail = getReplyToEmail(calEvent);
|
||||
const replyTo = additionalEmails
|
||||
? Array.isArray(additionalEmails)
|
||||
? [...additionalEmails, replyToEmail]
|
||||
: [additionalEmails, replyToEmail]
|
||||
: replyToEmail;
|
||||
const replyToEmail = getReplyToEmail(calEvent, excludeOrganizerEmail);
|
||||
const emailArray: string[] = [];
|
||||
|
||||
if (additionalEmails) {
|
||||
if (Array.isArray(additionalEmails)) {
|
||||
emailArray.push(...additionalEmails);
|
||||
} else {
|
||||
emailArray.push(additionalEmails);
|
||||
}
|
||||
}
|
||||
|
||||
if (replyToEmail) {
|
||||
emailArray.push(replyToEmail);
|
||||
}
|
||||
|
||||
if (emailArray.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const replyTo = emailArray.length === 1 ? emailArray[0] : emailArray;
|
||||
return { replyTo };
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function getTeamIdFromEventType({
|
||||
|
||||
// If it's a managed event we need to find the teamId for it from the parent
|
||||
if (eventType?.parentId) {
|
||||
const managedEvent = await prisma.eventType.findFirst({
|
||||
const managedEvent = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: eventType.parentId,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export const getBrand = async (orgId: number | null) => {
|
||||
if (!orgId) {
|
||||
return null;
|
||||
}
|
||||
const org = await prisma.team.findFirst({
|
||||
const org = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: orgId,
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ async function getAttributeRoutingConfig(
|
||||
}
|
||||
const { routingFormResponseId, eventTypeId } = data;
|
||||
|
||||
const routingFormResponseQuery = await prisma.app_RoutingForms_FormResponse.findFirst({
|
||||
const routingFormResponseQuery = await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
id: routingFormResponseId,
|
||||
},
|
||||
|
||||
@@ -26,10 +26,12 @@ export async function isOrganisationOwner(userId: number, orgId: number) {
|
||||
}
|
||||
|
||||
export async function isOrganisationMember(userId: number, orgId: number) {
|
||||
return !!(await prisma.membership.findFirst({
|
||||
return !!(await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
teamId: orgId,
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId: orgId,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ export class BookingRepository {
|
||||
|
||||
/** Determines if the user is the organizer, team admin, or org admin that the booking was created under */
|
||||
static async doesUserIdHaveAccessToBooking({ userId, bookingId }: { userId: number; bookingId: number }) {
|
||||
const booking = await prisma.booking.findFirst({
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
@@ -165,7 +165,7 @@ export class BookingRepository {
|
||||
}
|
||||
|
||||
static async findReschedulerByUid({ uid }: { uid: string }) {
|
||||
return await prisma.booking.findFirst({
|
||||
return await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ export class CredentialRepository {
|
||||
* Doesn't retrieve key field as that has credentials
|
||||
*/
|
||||
static async findFirstByIdWithUser({ id }: { id: number }) {
|
||||
const credential = await prisma.credential.findFirst({ where: { id }, select: safeCredentialSelect });
|
||||
const credential = await prisma.credential.findUnique({ where: { id }, select: safeCredentialSelect });
|
||||
return buildNonDelegationCredential(credential);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export class CredentialRepository {
|
||||
* Includes 'key' field which is sensitive data.
|
||||
*/
|
||||
static async findFirstByIdWithKeyAndUser({ id }: { id: number }) {
|
||||
const credential = await prisma.credential.findFirst({
|
||||
const credential = await prisma.credential.findUnique({
|
||||
where: { id },
|
||||
select: { ...safeCredentialSelect, key: true },
|
||||
});
|
||||
|
||||
@@ -233,10 +233,12 @@ export class MembershipRepository {
|
||||
}
|
||||
|
||||
static async findFirstByUserIdAndTeamId({ userId, teamId }: { userId: number; teamId: number }) {
|
||||
return await prisma.membership.findFirst({
|
||||
return await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,11 +247,11 @@ export class OrganizationRepository {
|
||||
}
|
||||
|
||||
static async findCurrentOrg({ userId, orgId }: { userId: number; orgId: number }) {
|
||||
const membership = await prisma.membership.findFirst({
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
team: {
|
||||
id: orgId,
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId: orgId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
|
||||
@@ -364,10 +364,12 @@ export class ProfileRepository {
|
||||
if (!organizationId) {
|
||||
return null;
|
||||
}
|
||||
const profile = await prisma.profile.findFirst({
|
||||
const profile = await prisma.profile.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
organizationId,
|
||||
userId_organizationId: {
|
||||
userId,
|
||||
organizationId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organization: {
|
||||
@@ -401,10 +403,12 @@ export class ProfileRepository {
|
||||
organizationId: number;
|
||||
username: string;
|
||||
}) {
|
||||
const profile = await prisma.profile.findFirst({
|
||||
const profile = await prisma.profile.findUnique({
|
||||
where: {
|
||||
username,
|
||||
organizationId,
|
||||
username_organizationId: {
|
||||
username,
|
||||
organizationId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organization: {
|
||||
|
||||
@@ -636,7 +636,7 @@ export class UserRepository {
|
||||
return user;
|
||||
}
|
||||
static async getUserAdminTeams(userId: number) {
|
||||
return prisma.user.findFirst({
|
||||
return prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
@@ -713,10 +713,12 @@ export class UserRepository {
|
||||
return !!teams.length;
|
||||
}
|
||||
static async isAdminOrOwnerOfTeam({ userId, teamId }: { userId: number; teamId: number }) {
|
||||
const isAdminOrOwnerOfTeam = await prisma.membership.findFirst({
|
||||
const isAdminOrOwnerOfTeam = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId,
|
||||
},
|
||||
role: { in: [MembershipRole.ADMIN, MembershipRole.OWNER] },
|
||||
accepted: true,
|
||||
},
|
||||
@@ -876,7 +878,7 @@ export class UserRepository {
|
||||
}
|
||||
|
||||
static async getUserStats({ userId }: { userId: number }) {
|
||||
const user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import { EventTypeMetaDataSchema, EventTypeAppMetadataSchema } from "@calcom/pri
|
||||
|
||||
export class EventTypeService {
|
||||
static async getEventTypeAppDataFromId(eventTypeId: number, appSlug: keyof typeof appDataSchemas) {
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { WorkflowRepository } from "../repository/workflow";
|
||||
// TODO (Sean): Move most of the logic migrated in 16861 to this service
|
||||
export class WorkflowService {
|
||||
static async deleteWorkflowRemindersOfRemovedTeam(teamId: number) {
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
|
||||
@@ -85,7 +85,7 @@ export const validateAndGetCorrectedUsernameInTeam = async (
|
||||
isSignup: boolean
|
||||
) => {
|
||||
try {
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
|
||||
@@ -92,7 +92,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
|
||||
};
|
||||
try {
|
||||
// Check to see if video app is enabled
|
||||
const enabledApp = await prisma.app.findFirst({
|
||||
const enabledApp = await prisma.app.findUnique({
|
||||
where: {
|
||||
slug: credential.appId,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"description": "Customizable UI components to integrate scheduling into your product.",
|
||||
"authors": "Cal.com, Inc.",
|
||||
"version": "1.0.52",
|
||||
"version": "1.0.105",
|
||||
"scripts": {
|
||||
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
||||
"build": "NODE_OPTIONS='--max_old_space_size=16384' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
getRecordingsOfCalVideoByRoomName,
|
||||
getDownloadLinkOfCalVideoByRecordingId,
|
||||
getAllTranscriptsAccessLinkFromRoomName,
|
||||
} from "@calcom/lib/videoClient";
|
||||
@@ -74,12 +74,6 @@ export { getTranslation };
|
||||
export { roundRobinReassignment } from "@calcom/features/ee/round-robin/roundRobinReassignment";
|
||||
export { roundRobinManualReassignment } from "@calcom/features/ee/round-robin/roundRobinManualReassignment";
|
||||
|
||||
export {
|
||||
getRecordingsOfCalVideoByRoomName,
|
||||
getDownloadLinkOfCalVideoByRecordingId,
|
||||
getAllTranscriptsAccessLinkFromRoomName,
|
||||
} from "@calcom/lib/videoClient";
|
||||
|
||||
export { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
|
||||
export { validateCustomEventName } from "@calcom/lib/event";
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
"import": "./dist/slots.js",
|
||||
"require": "./dist/slots.cjs",
|
||||
"types": "./dist/slots.d.ts"
|
||||
},
|
||||
"./conferencing": {
|
||||
"import": "./dist/conferencing.js",
|
||||
"require": "./dist/conferencing.cjs",
|
||||
"types": "./dist/conferencing.d.ts"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
@@ -84,6 +89,9 @@
|
||||
],
|
||||
"workflows": [
|
||||
"dist/workflows.d.ts"
|
||||
],
|
||||
"conferencing": [
|
||||
"dist/conferencing.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default defineConfig({
|
||||
"app-store": resolve(__dirname, "./app-store.ts"),
|
||||
workflows: resolve(__dirname, "./workflows.ts"),
|
||||
slots: resolve(__dirname, "./slots.ts"),
|
||||
conferencing: resolve(__dirname, "./conferencing.ts"),
|
||||
},
|
||||
name: "calcom-lib",
|
||||
fileName: "calcom-lib",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user