Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76bd90b228 | ||
|
|
d908403a99 | ||
|
|
0fe775962f | ||
|
|
412a6992c7 | ||
|
|
cfd1992733 | ||
|
|
e86d6169ba | ||
|
|
a379bd2b52 | ||
|
|
1137047606 | ||
|
|
ad4cf0cc3a | ||
|
|
e7d2e0dc2f | ||
|
|
aa51218428 | ||
|
|
38bc5fb71e | ||
|
|
411516ddb9 | ||
|
|
c6c17ac388 |
@@ -3,7 +3,8 @@ import type { NextApiRequest } from "next";
|
||||
import { OAuth2UniversalSchema } from "@calcom/app-store/_utils/oauth/universalSchema";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
import { schemaCredentialPatchParams, schemaCredentialPatchBody } from "~/lib/validations/credential-sync";
|
||||
|
||||
@@ -71,7 +72,7 @@ async function handler(req: NextApiRequest) {
|
||||
userId,
|
||||
},
|
||||
data: {
|
||||
key,
|
||||
key: key as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -6,7 +6,8 @@ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
|
||||
import { schemaCredentialPostBody, schemaCredentialPostParams } from "~/lib/validations/credential-sync";
|
||||
@@ -92,7 +93,7 @@ async function handler(req: NextApiRequest) {
|
||||
data: {
|
||||
userId,
|
||||
appId: appSlug,
|
||||
key,
|
||||
key: key as unknown as Prisma.InputJsonValue,
|
||||
type: appMetadata.type,
|
||||
},
|
||||
select: credentialForCalendarServiceSelect,
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("POST /api/attendees", () => {
|
||||
|
||||
const userBooking = { id: 1 };
|
||||
|
||||
prismaMock.booking.findFirst.mockResolvedValue(userBooking);
|
||||
prismaMock.booking.findFirst.mockResolvedValue(userBooking as any);
|
||||
|
||||
const attendeeData = {
|
||||
id: 1,
|
||||
|
||||
@@ -25,7 +25,7 @@ beforeEach(() => {
|
||||
id: userId,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ beforeEach(() => {
|
||||
id: userId,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -30,12 +30,13 @@ beforeEach(() => {
|
||||
id: userId,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
});
|
||||
} as any);
|
||||
(getAccessibleUsers as any).mockResolvedValue([userId]);
|
||||
(retrieveOrgScopedAccessibleUsers as any).mockResolvedValue([userId]);
|
||||
|
||||
prismaMock.membership.findMany.mockResolvedValue([
|
||||
{
|
||||
// @ts-expect-error Will be fixed by Prisma 6.7.0 upgrade mock changes - which uses vitest-mock-extended
|
||||
team: {
|
||||
id: 1,
|
||||
isOrganization: true,
|
||||
@@ -48,7 +49,7 @@ beforeEach(() => {
|
||||
id: userId,
|
||||
email: "test@example.com",
|
||||
},
|
||||
]);
|
||||
] as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -11,9 +11,9 @@ import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBook
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { buildBooking, buildEventType, buildWebhook, buildUser } from "@calcom/lib/test/builder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Booking } from "@calcom/prisma/client";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { CreationSource, BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
import handler from "../../../pages/api/bookings/_post";
|
||||
|
||||
@@ -303,6 +303,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -340,6 +341,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -376,6 +378,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ profileId: null }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -417,6 +420,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ profileId: null, length: 15 }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -468,7 +472,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.booking.findUnique.mockResolvedValue({
|
||||
...originalBooking,
|
||||
status: "cancelled",
|
||||
status: "CANCELLED",
|
||||
});
|
||||
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
@@ -492,6 +496,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ profileId: null, length: 15 }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -532,7 +537,7 @@ describe("POST /api/bookings", () => {
|
||||
const previousBooking = await prisma.booking.findUnique({
|
||||
where: { uid: "original-booking-uid" },
|
||||
});
|
||||
expect(previousBooking?.status).toBe("cancelled");
|
||||
expect(previousBooking?.status).toBe(BookingStatus.CANCELLED);
|
||||
});
|
||||
|
||||
test("Creates source as api_v1", async () => {
|
||||
@@ -556,6 +561,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ profileId: null, length: 15 }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -627,6 +633,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ profileId: null, length: 15 }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
@@ -684,6 +691,7 @@ describe("POST /api/bookings", () => {
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
|
||||
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
profile: { organizationId: null },
|
||||
hosts: [],
|
||||
users: [buildUser()],
|
||||
|
||||
@@ -50,6 +50,7 @@ describe("DELETE /api/event-types/[id]", () => {
|
||||
// Mocking team.findUnique
|
||||
prismaMock.team.findUnique.mockResolvedValue({
|
||||
id: teamId,
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
members: [
|
||||
{ userId: memberUser, role: MembershipRole.MEMBER, teamId: teamId },
|
||||
{ userId: adminUser, role: MembershipRole.ADMIN, teamId: teamId },
|
||||
|
||||
@@ -84,6 +84,7 @@ describe("GET /api/event-types/[id]", () => {
|
||||
|
||||
prismaMock.team.findFirst.mockResolvedValue({
|
||||
id: teamId,
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
members: [
|
||||
{
|
||||
userId,
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.347",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.349",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
+215
-40
@@ -12,6 +12,7 @@ import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { PlatformOAuthClient, Team, User, Schedule, EventType } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -88,11 +89,16 @@ describe("Event types Endpoints", () => {
|
||||
let schedulesRepostoryFixture: SchedulesRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let apiKeyString: string;
|
||||
let apiKeyOrgUser: string;
|
||||
|
||||
const userEmail = `event-types-2024-06-14-user-${randomString()}@api.com`;
|
||||
const falseTestEmail = `event-types-2024-06-14-false-user-${randomString()}@api.com`;
|
||||
const name = `event-types-2024-06-14-user-${randomString()}`;
|
||||
const username = name;
|
||||
let eventType: EventTypeOutput_2024_06_14;
|
||||
let hiddenEventType: EventTypeOutput_2024_06_14;
|
||||
let user: User;
|
||||
let orgUser: User;
|
||||
let falseTestUser: User;
|
||||
@@ -165,13 +171,10 @@ describe("Event types Endpoints", () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
|
||||
})
|
||||
)
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
|
||||
})
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
@@ -198,12 +201,20 @@ describe("Event types Endpoints", () => {
|
||||
name,
|
||||
username,
|
||||
});
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = `cal_test_${keyString}`;
|
||||
|
||||
orgUser = await userRepositoryFixture.create({
|
||||
email: `event-types-2024-06-14-org-user-${randomString()}@example.com`,
|
||||
name: `event-types-2024-06-14-org-user-${randomString()}`,
|
||||
username: `event-types-2024-06-14-org-user-${randomString()}`,
|
||||
});
|
||||
const { keyString: orgUserApiKeyString } = await apiKeysRepositoryFixture.createApiKey(
|
||||
orgUser.id,
|
||||
null
|
||||
);
|
||||
apiKeyOrgUser = `cal_test_${orgUserApiKeyString}`;
|
||||
|
||||
profileRepositoryFixture.create({
|
||||
uid: `usr-${orgUser.id}`,
|
||||
@@ -334,6 +345,7 @@ describe("Event types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(404);
|
||||
});
|
||||
@@ -370,6 +382,7 @@ describe("Event types Endpoints", () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
|
||||
@@ -395,6 +408,7 @@ describe("Event types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
@@ -504,6 +518,7 @@ describe("Event types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
@@ -576,21 +591,52 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a hidden event type", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class hidden",
|
||||
slug: "coding-class-hidden",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
],
|
||||
hidden: true,
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
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.hidden).toEqual(body.hidden);
|
||||
expect(createdEventType.ownerId).toEqual(user.id);
|
||||
hiddenEventType = responseBody.data;
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}`)
|
||||
.get(`/api/v2/event-types?username=${user.username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
expect(responseBody.data?.length).toEqual(2);
|
||||
|
||||
const fetchedEventType = responseBody.data?.[0];
|
||||
const fetchedEventType = responseBody.data?.find((et) => et.id === eventType.id);
|
||||
const fetchedHiddenEventType = responseBody.data?.find((et) => et.id === hiddenEventType.id);
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
@@ -602,33 +648,149 @@ describe("Event types Endpoints", () => {
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
expect(fetchedEventType.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType.requiresBookerEmailVerification).toEqual(
|
||||
expect(fetchedEventType?.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType?.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
expect(fetchedEventType?.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType?.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType?.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType?.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType?.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType?.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType?.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType?.requiresBookerEmailVerification).toEqual(
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType.hideOrganizerEmail).toEqual(eventType.hideOrganizerEmail);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
expect(fetchedEventType?.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType?.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType?.hideOrganizerEmail).toEqual(eventType.hideOrganizerEmail);
|
||||
expect(fetchedEventType?.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType.color).toEqual(eventType.color);
|
||||
expect(fetchedEventType?.color).toEqual(eventType.color);
|
||||
expect(fetchedEventType?.hidden).toEqual(false);
|
||||
|
||||
expect(fetchedHiddenEventType?.id).toEqual(hiddenEventType.id);
|
||||
expect(fetchedHiddenEventType?.hidden).toEqual(true);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username should not return hidden event type if auth of non event type owner provided`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${user.username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyOrgUser}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.find((et) => et.id === eventType.id);
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType?.hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username should not return hidden event type if no auth provided`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${user.username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.find((et) => et.id === eventType.id);
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType?.hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and eventSlug`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${user.username}&eventSlug=${eventType.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.find((et) => et.id === eventType.id);
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType?.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields.sort(orderBySlug)).toEqual(
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType?.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType?.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
expect(fetchedEventType?.bookingLimitsDuration).toEqual(eventType.bookingLimitsDuration);
|
||||
expect(fetchedEventType?.offsetStart).toEqual(eventType.offsetStart);
|
||||
expect(fetchedEventType?.bookingWindow).toEqual(eventType.bookingWindow);
|
||||
expect(fetchedEventType?.bookerLayouts).toEqual(eventType.bookerLayouts);
|
||||
expect(fetchedEventType?.confirmationPolicy).toEqual(eventType.confirmationPolicy);
|
||||
expect(fetchedEventType?.recurrence).toEqual(eventType.recurrence);
|
||||
expect(fetchedEventType?.customName).toEqual(eventType.customName);
|
||||
expect(fetchedEventType?.requiresBookerEmailVerification).toEqual(
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType?.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType?.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType?.hideOrganizerEmail).toEqual(eventType.hideOrganizerEmail);
|
||||
expect(fetchedEventType?.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType?.color).toEqual(eventType.color);
|
||||
expect(fetchedEventType?.hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and eventSlug should not return hidden event type if auth of non event type owner provided`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${user.username}&eventSlug=${hiddenEventType.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyOrgUser}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(0);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and eventSlug should not return hidden event type if no auth provided`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${user.username}&eventSlug=${hiddenEventType.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(0);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and orgSlug`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${orgUser.username}&orgSlug=${organization.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
@@ -647,8 +809,7 @@ describe("Event types Endpoints", () => {
|
||||
`/api/v2/event-types?username=${orgUser.username}&orgSlug=${organization.slug}&eventSlug=${orgUserEventType1.slug}`
|
||||
)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
@@ -663,8 +824,7 @@ describe("Event types Endpoints", () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${orgUser.username}&orgId=${organization.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
@@ -705,6 +865,7 @@ describe("Event types Endpoints", () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
@@ -732,6 +893,7 @@ describe("Event types Endpoints", () => {
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
@@ -748,6 +910,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
seats: {
|
||||
@@ -779,6 +942,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
@@ -799,6 +963,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
@@ -817,6 +982,7 @@ describe("Event types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send({
|
||||
seats: {
|
||||
seatsPerTimeSlot: 4,
|
||||
@@ -844,6 +1010,7 @@ describe("Event types Endpoints", () => {
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
|
||||
@@ -860,6 +1027,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
locations: [
|
||||
@@ -897,6 +1065,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
@@ -918,6 +1087,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201);
|
||||
@@ -935,6 +1105,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${createdEventType.id}`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send({
|
||||
confirmationPolicy: {
|
||||
@@ -1034,6 +1205,7 @@ describe("Event types Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
@@ -1135,6 +1307,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventType.id}`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(404);
|
||||
@@ -1152,6 +1325,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventType.id}`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
@@ -1161,8 +1335,7 @@ describe("Event types Endpoints", () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types/${eventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
@@ -1203,8 +1376,7 @@ describe("Event types Endpoints", () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}&eventSlug=${eventType.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
@@ -1245,13 +1417,16 @@ describe("Event types Endpoints", () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types/1000`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should delete event type", async () => {
|
||||
return request(app.getHttpServer()).delete(`/api/v2/event-types/${eventType.id}`).expect(200);
|
||||
return request(app.getHttpServer())
|
||||
.delete(`/api/v2/event-types/${eventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.set("Authorization", `Bearer ${apiKeyString}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
+28
-24
@@ -6,11 +6,22 @@ import { UpdateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2
|
||||
import { EventTypeResponseTransformPipe } from "@/ee/event-types/event-types_2024_06_14/pipes/event-type-response.transformer";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { VERSION_2024_06_14_VALUE } from "@/lib/api-versions";
|
||||
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
|
||||
import {
|
||||
API_KEY_OR_ACCESS_TOKEN_HEADER,
|
||||
OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER,
|
||||
OPTIONAL_X_CAL_CLIENT_ID_HEADER,
|
||||
OPTIONAL_X_CAL_SECRET_KEY_HEADER,
|
||||
} from "@/lib/docs/headers";
|
||||
import {
|
||||
AuthOptionalUser,
|
||||
GetOptionalUser,
|
||||
} from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { OptionalApiAuthGuard } from "@/modules/auth/guards/optional-api-auth/optional-api-auth.guard";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import {
|
||||
@@ -40,7 +51,6 @@ import {
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
@@ -62,7 +72,8 @@ export class EventTypesController_2024_06_14 {
|
||||
constructor(
|
||||
private readonly eventTypesService: EventTypesService_2024_06_14,
|
||||
private readonly inputEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly eventTypeResponseTransformPipe: EventTypeResponseTransformPipe
|
||||
private readonly eventTypeResponseTransformPipe: EventTypeResponseTransformPipe,
|
||||
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14
|
||||
) {}
|
||||
|
||||
@Post("/")
|
||||
@@ -109,30 +120,23 @@ export class EventTypesController_2024_06_14 {
|
||||
}
|
||||
|
||||
@Get("/")
|
||||
@ApiOperation({ summary: "Get all event types" })
|
||||
@ApiOperation({
|
||||
summary: "Get all event types",
|
||||
description:
|
||||
"Hidden event types are returned only if authentication is provided and it belongs to the event type owner.",
|
||||
})
|
||||
@UseGuards(OptionalApiAuthGuard)
|
||||
@ApiHeader(OPTIONAL_X_CAL_CLIENT_ID_HEADER)
|
||||
@ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER)
|
||||
@ApiHeader(OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
async getEventTypes(
|
||||
@Query() queryParams: GetEventTypesQuery_2024_06_14
|
||||
@Query() queryParams: GetEventTypesQuery_2024_06_14,
|
||||
@GetOptionalUser() authUser: AuthOptionalUser
|
||||
): Promise<GetEventTypesOutput_2024_06_14> {
|
||||
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
|
||||
if (!eventTypes || eventTypes.length === 0) {
|
||||
throw new NotFoundException(`Event types not found`);
|
||||
}
|
||||
const eventTypes = await this.eventTypesService.getEventTypes(queryParams, authUser);
|
||||
const eventTypesFormatted = this.eventTypeResponseTransformPipe.transform(eventTypes);
|
||||
const eventTypesWithoutHiddenFields = eventTypesFormatted.map((eventType) => {
|
||||
return {
|
||||
...eventType,
|
||||
bookingFields: Array.isArray(eventType?.bookingFields)
|
||||
? eventType?.bookingFields
|
||||
.map((field) => {
|
||||
if ("hidden" in field) {
|
||||
return field.hidden !== true ? field : null;
|
||||
}
|
||||
return field;
|
||||
})
|
||||
.filter((f) => f)
|
||||
: [],
|
||||
};
|
||||
}) as EventTypeOutput_2024_06_14[];
|
||||
const eventTypesWithoutHiddenFields =
|
||||
this.outputEventTypesService.getResponseEventTypesWithoutHiddenFields(eventTypesFormatted);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
|
||||
@@ -75,6 +75,16 @@ export class EventTypesRepository_2024_06_14 {
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventTypesPublic(userId: number) {
|
||||
return this.dbRead.prisma.eventType.findMany({
|
||||
where: {
|
||||
userId,
|
||||
hidden: false,
|
||||
},
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeById(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
|
||||
+47
-13
@@ -3,6 +3,7 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_20
|
||||
import { InputEventTransformed_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/transformed";
|
||||
import { SystemField, CustomField } from "@/ee/event-types/event-types_2024_06_14/transformers";
|
||||
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
|
||||
import { AuthOptionalUser } from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
@@ -38,6 +39,7 @@ export class EventTypesService_2024_06_14 {
|
||||
}
|
||||
await this.checkCanCreateEventType(user.id, body);
|
||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { destinationCalendar: _destinationCalendar, ...rest } = body;
|
||||
|
||||
const { eventType: eventTypeCreated } = await createEventType({
|
||||
@@ -93,34 +95,47 @@ export class EventTypesService_2024_06_14 {
|
||||
}
|
||||
}
|
||||
|
||||
async getEventTypeByUsernameAndSlug(
|
||||
username: string,
|
||||
eventTypeSlug: string,
|
||||
orgSlug?: string,
|
||||
orgId?: number
|
||||
) {
|
||||
const user = await this.usersRepository.findByUsername(username, orgSlug, orgId);
|
||||
async getEventTypeByUsernameAndSlug(params: {
|
||||
username: string;
|
||||
eventTypeSlug: string;
|
||||
orgSlug?: string;
|
||||
orgId?: number;
|
||||
authUser?: AuthOptionalUser;
|
||||
}) {
|
||||
const user = await this.usersRepository.findByUsername(params.username, params.orgSlug, params.orgId);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const eventType = await this.eventTypesRepository.getUserEventTypeBySlug(user.id, eventTypeSlug);
|
||||
const eventType = await this.eventTypesRepository.getUserEventTypeBySlug(user.id, params.eventTypeSlug);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (eventType.hidden && params.authUser?.id !== user.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
ownerId: user.id,
|
||||
...eventType,
|
||||
};
|
||||
}
|
||||
|
||||
async getEventTypesByUsername(username: string, orgSlug?: string, orgId?: number) {
|
||||
const user = await this.usersRepository.findByUsername(username, orgSlug, orgId);
|
||||
async getEventTypesByUsername(params: {
|
||||
username: string;
|
||||
orgSlug?: string;
|
||||
orgId?: number;
|
||||
authUser?: AuthOptionalUser;
|
||||
}) {
|
||||
const user = await this.usersRepository.findByUsername(params.username, params.orgSlug, params.orgId);
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
if (params.authUser?.id !== user.id) {
|
||||
return await this.getUserEventTypesPublic(user.id);
|
||||
}
|
||||
return await this.getUserEventTypes(user.id);
|
||||
}
|
||||
|
||||
@@ -172,6 +187,14 @@ export class EventTypesService_2024_06_14 {
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventTypesPublic(userId: number) {
|
||||
const eventTypes = await this.eventTypesRepository.getUserEventTypesPublic(userId);
|
||||
|
||||
return eventTypes.map((eventType) => {
|
||||
return { ownerId: userId, ...eventType };
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypesPublicByUsername(username: string): Promise<EventTypesPublic> {
|
||||
const user = await this.usersRepository.findByUsername(username);
|
||||
if (!user) {
|
||||
@@ -181,15 +204,26 @@ export class EventTypesService_2024_06_14 {
|
||||
return await getEventTypesPublic(user.id);
|
||||
}
|
||||
|
||||
async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14) {
|
||||
async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14, authUser?: AuthOptionalUser) {
|
||||
const { username, eventSlug, usernames, orgSlug, orgId } = queryParams;
|
||||
if (username && eventSlug) {
|
||||
const eventType = await this.getEventTypeByUsernameAndSlug(username, eventSlug, orgSlug, orgId);
|
||||
const eventType = await this.getEventTypeByUsernameAndSlug({
|
||||
username,
|
||||
eventTypeSlug: eventSlug,
|
||||
orgSlug,
|
||||
orgId,
|
||||
authUser,
|
||||
});
|
||||
return eventType ? [eventType] : [];
|
||||
}
|
||||
|
||||
if (username) {
|
||||
return await this.getEventTypesByUsername(username, orgSlug, orgId);
|
||||
return await this.getEventTypesByUsername({
|
||||
username,
|
||||
orgSlug,
|
||||
orgId,
|
||||
authUser,
|
||||
});
|
||||
}
|
||||
|
||||
if (usernames) {
|
||||
|
||||
+24
-1
@@ -31,6 +31,7 @@ import {
|
||||
EventTypeOutput_2024_06_14,
|
||||
OutputUnknownLocation_2024_06_14,
|
||||
OutputUnknownBookingField_2024_06_14,
|
||||
OutputBookingField_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
type EventTypeRelations = {
|
||||
@@ -253,7 +254,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
type: "unknown",
|
||||
slug: "unknown",
|
||||
bookingField: JSON.stringify(bookingField),
|
||||
});
|
||||
} satisfies OutputUnknownBookingField_2024_06_14);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,4 +345,26 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
seatsShowAvailabilityCount: !!seatsShowAvailabilityCount,
|
||||
});
|
||||
}
|
||||
|
||||
getResponseEventTypesWithoutHiddenFields(
|
||||
eventTypes: EventTypeOutput_2024_06_14[]
|
||||
): EventTypeOutput_2024_06_14[] {
|
||||
return eventTypes.map((eventType) => this.getResponseEventTypeWithoutHiddenFields(eventType));
|
||||
}
|
||||
|
||||
getResponseEventTypeWithoutHiddenFields(eventType: EventTypeOutput_2024_06_14): EventTypeOutput_2024_06_14 {
|
||||
if (!Array.isArray(eventType?.bookingFields) || eventType.bookingFields.length === 0) return eventType;
|
||||
|
||||
const visibleBookingFields: OutputBookingField_2024_06_14[] = [];
|
||||
for (const bookingField of eventType.bookingFields) {
|
||||
if ("hidden" in bookingField && bookingField.hidden === true) {
|
||||
continue;
|
||||
}
|
||||
visibleBookingFields.push(bookingField);
|
||||
}
|
||||
return {
|
||||
...eventType,
|
||||
bookingFields: visibleBookingFields,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/inde
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
|
||||
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
@@ -36,6 +37,7 @@ import { Module } from "@nestjs/common";
|
||||
SchedulesAtomsService,
|
||||
VerificationAtomsService,
|
||||
RedisService,
|
||||
TeamsRepository,
|
||||
],
|
||||
exports: [EventTypesAtomService],
|
||||
controllers: [
|
||||
|
||||
@@ -64,4 +64,47 @@ export class AtomsRepository {
|
||||
|
||||
return userTeams;
|
||||
}
|
||||
|
||||
async getSecondaryEmails(userId: number) {
|
||||
return await this.dbRead.prisma.secondaryEmail.findMany({
|
||||
where: {
|
||||
userId,
|
||||
emailVerified: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getExistingSecondaryEmailByUserAndEmail(userId: number, email: string) {
|
||||
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
|
||||
where: {
|
||||
userId_email: { userId, email },
|
||||
},
|
||||
});
|
||||
|
||||
return existingSecondaryEmailRecord?.email;
|
||||
}
|
||||
|
||||
async getExistingSecondaryEmail(email: string) {
|
||||
const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
return existingSecondaryEmailRecord?.email;
|
||||
}
|
||||
|
||||
async addSecondaryEmail(userId: number, email: string) {
|
||||
const existingSecondaryEmailRecord = await this.dbWrite.prisma.secondaryEmail.create({
|
||||
data: {
|
||||
userId,
|
||||
email,
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return existingSecondaryEmailRecord?.email;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { Throttle } from "@/lib/endpoint-throttler-decorator";
|
||||
import { AddVerifiedEmailInput } from "@/modules/atoms/inputs/add-verified-email.input";
|
||||
import { CheckEmailVerificationRequiredParams } from "@/modules/atoms/inputs/check-email-verification-required-params";
|
||||
import { GetVerifiedEmailsParams } from "@/modules/atoms/inputs/get-verified-emails-params";
|
||||
import { SendVerificationEmailInput } from "@/modules/atoms/inputs/send-verification-email.input";
|
||||
import { VerifyEmailCodeInput } from "@/modules/atoms/inputs/verify-email-code.input";
|
||||
import { GetVerifiedEmailsOutput } from "@/modules/atoms/outputs/get-verified-emails-output";
|
||||
import { SendVerificationEmailOutput } from "@/modules/atoms/outputs/send-verification-email.output";
|
||||
import { VerifyEmailCodeOutput } from "@/modules/atoms/outputs/verify-email-code.output";
|
||||
import { VerificationAtomsService } from "@/modules/atoms/services/verification-atom.service";
|
||||
@@ -105,4 +108,44 @@ export class AtomsVerificationController {
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/emails/verified-emails")
|
||||
@Version(VERSION_NEUTRAL)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getVerifiedEmails(
|
||||
@Query() query: GetVerifiedEmailsParams,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<GetVerifiedEmailsOutput> {
|
||||
const verifiedEmails = await this.verificationService.getVerifiedEmails({
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
teamId: query.teamId ? Number(query.teamId) : undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
data: verifiedEmails,
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
}
|
||||
|
||||
@Post("/emails/verified-emails")
|
||||
@Version(VERSION_NEUTRAL)
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async addVerifiedEmails(
|
||||
@Body() body: AddVerifiedEmailInput,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<ApiResponse<{ emailVerified: boolean }>> {
|
||||
const emailVerified = await this.verificationService.addVerifiedEmail({
|
||||
userId: user.id,
|
||||
existingPrimaryEmail: user.email,
|
||||
email: body.email,
|
||||
});
|
||||
|
||||
return {
|
||||
data: { emailVerified },
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEmail } from "class-validator";
|
||||
|
||||
export class AddVerifiedEmailInput {
|
||||
@ApiProperty({ example: "user@example.com" })
|
||||
@IsEmail()
|
||||
email!: string;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsNumber, IsOptional, IsString, IsEmail } from "class-validator";
|
||||
|
||||
export class GetVerifiedEmailsParams {
|
||||
@ApiPropertyOptional({ example: "12345" })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
teamId?: string;
|
||||
}
|
||||
|
||||
export class GetVerifiedEmailsInput {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
userId!: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsEmail()
|
||||
@IsString()
|
||||
userEmail!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
teamId?: number;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsString, IsArray } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class GetVerifiedEmailsOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsString()
|
||||
@Expose()
|
||||
readonly status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ApiProperty({ type: [String] })
|
||||
readonly data!: string[];
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
|
||||
import { CheckEmailVerificationRequiredParams } from "@/modules/atoms/inputs/check-email-verification-required-params";
|
||||
import { GetVerifiedEmailsInput } from "@/modules/atoms/inputs/get-verified-emails-params";
|
||||
import { SendVerificationEmailInput } from "@/modules/atoms/inputs/send-verification-email.input";
|
||||
import { VerifyEmailCodeInput } from "@/modules/atoms/inputs/verify-email-code.input";
|
||||
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, BadRequestException, UnauthorizedException } from "@nestjs/common";
|
||||
|
||||
@@ -13,6 +16,11 @@ import {
|
||||
|
||||
@Injectable()
|
||||
export class VerificationAtomsService {
|
||||
constructor(
|
||||
private readonly atomsRepository: AtomsRepository,
|
||||
private readonly teamsRepository: TeamsRepository
|
||||
) {}
|
||||
|
||||
async checkEmailVerificationRequired(input: CheckEmailVerificationRequiredParams) {
|
||||
return await checkEmailVerificationRequired(input);
|
||||
}
|
||||
@@ -61,4 +69,78 @@ export class VerificationAtomsService {
|
||||
isVerifyingEmail: input.isVerifyingEmail,
|
||||
});
|
||||
}
|
||||
|
||||
async getVerifiedEmails(input: GetVerifiedEmailsInput): Promise<string[]> {
|
||||
const { userId, userEmail, teamId } = input;
|
||||
const userEmailWithoutOauthClientId = this.removeClientIdFromEmail(userEmail);
|
||||
|
||||
if (teamId) {
|
||||
const verifiedEmails: string[] = [];
|
||||
const teamMembers = await this.teamsRepository.getTeamMemberEmails(teamId);
|
||||
|
||||
if (teamMembers.length === 0) {
|
||||
return verifiedEmails;
|
||||
}
|
||||
|
||||
teamMembers.forEach((member) => {
|
||||
const memberEmailWithoutOauthClientId = this.removeClientIdFromEmail(member.email);
|
||||
|
||||
verifiedEmails.push(memberEmailWithoutOauthClientId);
|
||||
member.secondaryEmails.forEach((secondaryEmail) => {
|
||||
verifiedEmails.push(this.removeClientIdFromEmail(secondaryEmail.email));
|
||||
});
|
||||
});
|
||||
|
||||
return verifiedEmails;
|
||||
}
|
||||
|
||||
let verifiedEmails = [userEmailWithoutOauthClientId];
|
||||
|
||||
const secondaryEmails = await this.atomsRepository.getSecondaryEmails(userId);
|
||||
verifiedEmails = verifiedEmails.concat(
|
||||
secondaryEmails.map((secondaryEmail) => this.removeClientIdFromEmail(secondaryEmail.email))
|
||||
);
|
||||
|
||||
return verifiedEmails;
|
||||
}
|
||||
|
||||
async addVerifiedEmail({
|
||||
userId,
|
||||
existingPrimaryEmail,
|
||||
email,
|
||||
}: {
|
||||
userId: number;
|
||||
existingPrimaryEmail: string;
|
||||
email: string;
|
||||
}): Promise<boolean> {
|
||||
const existingSecondaryEmail = await this.atomsRepository.getExistingSecondaryEmailByUserAndEmail(
|
||||
userId,
|
||||
email
|
||||
);
|
||||
const alreadyExistingEmail = await this.atomsRepository.getExistingSecondaryEmail(email);
|
||||
|
||||
if (alreadyExistingEmail) {
|
||||
throw new BadRequestException("Email already exists");
|
||||
}
|
||||
|
||||
if (existingPrimaryEmail === email || existingSecondaryEmail === email) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.atomsRepository.addSecondaryEmail(userId, email);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
removeClientIdFromEmail(email: string): string {
|
||||
const [localPart, domain] = email.split("@");
|
||||
const localPartSegments = localPart.split("+");
|
||||
|
||||
localPartSegments.pop();
|
||||
|
||||
const baseEmail = localPartSegments.join("+");
|
||||
const normalizedEmail = `${baseEmail}@${domain}`;
|
||||
|
||||
return normalizedEmail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +120,30 @@ export class TeamsRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamMemberEmails(teamId: number) {
|
||||
return this.dbRead.prisma.user.findMany({
|
||||
where: {
|
||||
teams: {
|
||||
some: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
secondaryEmails: {
|
||||
where: {
|
||||
emailVerified: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9858,6 +9858,7 @@
|
||||
"get": {
|
||||
"operationId": "EventTypesController_2024_06_14_getEventTypes",
|
||||
"summary": "Get all event types",
|
||||
"description": "Hidden event types are returned only if authentication is provided and it belongs to the event type owner.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "cal-api-version",
|
||||
@@ -9913,6 +9914,33 @@
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-cal-secret-key",
|
||||
"in": "header",
|
||||
"description": "For platform customers - OAuth client secret key",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-cal-client-id",
|
||||
"in": "header",
|
||||
"description": "For platform customers - OAuth client ID",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { hasInsightsPermission } from "@calcom/features/insights/server/hasInsightsPermission";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
export async function checkInsightsPagePermission() {
|
||||
const featuresRepository = new FeaturesRepository(prisma);
|
||||
const insightsEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("insights");
|
||||
|
||||
if (!insightsEnabled) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
if (!session?.user?.id) {
|
||||
redirect("/auth/login");
|
||||
}
|
||||
|
||||
const hasPermission = await hasInsightsPermission({
|
||||
userId: session.user.id,
|
||||
organizationId: session.user.org?.id,
|
||||
});
|
||||
|
||||
if (!hasPermission) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
@@ -1,34 +1,22 @@
|
||||
import { getTranslate } from "app/_utils";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { CTA_CONTAINER_CLASS_NAME } from "@calcom/features/data-table/lib/utils";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
import UpgradeTipWrapper from "./UpgradeTipWrapper";
|
||||
|
||||
export default async function InsightsLayout({ children }: { children: React.ReactNode }) {
|
||||
const featuresRepository = new FeaturesRepository(prisma);
|
||||
const insightsEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("insights");
|
||||
|
||||
if (!insightsEnabled) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Shell
|
||||
withoutMain={false}
|
||||
heading={t("insights")}
|
||||
subtitle={t("insights_subtitle")}
|
||||
actions={<div className={`mb-2 flex items-center gap-2 ${CTA_CONTAINER_CLASS_NAME}`} />}>
|
||||
<UpgradeTipWrapper>{children}</UpgradeTipWrapper>
|
||||
</Shell>
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<Shell
|
||||
withoutMain={false}
|
||||
heading={t("insights")}
|
||||
subtitle={t("insights_subtitle")}
|
||||
actions={<div className={`mb-2 flex items-center gap-2 ${CTA_CONTAINER_CLASS_NAME}`} />}>
|
||||
<UpgradeTipWrapper>{children}</UpgradeTipWrapper>
|
||||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
import InsightsPage from "~/insights/insights-view";
|
||||
|
||||
import { checkInsightsPagePermission } from "./checkInsightsPagePermission";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("insights"),
|
||||
@@ -18,7 +16,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const ServerPage = async () => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await checkInsightsPagePermission();
|
||||
|
||||
const { timeZone } = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: session?.user.id ?? -1 },
|
||||
|
||||
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import InsightsVirtualQueuesPage from "~/insights/insights-virtual-queues-view";
|
||||
|
||||
import { checkInsightsPagePermission } from "../checkInsightsPagePermission";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("insights"),
|
||||
@@ -12,5 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
export default async function Page() {
|
||||
await checkInsightsPagePermission();
|
||||
|
||||
return <InsightsVirtualQueuesPage />;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import InsightsRoutingPage from "~/insights/insights-routing-view";
|
||||
|
||||
import { checkInsightsPagePermission } from "../checkInsightsPagePermission";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("insights"),
|
||||
@@ -12,5 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
export default async function Page() {
|
||||
await checkInsightsPagePermission();
|
||||
|
||||
return <InsightsRoutingPage />;
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import OrgAttributesEditPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-edit-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("attribute"),
|
||||
@@ -12,6 +14,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
);
|
||||
|
||||
const OrgAttributesEditPageWrapper = async () => {
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return <OrgAttributesEditPage />;
|
||||
};
|
||||
|
||||
|
||||
+4
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import OrgAttributesCreatePage from "@calcom/ee/organizations/pages/settings/attributes/attributes-create-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("attribute"),
|
||||
@@ -12,6 +14,8 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const OrgAttributesCreatePageWrapper = async () => {
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return <OrgAttributesCreatePage />;
|
||||
};
|
||||
|
||||
|
||||
+2
-8
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import OrgSettingsAttributesPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-list-view";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,11 +20,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
const { canRead, canEdit, canDelete, canCreate } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
+33
-3
@@ -1,5 +1,35 @@
|
||||
import BillingPage, { generateMetadata } from "../../../billing/page";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getTranslate } from "app/_utils";
|
||||
|
||||
export { generateMetadata };
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
export default BillingPage;
|
||||
import BillingView from "~/settings/billing/billing-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("billing"),
|
||||
(t) => t("manage_billing_description"),
|
||||
undefined,
|
||||
undefined,
|
||||
"/settings/organizations/billing"
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
// TODO(SEAN): Add PBAC to this page in the next PR
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("billing")}
|
||||
description={t("manage_billing_description")}
|
||||
borderInShellHeader={true}>
|
||||
<BillingView />
|
||||
</SettingsHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import DelegationCredentialList from "@calcom/features/ee/organizations/pages/settings/delegationCredential";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("delegation_credential"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
borderInShellHeader
|
||||
|
||||
+2
-9
@@ -1,15 +1,12 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import DirectorySyncTeamView from "@calcom/features/ee/dsync/page/team-dsync-view";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,11 +19,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/organizations/general");
|
||||
}
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
const { canEdit } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
+3
-5
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import PrivacyView from "@calcom/features/ee/organizations/pages/settings/privacy";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -21,9 +19,9 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const [t, _headers, _cookies] = await Promise.all([getTranslate(), headers(), cookies()]);
|
||||
const t = await getTranslate();
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(_headers, _cookies) });
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
|
||||
+2
-4
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import OrgSSOView from "@calcom/features/ee/sso/page/orgs-sso-view";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,7 +20,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/organizations/general");
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { describe, it, vi, expect, beforeEach, type MockedFunction } from "vitest";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { validateUserHasOrg, type ValidatedOrgSession } from "./validateUserHasOrg";
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: vi.fn(() => Promise.resolve({})),
|
||||
headers: vi.fn(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/getServerSession", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@lib/buildLegacyCtx", () => ({
|
||||
buildLegacyRequest: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
const mockedGetServerSession = getServerSession as MockedFunction<typeof getServerSession>;
|
||||
const mockedRedirect = vi.mocked(redirect);
|
||||
|
||||
describe("validateUserHasOrg", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockSession = (overrides: Partial<Session> = {}): Session => ({
|
||||
expires: "2025-01-01",
|
||||
hasValidLicense: true,
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
profileId: 789,
|
||||
upId: "usr-123",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("when user has valid organization", () => {
|
||||
it("should return session when user has org in user.org", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result = await validateUserHasOrg();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org?.id).toBe(456);
|
||||
expect(result.user.profile?.organizationId).toBe(456);
|
||||
});
|
||||
|
||||
it("should return session when user has organizationId but no org", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result = await validateUserHasOrg();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user does not have valid organization", () => {
|
||||
it("should redirect when session is null", async () => {
|
||||
mockedGetServerSession.mockResolvedValue(null);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no organization data", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no user id", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: undefined as unknown as number,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
});
|
||||
|
||||
describe("return type validation", () => {
|
||||
it("should return ValidatedOrgSession type with non-nullable properties", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result: ValidatedOrgSession = await validateUserHasOrg();
|
||||
|
||||
// Type assertions to ensure proper typing
|
||||
expect(typeof result.user.id).toBe("number");
|
||||
expect(result.user.org).toBeTruthy();
|
||||
expect(result.user.profile).toBeTruthy();
|
||||
expect(typeof result.user.profile.organizationId).toBe("number");
|
||||
|
||||
// Runtime checks that the types guarantee these properties exist
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org.id).toBe(456);
|
||||
expect(result.user.profile.organizationId).toBe(456);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should propagate errors from getServerSession", async () => {
|
||||
const error = new Error("Session error");
|
||||
mockedGetServerSession.mockRejectedValue(error);
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("Session error");
|
||||
});
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
export type ValidatedOrgSession = NonNullable<Session> & {
|
||||
user: NonNullable<Session["user"]> & {
|
||||
id: number;
|
||||
org: NonNullable<NonNullable<Session["user"]>["org"]>;
|
||||
profile: NonNullable<NonNullable<Session["user"]>["profile"]> & {
|
||||
organizationId: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function validateUserHasOrg(): Promise<ValidatedOrgSession> {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
|
||||
if (!orgExists || !session?.user?.id || !session?.user?.profile?.organizationId) {
|
||||
redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
return session as ValidatedOrgSession;
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { describe, it, vi, expect, beforeEach, type MockedFunction } from "vitest";
|
||||
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { validateUserHasOrgAdmin, type ValidatedOrgAdminSession } from "./validateUserHasOrgAdmin";
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: vi.fn(() => Promise.resolve({})),
|
||||
headers: vi.fn(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/getServerSession", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/checkAdminOrOwner", () => ({
|
||||
checkAdminOrOwner: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@lib/buildLegacyCtx", () => ({
|
||||
buildLegacyRequest: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
const mockedGetServerSession = getServerSession as MockedFunction<typeof getServerSession>;
|
||||
const mockedCheckAdminOrOwner = vi.mocked(checkAdminOrOwner);
|
||||
const mockedRedirect = vi.mocked(redirect);
|
||||
|
||||
describe("validateUserHasOrgAdmin", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockSession = (overrides: Partial<Session> = {}): Session => ({
|
||||
expires: "2025-01-01",
|
||||
hasValidLicense: true,
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
profileId: 789,
|
||||
upId: "usr-123",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("when user has valid organization and admin role", () => {
|
||||
it("should return session when user is admin via user.org.role", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org?.id).toBe(456);
|
||||
expect(result.user.profile?.organizationId).toBe(456);
|
||||
});
|
||||
|
||||
it("should return session when user is owner", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.OWNER,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.OWNER);
|
||||
});
|
||||
|
||||
it("should return session when role comes from organization members", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 123, role: MembershipRole.ADMIN },
|
||||
{ userId: 124, role: MembershipRole.MEMBER },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user does not have valid organization", () => {
|
||||
it("should redirect when session is null", async () => {
|
||||
mockedGetServerSession.mockResolvedValue(null);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no organization data", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user has organization but is not admin", () => {
|
||||
it("should redirect when user is only a member", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.MEMBER,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(false);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/organizations/profile");
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.MEMBER);
|
||||
});
|
||||
|
||||
it("should redirect when role is undefined", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 999, role: MembershipRole.ADMIN }, // Different user
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(false);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/organizations/profile");
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("return type validation", () => {
|
||||
it("should return ValidatedOrgAdminSession type with non-nullable properties", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result: ValidatedOrgAdminSession = await validateUserHasOrgAdmin();
|
||||
|
||||
// Type assertions to ensure proper typing
|
||||
expect(typeof result.user.id).toBe("number");
|
||||
expect(result.user.org).toBeTruthy();
|
||||
expect(result.user.profile).toBeTruthy();
|
||||
expect(typeof result.user.profile.organizationId).toBe("number");
|
||||
expect(result.user.org.role).toBeTruthy();
|
||||
|
||||
// Runtime checks that the types guarantee these properties exist
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org.id).toBe(456);
|
||||
expect(result.user.org.role).toBe(MembershipRole.ADMIN);
|
||||
expect(result.user.profile.organizationId).toBe(456);
|
||||
});
|
||||
});
|
||||
|
||||
describe("role resolution logic", () => {
|
||||
it("should prefer org role over organization members role", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.OWNER, // This should be preferred
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 123, role: MembershipRole.MEMBER }, // Different role
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.OWNER);
|
||||
});
|
||||
|
||||
it("should fall back to organization members role when org role is not available", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [{ userId: 123, role: MembershipRole.ADMIN }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should propagate errors from getServerSession", async () => {
|
||||
const error = new Error("Session error");
|
||||
mockedGetServerSession.mockRejectedValue(error);
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("Session error");
|
||||
});
|
||||
|
||||
it("should propagate errors from checkAdminOrOwner", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const error = new Error("Permission check error");
|
||||
mockedCheckAdminOrOwner.mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("Permission check error");
|
||||
});
|
||||
});
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
export type ValidatedOrgAdminSession = NonNullable<Session> & {
|
||||
user: NonNullable<Session["user"]> & {
|
||||
id: number;
|
||||
org: NonNullable<NonNullable<Session["user"]>["org"]> & {
|
||||
role: MembershipRole;
|
||||
};
|
||||
profile: NonNullable<NonNullable<Session["user"]>["profile"]> & {
|
||||
organizationId: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function validateUserHasOrgAdmin(): Promise<ValidatedOrgAdminSession> {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
|
||||
if (!orgExists || !session?.user?.id || !session?.user?.profile?.organizationId) {
|
||||
redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
const userProfile = session?.user?.profile;
|
||||
const userId = session?.user?.id;
|
||||
const orgRole =
|
||||
session?.user?.org?.role ??
|
||||
userProfile?.organization?.members.find((m: { userId: number }) => m.userId === userId)?.role;
|
||||
const isOrgAdminOrOwner = checkAdminOrOwner(orgRole);
|
||||
|
||||
if (!isOrgAdminOrOwner) {
|
||||
redirect("/settings/organizations/profile");
|
||||
}
|
||||
|
||||
return session as ValidatedOrgAdminSession;
|
||||
}
|
||||
+4
@@ -3,6 +3,8 @@ import { getTranslate, _generateMetadata } from "app/_utils";
|
||||
import { AdminAPIView } from "@calcom/features/ee/organizations/pages/settings/admin-api";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => `${t("admin")} ${t("api_reference")}`,
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={`${t("admin")} ${t("api_reference")}`}
|
||||
|
||||
+2
-9
@@ -1,15 +1,12 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/general";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -23,11 +20,7 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
const { canRead, canEdit } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
const SettingsOrganizationsLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
if (!orgExists) {
|
||||
return redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
|
||||
+6
-8
@@ -1,8 +1,6 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/profile";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
@@ -10,7 +8,7 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import type { Membership } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,20 +20,20 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
const t = await getTranslate();
|
||||
|
||||
const orgRole = session?.user.profile?.organization.members?.find(
|
||||
(member: Membership) => member.userId === session?.user.id
|
||||
const orgRole = session.user.profile.organization.members?.find(
|
||||
(member: Membership) => member.userId === session.user.id
|
||||
)?.role;
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !orgRole) {
|
||||
if (!orgRole) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
|
||||
const { canRead, canEdit, canDelete } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
teamId: session?.user.profile?.organizationId,
|
||||
teamId: session.user.profile.organizationId,
|
||||
resource: Resource.Organization,
|
||||
userRole: orgRole,
|
||||
fallbackRoles: {
|
||||
|
||||
+2
-5
@@ -1,9 +1,7 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { AppFlags } from "@calcom/features/flags/config";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { PermissionMapper } from "@calcom/features/pbac/domain/mappers/PermissionMapper";
|
||||
@@ -13,8 +11,7 @@ import { RoleService } from "@calcom/features/pbac/services/role.service";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
import { CreateRoleCTA } from "./_components/CreateRoleCta";
|
||||
import { RolesList } from "./_components/RolesList";
|
||||
import { roleSearchParamsCache } from "./_components/searchParams";
|
||||
@@ -58,7 +55,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async ({ searchParams }: { searchParams: Record<string, string | string[] | undefined> }) => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
if (!session?.user?.org?.id || !session.user.id) {
|
||||
return notFound();
|
||||
|
||||
+2
-4
@@ -1,13 +1,11 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { OtherTeamsListing } from "@calcom/features/ee/organizations/pages/components/OtherTeamsListing";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -20,7 +18,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
redirect("/auth/login");
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import LegacyPage from "@calcom/features/ee/teams/pages/team-appearance-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("booking_appearance"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("booking_appearance")}
|
||||
|
||||
+4
@@ -5,6 +5,8 @@ import LegacyPage, {
|
||||
} from "@calcom/features/ee/organizations/pages/settings/other-team-members-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("team_members"),
|
||||
@@ -17,6 +19,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("team_members")}
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/other-team-profile-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("profile"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader title={t("profile")} description={t("profile_team_description")}>
|
||||
<LegacyPage />
|
||||
|
||||
@@ -26,12 +26,12 @@ import {
|
||||
SystemField,
|
||||
TITLE_FIELD,
|
||||
} from "@calcom/features/bookings/lib/SystemField";
|
||||
import { getCalendarLinks, CalendarLinkType } from "@calcom/features/bookings/lib/getCalendarLinks";
|
||||
import { RATING_OPTIONS, validateRating } from "@calcom/features/bookings/lib/rating";
|
||||
import { getCalendarLinks, CalendarLinkType } from "@calcom/lib/bookings/getCalendarLinks";
|
||||
import type { nameObjectSchema } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||
import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { formatToLocalizedDate, formatToLocalizedTime, formatToLocalizedTimezone } from "@calcom/lib/dayjs";
|
||||
import type { nameObjectSchema } from "@calcom/lib/event";
|
||||
import { getEventName } from "@calcom/lib/event";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
@@ -35,7 +35,7 @@ vi.mock("@calcom/app-store/utils", () => ({
|
||||
getEventTypeAppData: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/event", () => ({
|
||||
vi.mock("@calcom/features/eventtypes/lib/eventNaming", () => ({
|
||||
getEventName: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"analyze:browser": "BUNDLE_ANALYZE=browser next build",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"dev": "yarn copy-static && next dev --turbopack",
|
||||
"dev:scan": "yarn dev & npx --yes react-scan@latest localhost:3000",
|
||||
"dev:cron": "npx tsx cron-tester.ts",
|
||||
"dev-https": "NODE_TLS_REJECT_UNAUTHORIZED=0 next dev --experimental-https",
|
||||
"dx": "yarn dev",
|
||||
|
||||
@@ -29,8 +29,11 @@ interface RouteConfig {
|
||||
value: string;
|
||||
};
|
||||
isFallback?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
queryValue?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
attributesQueryValue?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fallbackAttributesQueryValue?: any;
|
||||
}
|
||||
|
||||
@@ -129,6 +132,7 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
|
||||
const teamEventsConfig = config.attributeRouting.teamEvents;
|
||||
|
||||
// Create team events
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const teamEvents: { [key: string]: any } = {};
|
||||
for (const eventConfig of teamEventsConfig) {
|
||||
const eventType = await createRoundRobinTeamEventType({
|
||||
@@ -149,10 +153,8 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
|
||||
throw new Error("Attributes not found");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const skillAttribute = attributes.find((attr) => attr.name === "Skills")!;
|
||||
const locationAttribute = attributes.find((attr) => attr.name === "Location")!;
|
||||
|
||||
console.log("Attributes", JSON.stringify(attributes, null, 2));
|
||||
|
||||
const javascriptEventRoute = {
|
||||
id: "8a898988-89ab-4cde-b012-31823f708642",
|
||||
@@ -316,41 +318,44 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
|
||||
queryValue: { id: "814899aa-4567-489a-bcde-f1823f708646", type: "group" },
|
||||
},
|
||||
],
|
||||
fields: config.form?.fields || [
|
||||
{
|
||||
id: form.formFieldLocation.id,
|
||||
type: "select",
|
||||
label: "Location",
|
||||
options: formFieldLocationOptions,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldSkills.id,
|
||||
type: "multiselect",
|
||||
label: "skills",
|
||||
options: formFieldSkillsOptions,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldEmail.id,
|
||||
type: "email",
|
||||
label: "Email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldName.id,
|
||||
identifier: "name",
|
||||
type: "text",
|
||||
label: "Name",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: form.formFieldRating.id,
|
||||
type: "number",
|
||||
label: "Rating",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
fields: (
|
||||
config.form?.fields ||
|
||||
([
|
||||
{
|
||||
id: form.formFieldLocation.id,
|
||||
type: "select",
|
||||
label: "Location",
|
||||
options: formFieldLocationOptions,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldSkills.id,
|
||||
type: "multiselect",
|
||||
label: "skills",
|
||||
options: formFieldSkillsOptions,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldEmail.id,
|
||||
type: "email",
|
||||
label: "Email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: form.formFieldName.id,
|
||||
identifier: "name",
|
||||
type: "text",
|
||||
label: "Name",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: form.formFieldRating.id,
|
||||
type: "number",
|
||||
label: "Rating",
|
||||
required: false,
|
||||
},
|
||||
] satisfies FieldConfig[])
|
||||
).map((field) => ({ ...field })),
|
||||
team: {
|
||||
connect: {
|
||||
id: teamId,
|
||||
@@ -379,172 +384,178 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.create({
|
||||
data: {
|
||||
routes: config.form?.routes || [
|
||||
{
|
||||
id: "8a898988-89ab-4cde-b012-31823f708642",
|
||||
action: { type: "eventTypeRedirectUrl", value: "pro/30min" },
|
||||
queryValue: {
|
||||
routes: (
|
||||
config.form?.routes ||
|
||||
([
|
||||
{
|
||||
id: "8a898988-89ab-4cde-b012-31823f708642",
|
||||
type: "group",
|
||||
children1: {
|
||||
"8988bbb8-0123-4456-b89a-b1823f70c5ff": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["event-routing"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
action: { type: "eventTypeRedirectUrl", value: "pro/30min" },
|
||||
queryValue: {
|
||||
id: "8a898988-89ab-4cde-b012-31823f708642",
|
||||
type: "group",
|
||||
children1: {
|
||||
"8988bbb8-0123-4456-b89a-b1823f70c5ff": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["event-routing"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
|
||||
action: { type: "customPageMessage", value: "Custom Page Result" },
|
||||
queryValue: {
|
||||
{
|
||||
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b99b8a89-89ab-4cde-b012-31823f718ff5": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["custom-page"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
action: { type: "customPageMessage", value: "Custom Page Result" },
|
||||
queryValue: {
|
||||
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b99b8a89-89ab-4cde-b012-31823f718ff5": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["custom-page"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
|
||||
action: { type: "externalRedirectUrl", value: `${WEBAPP_URL}/pro` },
|
||||
queryValue: {
|
||||
{
|
||||
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
|
||||
type: "group",
|
||||
children1: {
|
||||
"998b9b9a-0123-4456-b89a-b1823f7232b9": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["external-redirect"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
action: { type: "externalRedirectUrl", value: `${WEBAPP_URL}/pro` },
|
||||
queryValue: {
|
||||
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
|
||||
type: "group",
|
||||
children1: {
|
||||
"998b9b9a-0123-4456-b89a-b1823f7232b9": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
value: ["external-redirect"],
|
||||
operator: "equal",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["text"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
|
||||
action: { type: "customPageMessage", value: "Multiselect(Legacy) chosen" },
|
||||
queryValue: {
|
||||
{
|
||||
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b98a8abb-cdef-4012-b456-718262343d27": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: multiSelectLegacyFieldUuid,
|
||||
value: [["Option-2"]],
|
||||
operator: "multiselect_equals",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["multiselect"],
|
||||
action: { type: "customPageMessage", value: "Multiselect(Legacy) chosen" },
|
||||
queryValue: {
|
||||
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b98a8abb-cdef-4012-b456-718262343d27": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: multiSelectLegacyFieldUuid,
|
||||
value: [["Option-2"]],
|
||||
operator: "multiselect_equals",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["multiselect"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bb9ea8b9-0123-4456-b89a-b182623406d8",
|
||||
action: { type: "customPageMessage", value: "Multiselect chosen" },
|
||||
queryValue: {
|
||||
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b98a8abb-cdef-4012-b456-718262343d27": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: multiSelectFieldUuid,
|
||||
value: [[multiSelectOption2Uuid]],
|
||||
operator: "multiselect_equals",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["multiselect"],
|
||||
{
|
||||
id: "bb9ea8b9-0123-4456-b89a-b182623406d8",
|
||||
action: { type: "customPageMessage", value: "Multiselect chosen" },
|
||||
queryValue: {
|
||||
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
|
||||
type: "group",
|
||||
children1: {
|
||||
"b98a8abb-cdef-4012-b456-718262343d27": {
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: multiSelectFieldUuid,
|
||||
value: [[multiSelectOption2Uuid]],
|
||||
operator: "multiselect_equals",
|
||||
valueSrc: ["value"],
|
||||
valueType: ["multiselect"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "898899aa-4567-489a-bcde-f1823f708646",
|
||||
action: { type: "customPageMessage", value: "Fallback Message" },
|
||||
isFallback: true,
|
||||
queryValue: { id: "898899aa-4567-489a-bcde-f1823f708646", type: "group" },
|
||||
},
|
||||
],
|
||||
fields: config.form?.fields || [
|
||||
{
|
||||
id: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
type: "text",
|
||||
label: "Test field",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: multiSelectLegacyFieldUuid,
|
||||
type: "multiselect",
|
||||
label: "Multi Select(with Legacy `selectText`)",
|
||||
identifier: "multi",
|
||||
selectText: "Option-1\nOption-2",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: multiSelectFieldUuid,
|
||||
type: "multiselect",
|
||||
label: "Multi Select",
|
||||
identifier: "multi-new-format",
|
||||
options: [
|
||||
{
|
||||
id: multiSelectOption1Uuid,
|
||||
label: "Option-1",
|
||||
},
|
||||
{
|
||||
id: multiSelectOption2Uuid,
|
||||
label: "Option-2",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: legacySelectFieldUuid,
|
||||
type: "select",
|
||||
label: "Legacy Select",
|
||||
identifier: "test-select",
|
||||
selectText: "Option-1\nOption-2",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: selectFieldUuid,
|
||||
type: "select",
|
||||
label: "Select",
|
||||
identifier: "test-select-new-format",
|
||||
options: [
|
||||
{
|
||||
id: selectOption1Uuid,
|
||||
label: "Option-1",
|
||||
},
|
||||
{
|
||||
id: selectOption2Uuid,
|
||||
label: "Option-2",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
{
|
||||
id: "898899aa-4567-489a-bcde-f1823f708646",
|
||||
action: { type: "customPageMessage", value: "Fallback Message" },
|
||||
isFallback: true,
|
||||
queryValue: { id: "898899aa-4567-489a-bcde-f1823f708646", type: "group" },
|
||||
},
|
||||
] satisfies RouteConfig[])
|
||||
).map((field) => ({ ...field })),
|
||||
fields: (
|
||||
config.form?.fields ||
|
||||
([
|
||||
{
|
||||
id: "c4296635-9f12-47b1-8153-c3a854649182",
|
||||
type: "text",
|
||||
label: "Test field",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: multiSelectLegacyFieldUuid,
|
||||
type: "multiselect",
|
||||
label: "Multi Select(with Legacy `selectText`)",
|
||||
identifier: "multi",
|
||||
selectText: "Option-1\nOption-2",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: multiSelectFieldUuid,
|
||||
type: "multiselect",
|
||||
label: "Multi Select",
|
||||
identifier: "multi-new-format",
|
||||
options: [
|
||||
{
|
||||
id: multiSelectOption1Uuid,
|
||||
label: "Option-1",
|
||||
},
|
||||
{
|
||||
id: multiSelectOption2Uuid,
|
||||
label: "Option-2",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: legacySelectFieldUuid,
|
||||
type: "select",
|
||||
label: "Legacy Select",
|
||||
identifier: "test-select",
|
||||
selectText: "Option-1\nOption-2",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: selectFieldUuid,
|
||||
type: "select",
|
||||
label: "Select",
|
||||
identifier: "test-select-new-format",
|
||||
options: [
|
||||
{
|
||||
id: selectOption1Uuid,
|
||||
label: "Option-1",
|
||||
},
|
||||
{
|
||||
id: selectOption2Uuid,
|
||||
label: "Option-2",
|
||||
},
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
] satisfies FieldConfig[])
|
||||
).map((field) => ({ ...field })),
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "تمت ترقية خطة Organization بنجاح!",
|
||||
"use_link_to_reset_password": "استخدم الرابط أدناه لإعادة تعيين كلمة المرور",
|
||||
"hey_there": "مرحبًا",
|
||||
"there": "هناك",
|
||||
"forgot_your_password_calcom": "نسيت كلمة المرور الخاصة بك؟ - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "هل أنت متأكد أنك تريد حذف الويب هوك هذا؟ لن تتلقى بعد الآن بيانات اجتماعات {{appName}} على عنوان URL محدد، وفي الوقت الفعلي، عندما تتم جدولة حدث أو يتم إلغاؤه.",
|
||||
"confirm_delete_webhook": "نعم، قم بحذف الويبهوك",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "إخفاء عنوان البريد الإلكتروني للمنظم من شاشة الحجز وإشعارات البريد الإلكتروني وأحداث التقويم",
|
||||
"you_and_conjunction": "& أنت",
|
||||
"select_verified_email": "اختر بريدًا إلكترونيًا تم التحقق منه",
|
||||
"add_verified_email": "إضافة بريد إلكتروني موثق",
|
||||
"add_verified_emails": "إضافة بريد إلكتروني موثق",
|
||||
"custom_reply_to_email_title": "بريد إلكتروني مخصص للرد",
|
||||
"custom_reply_to_email_description": "استخدم عنوان بريد إلكتروني مختلف كعنوان للرد على رسائل التأكيد بدلاً من بريد المنظم",
|
||||
"email_survey_triggered_by_workflow": "تم تشغيل هذا الاستبيان بواسطة سير العمل في كال.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Təşkilatınız uğurla təkmilləşdirildi!",
|
||||
"use_link_to_reset_password": "Şifrənizi sıfırlamaq üçün aşağıdakı linkdən istifadə edin",
|
||||
"hey_there": "Salam",
|
||||
"there": "orada",
|
||||
"forgot_your_password_calcom": "Şifrənizi unutmusunuz? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Bu webhook-u silmək istədiyinizə əminsinizmi? Tədbir planlaşdırıldıqda və ya ləğv edildikdə, real vaxtda müəyyən edilmiş URL-də {{appName}} görüş məlumatlarını artıq almayacaqsınız.",
|
||||
"confirm_delete_webhook": "Bəli, webhook-u silin",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Təşkilatçının e-poçt ünvanını rezervasiya ekranından, e-poçt bildirişlərindən və təqvim tədbirlərindən gizlədin",
|
||||
"you_and_conjunction": "Siz &",
|
||||
"select_verified_email": "Təsdiqlənmiş e-poçt seçin",
|
||||
"add_verified_email": "Təsdiqlənmiş e-poçt əlavə et",
|
||||
"add_verified_emails": "Təsdiqlənmiş e-poçtlar əlavə et",
|
||||
"custom_reply_to_email_title": "Xüsusi 'Cavab-Ünvanı' e-poçtu",
|
||||
"custom_reply_to_email_description": "Təsdiq e-poçtları üçün təşkilatçının e-poçtu əvəzinə fərqli bir e-poçt ünvanını cavab ünvanı kimi istifadə edin",
|
||||
"email_survey_triggered_by_workflow": "Bu sorğu Cal-da Workflow tərəfindən başladıldı.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Вашата организация беше успешно ъпгрейдната!",
|
||||
"use_link_to_reset_password": "Използвайте линка по-долу, за да възстановите паролата си",
|
||||
"hey_there": "Здравейте",
|
||||
"there": "там",
|
||||
"forgot_your_password_calcom": "Забравена парола? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Сигурни ли сте, че искате да изтриете този уебхук? Вече няма да получавате данни за срещите от {{appName}} на посочения URL адрес в реално време, когато събитие бъде планирано или отменено.",
|
||||
"confirm_delete_webhook": "Да, изтрий уебхука",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Скриване на имейл адреса на организатора от екрана за резервации, имейл известията и календарните събития",
|
||||
"you_and_conjunction": "Вие &",
|
||||
"select_verified_email": "Изберете потвърден имейл",
|
||||
"add_verified_email": "Добави потвърден имейл",
|
||||
"add_verified_emails": "Добави потвърдени имейли",
|
||||
"custom_reply_to_email_title": "Персонализиран имейл за отговор",
|
||||
"custom_reply_to_email_description": "Използвайте различен имейл адрес като адрес за отговор в имейлите за потвърждение вместо имейла на организатора",
|
||||
"email_survey_triggered_by_workflow": "Тази анкета беше стартирана от Workflow в Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "আপনার সংস্থা সফলভাবে আপগ্রেড করা হয়েছিল!",
|
||||
"use_link_to_reset_password": "আপনার পাসওয়ার্ডটি পুনরায় সেট করতে নীচের লিঙ্কটি ব্যবহার করুন",
|
||||
"hey_there": "আরে ওখানে",
|
||||
"there": "সেখানে",
|
||||
"forgot_your_password_calcom": "আপনার পাসওয়ার্ড ভুলে গেছেন? {{appName}}",
|
||||
"delete_webhook_confirmation_message": "আপনি কি নিশ্চিত যে আপনি এই ওয়েবহুকটি মুছতে চান? {{appName}} রিয়েল-টাইমে একটি নির্দিষ্ট ইউআরএলে ডেটা সভা করা, যখন কোনও ইভেন্ট নির্ধারিত বা বাতিল হয়।",
|
||||
"confirm_delete_webhook": "হ্যাঁ, ওয়েবহুক মুছুন",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "বুকিং স্ক্রিন, ইমেইল বিজ্ঞপ্তি এবং ক্যালেন্ডার ইভেন্ট থেকে আয়োজকের ইমেইল ঠিকানা লুকান",
|
||||
"you_and_conjunction": "আপনি এবং",
|
||||
"select_verified_email": "একটি যাচাইকৃত ইমেইল নির্বাচন করুন",
|
||||
"add_verified_email": "যাচাইকৃত ইমেইল যোগ করুন",
|
||||
"add_verified_emails": "যাচাইকৃত ইমেইলগুলি যোগ করুন",
|
||||
"custom_reply_to_email_title": "কাস্টম 'রিপ্লাই-টু' ইমেইল",
|
||||
"custom_reply_to_email_description": "আয়োজকের ইমেইলের পরিবর্তে নিশ্চিতকরণ ইমেইলের জন্য রিপ্লাইটু হিসাবে একটি ভিন্ন ইমেইল ঠিকানা ব্যবহার করুন",
|
||||
"email_survey_triggered_by_workflow": "এই সমীক্ষাটি Cal-এ একটি ওয়ার্কফ্লো দ্বারা ট্রিগার করা হয়েছিল।",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "La teva Organització s'ha actualitzat correctament!",
|
||||
"use_link_to_reset_password": "Utilitza l'enllaç següent per restablir la teva contrasenya",
|
||||
"hey_there": "Hola",
|
||||
"there": "allà",
|
||||
"forgot_your_password_calcom": "Has oblidat la teva contrasenya? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Estàs segur que vols eliminar aquest webhook? Ja no rebràs dades de reunions de {{appName}} a l'URL especificada en temps real quan es programi o cancel·li un esdeveniment.",
|
||||
"confirm_delete_webhook": "Sí, elimina el webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Amaga l'adreça de correu electrònic de l'organitzador de la pantalla de reserva, les notificacions per correu electrònic i els esdeveniments del calendari",
|
||||
"you_and_conjunction": "Vosaltres &",
|
||||
"select_verified_email": "Selecciona un correu electrònic verificat",
|
||||
"add_verified_email": "Afegir correu electrònic verificat",
|
||||
"add_verified_emails": "Afegir correus electrònics verificats",
|
||||
"custom_reply_to_email_title": "Correu electrònic personalitzat de 'Respondre a'",
|
||||
"custom_reply_to_email_description": "Utilitza una adreça de correu electrònic diferent com a resposta per als correus de confirmació en lloc del correu de l'organitzador",
|
||||
"email_survey_triggered_by_workflow": "Aquesta enquesta va ser activada per un Workflow a Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Váš upgrade na tarif Organization byl úspěšně dokončen!",
|
||||
"use_link_to_reset_password": "Pro obnovení hesla použijte odkaz níž",
|
||||
"hey_there": "Ahoj",
|
||||
"there": "tam",
|
||||
"forgot_your_password_calcom": "Zapomněli jste heslo? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Opravdu chcete smazat tento webhook? Přestanete v reálném čase dostávat {{appName}} data o schůzce na dané URL, pokud bude událost naplánována nebo zrušena.",
|
||||
"confirm_delete_webhook": "Ano, smazat webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Skrýt e-mailovou adresu organizátora na obrazovce rezervace, v e-mailových oznámeních a událostech kalendáře",
|
||||
"you_and_conjunction": "Vy &",
|
||||
"select_verified_email": "Vyberte ověřený e-mail",
|
||||
"add_verified_email": "Přidat ověřený e-mail",
|
||||
"add_verified_emails": "Přidat ověřené e-maily",
|
||||
"custom_reply_to_email_title": "Vlastní e-mail pro odpověď",
|
||||
"custom_reply_to_email_description": "Použít jinou e-mailovou adresu jako adresu pro odpověď v potvrzovacích e-mailech místo e-mailu organizátora",
|
||||
"email_survey_triggered_by_workflow": "Tento průzkum byl spuštěn pracovním postupem v Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Din organisation blev opgraderet med succes!",
|
||||
"use_link_to_reset_password": "Brug nedenstående link for at nulstille din adgangskode",
|
||||
"hey_there": "Hej der",
|
||||
"there": "der",
|
||||
"forgot_your_password_calcom": "Glemt din adgangskode? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Er du sikker på, at du vil slette denne webhook? Du vil ikke længere modtage {{appName}} mødedata på en specificeret URL, i realtid, når en begivenhed bliver planlagt eller annulleret.",
|
||||
"confirm_delete_webhook": "Ja, slet webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Skjul arrangørens e-mailadresse fra bookingskærmen, e-mailnotifikationer og kalenderbegivenheder",
|
||||
"you_and_conjunction": "Du &",
|
||||
"select_verified_email": "Vælg en verificeret e-mail",
|
||||
"add_verified_email": "Tilføj verificeret e-mail",
|
||||
"add_verified_emails": "Tilføj verificerede e-mails",
|
||||
"custom_reply_to_email_title": "Brugerdefineret 'Svar-til'-e-mail",
|
||||
"custom_reply_to_email_description": "Brug en anden e-mailadresse som svar-til for bekræftelses-e-mails i stedet for arrangørens e-mail",
|
||||
"email_survey_triggered_by_workflow": "Denne undersøgelse blev udløst af en Arbejdsgang i Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Das Upgrade für Ihre Organization war erfolgreich!",
|
||||
"use_link_to_reset_password": "Benutzen Sie den Link, um Ihr Passwort zurückzusetzen",
|
||||
"hey_there": "Hallo",
|
||||
"there": "dort",
|
||||
"forgot_your_password_calcom": "Passwort vergessen? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Sind Sie sicher, dass Sie diesen Webhook löschen möchten? Sie werden keine {{appName}}-Meetingdaten mehr unter einer bestimmten URL, in Echtzeit erhalten, wenn ein Ereignis geplant oder abgesagt wird.",
|
||||
"confirm_delete_webhook": "Ja, Webhook löschen",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "E-Mail-Adresse des Organisators im Buchungsbildschirm, in E-Mail-Benachrichtigungen und Kalenderereignissen verbergen",
|
||||
"you_and_conjunction": "Sie &",
|
||||
"select_verified_email": "Verifizierte E-Mail-Adresse auswählen",
|
||||
"add_verified_email": "Verifizierte E-Mail hinzufügen",
|
||||
"add_verified_emails": "Verifizierte E-Mails hinzufügen",
|
||||
"custom_reply_to_email_title": "Benutzerdefinierte 'Antwort an'-E-Mail",
|
||||
"custom_reply_to_email_description": "Verwende eine andere E-Mail-Adresse als 'Antwort an' für Bestätigungs-E-Mails anstelle der E-Mail-Adresse des Organisators",
|
||||
"email_survey_triggered_by_workflow": "Diese Umfrage wurde durch einen Workflow in Cal ausgelöst.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Ο οργανισμός σας αναβαθμίστηκε με επιτυχία!",
|
||||
"use_link_to_reset_password": "Χρησιμοποιήστε τον παρακάτω σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασης",
|
||||
"hey_there": "Γεια σας,",
|
||||
"there": "εκεί",
|
||||
"forgot_your_password_calcom": "Ξεχάσατε τον κωδικό πρόσβασης? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το webhook; Δεν θα λαμβάνετε πλέον δεδομένα συναντήσεων του {{appName}} σε συγκεκριμένο URL, σε πραγματικό χρόνο, όταν προγραμματίζεται ή ακυρώνεται μια εκδήλωση.",
|
||||
"confirm_delete_webhook": "Ναι, διαγραφή webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Απόκρυψη της διεύθυνσης email του διοργανωτή από την οθόνη κράτησης, τις ειδοποιήσεις email και τα συμβάντα ημερολογίου",
|
||||
"you_and_conjunction": "Εσείς &",
|
||||
"select_verified_email": "Επιλογή επαληθευμένου email",
|
||||
"add_verified_email": "Προσθήκη επαληθευμένου email",
|
||||
"add_verified_emails": "Προσθήκη επαληθευμένων email",
|
||||
"custom_reply_to_email_title": "Προσαρμοσμένο email 'Απάντησης-Προς'",
|
||||
"custom_reply_to_email_description": "Χρησιμοποιήστε διαφορετική διεύθυνση email ως replyTo για τα email επιβεβαίωσης αντί για το email του διοργανωτή",
|
||||
"email_survey_triggered_by_workflow": "Αυτή η έρευνα ενεργοποιήθηκε από μια ροή εργασίας στο Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Your Organization was upgraded successfully!",
|
||||
"use_link_to_reset_password": "Use the link below to reset your password",
|
||||
"hey_there": "Hey there",
|
||||
"there": "there",
|
||||
"forgot_your_password_calcom": "Forgot your password? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive {{appName}} meeting data at a specified URL, in real-time, when an event is scheduled or canceled.",
|
||||
"confirm_delete_webhook": "Yes, delete webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Hide organizer's email address from the booking screen, email notifications, and calendar events",
|
||||
"you_and_conjunction": "You &",
|
||||
"select_verified_email": "Select a verified email",
|
||||
"add_verified_email": "Add verified email",
|
||||
"add_verified_emails": "Add verified emails",
|
||||
"custom_reply_to_email_title": "Custom 'Reply-To' email",
|
||||
"custom_reply_to_email_description": "Use a different email address as the replyTo for confirmation emails instead of the organizer's email",
|
||||
"email_survey_triggered_by_workflow": "This survey was triggered by a Workflow in Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "¡Tu Organización se actualizó con éxito!",
|
||||
"use_link_to_reset_password": "Usa el enlace a continuación para restablecer tu contraseña",
|
||||
"hey_there": "Hola",
|
||||
"there": "ahí",
|
||||
"forgot_your_password_calcom": "¿Olvidaste tu contraseña? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "¿Estás seguro de que deseas eliminar este webhook? Ya no recibirás datos de reuniones de {{appName}} en una URL especificada, en tiempo real, cuando se programe o cancele un evento.",
|
||||
"confirm_delete_webhook": "Sí, eliminar webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ocultar la dirección de correo electrónico del organizador en la pantalla de reserva, notificaciones por correo electrónico y eventos del calendario",
|
||||
"you_and_conjunction": "Tú &",
|
||||
"select_verified_email": "Seleccionar un correo electrónico verificado",
|
||||
"add_verified_email": "Agregar correo electrónico verificado",
|
||||
"add_verified_emails": "Agregar correos electrónicos verificados",
|
||||
"custom_reply_to_email_title": "Correo 'Responder a' personalizado",
|
||||
"custom_reply_to_email_description": "Usar una dirección de correo electrónico diferente como destinatario de respuesta para los correos de confirmación en lugar del correo del organizador",
|
||||
"email_survey_triggered_by_workflow": "Esta encuesta se activó mediante un Workflow en Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Su Organization se actualizó con éxito.",
|
||||
"use_link_to_reset_password": "Utilice el enlace de abajo para restablecer su contraseña",
|
||||
"hey_there": "Hola",
|
||||
"there": "ahí",
|
||||
"forgot_your_password_calcom": "¿Olvidaste tu contraseña? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "¿Está seguro de que desea eliminar este webhook? Ya no recibirá datos de reuniones de {{appName}} en una URL específica, en tiempo real, cuando se programe o cancele un evento.",
|
||||
"confirm_delete_webhook": "Sí, elimina este webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ocultar la dirección de correo electrónico del organizador en la pantalla de reserva, notificaciones por correo electrónico y eventos del calendario",
|
||||
"you_and_conjunction": "Tú &",
|
||||
"select_verified_email": "Seleccionar un correo electrónico verificado",
|
||||
"add_verified_email": "Añadir correo electrónico verificado",
|
||||
"add_verified_emails": "Añadir correos electrónicos verificados",
|
||||
"custom_reply_to_email_title": "Correo electrónico personalizado para 'Responder a'",
|
||||
"custom_reply_to_email_description": "Usar una dirección de correo electrónico diferente como destinatario de respuesta para los correos de confirmación en lugar del correo del organizador",
|
||||
"email_survey_triggered_by_workflow": "Esta encuesta fue activada por un Workflow en Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Teie organisatsiooni uuendamine õnnestus!",
|
||||
"use_link_to_reset_password": "Kasutage oma parooli lähtestamiseks allolevat linki",
|
||||
"hey_there": "Tere",
|
||||
"there": "seal",
|
||||
"forgot_your_password_calcom": "Unustasite oma parooli? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Kas soovite kindlasti selle veebihaagi kustutada? Kui üritus on ajastatud või tühistatud, ei saa te enam reaalajas {{appName}} koosoleku andmeid määratud URL-il.",
|
||||
"confirm_delete_webhook": "Jah, kustuta veebihaak",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Peida korraldaja e-posti aadress broneerimisekraanil, e-posti teavitustes ja kalendrisündmustes.",
|
||||
"you_and_conjunction": "Sina &",
|
||||
"select_verified_email": "Vali kinnitatud e-posti aadress",
|
||||
"add_verified_email": "Lisa kinnitatud e-post",
|
||||
"add_verified_emails": "Lisa kinnitatud e-posti aadressid",
|
||||
"custom_reply_to_email_title": "Kohandatud 'Vasta-aadress' e-post",
|
||||
"custom_reply_to_email_description": "Kasutage kinnituskirjade jaoks vastuse aadressina teist e-posti aadressi, mitte korraldaja e-posti aadressi.",
|
||||
"email_survey_triggered_by_workflow": "See küsitlus käivitati Cali töövooga.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Zure erakundea arrakastaz eguneratu da!",
|
||||
"use_link_to_reset_password": "Erabili beheko esteka pasahitza berrezartzeko",
|
||||
"hey_there": "Kaixo,",
|
||||
"there": "han",
|
||||
"forgot_your_password_calcom": "Pasahitza ahaztu duzu? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Ziur zaude webhook hau ezabatu nahi duzula? Ez duzu gehiago {{appName}} bileraren daturik jasoko zehaztutako URLan denbora errealean, gertaera bat programatzen edo bertan behera uzten denean.",
|
||||
"confirm_delete_webhook": "Bai, ezabatu webhooka",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ezkutatu antolatzailearen helbide elektronikoa erreserba pantailatik, mezu elektronikoetatik eta egutegiko ekitaldietatik",
|
||||
"you_and_conjunction": "Zu &",
|
||||
"select_verified_email": "Hautatu egiaztatutako helbide elektronikoa",
|
||||
"add_verified_email": "Gehitu egiaztatutako helbide elektronikoa",
|
||||
"add_verified_emails": "Gehitu egiaztatutako helbide elektronikoak",
|
||||
"custom_reply_to_email_title": "'Erantzun' helbide elektroniko pertsonalizatua",
|
||||
"custom_reply_to_email_description": "Erabili beste helbide elektroniko bat baieztapen mezuen erantzun-helbide gisa antolatzailearen helbidearen ordez",
|
||||
"email_survey_triggered_by_workflow": "Galdeketa hau Cal-eko Workflow baten bidez aktibatu da.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organisaatiosi päivitys onnistui!",
|
||||
"use_link_to_reset_password": "Käytä alla olevaa linkkiä salasanan vaihtamiseen",
|
||||
"hey_there": "Hei siellä",
|
||||
"there": "siellä",
|
||||
"forgot_your_password_calcom": "Unohditko salasanasi? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Haluatko varmasti poistaa tämän webhookin? Et enää vastaanota {{appName}}-kokoustietoja määritettyyn URL-osoitteeseen reaaliajassa, kun tapahtuma aikataulutetaan tai peruutetaan.",
|
||||
"confirm_delete_webhook": "Kyllä, poista webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Piilota järjestäjän sähköpostiosoite varausnäytöltä, sähköposti-ilmoituksista ja kalenteritapahtumista",
|
||||
"you_and_conjunction": "Sinä &",
|
||||
"select_verified_email": "Valitse vahvistettu sähköposti",
|
||||
"add_verified_email": "Lisää vahvistettu sähköposti",
|
||||
"add_verified_emails": "Lisää vahvistettuja sähköposteja",
|
||||
"custom_reply_to_email_title": "Mukautettu 'Vastaa'-sähköposti",
|
||||
"custom_reply_to_email_description": "Käytä eri sähköpostiosoitetta vastausosoitteena vahvistussähköposteissa järjestäjän sähköpostin sijaan",
|
||||
"email_survey_triggered_by_workflow": "Tämä kysely käynnistettiin Workflow-ohjelmalla Cal-palvelussa.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Votre organisation a été mise à niveau avec succès !",
|
||||
"use_link_to_reset_password": "Utilisez le lien ci-dessous pour réinitialiser votre mot de passe",
|
||||
"hey_there": "Bonjour",
|
||||
"there": "là",
|
||||
"forgot_your_password_calcom": "Mot de passe oublié ? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Voulez-vous vraiment supprimer ce webhook ? Vous ne recevrez plus les données de rendez-vous de {{appName}} à un lien spécifié, en temps réel, lorsqu'un événement est planifié ou annulé.",
|
||||
"confirm_delete_webhook": "Oui, supprimer le webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Masquer l'adresse e-mail de l'organisateur sur l'écran de réservation, les notifications par e-mail et les événements du calendrier",
|
||||
"you_and_conjunction": "Vous &",
|
||||
"select_verified_email": "Sélectionner un e-mail vérifié",
|
||||
"add_verified_email": "Ajouter une adresse e-mail vérifiée",
|
||||
"add_verified_emails": "Ajouter des adresses e-mail vérifiées",
|
||||
"custom_reply_to_email_title": "E-mail 'Répondre à' personnalisé",
|
||||
"custom_reply_to_email_description": "Utiliser une adresse e-mail différente comme destinataire de réponse pour les e-mails de confirmation au lieu de l'e-mail de l'organisateur",
|
||||
"email_survey_triggered_by_workflow": "Ce questionnaire a été déclenché par un Workflow dans Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "ה-Organization שלך שודרג בהצלחה!",
|
||||
"use_link_to_reset_password": "נא להשתמש בקישור הבא כדי לאפס את הסיסמה",
|
||||
"hey_there": "היי שם",
|
||||
"there": "שם",
|
||||
"forgot_your_password_calcom": "שכחתה את הסיסמה? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "בטוח שברצונך למחוק את ה-Webhook הזה? אם אירוע יבוטל או שייקבע עבורו מועד, לא תקבל יותר נתוני פגישות מ-{{appName}} בזמן אמת או בכתובת ה-URL שציינת.",
|
||||
"confirm_delete_webhook": "כן, אני רוצה למחוק את ה-Webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "הסתר את כתובת האימייל של המארגן ממסך ההזמנה, התראות האימייל ואירועי היומן",
|
||||
"you_and_conjunction": "את/ה &",
|
||||
"select_verified_email": "בחר אימייל מאומת",
|
||||
"add_verified_email": "הוסף אימייל מאומת",
|
||||
"add_verified_emails": "הוסף אימיילים מאומתים",
|
||||
"custom_reply_to_email_title": "אימייל 'השב-אל' מותאם אישית",
|
||||
"custom_reply_to_email_description": "השתמש בכתובת אימייל אחרת כ-replyTo עבור אימיילי אישור במקום האימייל של המארגן",
|
||||
"email_survey_triggered_by_workflow": "הסקר הזה הופעל על ידי Workflow ב-Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Szervezete sikeresen frissítve!",
|
||||
"use_link_to_reset_password": "Jelszó visszaállításához használja az alábbi linket",
|
||||
"hey_there": "Szia",
|
||||
"there": "ott",
|
||||
"forgot_your_password_calcom": "Elfelejtette a jelszavát? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Biztosan törli ezt a webhookot? A továbbiakban nem kapja meg valós időben a(z) {{appName}} megbeszélés adatait egy megadott URL-címen, ha egy esemény ütemezett vagy lemondott.",
|
||||
"confirm_delete_webhook": "Igen, webhook törlése",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Szervező e-mail címének elrejtése a foglalási képernyőn, e-mail értesítésekben és naptáreseményekben",
|
||||
"you_and_conjunction": "Te &",
|
||||
"select_verified_email": "Válasszon egy ellenőrzött e-mail címet",
|
||||
"add_verified_email": "Hitelesített e-mail hozzáadása",
|
||||
"add_verified_emails": "Hitelesített e-mailek hozzáadása",
|
||||
"custom_reply_to_email_title": "Egyéni 'Válaszcím' e-mail",
|
||||
"custom_reply_to_email_description": "Használjon másik e-mail címet válaszcímként a visszaigazoló e-mailekhez a szervező e-mail címe helyett",
|
||||
"email_survey_triggered_by_workflow": "Ezt a kérdőívet egy Workflow indította a Cal-ban.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "È stato eseguito l'upgrade del piano Organization!",
|
||||
"use_link_to_reset_password": "Usa il link qui sotto per reimpostare la tua password",
|
||||
"hey_there": "Ciao",
|
||||
"there": "lì",
|
||||
"forgot_your_password_calcom": "Hai dimenticato la password? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Sei sicuro di voler eliminare questo webhook? Non riceverai più {{appName}} dati di riunione in un URL specificato, in tempo reale, quando un evento è programmato o annullato.",
|
||||
"confirm_delete_webhook": "Sì, elimina webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Nascondi l'indirizzo email dell'organizzatore dalla schermata di prenotazione, dalle notifiche email e dagli eventi del calendario",
|
||||
"you_and_conjunction": "Tu &",
|
||||
"select_verified_email": "Seleziona un'email verificata",
|
||||
"add_verified_email": "Aggiungi email verificata",
|
||||
"add_verified_emails": "Aggiungi email verificate",
|
||||
"custom_reply_to_email_title": "Email 'Rispondi a' personalizzata",
|
||||
"custom_reply_to_email_description": "Usa un indirizzo email diverso come replyTo per le email di conferma invece dell'email dell'organizzatore",
|
||||
"email_survey_triggered_by_workflow": "Questo sondaggio è stato attivato da un Workflow in Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "あなたの Organization プランは正常にアップグレードされました!",
|
||||
"use_link_to_reset_password": "以下のリンクを使用してパスワードをリセットしてください",
|
||||
"hey_there": "こんにちは",
|
||||
"there": "そちら",
|
||||
"forgot_your_password_calcom": "パスワードをお忘れですか? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "この Webhook を削除してもよろしいですか? イベントがスケジュールまたはキャンセルされた場合、指定された URL で {{appName}} ミーティング データをリアルタイムで受信しなくなります。",
|
||||
"confirm_delete_webhook": "はい、ウェブフックを削除します",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "予約画面、メール通知、カレンダーイベントから主催者のメールアドレスを非表示にします",
|
||||
"you_and_conjunction": "あなた &",
|
||||
"select_verified_email": "確認済みメールを選択",
|
||||
"add_verified_email": "認証済みメールアドレスを追加",
|
||||
"add_verified_emails": "認証済みメールアドレスを追加",
|
||||
"custom_reply_to_email_title": "カスタム「返信先」メール",
|
||||
"custom_reply_to_email_description": "主催者のメールの代わりに、確認メールの返信先として別のメールアドレスを使用します",
|
||||
"email_survey_triggered_by_workflow": "このアンケートはCalのワークフローによってトリガーされました。",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organization របស់អ្នកត្រូវបានដំឡើងកំណែដោយជោគជ័យ!",
|
||||
"use_link_to_reset_password": "ប្រើតំណខាងក្រោមដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកឡើងវិញ",
|
||||
"hey_there": "សួស្តី",
|
||||
"there": "នៅទីនោះ",
|
||||
"forgot_your_password_calcom": "ភ្លេចពាក្យសម្ងាត់? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "តើអ្នកប្រាកដថាចង់លុប webhook នេះទេ? អ្នកនឹងលែងទទួលបានទិន្នន័យប្រជុំ {{appName}} តាម URL ដែលបានបញ្ជាក់, ក្នុងពេលវេលាជាក់ស្តែង នៅពេលដែលព្រឹត្តិការណ៍មួយត្រូវបានកំណត់ពេល ឬលុបចោល។",
|
||||
"confirm_delete_webhook": "បាទ/ចាស លុប webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "លាក់អាសយដ្ឋានអ៊ីមែលរបស់អ្នករៀបចំពីអេក្រង់ការកក់ ការជូនដំណឹងតាមអ៊ីមែល និងព្រឹត្តិការណ៍ប្រតិទិន",
|
||||
"you_and_conjunction": "អ្នក &",
|
||||
"select_verified_email": "ជ្រើសរើសអ៊ីមែលដែលបានផ្ទៀងផ្ទាត់",
|
||||
"add_verified_email": "បន្ថែមអ៊ីមែលដែលបានផ្ទៀងផ្ទាត់",
|
||||
"add_verified_emails": "បន្ថែមអ៊ីមែលដែលបានផ្ទៀងផ្ទាត់",
|
||||
"custom_reply_to_email_title": "អ៊ីមែល 'ឆ្លើយតប' ផ្ទាល់ខ្លួន",
|
||||
"custom_reply_to_email_description": "ប្រើអាសយដ្ឋានអ៊ីមែលផ្សេងជា replyTo សម្រាប់អ៊ីមែលបញ្ជាក់ជំនួសឱ្យអ៊ីមែលរបស់អ្នករៀបចំ",
|
||||
"email_survey_triggered_by_workflow": "ការស្ទង់មតិនេះត្រូវបានបង្កើតឡើងដោយលំហូរការងារនៅក្នុង Cal។",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organization이 성공적으로 업그레이드되었습니다!",
|
||||
"use_link_to_reset_password": "비밀번호를 재설정하시려면 아래 링크를 사용하세요.",
|
||||
"hey_there": "안녕하세요",
|
||||
"there": "거기",
|
||||
"forgot_your_password_calcom": "비밀번호를 잊어버리셨나요? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "웹훅을 정말로 삭제하시겠습니까? 이벤트 일정이 잡혔거나 취소되었을 때 {{appName}}의 회의 정보를 더 이상 실시간으로 받아보실 수 없게 됩니다.",
|
||||
"confirm_delete_webhook": "네, 웹훅을 삭제하겠습니다.",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "예약 화면, 이메일 알림 및 캘린더 이벤트에서 주최자의 이메일 주소 숨기기",
|
||||
"you_and_conjunction": "당신 &",
|
||||
"select_verified_email": "인증된 이메일 선택",
|
||||
"add_verified_email": "인증된 이메일 추가",
|
||||
"add_verified_emails": "인증된 이메일 추가",
|
||||
"custom_reply_to_email_title": "사용자 지정 '회신' 이메일",
|
||||
"custom_reply_to_email_description": "주최자의 이메일 대신 확인 이메일의 회신 주소로 다른 이메일 주소 사용",
|
||||
"email_survey_triggered_by_workflow": "이 설문은 Cal의 워크플로우에 의해 트리거되었습니다.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Uw organisatie is geüpgraded!",
|
||||
"use_link_to_reset_password": "Gebruik de onderstaande link om uw wachtwoord te resetten",
|
||||
"hey_there": "Hoi daar",
|
||||
"there": "daar",
|
||||
"forgot_your_password_calcom": "Wachtwoord vergeten? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Weet u zeker dat u deze webhook wilt verwijderen? U ontvangt dan niet langer {{appName}} vergadergegevens op een opgegeven URL, in real-time, wanneer een evenement is geboekt of geannuleerd.",
|
||||
"confirm_delete_webhook": "Ja, webhook verwijderen",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Verberg het e-mailadres van de organisator op het boekingsscherm, in e-mailnotificaties en in agenda-afspraken",
|
||||
"you_and_conjunction": "U &",
|
||||
"select_verified_email": "Selecteer een geverifieerd e-mailadres",
|
||||
"add_verified_email": "Geverifieerde e-mail toevoegen",
|
||||
"add_verified_emails": "Geverifieerde e-mails toevoegen",
|
||||
"custom_reply_to_email_title": "Aangepast 'Antwoord-aan' e-mailadres",
|
||||
"custom_reply_to_email_description": "Gebruik een ander e-mailadres als antwoordadres voor bevestigingsmails in plaats van het e-mailadres van de organisator",
|
||||
"email_survey_triggered_by_workflow": "Deze enquête is geactiveerd door een Workflow in Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organisasjonen din ble oppgradert!",
|
||||
"use_link_to_reset_password": "Bruk lenken nedenfor for å tilbakestille passordet ditt",
|
||||
"hey_there": "Hei der",
|
||||
"there": "der",
|
||||
"forgot_your_password_calcom": "Glemt passordet? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Er du sikker på at du vil slette denne webhooken? Du vil ikke lenger motta {{appName}} møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller avlyst.",
|
||||
"confirm_delete_webhook": "Ja, slett webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Skjul arrangørens e-postadresse fra bestillingsskjermen, e-postvarsler og kalenderarrangementer",
|
||||
"you_and_conjunction": "Du &",
|
||||
"select_verified_email": "Velg en verifisert e-post",
|
||||
"add_verified_email": "Legg til verifisert e-post",
|
||||
"add_verified_emails": "Legg til verifiserte e-poster",
|
||||
"custom_reply_to_email_title": "Egendefinert 'Svar-til' e-post",
|
||||
"custom_reply_to_email_description": "Bruk en annen e-postadresse som svar-til for bekreftelsese-poster i stedet for arrangørens e-post",
|
||||
"email_survey_triggered_by_workflow": "Denne undersøkelsen ble utløst av en arbeidsflyt i Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Twój zespół z planu Organization przeszedł na wyższą wersję!",
|
||||
"use_link_to_reset_password": "Użyj poniższego linku, aby zresetować swoje hasło",
|
||||
"hey_there": "Cześć",
|
||||
"there": "tam",
|
||||
"forgot_your_password_calcom": "Nie pamiętasz swojego hasła? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Czy na pewno chcesz usunąć ten webhook? Nie będziesz już otrzymywać danych o spotkaniach w {{appName}} w określonym URL, w czasie rzeczywistym, gdy zdarzenie jest zaplanowane lub anulowane.",
|
||||
"confirm_delete_webhook": "Tak, usuń webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ukryj adres e-mail organizatora na ekranie rezerwacji, w powiadomieniach e-mail i wydarzeniach w kalendarzu.",
|
||||
"you_and_conjunction": "Ty &",
|
||||
"select_verified_email": "Wybierz zweryfikowany e-mail",
|
||||
"add_verified_email": "Dodaj zweryfikowany e-mail",
|
||||
"add_verified_emails": "Dodaj zweryfikowane e-maile",
|
||||
"custom_reply_to_email_title": "Niestandardowy e-mail 'Odpowiedz do'",
|
||||
"custom_reply_to_email_description": "Użyj innego adresu e-mail jako 'replyTo' dla e-maili potwierdzających zamiast e-maila organizatora.",
|
||||
"email_survey_triggered_by_workflow": "Ta ankieta została uruchomiona przez Workflow w Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Sua Organização foi atualizada com êxito!",
|
||||
"use_link_to_reset_password": "Use o link abaixo para redefinir a sua senha",
|
||||
"hey_there": "Olá",
|
||||
"there": "lá",
|
||||
"forgot_your_password_calcom": "Esqueceu sua senha? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Você tem certeza que quer remover este Webhook? Você não receberá mais dados de reuniões de {{appName}} na URL especificada, em tempo real, quando um evento for agendado ou cancelado.",
|
||||
"confirm_delete_webhook": "Sim, remover o Webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ocultar o endereço de e-mail do organizador na tela de agendamento, notificações por e-mail e eventos do calendário",
|
||||
"you_and_conjunction": "Você &",
|
||||
"select_verified_email": "Selecione um e-mail verificado",
|
||||
"add_verified_email": "Adicionar e-mail verificado",
|
||||
"add_verified_emails": "Adicionar e-mails verificados",
|
||||
"custom_reply_to_email_title": "E-mail personalizado para 'Responder a'",
|
||||
"custom_reply_to_email_description": "Use um endereço de e-mail diferente como resposta para e-mails de confirmação em vez do e-mail do organizador",
|
||||
"email_survey_triggered_by_workflow": "Esta pesquisa foi acionada por um Workflow no Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "A sua Organização foi atualizada com sucesso!",
|
||||
"use_link_to_reset_password": "Use a ligação abaixo para redefinir a sua senha",
|
||||
"hey_there": "Olá",
|
||||
"there": "lá",
|
||||
"forgot_your_password_calcom": "Esqueceu-se da sua senha? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "De certeza que quer eliminar este Webhook? Não receberá mais dados de reuniões de {{appName}} do URL especificado, em tempo real, quando um evento for agendado ou cancelado.",
|
||||
"confirm_delete_webhook": "Sim, eliminar o Webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ocultar o endereço de email do organizador na tela de agendamento, notificações por email e eventos do calendário",
|
||||
"you_and_conjunction": "Você &",
|
||||
"select_verified_email": "Selecione um email verificado",
|
||||
"add_verified_email": "Adicionar e-mail verificado",
|
||||
"add_verified_emails": "Adicionar e-mails verificados",
|
||||
"custom_reply_to_email_title": "Email personalizado para 'Responder a'",
|
||||
"custom_reply_to_email_description": "Use um endereço de email diferente como resposta para emails de confirmação em vez do email do organizador",
|
||||
"email_survey_triggered_by_workflow": "Este questionário foi acionado por um Workflow no Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Upgrade-ul pentru Organization a fost realizat cu succes!",
|
||||
"use_link_to_reset_password": "Utilizați linkul de mai jos pentru a vă reseta parola",
|
||||
"hey_there": "Salut",
|
||||
"there": "acolo",
|
||||
"forgot_your_password_calcom": "Ți-ai uitat parola? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Ești sigur că vrei să ștergi acest webhook? Nu vei mai primi {{appName}} date de întâlnire la un URL specificat, în timp real, atunci când un eveniment este programat sau anulat.",
|
||||
"confirm_delete_webhook": "Da, șterge webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ascundeți adresa de email a organizatorului de pe ecranul de rezervare, notificările prin email și evenimentele din calendar",
|
||||
"you_and_conjunction": "Tu &",
|
||||
"select_verified_email": "Selectați un email verificat",
|
||||
"add_verified_email": "Adaugă un email verificat",
|
||||
"add_verified_emails": "Adaugă emailuri verificate",
|
||||
"custom_reply_to_email_title": "Email personalizat 'Reply-To'",
|
||||
"custom_reply_to_email_description": "Utilizați o altă adresă de email ca replyTo pentru emailurile de confirmare în locul emailului organizatorului",
|
||||
"email_survey_triggered_by_workflow": "Acest sondaj a fost declanșat de un Workflow în Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Вы успешно перешли на тариф Organization!",
|
||||
"use_link_to_reset_password": "Используйте ссылку ниже, чтобы сбросить пароль",
|
||||
"hey_there": "Привет,",
|
||||
"there": "там",
|
||||
"forgot_your_password_calcom": "Забыли пароль? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Вы уверены, что хотите удалить этот вебхук? Вы больше не будете получать от {{appName}} данные о новых или отменённых встречах по указанному URL в реальном времени.",
|
||||
"confirm_delete_webhook": "Да, удалить веб-хук",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Скрыть адрес электронной почты организатора на экране бронирования, в email-уведомлениях и событиях календаря",
|
||||
"you_and_conjunction": "Вы &",
|
||||
"select_verified_email": "Выбрать подтвержденный email",
|
||||
"add_verified_email": "Добавить подтверждённый email",
|
||||
"add_verified_emails": "Добавить подтверждённые email-адреса",
|
||||
"custom_reply_to_email_title": "Пользовательский email для ответа",
|
||||
"custom_reply_to_email_description": "Используйте другой адрес электронной почты в качестве адреса для ответа в подтверждающих письмах вместо email организатора",
|
||||
"email_survey_triggered_by_workflow": "Этот опрос был запущен рабочим процессом в Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Vaša organizácia bola úspešne upgradovaná!",
|
||||
"use_link_to_reset_password": "Použite nasledujúci odkaz na obnovenie hesla",
|
||||
"hey_there": "Ahoj",
|
||||
"there": "tam",
|
||||
"forgot_your_password_calcom": "Zabudli ste heslo? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Naozaj chcete odstrániť tento webhook? Už nebudete dostávať údaje o stretnutiach {{appName}} v reálnom čase na určenú URL adresu pri naplánovaní alebo zrušení udalosti.",
|
||||
"confirm_delete_webhook": "Áno, odstrániť webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Skryť e-mailovú adresu organizátora z obrazovky rezervácie, e-mailových upozornení a kalendárových udalostí",
|
||||
"you_and_conjunction": "Vy &",
|
||||
"select_verified_email": "Vyberte overený e-mail",
|
||||
"add_verified_email": "Pridať overený e-mail",
|
||||
"add_verified_emails": "Pridať overené e-maily",
|
||||
"custom_reply_to_email_title": "Vlastný e-mail 'Odpovedať na'",
|
||||
"custom_reply_to_email_description": "Použiť inú e-mailovú adresu ako adresu pre odpoveď v potvrdzovacích e-mailoch namiesto e-mailu organizátora",
|
||||
"email_survey_triggered_by_workflow": "Tento prieskum bol aktivovaný pracovným postupom v Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Vaš Organization je uspešno nadograđen!",
|
||||
"use_link_to_reset_password": "Resetujte lozinku koristeći link ispod",
|
||||
"hey_there": "Zdravo",
|
||||
"there": "tamo",
|
||||
"forgot_your_password_calcom": "Zaboravili ste lozinku? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Da li ste sigurni da želite da obrišete ovaj webhook? Nećete više dobijati {{appName}} podatke o sastancima preko naznačenog URL, istovremeno kad je događaj zakazan ili otkazan.",
|
||||
"confirm_delete_webhook": "Da, obriši webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Sakrij email adresu organizatora sa ekrana za rezervacije, email obaveštenja i kalendarske događaje",
|
||||
"you_and_conjunction": "Ви &",
|
||||
"select_verified_email": "Izaberite verifikovani email",
|
||||
"add_verified_email": "Dodaj verifikovanu e-poštu",
|
||||
"add_verified_emails": "Dodaj verifikovane e-pošte",
|
||||
"custom_reply_to_email_title": "Prilagođeni 'Reply-To' email",
|
||||
"custom_reply_to_email_description": "Koristite drugačiju email adresu kao replyTo za email potvrde umesto email-a organizatora",
|
||||
"email_survey_triggered_by_workflow": "Ова анкета је покренута Воркфловом у Цал-у.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Din Organization har uppgraderats!",
|
||||
"use_link_to_reset_password": "Använd länken nedan för att återställa ditt lösenord",
|
||||
"hey_there": "Hej där",
|
||||
"there": "där",
|
||||
"forgot_your_password_calcom": "Glömt ditt lösenord? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Är du säker på att du vill ta bort denna webhook? Du kommer inte längre att få {{appName}} mötesdata på en angiven webbadress, i realtid, när en händelse är schemalagd eller inställd.",
|
||||
"confirm_delete_webhook": "Ja, ta bort webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Dölj arrangörens e-postadress från bokningsskärmen, e-postmeddelanden och kalenderhändelser",
|
||||
"you_and_conjunction": "Du &",
|
||||
"select_verified_email": "Välj en verifierad e-postadress",
|
||||
"add_verified_email": "Lägg till verifierad e-post",
|
||||
"add_verified_emails": "Lägg till verifierade e-postadresser",
|
||||
"custom_reply_to_email_title": "Anpassad 'Svara till'-adress",
|
||||
"custom_reply_to_email_description": "Använd en annan e-postadress som 'Svara till' för bekräftelsemail istället för arrangörens e-postadress",
|
||||
"email_survey_triggered_by_workflow": "Den här enkäten utlöstes av ett Workflow i Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organization'ınız başarıyla yükseltildi!",
|
||||
"use_link_to_reset_password": "Şifrenizi sıfırlamak için aşağıdaki bağlantıyı kullanın",
|
||||
"hey_there": "Merhaba",
|
||||
"there": "orada",
|
||||
"forgot_your_password_calcom": "Şifrenizi mi unuttunuz? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Bu web kancasını silmek istediğinizden emin misiniz? Bir etkinlik planlandığında veya iptal edildiğinde, {{appName}} toplantı verilerini gerçek zamanlı olarak belirli bir URL'de almazsınız.",
|
||||
"confirm_delete_webhook": "Evet, web kancasını sil",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Organizatörün e-posta adresini rezervasyon ekranından, e-posta bildirimlerinden ve takvim etkinliklerinden gizle",
|
||||
"you_and_conjunction": "Sen &",
|
||||
"select_verified_email": "Doğrulanmış bir e-posta seç",
|
||||
"add_verified_email": "Doğrulanmış e-posta ekle",
|
||||
"add_verified_emails": "Doğrulanmış e-postalar ekle",
|
||||
"custom_reply_to_email_title": "Özel 'Yanıtla' e-postası",
|
||||
"custom_reply_to_email_description": "Organizatörün e-postası yerine onay e-postaları için farklı bir e-posta adresini yanıtla adresi olarak kullan",
|
||||
"email_survey_triggered_by_workflow": "Bu anket, Cal'da bir İş Akışı tarafından tetiklendi.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Рівень плану вашої організації підвищено!",
|
||||
"use_link_to_reset_password": "Скористайтеся посиланням нижче, щоб скинути пароль",
|
||||
"hey_there": "Привіт",
|
||||
"there": "там",
|
||||
"forgot_your_password_calcom": "Забули пароль? – {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Справді видалити цей вебгук? Ви більше не отримуватимете дані про нараду {{appName}} за вказаною URL-адресою: у реальному часі, а також під час планування та скасування.",
|
||||
"confirm_delete_webhook": "Так, видалити вебгук",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Приховати електронну адресу організатора з екрану бронювання, сповіщень електронною поштою та подій календаря",
|
||||
"you_and_conjunction": "Ви &",
|
||||
"select_verified_email": "Виберіть підтверджену електронну пошту",
|
||||
"add_verified_email": "Додати підтверджену електронну пошту",
|
||||
"add_verified_emails": "Додати підтверджені електронні пошти",
|
||||
"custom_reply_to_email_title": "Власна адреса 'Відповісти на'",
|
||||
"custom_reply_to_email_description": "Використовувати іншу електронну адресу як адресу для відповіді в листах підтвердження замість електронної пошти організатора",
|
||||
"email_survey_triggered_by_workflow": "Це опитування було запущено робочим процесом у Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "Organization của bạn đã được nâng cấp thành công!",
|
||||
"use_link_to_reset_password": "Sử dụng đường link dưới đây để thay đổi mật khẩu của bạn",
|
||||
"hey_there": "Chào bạn",
|
||||
"there": "ở đó",
|
||||
"forgot_your_password_calcom": "Quên mật khẩu của bạn? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "Bạn có chắc bạn muốn xoá webhook này? Bạn sẽ không còn nhận dữ liệu cuộc họp {{appName}} tại đường URL này, trong thời gian thực, khi một sự kiện được lên lịch hoặc huỷ.",
|
||||
"confirm_delete_webhook": "Vâng, hãy xoá webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "Ẩn địa chỉ email của người tổ chức khỏi màn hình đặt lịch, thông báo email và sự kiện lịch",
|
||||
"you_and_conjunction": "Bạn &",
|
||||
"select_verified_email": "Chọn email đã xác minh",
|
||||
"add_verified_email": "Thêm email đã xác minh",
|
||||
"add_verified_emails": "Thêm các email đã xác minh",
|
||||
"custom_reply_to_email_title": "Email 'Trả lời-Cho' tùy chỉnh",
|
||||
"custom_reply_to_email_description": "Sử dụng địa chỉ email khác làm email trả lời cho các email xác nhận thay vì email của người tổ chức",
|
||||
"email_survey_triggered_by_workflow": "Khảo sát này được kích hoạt bởi Quy trình làm việc trong Cal.",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "您的 Organization 已成功升级!",
|
||||
"use_link_to_reset_password": "使用下面的链接重置您的密码",
|
||||
"hey_there": "您好",
|
||||
"there": "那里",
|
||||
"forgot_your_password_calcom": "忘记密码?- {{appName}}",
|
||||
"delete_webhook_confirmation_message": "您确定要删除此Webhook吗?当活动被安排或取消时,您将不再会在指定的链接收到 {{appName}} 实时提供的会议数据。",
|
||||
"confirm_delete_webhook": "是,删除 Webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "在预订页面、电子邮件通知和日历事件中隐藏组织者的电子邮件地址",
|
||||
"you_and_conjunction": "你 &",
|
||||
"select_verified_email": "选择已验证的电子邮件",
|
||||
"add_verified_email": "添加已验证的邮箱",
|
||||
"add_verified_emails": "添加已验证的邮箱",
|
||||
"custom_reply_to_email_title": "自定义“回复至”电子邮件",
|
||||
"custom_reply_to_email_description": "使用不同的电子邮件地址作为确认邮件的回复地址,而不是组织者的电子邮件",
|
||||
"email_survey_triggered_by_workflow": "该调查由 Cal 中的工作流触发。",
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
"org_upgraded_successfully": "您的 Organization 成功升級!",
|
||||
"use_link_to_reset_password": "使用以下連結重置密碼",
|
||||
"hey_there": "嗨,您好",
|
||||
"there": "那裡",
|
||||
"forgot_your_password_calcom": "忘記密碼了? - {{appName}}",
|
||||
"delete_webhook_confirmation_message": "您確定要刪除這組 Webhook 嗎?之後活動預定或取消的時候,您就不會即時經特定連結收到 {{appName}} 的會議資料。",
|
||||
"confirm_delete_webhook": "是的,刪除 Webhook",
|
||||
@@ -3344,6 +3345,8 @@
|
||||
"hide_organizer_email_description": "在預訂畫面、電子郵件通知和日曆事件中隱藏組織者的電子郵件地址",
|
||||
"you_and_conjunction": "你 &",
|
||||
"select_verified_email": "選擇已驗證的電子郵件",
|
||||
"add_verified_email": "新增已驗證的電子郵件",
|
||||
"add_verified_emails": "新增已驗證的電子郵件地址",
|
||||
"custom_reply_to_email_title": "自訂「回覆至」電子郵件",
|
||||
"custom_reply_to_email_description": "使用不同的電子郵件地址作為確認電子郵件的回覆地址,而非組織者的電子郵件",
|
||||
"email_survey_triggered_by_workflow": "該調查由 Cal 中的工作流程觸發。",
|
||||
|
||||
+3650
-852
File diff suppressed because it is too large
Load Diff
@@ -232,6 +232,7 @@ checksums:
|
||||
org_upgraded_successfully: b803e2b8a503792206beaf1f4f2c861e
|
||||
use_link_to_reset_password: 75eea563335b8773fa581c61307b4b22
|
||||
hey_there: abc179eec007507d2e54e5ef922c04d7
|
||||
there: 7d63e12738c8b7b3bea05fa04ca86552
|
||||
forgot_your_password_calcom: e25822f11d138676f1151900a12be61f
|
||||
delete_webhook_confirmation_message: e6c1794d4d697286d2b55eb24c08990f
|
||||
confirm_delete_webhook: ef3672fcdca099306496ef20d846724d
|
||||
@@ -3341,6 +3342,8 @@ checksums:
|
||||
hide_organizer_email_description: 6e2cfd69fe84add77222a34bc8f5f157
|
||||
you_and_conjunction: be0533b04f42df498d15c071896e6257
|
||||
select_verified_email: 8a5d46d7989a61ba7248fbd528cf0b5f
|
||||
add_verified_email: 9d07089e0e387f69c9686acb3d0c0f84
|
||||
add_verified_emails: 62c56528e70d6818c1349089ce209ca8
|
||||
custom_reply_to_email_title: 87587e5dca76926f94e532d76e661d0b
|
||||
custom_reply_to_email_description: b94178b32d14b7c192aa5b4bb4b282ec
|
||||
email_survey_triggered_by_workflow: 8ee502957ac3828ba4066e295f4ff93f
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type z from "zod";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
import type { OAuth2UniversalSchemaWithCalcomBackwardCompatibility } from "./universalSchema";
|
||||
|
||||
@@ -23,7 +23,7 @@ export const updateTokenObject = async ({
|
||||
id: credentialId,
|
||||
},
|
||||
data: {
|
||||
key: tokenObject,
|
||||
key: tokenObject as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
|
||||
import { fetcher } from "@calcom/lib/dailyApiFetcher";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
|
||||
import {
|
||||
@@ -16,6 +15,7 @@ import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapt
|
||||
|
||||
import { ZSubmitBatchProcessorJobRes, ZGetTranscriptAccessLink } from "../zod";
|
||||
import type { TSubmitBatchProcessorJobRes, TGetTranscriptAccessLink, batchProcessorBody } from "../zod";
|
||||
import { fetcher } from "./dailyApiFetcher";
|
||||
import {
|
||||
dailyReturnTypeSchema,
|
||||
getTranscripts,
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
|
||||
import { handleErrorsJson } from "@calcom/lib/errors";
|
||||
|
||||
import { getDailyAppKeys } from "./getDailyAppKeys";
|
||||
|
||||
export const fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
|
||||
const { api_key } = await getDailyAppKeys();
|
||||
return fetch(`https://api.daily.co/v1${endpoint}`, {
|
||||
@@ -5,6 +5,7 @@ import { z } from "zod";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
@@ -102,7 +103,7 @@ const NextcloudTalkVideoApiAdapter = (credential: CredentialPayload): VideoApiAd
|
||||
id: credential.id,
|
||||
},
|
||||
data: {
|
||||
key: newTokenObject,
|
||||
key: newTokenObject as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user