Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b323cbcc8 | ||
|
|
18a0e2d75c | ||
|
|
87c89a9968 | ||
|
|
e237749abd | ||
|
|
994a36d039 | ||
|
|
c989817373 |
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@axiomhq/winston": "^1.2.0",
|
"@axiomhq/winston": "^1.2.0",
|
||||||
"@calcom/platform-constants": "*",
|
"@calcom/platform-constants": "*",
|
||||||
"@calcom/platform-enums": "*",
|
"@calcom/platform-enums": "*",
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.306",
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.311",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { createRouterCaller } from "app/_trpc/context";
|
import { createRouterCaller } from "app/_trpc/context";
|
||||||
import type { PageProps } from "app/_types";
|
import type { PageProps } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
import { AppCategories } from "@calcom/prisma/enums";
|
||||||
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
|
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
|
||||||
import { calendarsRouter } from "@calcom/trpc/server/routers/viewer/calendars/_router";
|
import { calendarsRouter } from "@calcom/trpc/server/routers/viewer/calendars/_router";
|
||||||
|
|
||||||
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
import InstalledApps from "~/apps/installed/[category]/installed-category-view";
|
import InstalledApps from "~/apps/installed/[category]/installed-category-view";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
@@ -31,6 +35,11 @@ const InstalledAppsWrapper = async ({ params }: PageProps) => {
|
|||||||
redirect("/apps/installed/calendar");
|
redirect("/apps/installed/calendar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return redirect("/auth/login");
|
||||||
|
}
|
||||||
|
|
||||||
const [calendarsCaller, appsCaller] = await Promise.all([
|
const [calendarsCaller, appsCaller] = await Promise.all([
|
||||||
createRouterCaller(calendarsRouter),
|
createRouterCaller(calendarsRouter),
|
||||||
createRouterCaller(appsRouter),
|
createRouterCaller(appsRouter),
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/about-view";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/about-view";
|
||||||
|
|
||||||
@@ -17,10 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new/about"
|
"/settings/organizations/new/about"
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const ServerPage = async () => {
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LegacyPage />
|
<LegacyPage />
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/add-teams-view";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/add-teams-view";
|
||||||
|
|
||||||
@@ -17,10 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new/add-teams"
|
"/settings/organizations/new/add-teams"
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const ServerPage = async () => {
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LegacyPage />
|
<LegacyPage />
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies } from "next/headers";
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/onboarding-handover";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/onboarding-handover";
|
||||||
|
|
||||||
@@ -18,10 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new/handover"
|
"/settings/organizations/new/handover"
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const ServerPage = async () => {
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LegacyPage />
|
<LegacyPage />
|
||||||
|
|||||||
+7
-12
@@ -1,21 +1,16 @@
|
|||||||
import type { GetServerSidePropsResult } from "next";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
|
|
||||||
export const getServerSideProps = async (): Promise<GetServerSidePropsResult<{ isOrg: boolean }>> => {
|
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const featuresRepository = new FeaturesRepository(prisma);
|
const featuresRepository = new FeaturesRepository(prisma);
|
||||||
const organizations = await featuresRepository.checkIfFeatureIsEnabledGlobally("organizations");
|
const organizations = await featuresRepository.checkIfFeatureIsEnabledGlobally("organizations");
|
||||||
// Check if organizations are enabled
|
|
||||||
if (!organizations) {
|
if (!organizations) {
|
||||||
return {
|
return notFound();
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return children;
|
||||||
props: {
|
}
|
||||||
isOrg: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
+1
-10
@@ -1,10 +1,4 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/onboard-members-view";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/onboard-members-view";
|
||||||
|
|
||||||
@@ -17,10 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new/onboard-members"
|
"/settings/organizations/new/onboard-members"
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const ServerPage = async () => {
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LegacyPage />
|
<LegacyPage />
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/create-new-view";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/create-new-view";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
@@ -19,14 +13,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new"
|
"/settings/organizations/new"
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = {
|
const ServerPage = async () => {
|
||||||
isOrg: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = withAppDirSsr<Props>(getServerSideProps);
|
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LicenseRequired>
|
<LicenseRequired>
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
|
||||||
import type { PageProps } from "app/_types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
|
||||||
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
|
|
||||||
|
|
||||||
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/payment-status-view";
|
import LegacyPage, { LayoutWrapper } from "~/settings/organizations/new/payment-status-view";
|
||||||
|
|
||||||
@@ -17,10 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
"/settings/organizations/new/status"
|
"/settings/organizations/new/status"
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const ServerPage = async () => {
|
||||||
|
|
||||||
const ServerPage = async ({ params, searchParams }: PageProps) => {
|
|
||||||
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
|
|
||||||
return (
|
return (
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<LegacyPage />
|
<LegacyPage />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.5.17",
|
"version": "5.5.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|||||||
@@ -4264,9 +4264,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": ["Orgs / Teams / Event Types / Private Links"]
|
||||||
"Orgs / Teams / Event Types / Private Links"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "OrganizationsEventTypesPrivateLinksController_getPrivateLinks",
|
"operationId": "OrganizationsEventTypesPrivateLinksController_getPrivateLinks",
|
||||||
@@ -4328,9 +4326,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": ["Orgs / Teams / Event Types / Private Links"]
|
||||||
"Orgs / Teams / Event Types / Private Links"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}": {
|
"/v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId}/private-links/{linkId}": {
|
||||||
@@ -4402,9 +4398,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": ["Orgs / Teams / Event Types / Private Links"]
|
||||||
"Orgs / Teams / Event Types / Private Links"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"operationId": "OrganizationsEventTypesPrivateLinksController_deletePrivateLink",
|
"operationId": "OrganizationsEventTypesPrivateLinksController_deletePrivateLink",
|
||||||
@@ -4474,9 +4468,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": ["Orgs / Teams / Event Types / Private Links"]
|
||||||
"Orgs / Teams / Event Types / Private Links"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
|
||||||
@@ -22121,13 +22113,7 @@
|
|||||||
"example": "2025-12-31T23:59:59.000Z"
|
"example": "2025-12-31T23:59:59.000Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["linkId", "eventTypeId", "isExpired", "bookingUrl", "expiresAt"]
|
||||||
"linkId",
|
|
||||||
"eventTypeId",
|
|
||||||
"isExpired",
|
|
||||||
"bookingUrl",
|
|
||||||
"expiresAt"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"UsageBasedPrivateLinkOutput": {
|
"UsageBasedPrivateLinkOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -22164,14 +22150,7 @@
|
|||||||
"example": 3
|
"example": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["linkId", "eventTypeId", "isExpired", "bookingUrl", "maxUsageCount", "usageCount"]
|
||||||
"linkId",
|
|
||||||
"eventTypeId",
|
|
||||||
"isExpired",
|
|
||||||
"bookingUrl",
|
|
||||||
"maxUsageCount",
|
|
||||||
"usageCount"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"CreatePrivateLinkOutput": {
|
"CreatePrivateLinkOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -22193,10 +22172,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["status", "data"]
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"GetPrivateLinksOutput": {
|
"GetPrivateLinksOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -22221,10 +22197,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["status", "data"]
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"UpdatePrivateLinkOutput": {
|
"UpdatePrivateLinkOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -22246,10 +22219,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["status", "data"]
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"DeletePrivateLinkOutput": {
|
"DeletePrivateLinkOutput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -22274,10 +22244,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["status", "data"]
|
||||||
"status",
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"StripConnectOutputDto": {
|
"StripConnectOutputDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -26267,205 +26234,6 @@
|
|||||||
},
|
},
|
||||||
"required": ["status", "data"]
|
"required": ["status", "data"]
|
||||||
},
|
},
|
||||||
"CreatePrivateLinkInput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"expiresAt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Expiration date for time-based links",
|
|
||||||
"format": "date-time",
|
|
||||||
"example": "2024-12-31T23:59:59.000Z"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum number of times the link can be used. If omitted and expiresAt is not provided, defaults to 1 (one time use).",
|
|
||||||
"example": 10,
|
|
||||||
"minimum": 1,
|
|
||||||
"default": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"TimeBasedPrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The private link ID",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"eventTypeId": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Event type ID this link belongs to",
|
|
||||||
"example": 123
|
|
||||||
},
|
|
||||||
"isExpired": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the link is currently expired",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"bookingUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Full booking URL for this private link",
|
|
||||||
"format": "uri",
|
|
||||||
"example": "https://cal.com/d/abc123def456/30min"
|
|
||||||
},
|
|
||||||
"expiresAt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Expiration date for this time-based link",
|
|
||||||
"format": "date-time",
|
|
||||||
"example": "2025-12-31T23:59:59.000Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["linkId", "eventTypeId", "isExpired", "bookingUrl", "expiresAt"]
|
|
||||||
},
|
|
||||||
"UsageBasedPrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The private link ID",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"eventTypeId": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Event type ID this link belongs to",
|
|
||||||
"example": 123
|
|
||||||
},
|
|
||||||
"isExpired": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the link is currently expired",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"bookingUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Full booking URL for this private link",
|
|
||||||
"format": "uri",
|
|
||||||
"example": "https://cal.com/d/abc123def456/30min"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum number of times this link can be used",
|
|
||||||
"example": 10
|
|
||||||
},
|
|
||||||
"usageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Current usage count for this link",
|
|
||||||
"example": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["linkId", "eventTypeId", "isExpired", "bookingUrl", "maxUsageCount", "usageCount"]
|
|
||||||
},
|
|
||||||
"CreatePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"description": "Created private link data (either time-based or usage-based)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["status", "data"]
|
|
||||||
},
|
|
||||||
"GetPrivateLinksOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "Array of private links for the event type (mix of time-based and usage-based)",
|
|
||||||
"items": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["status", "data"]
|
|
||||||
},
|
|
||||||
"UpdatePrivateLinkBody": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"expiresAt": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "New expiration date for time-based links",
|
|
||||||
"example": "2024-12-31T23:59:59.000Z"
|
|
||||||
},
|
|
||||||
"maxUsageCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "New maximum number of times the link can be used",
|
|
||||||
"example": 10,
|
|
||||||
"minimum": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"UpdatePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"description": "Updated private link data (either time-based or usage-based)",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/TimeBasedPrivateLinkOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/UsageBasedPrivateLinkOutput"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["status", "data"]
|
|
||||||
},
|
|
||||||
"DeletePrivateLinkOutput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Response status",
|
|
||||||
"example": "success"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Deleted link information",
|
|
||||||
"properties": {
|
|
||||||
"linkId": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "abc123def456"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "Private link deleted successfully"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["status", "data"]
|
|
||||||
},
|
|
||||||
"UserWebhookOutputDto": {
|
"UserWebhookOutputDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1430,6 +1430,8 @@ async function handler(
|
|||||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN &&
|
eventType.schedulingType === SchedulingType.ROUND_ROBIN &&
|
||||||
originalRescheduledBooking.userId !== evt.organizer.id;
|
originalRescheduledBooking.userId !== evt.organizer.id;
|
||||||
|
|
||||||
|
const skipDeleteEventsAndMeetings = changedOrganizer;
|
||||||
|
|
||||||
const isBookingRequestedReschedule =
|
const isBookingRequestedReschedule =
|
||||||
!!originalRescheduledBooking &&
|
!!originalRescheduledBooking &&
|
||||||
!!originalRescheduledBooking.rescheduled &&
|
!!originalRescheduledBooking.rescheduled &&
|
||||||
@@ -1702,7 +1704,8 @@ async function handler(
|
|||||||
undefined,
|
undefined,
|
||||||
changedOrganizer,
|
changedOrganizer,
|
||||||
previousHostDestinationCalendar,
|
previousHostDestinationCalendar,
|
||||||
isBookingRequestedReschedule
|
isBookingRequestedReschedule,
|
||||||
|
skipDeleteEventsAndMeetings
|
||||||
);
|
);
|
||||||
// This gets overridden when updating the event - to check if notes have been hidden or not. We just reset this back
|
// This gets overridden when updating the event - to check if notes have been hidden or not. We just reset this back
|
||||||
// to the default description when we are sending the emails.
|
// to the default description when we are sending the emails.
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* Tests for calendar event deletion during round robin organizer change operations.
|
||||||
|
* Covers scenarios where events need to be deleted from original hosts and
|
||||||
|
* credential handling during organizer changes.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getDate,
|
||||||
|
createBookingScenario,
|
||||||
|
getScenarioData,
|
||||||
|
getMockBookingAttendee,
|
||||||
|
TestData,
|
||||||
|
getOrganizer,
|
||||||
|
getGoogleCalendarCredential,
|
||||||
|
mockCalendarToHaveNoBusySlots,
|
||||||
|
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||||
|
import {
|
||||||
|
expectBookingToBeInDatabase,
|
||||||
|
expectSuccessfulRoundRobinReschedulingEmails,
|
||||||
|
} from "@calcom/web/test/utils/bookingScenario/expects";
|
||||||
|
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
||||||
|
|
||||||
|
import { describe, expect } from "vitest";
|
||||||
|
|
||||||
|
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||||
|
import { SchedulingType, BookingStatus } from "@calcom/prisma/enums";
|
||||||
|
import { test } from "@calcom/web/test/fixtures/fixtures";
|
||||||
|
|
||||||
|
describe("roundRobinReassignment test", () => {
|
||||||
|
setupAndTeardown();
|
||||||
|
|
||||||
|
test("should delete calendar events from original host when round robin reassignment changes organizer", async ({
|
||||||
|
emails,
|
||||||
|
}) => {
|
||||||
|
const roundRobinReassignment = (await import("./roundRobinReassignment")).default;
|
||||||
|
|
||||||
|
const originalHost = getOrganizer({
|
||||||
|
name: "Original Host",
|
||||||
|
email: "originalhost@example.com",
|
||||||
|
id: 101,
|
||||||
|
schedules: [TestData.schedules.IstWorkHours],
|
||||||
|
credentials: [getGoogleCalendarCredential()],
|
||||||
|
selectedCalendars: [TestData.selectedCalendars.google],
|
||||||
|
});
|
||||||
|
|
||||||
|
const newHost = getOrganizer({
|
||||||
|
name: "New Host",
|
||||||
|
email: "newhost@example.com",
|
||||||
|
id: 102,
|
||||||
|
schedules: [TestData.schedules.IstWorkHours],
|
||||||
|
credentials: [getGoogleCalendarCredential()],
|
||||||
|
selectedCalendars: [TestData.selectedCalendars.google],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { dateString: dateStringPlusOne } = getDate({ dateIncrement: 1 });
|
||||||
|
const bookingToReassignUid = "booking-to-reassign-with-deletion";
|
||||||
|
|
||||||
|
await createBookingScenario(
|
||||||
|
getScenarioData({
|
||||||
|
eventTypes: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
slug: "round-robin-event",
|
||||||
|
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||||
|
length: 45,
|
||||||
|
users: [{ id: 101 }, { id: 102 }],
|
||||||
|
hosts: [
|
||||||
|
{ userId: 101, isFixed: false },
|
||||||
|
{ userId: 102, isFixed: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bookings: [
|
||||||
|
{
|
||||||
|
id: 123,
|
||||||
|
eventTypeId: 1,
|
||||||
|
userId: originalHost.id,
|
||||||
|
uid: bookingToReassignUid,
|
||||||
|
status: BookingStatus.ACCEPTED,
|
||||||
|
startTime: `${dateStringPlusOne}T05:00:00.000Z`,
|
||||||
|
endTime: `${dateStringPlusOne}T05:45:00.000Z`,
|
||||||
|
attendees: [
|
||||||
|
getMockBookingAttendee({
|
||||||
|
id: 2,
|
||||||
|
name: "attendee",
|
||||||
|
email: "attendee@test.com",
|
||||||
|
locale: "en",
|
||||||
|
timeZone: "Asia/Kolkata",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: appStoreMetadata.googlecalendar.type,
|
||||||
|
uid: "ORIGINAL_EVENT_ID",
|
||||||
|
meetingId: "ORIGINAL_EVENT_ID",
|
||||||
|
meetingPassword: null,
|
||||||
|
meetingUrl: null,
|
||||||
|
externalCalendarId: "MOCK_EXTERNAL_CALENDAR_ID",
|
||||||
|
credentialId: 1,
|
||||||
|
deleted: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: originalHost,
|
||||||
|
usersApartFromOrganizer: [newHost],
|
||||||
|
apps: [TestData.apps["google-calendar"]],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||||
|
create: { uid: "NEW_EVENT_ID" },
|
||||||
|
update: { uid: "UPDATED_EVENT_ID" },
|
||||||
|
});
|
||||||
|
|
||||||
|
await roundRobinReassignment({
|
||||||
|
bookingId: 123,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that calendar deletion occurred (may be called multiple times due to duplicate references)
|
||||||
|
expect(calendarMock.deleteEventCalls.length).toBeGreaterThanOrEqual(1);
|
||||||
|
const deleteCall = calendarMock.deleteEventCalls[0];
|
||||||
|
expect(deleteCall.args.uid).toBe("ORIGINAL_EVENT_ID");
|
||||||
|
expect(deleteCall.args.externalCalendarId).toBe("MOCK_EXTERNAL_CALENDAR_ID");
|
||||||
|
expect(deleteCall.args.event.organizer.email).toBe(newHost.email); // Current implementation uses new host credentials
|
||||||
|
expect(deleteCall.args.event.uid).toBe(bookingToReassignUid);
|
||||||
|
|
||||||
|
// Verify that creation occurred with new host credentials
|
||||||
|
expect(calendarMock.createEventCalls.length).toBe(1);
|
||||||
|
const createCall = calendarMock.createEventCalls[0];
|
||||||
|
expect(createCall.args.calEvent.organizer.email).toBe(newHost.email);
|
||||||
|
|
||||||
|
// Verify the booking was reassigned to the new host
|
||||||
|
expectBookingToBeInDatabase({
|
||||||
|
uid: bookingToReassignUid,
|
||||||
|
userId: newHost.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expectSuccessfulRoundRobinReschedulingEmails({
|
||||||
|
prevOrganizer: originalHost,
|
||||||
|
newOrganizer: newHost,
|
||||||
|
emails,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -570,7 +570,8 @@ export default class EventManager {
|
|||||||
newBookingId?: number,
|
newBookingId?: number,
|
||||||
changedOrganizer?: boolean,
|
changedOrganizer?: boolean,
|
||||||
previousHostDestinationCalendar?: DestinationCalendar[] | null,
|
previousHostDestinationCalendar?: DestinationCalendar[] | null,
|
||||||
isBookingRequestedReschedule?: boolean
|
isBookingRequestedReschedule?: boolean,
|
||||||
|
skipDeleteEventsAndMeetings?: boolean
|
||||||
): Promise<CreateUpdateResult> {
|
): Promise<CreateUpdateResult> {
|
||||||
const originalEvt = processLocation(event);
|
const originalEvt = processLocation(event);
|
||||||
const evt = cloneDeep(originalEvt);
|
const evt = cloneDeep(originalEvt);
|
||||||
@@ -637,7 +638,7 @@ export default class EventManager {
|
|||||||
const shouldUpdateBookingReferences =
|
const shouldUpdateBookingReferences =
|
||||||
!!changedOrganizer || isLocationChanged || !!isBookingRequestedReschedule || isDailyVideoRoomExpired;
|
!!changedOrganizer || isLocationChanged || !!isBookingRequestedReschedule || isDailyVideoRoomExpired;
|
||||||
|
|
||||||
if (evt.requiresConfirmation && !changedOrganizer) {
|
if (evt.requiresConfirmation && !skipDeleteEventsAndMeetings) {
|
||||||
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
|
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
|
||||||
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
|
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
|
||||||
await this.deleteEventsAndMeetings({
|
await this.deleteEventsAndMeetings({
|
||||||
@@ -646,6 +647,14 @@ export default class EventManager {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (changedOrganizer) {
|
if (changedOrganizer) {
|
||||||
|
if (!skipDeleteEventsAndMeetings) {
|
||||||
|
log.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
|
||||||
|
await this.deleteEventsAndMeetings({
|
||||||
|
event: { ...event, destinationCalendar: previousHostDestinationCalendar },
|
||||||
|
bookingReferences: booking.references,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking");
|
log.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking");
|
||||||
const createdEvent = await this.create(originalEvt);
|
const createdEvent = await this.create(originalEvt);
|
||||||
results.push(...createdEvent.results);
|
results.push(...createdEvent.results);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class BusyTimesService {
|
|||||||
})[]
|
})[]
|
||||||
| null;
|
| null;
|
||||||
bypassBusyCalendarTimes: boolean;
|
bypassBusyCalendarTimes: boolean;
|
||||||
|
silentlyHandleCalendarFailures?: boolean;
|
||||||
shouldServeCache?: boolean;
|
shouldServeCache?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
@@ -70,6 +71,7 @@ export class BusyTimesService {
|
|||||||
rescheduleUid,
|
rescheduleUid,
|
||||||
duration,
|
duration,
|
||||||
bypassBusyCalendarTimes = false,
|
bypassBusyCalendarTimes = false,
|
||||||
|
silentlyHandleCalendarFailures = false,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@@ -198,13 +200,21 @@ export class BusyTimesService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!calendarBusyTimesQuery.success) {
|
if (!calendarBusyTimesQuery.success) {
|
||||||
|
if (silentlyHandleCalendarFailures) {
|
||||||
|
logger.warn(
|
||||||
|
`Calendar busy times fetch failed but handling silently due to silentlyHandleCalendarFailures flag for user ${username}`,
|
||||||
|
{
|
||||||
|
selectedCalendarIds: selectedCalendars.map((calendar) => calendar.id),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map(
|
`Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map(
|
||||||
(calendar) => calendar.id
|
(calendar) => calendar.id
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
const calendarBusyTimes = calendarBusyTimesQuery.data;
|
const calendarBusyTimes = calendarBusyTimesQuery.data;
|
||||||
const endConnectedCalendarsGet = performance.now();
|
const endConnectedCalendarsGet = performance.now();
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -229,7 +239,6 @@ export class BusyTimesService {
|
|||||||
|
|
||||||
if (rescheduleUid) {
|
if (rescheduleUid) {
|
||||||
const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid);
|
const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid);
|
||||||
// calendar busy time from original rescheduled booking should not be blocked
|
|
||||||
if (originalRescheduleBooking) {
|
if (originalRescheduleBooking) {
|
||||||
openSeatsDateRanges.push({
|
openSeatsDateRanges.push({
|
||||||
start: dayjs(originalRescheduleBooking.startTime),
|
start: dayjs(originalRescheduleBooking.startTime),
|
||||||
@@ -254,6 +263,7 @@ export class BusyTimesService {
|
|||||||
end: busyTime.end.add(beforeEventBuffer || 0, "minute").toDate(),
|
end: busyTime.end.add(beforeEventBuffer || 0, "minute").toDate(),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// TODO: Disabled until we can filter Zoom events by date. Also this is adding too much latency.
|
// TODO: Disabled until we can filter Zoom events by date. Also this is adding too much latency.
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const availabilitySchema = z
|
|||||||
withSource: z.boolean().optional(),
|
withSource: z.boolean().optional(),
|
||||||
returnDateOverrides: z.boolean(),
|
returnDateOverrides: z.boolean(),
|
||||||
bypassBusyCalendarTimes: z.boolean().optional(),
|
bypassBusyCalendarTimes: z.boolean().optional(),
|
||||||
|
silentlyHandleCalendarFailures: z.boolean().optional(),
|
||||||
shouldServeCache: z.boolean().optional(),
|
shouldServeCache: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
|
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
|
||||||
@@ -110,6 +111,7 @@ type GetUserAvailabilityQuery = {
|
|||||||
duration?: number;
|
duration?: number;
|
||||||
returnDateOverrides: boolean;
|
returnDateOverrides: boolean;
|
||||||
bypassBusyCalendarTimes: boolean;
|
bypassBusyCalendarTimes: boolean;
|
||||||
|
silentlyHandleCalendarFailures?: boolean;
|
||||||
shouldServeCache?: boolean;
|
shouldServeCache?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -295,6 +297,7 @@ export class UserAvailabilityService {
|
|||||||
duration,
|
duration,
|
||||||
returnDateOverrides,
|
returnDateOverrides,
|
||||||
bypassBusyCalendarTimes = false,
|
bypassBusyCalendarTimes = false,
|
||||||
|
silentlyHandleCalendarFailures = false,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
} = availabilitySchema.parse(query);
|
} = availabilitySchema.parse(query);
|
||||||
|
|
||||||
@@ -457,6 +460,7 @@ export class UserAvailabilityService {
|
|||||||
duration,
|
duration,
|
||||||
currentBookings: initialData?.currentBookings,
|
currentBookings: initialData?.currentBookings,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentlyHandleCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const BookerPlatformWrapperComponent = (
|
|||||||
onTimeslotsLoaded,
|
onTimeslotsLoaded,
|
||||||
startTime: customStartTime,
|
startTime: customStartTime,
|
||||||
showNoAvailabilityDialog,
|
showNoAvailabilityDialog,
|
||||||
|
silentlyHandleCalendarFailures = false,
|
||||||
} = props;
|
} = props;
|
||||||
const layout = BookerLayouts[view];
|
const layout = BookerLayouts[view];
|
||||||
|
|
||||||
@@ -310,6 +311,7 @@ const BookerPlatformWrapperComponent = (
|
|||||||
Boolean(event?.data?.id),
|
Boolean(event?.data?.id),
|
||||||
orgSlug: props.entity?.orgSlug ?? undefined,
|
orgSlug: props.entity?.orgSlug ?? undefined,
|
||||||
eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "",
|
eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "",
|
||||||
|
_silentCalendarFailures: silentlyHandleCalendarFailures,
|
||||||
...routingParams,
|
...routingParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
|||||||
onTimeslotsLoaded?: (slots: Record<string, Slot[]>) => void;
|
onTimeslotsLoaded?: (slots: Record<string, Slot[]>) => void;
|
||||||
startTime?: string | Date;
|
startTime?: string | Date;
|
||||||
roundRobinHideOrgAndTeam?: boolean;
|
roundRobinHideOrgAndTeam?: boolean;
|
||||||
|
silentlyHandleCalendarFailures?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type VIEW_TYPE = keyof typeof BookerLayouts;
|
type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
|||||||
: { username: props.calUsername })}
|
: { username: props.calUsername })}
|
||||||
hostsLimit={3}
|
hostsLimit={3}
|
||||||
allowUpdatingUrlParams={true}
|
allowUpdatingUrlParams={true}
|
||||||
|
silentlyHandleCalendarFailures={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -194,6 +194,12 @@ export class GetAvailableSlotsInput_2024_04_15 {
|
|||||||
@ApiHideProperty()
|
@ApiHideProperty()
|
||||||
_bypassCalendarBusyTimes?: boolean;
|
_bypassCalendarBusyTimes?: boolean;
|
||||||
|
|
||||||
|
@Transform(({ value }) => value && value.toLowerCase() === "true")
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@ApiHideProperty()
|
||||||
|
_silentCalendarFailures?: boolean;
|
||||||
|
|
||||||
@Transform(({ value }) => (value ? value.toLowerCase() === "true" : false))
|
@Transform(({ value }) => (value ? value.toLowerCase() === "true" : false))
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,46 @@
|
|||||||
|
import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
|
||||||
|
|
||||||
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
|
import { duplicateHandler } from "./duplicate.handler";
|
||||||
|
|
||||||
|
vi.mock("@calcom/prisma", () => ({
|
||||||
|
default: prismaMock,
|
||||||
|
}));
|
||||||
|
vi.mock("@calcom/lib/server/repository/eventTypeRepository");
|
||||||
|
|
||||||
|
describe("duplicateHandler", () => {
|
||||||
|
const ctx = { user: { id: 1, profile: { id: 1 } } } as any;
|
||||||
|
const input = { id: 123, slug: "test-event", title: "Test", description: "Test", length: 30, teamId: null };
|
||||||
|
const eventType = { id: 123, userId: 1, teamId: null, users: [{ id: 1 }] };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
prismaMock.eventType.findUnique.mockResolvedValue(eventType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw BAD_REQUEST in case of unique constraint violation", async () => {
|
||||||
|
const { EventTypeRepository } = await import("@calcom/lib/server/repository/eventTypeRepository");
|
||||||
|
vi.mocked(EventTypeRepository).mockImplementation(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
create: vi.fn().mockRejectedValue(
|
||||||
|
new PrismaClientKnownRequestError("Unique constraint failed", {
|
||||||
|
code: "P2002",
|
||||||
|
clientVersion: "mockedVersion",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
} as any)
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(duplicateHandler({ ctx, input })).rejects.toThrow(
|
||||||
|
new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Unique constraint violation while creating a duplicate event.",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -226,6 +226,13 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => {
|
|||||||
eventType: newEventType,
|
eventType: newEventType,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||||
|
// unique constraint violation
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Unique constraint violation while creating a duplicate event.",
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Error duplicating event type ${error}` });
|
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Error duplicating event type ${error}` });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const getScheduleSchemaObject = z.object({
|
|||||||
skipContactOwner: z.boolean().nullish(),
|
skipContactOwner: z.boolean().nullish(),
|
||||||
_enableTroubleshooter: z.boolean().optional(),
|
_enableTroubleshooter: z.boolean().optional(),
|
||||||
_bypassCalendarBusyTimes: z.boolean().optional(),
|
_bypassCalendarBusyTimes: z.boolean().optional(),
|
||||||
|
_silentCalendarFailures: z.boolean().optional(),
|
||||||
_shouldServeCache: z.boolean().optional(),
|
_shouldServeCache: z.boolean().optional(),
|
||||||
routingFormResponseId: z.number().optional(),
|
routingFormResponseId: z.number().optional(),
|
||||||
queuedFormResponseId: z.string().nullish(),
|
queuedFormResponseId: z.string().nullish(),
|
||||||
|
|||||||
@@ -737,6 +737,7 @@ export class AvailableSlotsService {
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
}: {
|
}: {
|
||||||
input: TGetScheduleInputSchema;
|
input: TGetScheduleInputSchema;
|
||||||
@@ -753,6 +754,7 @@ export class AvailableSlotsService {
|
|||||||
startTime: ReturnType<(typeof AvailableSlotsService)["prototype"]["getStartTime"]>;
|
startTime: ReturnType<(typeof AvailableSlotsService)["prototype"]["getStartTime"]>;
|
||||||
endTime: Dayjs;
|
endTime: Dayjs;
|
||||||
bypassBusyCalendarTimes: boolean;
|
bypassBusyCalendarTimes: boolean;
|
||||||
|
silentCalendarFailures: boolean;
|
||||||
shouldServeCache?: boolean;
|
shouldServeCache?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const usersWithCredentials = this.getUsersWithCredentials({
|
const usersWithCredentials = this.getUsersWithCredentials({
|
||||||
@@ -887,6 +889,7 @@ export class AvailableSlotsService {
|
|||||||
duration: input.duration || 0,
|
duration: input.duration || 0,
|
||||||
returnDateOverrides: false,
|
returnDateOverrides: false,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentlyHandleCalendarFailures: silentCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
},
|
},
|
||||||
initialData: {
|
initialData: {
|
||||||
@@ -958,6 +961,7 @@ export class AvailableSlotsService {
|
|||||||
const {
|
const {
|
||||||
_enableTroubleshooter: enableTroubleshooter = false,
|
_enableTroubleshooter: enableTroubleshooter = false,
|
||||||
_bypassCalendarBusyTimes: bypassBusyCalendarTimes = false,
|
_bypassCalendarBusyTimes: bypassBusyCalendarTimes = false,
|
||||||
|
_silentCalendarFailures: silentCalendarFailures = false,
|
||||||
_shouldServeCache,
|
_shouldServeCache,
|
||||||
routingFormResponseId,
|
routingFormResponseId,
|
||||||
queuedFormResponseId,
|
queuedFormResponseId,
|
||||||
@@ -1069,6 +1073,7 @@ export class AvailableSlotsService {
|
|||||||
? this.getStartTime(twoWeeksFromNow.format(), input.timeZone, eventType.minimumBookingNotice)
|
? this.getStartTime(twoWeeksFromNow.format(), input.timeZone, eventType.minimumBookingNotice)
|
||||||
: endTime,
|
: endTime,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1095,6 +1100,7 @@ export class AvailableSlotsService {
|
|||||||
startTime: dayjs(),
|
startTime: dayjs(),
|
||||||
endTime: twoWeeksFromNow,
|
endTime: twoWeeksFromNow,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
@@ -1129,6 +1135,7 @@ export class AvailableSlotsService {
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
bypassBusyCalendarTimes,
|
bypassBusyCalendarTimes,
|
||||||
|
silentCalendarFailures,
|
||||||
shouldServeCache,
|
shouldServeCache,
|
||||||
}));
|
}));
|
||||||
aggregatedAvailability = getAggregatedAvailability(allUsersAvailability, eventType.schedulingType);
|
aggregatedAvailability = getAggregatedAvailability(allUsersAvailability, eventType.schedulingType);
|
||||||
|
|||||||
@@ -2500,7 +2500,7 @@ __metadata:
|
|||||||
"@axiomhq/winston": ^1.2.0
|
"@axiomhq/winston": ^1.2.0
|
||||||
"@calcom/platform-constants": "*"
|
"@calcom/platform-constants": "*"
|
||||||
"@calcom/platform-enums": "*"
|
"@calcom/platform-enums": "*"
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.306"
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.311"
|
||||||
"@calcom/platform-types": "*"
|
"@calcom/platform-types": "*"
|
||||||
"@calcom/platform-utils": "*"
|
"@calcom/platform-utils": "*"
|
||||||
"@calcom/prisma": "*"
|
"@calcom/prisma": "*"
|
||||||
@@ -3559,13 +3559,13 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.306":
|
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.311":
|
||||||
version: 0.0.306
|
version: 0.0.311
|
||||||
resolution: "@calcom/platform-libraries@npm:0.0.306"
|
resolution: "@calcom/platform-libraries@npm:0.0.311"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@calcom/features": "*"
|
"@calcom/features": "*"
|
||||||
"@calcom/lib": "*"
|
"@calcom/lib": "*"
|
||||||
checksum: 602b8b446508429096b8e03e9edbe1ff744cf7c7af3fbe8a683aaaf0d3c96d1cd30ff356f72682449ca072beadaae95fdaa5c44b26e30cfce3bb838f519216f9
|
checksum: 7a38e8e0478dfe163a97ddab89285b98d7c7e86a73d3659bc29e31c059782708bfe2466e50657f6861e1c67bcfc66e1e3e44803ab3b5f65fd0fe656e880bb916
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user