Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae87fdcc9f | ||
|
|
35e29c4bee | ||
|
|
387a1704f3 | ||
|
|
3fb8c5bbc5 | ||
|
|
626585f5d0 | ||
|
|
4d3727db12 | ||
|
|
f6641d5173 | ||
|
|
f9b21206a2 | ||
|
|
96821c0026 | ||
|
|
3d5e4754af | ||
|
|
613e9f0788 | ||
|
|
1261247e94 | ||
|
|
2ad843ffbb | ||
|
|
6923b97cd2 | ||
|
|
e639237088 | ||
|
|
5bc1fbb8c2 | ||
|
|
e310931417 | ||
|
|
17944311c3 | ||
|
|
d82f87aa2a | ||
|
|
6aa21aceb8 | ||
|
|
870ad57a7e | ||
|
|
fddee71829 | ||
|
|
6f15a5ab0f | ||
|
|
1fd64c5d7b | ||
|
|
f8614f11ff | ||
|
|
5e5e77236a | ||
|
|
4cee53b07c | ||
|
|
c289fac008 | ||
|
|
68f9e86cdd | ||
|
|
5da754db65 |
@@ -111,7 +111,7 @@ The event types page UI components are located in `apps/web/modules/event-types/
|
|||||||
|
|
||||||
Changes to shared UI patterns (like tab layouts and button alignments) need to be checked across multiple views to maintain consistency:
|
Changes to shared UI patterns (like tab layouts and button alignments) need to be checked across multiple views to maintain consistency:
|
||||||
- Event types page layout: `apps/web/modules/event-types/views/event-types-listing-view.tsx`
|
- Event types page layout: `apps/web/modules/event-types/views/event-types-listing-view.tsx`
|
||||||
- Bookings page layout: `apps/web/modules/bookings/views/bookings-listing-view.tsx`
|
- Bookings page layout: `apps/web/modules/bookings/views/bookings-view.tsx`
|
||||||
- Common elements like tabs, search bars, and filter buttons should maintain consistent alignment across views
|
- Common elements like tabs, search bars, and filter buttons should maintain consistent alignment across views
|
||||||
|
|
||||||
## When working on workflow triggers or similar enum-based features in the Cal.com codebase
|
## When working on workflow triggers or similar enum-based features in the Cal.com codebase
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ runs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ inputs.path }}
|
path: ${{ inputs.path }}
|
||||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
|
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
|
||||||
- run: echo ${{ env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }} && yarn db-seed
|
- run: USE_POOL=true yarn db-seed
|
||||||
if: steps.cache-db.outputs.cache-hit != 'true'
|
if: steps.cache-db.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Postgres Dump Backup
|
- name: Postgres Dump Backup
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ jobs:
|
|||||||
if: steps.cache-api-v2-build.outputs.cache-hit != 'true'
|
if: steps.cache-api-v2-build.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
export NODE_OPTIONS="--max_old_space_size=8192"
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
yarn workspace @calcom/api-v2 run generate-schemas
|
yarn workspace @calcom/api-v2 run generate-schemas &&
|
||||||
rm -rf apps/api/v2/node_modules
|
rm -rf apps/api/v2/node_modules &&
|
||||||
yarn install
|
yarn install &&
|
||||||
yarn workspace @calcom/api-v2 run build
|
yarn workspace @calcom/api-v2 run build
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ env:
|
|||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ env:
|
|||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
@@ -36,7 +35,6 @@ env:
|
|||||||
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Web App
|
name: Build Web App
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ tsconfig.tsbuildinfo
|
|||||||
|
|
||||||
# Prisma generated files
|
# Prisma generated files
|
||||||
packages/prisma/client/*
|
packages/prisma/client/*
|
||||||
|
!packages/prisma/client/index.ts
|
||||||
|
apps/api/v2/generated/prisma/*
|
||||||
|
packages/prisma/generated/prisma/*
|
||||||
packages/prisma/zod/**/*.ts
|
packages/prisma/zod/**/*.ts
|
||||||
packages/prisma/enums
|
packages/prisma/enums
|
||||||
|
|
||||||
|
|||||||
Vendored
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
@@ -234,9 +234,10 @@ for Logger level to be set at info, for example.
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
If you don't want to create a local DB. Then you can also consider using services like railway.app, Northflank or render.
|
||||||
|
|
||||||
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
||||||
|
- [Setup postgres DB with Northflank](https://northflank.com/guides/deploy-postgres-database-on-northflank)
|
||||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||||
@@ -390,6 +391,12 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
|||||||
|
|
||||||
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
||||||
|
|
||||||
|
### Northflank
|
||||||
|
|
||||||
|
[](https://northflank.com/stacks/deploy-calcom)
|
||||||
|
|
||||||
|
You can deploy Cal.com on [Northflank](https://northflank.com) using the button above. The team at Northflank also have a [detailed blog post](https://northflank.com/guides/deploy-calcom-with-northflank) on deploying Cal.com on their platform.
|
||||||
|
|
||||||
### Vercel
|
### Vercel
|
||||||
|
|
||||||
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
|
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { get } from "@vercel/edge-config";
|
|
||||||
import type { NextMiddleware } from "next-api-middleware";
|
|
||||||
|
|
||||||
const safeGet = async <T = unknown>(key: string): Promise<T | undefined> => {
|
|
||||||
try {
|
|
||||||
return get<T>(key);
|
|
||||||
} catch (error) {
|
|
||||||
// Don't crash if EDGE_CONFIG env var is missing
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const config = { matcher: "/:path*" };
|
|
||||||
|
|
||||||
export const checkIsInMaintenanceMode: NextMiddleware = async (req, res, next) => {
|
|
||||||
const isInMaintenanceMode = await safeGet<boolean>("isInMaintenanceMode");
|
|
||||||
if (isInMaintenanceMode) {
|
|
||||||
return res
|
|
||||||
.status(503)
|
|
||||||
.json({ message: "API is currently under maintenance. Please try again at a later time." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,6 @@ import { label } from "next-api-middleware";
|
|||||||
import { addRequestId } from "./addRequestid";
|
import { addRequestId } from "./addRequestid";
|
||||||
import { captureErrors } from "./captureErrors";
|
import { captureErrors } from "./captureErrors";
|
||||||
import { captureUserId } from "./captureUserId";
|
import { captureUserId } from "./captureUserId";
|
||||||
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
|
|
||||||
import { extendRequest } from "./extendRequest";
|
import { extendRequest } from "./extendRequest";
|
||||||
import {
|
import {
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
@@ -26,7 +25,6 @@ const middleware = {
|
|||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
addRequestId,
|
addRequestId,
|
||||||
checkIsInMaintenanceMode,
|
|
||||||
verifyApiKey,
|
verifyApiKey,
|
||||||
rateLimitApiKey,
|
rateLimitApiKey,
|
||||||
extendRequest,
|
extendRequest,
|
||||||
@@ -38,16 +36,14 @@ const middleware = {
|
|||||||
|
|
||||||
type Middleware = keyof typeof middleware;
|
type Middleware = keyof typeof middleware;
|
||||||
|
|
||||||
const middlewareOrder = [
|
const middlewareOrder: Middleware[] = [
|
||||||
// The order here, determines the order of execution
|
|
||||||
"checkIsInMaintenanceMode",
|
|
||||||
"extendRequest",
|
"extendRequest",
|
||||||
"captureErrors",
|
"captureErrors",
|
||||||
"verifyApiKey",
|
"verifyApiKey",
|
||||||
"rateLimitApiKey",
|
"rateLimitApiKey",
|
||||||
"addRequestId",
|
"addRequestId",
|
||||||
"captureUserId",
|
"captureUserId",
|
||||||
] as Middleware[]; // <-- Provide a list of middleware to call automatically
|
];
|
||||||
|
|
||||||
const withMiddleware = label(middleware, middlewareOrder);
|
const withMiddleware = label(middleware, middlewareOrder);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const getAllAdminMemberships = async (userId: number) => {
|
|||||||
where: {
|
where: {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }],
|
role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] },
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
team: {
|
team: {
|
||||||
@@ -56,16 +56,19 @@ export const getAccessibleUsers = async ({
|
|||||||
memberUserIds,
|
memberUserIds,
|
||||||
adminUserId,
|
adminUserId,
|
||||||
}: AccessibleUsersType): Promise<number[]> => {
|
}: AccessibleUsersType): Promise<number[]> => {
|
||||||
|
const orConditions = [];
|
||||||
|
if (memberUserIds.length > 0) {
|
||||||
|
orConditions.push({ userId: { in: memberUserIds } });
|
||||||
|
}
|
||||||
|
orConditions.push({ userId: adminUserId, role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] } });
|
||||||
|
|
||||||
const memberships = await prisma.membership.findMany({
|
const memberships = await prisma.membership.findMany({
|
||||||
where: {
|
where: {
|
||||||
team: {
|
team: {
|
||||||
isOrganization: true,
|
isOrganization: true,
|
||||||
},
|
},
|
||||||
accepted: true,
|
accepted: true,
|
||||||
OR: [
|
OR: orConditions,
|
||||||
{ userId: { in: memberUserIds } },
|
|
||||||
{ userId: adminUserId, role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] } },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
userId: true,
|
userId: true,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async function authMiddleware(req: NextApiRequest) {
|
|||||||
if (isSystemWideAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Find all references where the user has bookings
|
// Find all references where the user has bookings
|
||||||
const bookingReference = await prisma.bookingReference.findFirst({
|
const bookingReference = await prisma.bookingReference.findFirst({
|
||||||
where: { id, booking: { userId } },
|
where: { id, booking: { userId }, deleted: null },
|
||||||
});
|
});
|
||||||
if (!bookingReference) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
if (!bookingReference) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||||||
export async function deleteHandler(req: NextApiRequest) {
|
export async function deleteHandler(req: NextApiRequest) {
|
||||||
const { query } = req;
|
const { query } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
await prisma.bookingReference.delete({ where: { id } });
|
await prisma.bookingReference.update({ where: { id }, data: { deleted: true } });
|
||||||
return { message: `BookingReference with id: ${id} deleted` };
|
return { message: `BookingReference with id: ${id} deleted` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||||||
export async function getHandler(req: NextApiRequest) {
|
export async function getHandler(req: NextApiRequest) {
|
||||||
const { query } = req;
|
const { query } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
const booking_reference = await prisma.bookingReference.findUniqueOrThrow({ where: { id } });
|
const booking_reference = await prisma.bookingReference.findUniqueOrThrow({ where: { id, deleted: null } });
|
||||||
return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) };
|
return { booking_reference: schemaBookingReferenceReadPublic.parse(booking_reference) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ import { schemaBookingReferenceReadPublic } from "~/lib/validations/booking-refe
|
|||||||
* 404:
|
* 404:
|
||||||
* description: No booking references were found
|
* description: No booking references were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
export async function handler(req: NextApiRequest) {
|
||||||
const { userId, isSystemWideAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const args: Prisma.BookingReferenceFindManyArgs = isSystemWideAdmin
|
const args: Prisma.BookingReferenceFindManyArgs = isSystemWideAdmin
|
||||||
? {}
|
? { where: { deleted: null } }
|
||||||
: { where: { booking: { userId } } };
|
: { where: { booking: { userId }, deleted: null } };
|
||||||
const data = await prisma.bookingReference.findMany(args);
|
const data = await prisma.bookingReference.findMany(args);
|
||||||
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
|
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(getHandler);
|
export default defaultResponder(handler);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import utc from "dayjs/plugin/utc";
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { isSupportedTimeZone } from "@calcom/lib/dayjs";
|
|
||||||
import { getAvailableSlotsService } from "@calcom/features/di/containers/AvailableSlots";
|
import { getAvailableSlotsService } from "@calcom/features/di/containers/AvailableSlots";
|
||||||
|
import { isSupportedTimeZone } from "@calcom/lib/dayjs";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import { createContext } from "@calcom/trpc/server/createContext";
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
@@ -48,7 +48,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
: availableSlots.slots;
|
: availableSlots.slots;
|
||||||
|
|
||||||
return { slots: slotsInProvidedTimeZone };
|
return { slots: slotsInProvidedTimeZone };
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
if (cause instanceof TRPCError) {
|
if (cause instanceof TRPCError) {
|
||||||
const statusCode = getHTTPStatusCodeFromError(cause);
|
const statusCode = getHTTPStatusCodeFromError(cause);
|
||||||
|
|||||||
@@ -0,0 +1,486 @@
|
|||||||
|
import type { Request, Response } from "express";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { createMocks } from "node-mocks-http";
|
||||||
|
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
||||||
|
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { Booking, Credential, EventType, User } from "@calcom/prisma/client";
|
||||||
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import { patchHandler } from "../../pages/api/booking-references/[id]/_patch";
|
||||||
|
import { handler } from "../../pages/api/booking-references/_get";
|
||||||
|
|
||||||
|
type CustomNextApiRequest = NextApiRequest & Request;
|
||||||
|
type CustomNextApiResponse = NextApiResponse & Response;
|
||||||
|
|
||||||
|
describe("GET /api/booking-references - Soft-delete filtering", () => {
|
||||||
|
let testUser: User;
|
||||||
|
let testCredential: Credential;
|
||||||
|
let testEventType: EventType;
|
||||||
|
let testBooking: Booking;
|
||||||
|
const createdBookingReferenceIds: number[] = [];
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testUser = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: "api-bookingreference-test@example.com",
|
||||||
|
username: "api-bookingreference-test",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testCredential = await prisma.credential.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
key: {},
|
||||||
|
userId: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testEventType = await prisma.eventType.create({
|
||||||
|
data: {
|
||||||
|
title: "Test Event Type",
|
||||||
|
slug: "api-test-event-type",
|
||||||
|
length: 30,
|
||||||
|
userId: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testBooking = await prisma.booking.create({
|
||||||
|
data: {
|
||||||
|
uid: "api-test-booking-uid",
|
||||||
|
title: "API Test Booking",
|
||||||
|
startTime: new Date(Date.now() + 86400000),
|
||||||
|
endTime: new Date(Date.now() + 90000000),
|
||||||
|
userId: testUser.id,
|
||||||
|
eventTypeId: testEventType.id,
|
||||||
|
status: BookingStatus.ACCEPTED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (createdBookingReferenceIds.length > 0) {
|
||||||
|
await prisma.bookingReference.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: createdBookingReferenceIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.booking.delete({
|
||||||
|
where: {
|
||||||
|
id: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.eventType.delete({
|
||||||
|
where: {
|
||||||
|
id: testEventType.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.credential.delete({
|
||||||
|
where: {
|
||||||
|
id: testCredential.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.user.delete({
|
||||||
|
where: {
|
||||||
|
id: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only return active booking references, excluding soft-deleted ones", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const activeRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
uid: `api-active-ref-${timestamp}`,
|
||||||
|
meetingId: `api-active-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef.id);
|
||||||
|
|
||||||
|
const deletedRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
uid: `api-deleted-ref-${timestamp}`,
|
||||||
|
meetingId: `api-deleted-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(deletedRef.id);
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
|
||||||
|
const responseData = await handler(req);
|
||||||
|
|
||||||
|
expect(responseData.booking_references).toBeDefined();
|
||||||
|
expect(Array.isArray(responseData.booking_references)).toBe(true);
|
||||||
|
|
||||||
|
const returnedActiveRef = responseData.booking_references.find(
|
||||||
|
(ref: { id: number }) => ref.id === activeRef.id
|
||||||
|
);
|
||||||
|
const returnedDeletedRef = responseData.booking_references.find(
|
||||||
|
(ref: { id: number }) => ref.id === deletedRef.id
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(returnedActiveRef).toBeDefined();
|
||||||
|
expect(returnedDeletedRef).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter booking references by user when not system admin", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const otherUser = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: `other-user-bookingreference-${timestamp}@example.com`,
|
||||||
|
username: `other-user-bookingreference-${timestamp}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherBooking = await prisma.booking.create({
|
||||||
|
data: {
|
||||||
|
uid: `other-user-booking-uid-${timestamp}`,
|
||||||
|
title: "Other User Booking",
|
||||||
|
startTime: new Date(Date.now() + 86400000),
|
||||||
|
endTime: new Date(Date.now() + 90000000),
|
||||||
|
userId: otherUser.id,
|
||||||
|
eventTypeId: testEventType.id,
|
||||||
|
status: BookingStatus.ACCEPTED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherUserRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "zoom_video",
|
||||||
|
uid: `other-user-ref-${timestamp}`,
|
||||||
|
meetingId: `other-user-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: otherBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(otherUserRef.id);
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
|
||||||
|
const responseData = await handler(req);
|
||||||
|
|
||||||
|
const userRefs = responseData.booking_references.filter(
|
||||||
|
(ref: { id: number }) => ref.id === otherUserRef.id
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(userRefs).toHaveLength(0);
|
||||||
|
|
||||||
|
await prisma.booking.delete({ where: { id: otherBooking.id } });
|
||||||
|
await prisma.user.delete({ where: { id: otherUser.id } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return only active booking references for user, not soft-deleted ones for system admin", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const activeRef1 = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "daily_video",
|
||||||
|
uid: `admin-active-ref-1-${timestamp}`,
|
||||||
|
meetingId: `admin-active-meeting-1-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef1.id);
|
||||||
|
|
||||||
|
const activeRef2 = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "daily_video",
|
||||||
|
uid: `admin-active-ref-2-${timestamp}`,
|
||||||
|
meetingId: `admin-active-meeting-2-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef2.id);
|
||||||
|
|
||||||
|
const deletedRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "daily_video",
|
||||||
|
uid: `admin-deleted-ref-${timestamp}`,
|
||||||
|
meetingId: `admin-deleted-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(deletedRef.id);
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
|
||||||
|
const responseData = await handler(req);
|
||||||
|
|
||||||
|
const activeRefs = responseData.booking_references.filter(
|
||||||
|
(ref: { id: number }) => ref.id === activeRef1.id || ref.id === activeRef2.id
|
||||||
|
);
|
||||||
|
const returnedDeletedRef = responseData.booking_references.find(
|
||||||
|
(ref: { id: number }) => ref.id === deletedRef.id
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(activeRefs.length).toBe(2);
|
||||||
|
expect(returnedDeletedRef).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should verify soft-deleted references are never returned through the API", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const activeRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "office365_calendar",
|
||||||
|
uid: `never-return-active-${timestamp}`,
|
||||||
|
meetingId: `never-return-active-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef.id);
|
||||||
|
|
||||||
|
await prisma.bookingReference.update({
|
||||||
|
where: { id: activeRef.id },
|
||||||
|
data: { deleted: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
|
||||||
|
const responseData = await handler(req);
|
||||||
|
|
||||||
|
const softDeletedRef = responseData.booking_references.find(
|
||||||
|
(ref: { id: number }) => ref.id === activeRef.id
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(softDeletedRef).toBeUndefined();
|
||||||
|
|
||||||
|
const refInDb = await prisma.bookingReference.findUnique({
|
||||||
|
where: { id: activeRef.id },
|
||||||
|
});
|
||||||
|
expect(refInDb).toBeDefined();
|
||||||
|
expect(refInDb?.deleted).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("PATCH /api/booking-references/[id] - Existing functionality", () => {
|
||||||
|
let testUser: User;
|
||||||
|
let testCredential: Credential;
|
||||||
|
let testEventType: EventType;
|
||||||
|
let testBooking: Booking;
|
||||||
|
const createdBookingReferenceIds: number[] = [];
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testUser = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: "api-bookingreference-patch-test@example.com",
|
||||||
|
username: "api-bookingreference-patch-test",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testCredential = await prisma.credential.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
key: {},
|
||||||
|
userId: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testEventType = await prisma.eventType.create({
|
||||||
|
data: {
|
||||||
|
title: "Test Event Type",
|
||||||
|
slug: "api-patch-test-event-type",
|
||||||
|
length: 30,
|
||||||
|
userId: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testBooking = await prisma.booking.create({
|
||||||
|
data: {
|
||||||
|
uid: "api-patch-test-booking-uid",
|
||||||
|
title: "API Patch Test Booking",
|
||||||
|
startTime: new Date(Date.now() + 86400000),
|
||||||
|
endTime: new Date(Date.now() + 90000000),
|
||||||
|
userId: testUser.id,
|
||||||
|
eventTypeId: testEventType.id,
|
||||||
|
status: BookingStatus.ACCEPTED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (createdBookingReferenceIds.length > 0) {
|
||||||
|
await prisma.bookingReference.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: createdBookingReferenceIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.booking.delete({
|
||||||
|
where: {
|
||||||
|
id: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.eventType.delete({
|
||||||
|
where: {
|
||||||
|
id: testEventType.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.credential.delete({
|
||||||
|
where: {
|
||||||
|
id: testCredential.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.user.delete({
|
||||||
|
where: {
|
||||||
|
id: testUser.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully update an active booking reference", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const activeRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
uid: `patch-active-ref-${timestamp}`,
|
||||||
|
meetingId: `patch-active-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef.id);
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "PATCH",
|
||||||
|
query: { id: activeRef.id.toString() },
|
||||||
|
body: {
|
||||||
|
meetingPassword: "new-password-123",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
req.isSystemWideAdmin = false;
|
||||||
|
|
||||||
|
const responseData = await patchHandler(req);
|
||||||
|
|
||||||
|
expect(responseData.booking_reference).toBeDefined();
|
||||||
|
expect(responseData.booking_reference.id).toBe(activeRef.id);
|
||||||
|
expect(responseData.booking_reference.meetingPassword).toBe("new-password-123");
|
||||||
|
|
||||||
|
const updatedRef = await prisma.bookingReference.findUnique({
|
||||||
|
where: { id: activeRef.id },
|
||||||
|
});
|
||||||
|
expect(updatedRef?.meetingPassword).toBe("new-password-123");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow updating a soft-deleted booking reference (existing functionality)", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const deletedRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "google_calendar",
|
||||||
|
uid: `patch-deleted-ref-${timestamp}`,
|
||||||
|
meetingId: `patch-deleted-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
deleted: true,
|
||||||
|
meetingPassword: "original-password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(deletedRef.id);
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "PATCH",
|
||||||
|
query: { id: deletedRef.id.toString() },
|
||||||
|
body: {
|
||||||
|
meetingPassword: "updated-password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
req.isSystemWideAdmin = false;
|
||||||
|
|
||||||
|
const responseData = await patchHandler(req);
|
||||||
|
|
||||||
|
expect(responseData.booking_reference).toBeDefined();
|
||||||
|
expect(responseData.booking_reference.id).toBe(deletedRef.id);
|
||||||
|
expect(responseData.booking_reference.meetingPassword).toBe("updated-password");
|
||||||
|
|
||||||
|
const updatedRef = await prisma.bookingReference.findUnique({
|
||||||
|
where: { id: deletedRef.id },
|
||||||
|
});
|
||||||
|
expect(updatedRef?.meetingPassword).toBe("updated-password");
|
||||||
|
expect(updatedRef?.deleted).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should verify that booking references can be updated regardless of deleted status", async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const activeRef = await prisma.bookingReference.create({
|
||||||
|
data: {
|
||||||
|
type: "zoom_video",
|
||||||
|
uid: `patch-verify-active-${timestamp}`,
|
||||||
|
meetingId: `patch-verify-active-meeting-${timestamp}`,
|
||||||
|
credentialId: testCredential.id,
|
||||||
|
bookingId: testBooking.id,
|
||||||
|
meetingPassword: "original-password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
createdBookingReferenceIds.push(activeRef.id);
|
||||||
|
|
||||||
|
await prisma.bookingReference.update({
|
||||||
|
where: { id: activeRef.id },
|
||||||
|
data: { deleted: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "PATCH",
|
||||||
|
query: { id: activeRef.id.toString() },
|
||||||
|
body: {
|
||||||
|
meetingPassword: "updated-password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
req.userId = testUser.id;
|
||||||
|
req.isSystemWideAdmin = false;
|
||||||
|
|
||||||
|
const responseData = await patchHandler(req);
|
||||||
|
|
||||||
|
expect(responseData.booking_reference).toBeDefined();
|
||||||
|
expect(responseData.booking_reference.meetingPassword).toBe("updated-password");
|
||||||
|
|
||||||
|
const refInDb = await prisma.bookingReference.findUnique({
|
||||||
|
where: { id: activeRef.id },
|
||||||
|
});
|
||||||
|
expect(refInDb?.meetingPassword).toBe("updated-password");
|
||||||
|
expect(refInDb?.deleted).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,11 +5,11 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { createMocks } from "node-mocks-http";
|
import { createMocks } from "node-mocks-http";
|
||||||
import { describe, expect, test, vi, afterEach } from "vitest";
|
import { describe, expect, test, vi, afterEach } from "vitest";
|
||||||
|
|
||||||
import { buildBooking } from "@calcom/lib/test/builder";
|
|
||||||
import {
|
import {
|
||||||
getRecordingsOfCalVideoByRoomName,
|
getRecordingsOfCalVideoByRoomName,
|
||||||
getDownloadLinkOfCalVideoByRecordingId,
|
getDownloadLinkOfCalVideoByRecordingId,
|
||||||
} from "@calcom/features/conferencing/lib/videoClient";
|
} from "@calcom/features/conferencing/lib/videoClient";
|
||||||
|
import { buildBooking } from "@calcom/lib/test/builder";
|
||||||
|
|
||||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { createMocks } from "node-mocks-http";
|
import { createMocks } from "node-mocks-http";
|
||||||
import { describe, expect, test, vi, afterEach } from "vitest";
|
import { describe, expect, test, vi, afterEach } from "vitest";
|
||||||
|
|
||||||
import { buildBooking } from "@calcom/lib/test/builder";
|
|
||||||
import { getAllTranscriptsAccessLinkFromRoomName } from "@calcom/features/conferencing/lib/videoClient";
|
import { getAllTranscriptsAccessLinkFromRoomName } from "@calcom/features/conferencing/lib/videoClient";
|
||||||
|
import { buildBooking } from "@calcom/lib/test/builder";
|
||||||
|
|
||||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,5 @@ lerna-debug.log*
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
!.env.appStore.example
|
!.env.appStore.example
|
||||||
/client/*
|
/client/*
|
||||||
|
/generated/prisma/*
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ const loadConfig = (): AppConfig => {
|
|||||||
db: {
|
db: {
|
||||||
readUrl: getEnv("DATABASE_READ_URL"),
|
readUrl: getEnv("DATABASE_READ_URL"),
|
||||||
writeUrl: getEnv("DATABASE_WRITE_URL"),
|
writeUrl: getEnv("DATABASE_WRITE_URL"),
|
||||||
|
readPoolMax: getEnv("DATABASE_READ_POOL_MAX", 9),
|
||||||
|
writePoolMax: getEnv("DATABASE_WRITE_POOL_MAX", 7),
|
||||||
|
workerReadPoolMax: getEnv("DATABASE_READ_WORKER_POOL_MAX", 4),
|
||||||
|
workerWritePoolMax: getEnv("DATABASE_WRITE_WORKER_POOL_MAX", 6),
|
||||||
redisUrl: getEnv("REDIS_URL"),
|
redisUrl: getEnv("REDIS_URL"),
|
||||||
},
|
},
|
||||||
next: {
|
next: {
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export type AppConfig = {
|
|||||||
db: {
|
db: {
|
||||||
readUrl: string;
|
readUrl: string;
|
||||||
writeUrl: string;
|
writeUrl: string;
|
||||||
|
readPoolMax: number;
|
||||||
|
writePoolMax: number;
|
||||||
|
workerReadPoolMax: number;
|
||||||
|
workerWritePoolMax: number;
|
||||||
redisUrl: string;
|
redisUrl: string;
|
||||||
};
|
};
|
||||||
next: {
|
next: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class BookingReferencesRepository_2024_08_13 {
|
|||||||
constructor(private readonly dbRead: PrismaReadService) {}
|
constructor(private readonly dbRead: PrismaReadService) {}
|
||||||
|
|
||||||
async getBookingReferences(bookingId: number, filter?: BookingReferencesFilterInput_2024_08_13) {
|
async getBookingReferences(bookingId: number, filter?: BookingReferencesFilterInput_2024_08_13) {
|
||||||
const whereClause: Prisma.BookingReferenceWhereInput = { bookingId };
|
const whereClause: Prisma.BookingReferenceWhereInput = { bookingId, deleted: null };
|
||||||
|
|
||||||
if (filter?.type) {
|
if (filter?.type) {
|
||||||
whereClause.type = filter.type;
|
whereClause.type = filter.type;
|
||||||
@@ -30,6 +30,7 @@ export class BookingReferencesRepository_2024_08_13 {
|
|||||||
return this.dbRead.prisma.bookingReference.findFirst({
|
return this.dbRead.prisma.bookingReference.findFirst({
|
||||||
where: {
|
where: {
|
||||||
uid: eventUid,
|
uid: eventUid,
|
||||||
|
deleted: null,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
credential: true,
|
credential: true,
|
||||||
|
|||||||
+3
-1
@@ -39,9 +39,11 @@ export class EventTypesService_2024_06_14 {
|
|||||||
}
|
}
|
||||||
await this.checkCanCreateEventType(user.id, body);
|
await this.checkCanCreateEventType(user.id, body);
|
||||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { destinationCalendar: _destinationCalendar, ...rest } = body;
|
const { destinationCalendar: _destinationCalendar, ...rest } = body;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
const { eventType: eventTypeCreated } = await createEventType({
|
const { eventType: eventTypeCreated } = await createEventType({
|
||||||
input: rest,
|
input: rest,
|
||||||
ctx: {
|
ctx: {
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ export type Environment = {
|
|||||||
IS_TEAM_BILLING_ENABLED: boolean;
|
IS_TEAM_BILLING_ENABLED: boolean;
|
||||||
// Used to enable/disable the rewrite of /api/v2 to /v2, active by default.
|
// Used to enable/disable the rewrite of /api/v2 to /v2, active by default.
|
||||||
REWRITE_API_V2_PREFIX: string;
|
REWRITE_API_V2_PREFIX: string;
|
||||||
|
DATABASE_READ_POOL_MAX: number;
|
||||||
|
DATABASE_WRITE_POOL_MAX: number;
|
||||||
|
DATABASE_READ_WORKER_POOL_MAX: number;
|
||||||
|
DATABASE_WRITE_WORKER_POOL_MAX: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environment[K]): Environment[K] => {
|
export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environment[K]): Environment[K] => {
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import { FilterHostsService } from "@/lib/services/filter-hosts.service";
|
|||||||
import { NoSlotsNotificationService } from "@/lib/services/no-slots-notification.service";
|
import { NoSlotsNotificationService } from "@/lib/services/no-slots-notification.service";
|
||||||
import { QualifiedHostsService } from "@/lib/services/qualified-hosts.service";
|
import { QualifiedHostsService } from "@/lib/services/qualified-hosts.service";
|
||||||
import { UserAvailabilityService } from "@/lib/services/user-availability.service";
|
import { UserAvailabilityService } from "@/lib/services/user-availability.service";
|
||||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
import { PrismaWorkerModule } from "@/modules/prisma/prisma-worker.module";
|
||||||
import { RedisService } from "@/modules/redis/redis.service";
|
import { RedisService } from "@/modules/redis/redis.service";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaWorkerModule],
|
||||||
providers: [
|
providers: [
|
||||||
PrismaOOORepository,
|
PrismaOOORepository,
|
||||||
PrismaScheduleRepository,
|
PrismaScheduleRepository,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { BookingCancelService } from "@/lib/services/booking-cancel.service";
|
||||||
|
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [BookingCancelService],
|
||||||
|
exports: [BookingCancelService],
|
||||||
|
})
|
||||||
|
export class BookingCancelModule {}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { BookingCancelService as BaseBookingCancelService } from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BookingCancelService extends BaseBookingCancelService {
|
||||||
|
constructor(prismaWriteService: PrismaWriteService) {
|
||||||
|
super({
|
||||||
|
prismaClient: prismaWriteService.prisma,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
import { PrismaAttributeRepository } from "@/lib/repositories/prisma-attribute.repository";
|
|
||||||
import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository";
|
import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository";
|
||||||
import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository";
|
|
||||||
import { PrismaHostRepository } from "@/lib/repositories/prisma-host.repository";
|
|
||||||
import { PrismaOOORepository } from "@/lib/repositories/prisma-ooo.repository";
|
|
||||||
import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
||||||
import { CacheService } from "@/lib/services/cache.service";
|
import { CacheService } from "@/lib/services/cache.service";
|
||||||
import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.service";
|
import { CheckBookingAndDurationLimitsService } from "@/lib/services/check-booking-and-duration-limits.service";
|
||||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
|
||||||
import { LuckyUserService } from "@/lib/services/lucky-user.service";
|
import { LuckyUserService } from "@/lib/services/lucky-user.service";
|
||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
@@ -21,26 +16,16 @@ export class RegularBookingService extends BaseRegularBookingService {
|
|||||||
checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService,
|
checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService,
|
||||||
prismaWriteService: PrismaWriteService,
|
prismaWriteService: PrismaWriteService,
|
||||||
bookingRepository: PrismaBookingRepository,
|
bookingRepository: PrismaBookingRepository,
|
||||||
featuresRepository: PrismaFeaturesRepository,
|
|
||||||
checkBookingLimitsService: CheckBookingLimitsService,
|
|
||||||
luckyUserService: LuckyUserService,
|
luckyUserService: LuckyUserService,
|
||||||
hostRepository: PrismaHostRepository,
|
userRepository: PrismaUserRepository
|
||||||
oooRepository: PrismaOOORepository,
|
|
||||||
userRepository: PrismaUserRepository,
|
|
||||||
attributeRepository: PrismaAttributeRepository
|
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
cacheService,
|
cacheService,
|
||||||
checkBookingAndDurationLimitsService,
|
checkBookingAndDurationLimitsService,
|
||||||
prismaClient: prismaWriteService.prisma as unknown as PrismaClient,
|
prismaClient: prismaWriteService.prisma as unknown as PrismaClient,
|
||||||
bookingRepository,
|
bookingRepository,
|
||||||
featuresRepository,
|
|
||||||
checkBookingLimitsService,
|
|
||||||
luckyUserService,
|
luckyUserService,
|
||||||
hostRepository,
|
|
||||||
oooRepository,
|
|
||||||
userRepository,
|
userRepository,
|
||||||
attributeRepository,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { PlatformPlan } from "@/modules/billing/types";
|
import { PlatformPlan } from "@/modules/billing/types";
|
||||||
import type { StripeService } from "@/modules/stripe/stripe.service";
|
import type { StripeService } from "@/modules/stripe/stripe.service";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
|
|
||||||
import { PlatformBilling, Team } from "@calcom/prisma/client";
|
import { PlatformBilling, Team } from "@calcom/prisma/client";
|
||||||
|
|
||||||
export type BillingData = {
|
export type BillingData = {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export class MembershipsRepository {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return userOrgAdminMemberships.map((m) => m.teamId);
|
return userOrgAdminMemberships.map((m: { teamId: number }) => m.teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMembership(teamId: number, userId: number, role: MembershipRole, accepted: boolean) {
|
async createMembership(teamId: number, userId: number, role: MembershipRole, accepted: boolean) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { MembershipsRepository } from "@/modules/memberships/memberships.reposit
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { intersectionBy } from "lodash";
|
import { intersectionBy } from "lodash";
|
||||||
|
|
||||||
|
import type { Membership } from "@calcom/prisma/client";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MembershipsService {
|
export class MembershipsService {
|
||||||
constructor(private readonly membershipsRepository: MembershipsRepository) {}
|
constructor(private readonly membershipsRepository: MembershipsRepository) {}
|
||||||
@@ -16,8 +18,8 @@ export class MembershipsService {
|
|||||||
const secondUserMemberships = await this.membershipsRepository.findUserMemberships(secondUserId);
|
const secondUserMemberships = await this.membershipsRepository.findUserMemberships(secondUserId);
|
||||||
|
|
||||||
return intersectionBy(
|
return intersectionBy(
|
||||||
firstUserMemberships.filter((m) => m.accepted),
|
firstUserMemberships.filter((m: Membership) => m.accepted),
|
||||||
secondUserMemberships.filter((m) => m.accepted),
|
secondUserMemberships.filter((m: Membership) => m.accepted),
|
||||||
"teamId"
|
"teamId"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-14
@@ -434,20 +434,19 @@ describe("Organizations Event Types Endpoints", () => {
|
|||||||
|
|
||||||
const responseTeamEvent = responseBody.data.find((event) => event.teamId === team.id);
|
const responseTeamEvent = responseBody.data.find((event) => event.teamId === team.id);
|
||||||
expect(responseTeamEvent).toBeDefined();
|
expect(responseTeamEvent).toBeDefined();
|
||||||
expect(responseTeamEvent?.hosts).toEqual([
|
expect(responseTeamEvent?.hosts?.find((host) => host.userId === teammate1.id)).toEqual({
|
||||||
{
|
userId: teammate1.id,
|
||||||
userId: teammate1.id,
|
name: teammate1.name,
|
||||||
name: teammate1.name,
|
username: teammate1.username,
|
||||||
username: teammate1.username,
|
avatarUrl: teammate1.avatarUrl,
|
||||||
avatarUrl: teammate1.avatarUrl,
|
});
|
||||||
},
|
|
||||||
{
|
expect(responseTeamEvent?.hosts?.find((host) => host.userId === teammate2.id)).toEqual({
|
||||||
userId: teammate2.id,
|
userId: teammate2.id,
|
||||||
name: teammate2.name,
|
name: teammate2.name,
|
||||||
username: teammate2.username,
|
username: teammate2.username,
|
||||||
avatarUrl: teammate2.avatarUrl,
|
avatarUrl: teammate2.avatarUrl,
|
||||||
},
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
if (!responseTeamEvent) {
|
if (!responseTeamEvent) {
|
||||||
throw new Error("Team event not found");
|
throw new Error("Team event not found");
|
||||||
|
|||||||
-3
@@ -122,7 +122,6 @@ describe("Organizations Teams Invite Endpoints", () => {
|
|||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await userRepositoryFixture.deleteByEmail(user.email);
|
await userRepositoryFixture.deleteByEmail(user.email);
|
||||||
await organizationsRepositoryFixture.delete(org.id);
|
await organizationsRepositoryFixture.delete(org.id);
|
||||||
@@ -131,5 +130,3 @@ describe("Organizations Teams Invite Endpoints", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,5 +1,9 @@
|
|||||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||||
import { OPTIONAL_API_KEY_HEADER, OPTIONAL_X_CAL_CLIENT_ID_HEADER, OPTIONAL_X_CAL_SECRET_KEY_HEADER } from "@/lib/docs/headers";
|
import {
|
||||||
|
OPTIONAL_API_KEY_HEADER,
|
||||||
|
OPTIONAL_X_CAL_CLIENT_ID_HEADER,
|
||||||
|
OPTIONAL_X_CAL_SECRET_KEY_HEADER,
|
||||||
|
} from "@/lib/docs/headers";
|
||||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||||
@@ -11,10 +15,8 @@ import { Controller, UseGuards, Post, Param, ParseIntPipe, HttpCode, HttpStatus
|
|||||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
|
||||||
import { TeamService } from "@calcom/platform-libraries";
|
import { TeamService } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
|
|
||||||
import { CreateInviteOutputDto } from "./outputs/invite.output";
|
import { CreateInviteOutputDto } from "./outputs/invite.output";
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
@@ -39,5 +41,3 @@ export class OrganizationsTeamsInviteController {
|
|||||||
return { status: SUCCESS_STATUS, data: result };
|
return { status: SUCCESS_STATUS, data: result };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export class InviteDataDto {
|
|||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description:
|
description:
|
||||||
"Complete invitation URL that can be shared with prospective members. Opens the signup page with the token and redirects to getting started after signup.",
|
"Complete invitation URL that can be shared with prospective members. Opens the signup page with the token and redirects to getting started after signup.",
|
||||||
example: "http://app.cal.com/signup?token=f6a5c8b1d2e34c7f90a1b2c3d4e5f6a5b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2&callbackUrl=/getting-started",
|
example:
|
||||||
|
"http://app.cal.com/signup?token=f6a5c8b1d2e34c7f90a1b2c3d4e5f6a5b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2&callbackUrl=/getting-started",
|
||||||
})
|
})
|
||||||
inviteLink!: string;
|
inviteLink!: string;
|
||||||
}
|
}
|
||||||
@@ -23,5 +24,3 @@ export class CreateInviteOutputDto {
|
|||||||
@ApiProperty({ type: InviteDataDto })
|
@ApiProperty({ type: InviteDataDto })
|
||||||
data!: InviteDataDto;
|
data!: InviteDataDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -404,7 +404,7 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
invalidWorkflow.steps = [
|
invalidWorkflow.steps = [
|
||||||
{
|
{
|
||||||
stepNumber: 1,
|
stepNumber: 1,
|
||||||
action: "sms_number",
|
action: "cal_ai_phone_call",
|
||||||
recipient: PHONE_NUMBER,
|
recipient: PHONE_NUMBER,
|
||||||
template: REMINDER,
|
template: REMINDER,
|
||||||
verifiedPhoneId: verifiedPhoneId,
|
verifiedPhoneId: verifiedPhoneId,
|
||||||
@@ -439,6 +439,18 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
|
html: "<p>Reminder for your event {EVENT_NAME}.</p>",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
stepNumber: 2,
|
||||||
|
action: "sms_attendee",
|
||||||
|
recipient: EMAIL,
|
||||||
|
template: REMINDER,
|
||||||
|
phoneRequired: false,
|
||||||
|
sender: "updatedSender",
|
||||||
|
message: {
|
||||||
|
subject: "Update Upcoming: {EVENT_NAME}",
|
||||||
|
text: "Update Reminder for your event {EVENT_NAME}.</p>",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.post(`${basePath}/routing-form`)
|
.post(`${basePath}/routing-form`)
|
||||||
@@ -459,12 +471,15 @@ describe("OrganizationsTeamsWorkflowsController (E2E)", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(responseBody.data.trigger.type).toEqual(sampleCreateWorkflowRoutingFormDto.trigger.type);
|
expect(responseBody.data.trigger.type).toEqual(sampleCreateWorkflowRoutingFormDto.trigger.type);
|
||||||
expect(responseBody.data.steps).toHaveLength(sampleCreateWorkflowRoutingFormDto.steps.length);
|
expect(responseBody.data.steps).toHaveLength(validWorkflow.steps.length);
|
||||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.id).toBeDefined();
|
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.id).toBeDefined();
|
||||||
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
|
expect(responseBody.data.steps.find((step) => step.stepNumber === 1)?.sender).toEqual(
|
||||||
"CalcomE2EStep1"
|
"CalcomE2EStep1"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(responseBody.data.steps.find((step) => step.stepNumber === 2)?.id).toBeDefined();
|
||||||
|
expect(responseBody.data.steps.find((step) => step.action === "sms_attendee")).toBeDefined();
|
||||||
|
|
||||||
const trigger = sampleCreateWorkflowRoutingFormDto.trigger as OnFormSubmittedTriggerDto;
|
const trigger = sampleCreateWorkflowRoutingFormDto.trigger as OnFormSubmittedTriggerDto;
|
||||||
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
expect(responseBody.data.trigger?.type).toEqual(trigger.type);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
|
||||||
|
import type { Prisma, AttributeToUser, Membership, User, Profile } from "@calcom/prisma/client";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationsUsersRepository {
|
export class OrganizationsUsersRepository {
|
||||||
@@ -60,7 +61,11 @@ export class OrganizationsUsersRepository {
|
|||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
});
|
});
|
||||||
return attributeToUsersWithProfile.map((attributeToUser) => attributeToUser.member.user);
|
return attributeToUsersWithProfile.map(
|
||||||
|
(
|
||||||
|
attributeToUser: AttributeToUser & { member: Membership & { user: User & { profiles: Profile[] } } }
|
||||||
|
) => attributeToUser.member.user
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrganizationUsersByEmails(
|
async getOrganizationUsersByEmails(
|
||||||
|
|||||||
@@ -1,36 +1,79 @@
|
|||||||
import type { OnModuleDestroy, OnModuleInit } from "@nestjs/common";
|
import type { OnModuleDestroy, OnModuleInit } from "@nestjs/common";
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { PrismaPg } from "@prisma/adapter-pg";
|
||||||
|
import { Pool } from "pg";
|
||||||
|
|
||||||
import { PrismaClient } from "@calcom/prisma/client";
|
import { PrismaClient } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
const DB_MAX_POOL_CONNECTION = 10;
|
||||||
|
|
||||||
|
export interface PrismaServiceOptions {
|
||||||
|
readUrl?: string;
|
||||||
|
maxReadConnections?: number;
|
||||||
|
e2e?: boolean;
|
||||||
|
type: "main" | "worker";
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaReadService implements OnModuleInit, OnModuleDestroy {
|
export class PrismaReadService implements OnModuleInit, OnModuleDestroy {
|
||||||
private logger = new Logger("PrismaReadService");
|
private logger = new Logger("PrismaReadService");
|
||||||
|
|
||||||
public prisma: PrismaClient;
|
public prisma!: PrismaClient;
|
||||||
|
private pool!: Pool;
|
||||||
|
private options!: PrismaServiceOptions;
|
||||||
|
|
||||||
constructor(readonly configService: ConfigService) {
|
constructor(private configService?: ConfigService) {
|
||||||
const dbUrl = configService.get("db.readUrl", { infer: true });
|
if (configService) {
|
||||||
|
// Use ConfigService defaults
|
||||||
|
const readUrl = configService.get<string>("db.readUrl", { infer: true });
|
||||||
|
const poolMax = parseInt(
|
||||||
|
configService.get<number>("db.readPoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION
|
||||||
|
);
|
||||||
|
const e2e = configService.get<boolean>("e2e", { infer: true }) ?? false;
|
||||||
|
|
||||||
this.prisma = new PrismaClient({
|
this.setOptions({
|
||||||
datasources: {
|
readUrl,
|
||||||
db: {
|
maxReadConnections: poolMax,
|
||||||
url: dbUrl,
|
e2e,
|
||||||
},
|
type: "main",
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOptions(options: PrismaServiceOptions) {
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
const dbUrl = options.readUrl;
|
||||||
|
const isE2E = options.e2e ?? false;
|
||||||
|
|
||||||
|
this.pool = new Pool({
|
||||||
|
connectionString: dbUrl,
|
||||||
|
max: isE2E ? 1 : options.maxReadConnections ?? DB_MAX_POOL_CONNECTION,
|
||||||
|
idleTimeoutMillis: 300000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const adapter = new PrismaPg(this.pool);
|
||||||
|
this.prisma = new PrismaClient({ adapter });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
this.prisma.$connect();
|
if (!this.prisma) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.prisma.$connect();
|
||||||
|
this.logger.log("Connected to read database");
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("Database connection failed", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
try {
|
try {
|
||||||
await this.prisma.$disconnect();
|
if (this.prisma) await this.prisma.$disconnect();
|
||||||
|
if (this.pool) await this.pool.end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error("Error disconnecting from read database", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PrismaReadService,
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
const service = new PrismaReadService();
|
||||||
|
service.setOptions({
|
||||||
|
readUrl: config.get<string>("db.readUrl", { infer: true }),
|
||||||
|
maxReadConnections: parseInt(config.get<number>("db.workerReadPoolMax", { infer: true })),
|
||||||
|
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
|
||||||
|
type: "worker",
|
||||||
|
});
|
||||||
|
return service;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PrismaWriteService,
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
const service = new PrismaWriteService();
|
||||||
|
service.setOptions({
|
||||||
|
writeUrl: config.get<string>("db.writeUrl", { infer: true }),
|
||||||
|
maxWriteConnections: parseInt(config.get<number>("db.workerWritePoolMax", { infer: true })),
|
||||||
|
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
|
||||||
|
type: "worker",
|
||||||
|
});
|
||||||
|
return service;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exports: [PrismaReadService, PrismaWriteService],
|
||||||
|
})
|
||||||
|
export class PrismaWorkerModule {}
|
||||||
@@ -1,36 +1,75 @@
|
|||||||
import { OnModuleDestroy, OnModuleInit } from "@nestjs/common";
|
import { OnModuleDestroy, OnModuleInit, Injectable, Logger } from "@nestjs/common";
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { PrismaPg } from "@prisma/adapter-pg";
|
||||||
|
import { Pool } from "pg";
|
||||||
|
|
||||||
import { PrismaClient } from "@calcom/prisma/client";
|
import { PrismaClient } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
const DB_MAX_POOL_CONNECTION = 5;
|
||||||
|
|
||||||
|
export interface PrismaServiceOptions {
|
||||||
|
writeUrl?: string;
|
||||||
|
maxWriteConnections?: number;
|
||||||
|
e2e?: boolean;
|
||||||
|
type: "main" | "worker";
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaWriteService implements OnModuleInit, OnModuleDestroy {
|
export class PrismaWriteService implements OnModuleInit, OnModuleDestroy {
|
||||||
private logger = new Logger("PrismaWriteService");
|
private logger = new Logger("PrismaWriteService");
|
||||||
|
public prisma!: PrismaClient;
|
||||||
|
private pool!: Pool;
|
||||||
|
private options!: PrismaServiceOptions;
|
||||||
|
|
||||||
public prisma: PrismaClient;
|
constructor(private configService?: ConfigService) {
|
||||||
|
if (configService) {
|
||||||
|
const writeUrl = configService.get<string>("db.writeUrl", { infer: true });
|
||||||
|
const poolMax = parseInt(
|
||||||
|
configService.get<number>("db.writePoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION
|
||||||
|
);
|
||||||
|
const e2e = configService.get<boolean>("e2e", { infer: true }) ?? false;
|
||||||
|
|
||||||
constructor(readonly configService: ConfigService) {
|
this.setOptions({
|
||||||
const dbUrl = configService.get("db.writeUrl", { infer: true });
|
writeUrl,
|
||||||
|
maxWriteConnections: poolMax,
|
||||||
|
e2e,
|
||||||
|
type: "main",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.prisma = new PrismaClient({
|
setOptions(options: PrismaServiceOptions) {
|
||||||
datasources: {
|
this.options = options;
|
||||||
db: {
|
|
||||||
url: dbUrl,
|
const dbUrl = options.writeUrl;
|
||||||
},
|
const isE2E = options.e2e ?? false;
|
||||||
},
|
this.pool = new Pool({
|
||||||
|
connectionString: dbUrl,
|
||||||
|
max: isE2E ? 1 : options.maxWriteConnections ?? DB_MAX_POOL_CONNECTION,
|
||||||
|
idleTimeoutMillis: 300000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const adapter = new PrismaPg(this.pool);
|
||||||
|
this.prisma = new PrismaClient({ adapter });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
this.prisma.$connect();
|
if (!this.prisma) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.prisma.$connect();
|
||||||
|
this.logger.log("Connected to write database");
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("Database connection failed", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
try {
|
try {
|
||||||
await this.prisma.$disconnect();
|
if (this.prisma) await this.prisma.$disconnect();
|
||||||
|
if (this.pool) await this.pool.end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error("Error disconnecting from write database", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,40 @@
|
|||||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [PrismaReadService, PrismaWriteService],
|
imports: [ConfigModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PrismaReadService,
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
const service = new PrismaReadService();
|
||||||
|
service.setOptions({
|
||||||
|
readUrl: config.get<string>("db.readUrl", { infer: true }),
|
||||||
|
maxReadConnections: parseInt(config.get<number>("db.readPoolMax", { infer: true })),
|
||||||
|
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
|
||||||
|
type: "main",
|
||||||
|
});
|
||||||
|
return service;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PrismaWriteService,
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
const service = new PrismaWriteService();
|
||||||
|
service.setOptions({
|
||||||
|
writeUrl: config.get<string>("db.writeUrl", { infer: true }),
|
||||||
|
maxWriteConnections: parseInt(config.get<number>("db.writePoolMax", { infer: true })),
|
||||||
|
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
|
||||||
|
type: "main",
|
||||||
|
});
|
||||||
|
return service;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
},
|
||||||
|
],
|
||||||
exports: [PrismaReadService, PrismaWriteService],
|
exports: [PrismaReadService, PrismaWriteService],
|
||||||
})
|
})
|
||||||
export class PrismaModule {}
|
export class PrismaModule {}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Needed if ConfigService is used anywhere
|
// Needed if ConfigService is used anywhere
|
||||||
import appConfig from "@/config/app";
|
import appConfig from "@/config/app";
|
||||||
import { AvailableSlotsModule } from "@/lib/modules/available-slots.module";
|
import { AvailableSlotsModule } from "@/lib/modules/available-slots.module";
|
||||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
import { PrismaWorkerModule } from "@/modules/prisma/prisma-worker.module";
|
||||||
import { Module, Logger } from "@nestjs/common";
|
import { Module, Logger } from "@nestjs/common";
|
||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { SentryModule } from "@sentry/nestjs/setup";
|
import { SentryModule } from "@sentry/nestjs/setup";
|
||||||
@@ -15,7 +15,7 @@ import { SentryModule } from "@sentry/nestjs/setup";
|
|||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
load: [appConfig],
|
load: [appConfig],
|
||||||
}),
|
}),
|
||||||
PrismaModule,
|
PrismaWorkerModule,
|
||||||
AvailableSlotsModule,
|
AvailableSlotsModule,
|
||||||
],
|
],
|
||||||
providers: [Logger],
|
providers: [Logger],
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export class StripeService {
|
|||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
|
|
||||||
const credentialIdsToDelete = existingCredentials.map((item) => item.id);
|
const credentialIdsToDelete = existingCredentials.map((item: Credential) => item.id);
|
||||||
if (credentialIdsToDelete.length > 0) {
|
if (credentialIdsToDelete.length > 0) {
|
||||||
await this.appsRepository.deleteAppCredentials(credentialIdsToDelete, userId);
|
await this.appsRepository.deleteAppCredentials(credentialIdsToDelete, userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const MembershipUserSelect: Prisma.UserSelect = {
|
|||||||
name: true,
|
name: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
bio: true,
|
bio: true,
|
||||||
};
|
} satisfies Prisma.UserSelect;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamsMembershipsRepository {
|
export class TeamsMembershipsRepository {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
|||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
|
||||||
import { teamMetadataSchema } from "@calcom/platform-libraries";
|
import { teamMetadataSchema } from "@calcom/platform-libraries";
|
||||||
import { Prisma } from "@calcom/prisma/client";
|
import type { Membership, Prisma } from "@calcom/prisma/client";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamsRepository {
|
export class TeamsRepository {
|
||||||
@@ -41,7 +41,7 @@ export class TeamsRepository {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return teamMembers.map((member) => member.userId);
|
return teamMembers.map((member: Membership) => member.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTeamManagedUsersIds(teamId: number) {
|
async getTeamManagedUsersIds(teamId: number) {
|
||||||
@@ -57,7 +57,7 @@ export class TeamsRepository {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return teamMembers.map((member) => member.userId);
|
return teamMembers.map((member: Membership) => member.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTeamsUserIsMemberOf(userId: number) {
|
async getTeamsUserIsMemberOf(userId: number) {
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ export class UsersRepository {
|
|||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return profiles.map((profile) => profile.user);
|
return profiles.map((profile: Profile & { user: User }) => profile.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultConferencingApp(userId: number, appSlug?: string, appLink?: string) {
|
async setDefaultConferencingApp(userId: number, appSlug?: string, appLink?: string) {
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import {
|
|||||||
EMAIL_ADDRESS,
|
EMAIL_ADDRESS,
|
||||||
EMAIL_ATTENDEE,
|
EMAIL_ATTENDEE,
|
||||||
FORM_ALLOWED_STEP_ACTIONS,
|
FORM_ALLOWED_STEP_ACTIONS,
|
||||||
|
SMS_NUMBER,
|
||||||
WorkflowEmailAddressStepDto,
|
WorkflowEmailAddressStepDto,
|
||||||
WorkflowEmailAttendeeStepDto,
|
WorkflowEmailAttendeeStepDto,
|
||||||
|
WorkflowPhoneNumberStepDto,
|
||||||
|
WorkflowPhoneAttendeeStepDto,
|
||||||
|
SMS_ATTENDEE,
|
||||||
} from "./workflow-step.input";
|
} from "./workflow-step.input";
|
||||||
import {
|
import {
|
||||||
RoutingFormWorkflowTriggerDto,
|
RoutingFormWorkflowTriggerDto,
|
||||||
@@ -85,6 +89,8 @@ export class CreateFormWorkflowDto {
|
|||||||
oneOf: [
|
oneOf: [
|
||||||
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
|
{ $ref: getSchemaPath(WorkflowEmailAddressStepDto) },
|
||||||
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
|
{ $ref: getSchemaPath(WorkflowEmailAttendeeStepDto) },
|
||||||
|
{ $ref: getSchemaPath(WorkflowPhoneAttendeeStepDto) },
|
||||||
|
{ $ref: getSchemaPath(WorkflowPhoneNumberStepDto) },
|
||||||
],
|
],
|
||||||
type: "array",
|
type: "array",
|
||||||
})
|
})
|
||||||
@@ -99,8 +105,15 @@ export class CreateFormWorkflowDto {
|
|||||||
subTypes: [
|
subTypes: [
|
||||||
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
|
{ value: WorkflowEmailAddressStepDto, name: EMAIL_ADDRESS },
|
||||||
{ value: WorkflowEmailAttendeeStepDto, name: EMAIL_ATTENDEE },
|
{ value: WorkflowEmailAttendeeStepDto, name: EMAIL_ATTENDEE },
|
||||||
|
{ value: WorkflowPhoneAttendeeStepDto, name: SMS_ATTENDEE },
|
||||||
|
{ value: WorkflowPhoneNumberStepDto, name: SMS_NUMBER },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
steps!: (WorkflowEmailAddressStepDto | WorkflowEmailAttendeeStepDto)[];
|
steps!: (
|
||||||
|
| WorkflowEmailAddressStepDto
|
||||||
|
| WorkflowEmailAttendeeStepDto
|
||||||
|
| WorkflowPhoneAttendeeStepDto
|
||||||
|
| WorkflowPhoneNumberStepDto
|
||||||
|
)[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
EMAIL_ADDRESS,
|
EMAIL_ADDRESS,
|
||||||
EMAIL_ATTENDEE,
|
EMAIL_ATTENDEE,
|
||||||
FORM_ALLOWED_STEP_ACTIONS,
|
FORM_ALLOWED_STEP_ACTIONS,
|
||||||
|
SMS_ATTENDEE,
|
||||||
|
SMS_NUMBER,
|
||||||
UpdateEmailAddressWorkflowStepDto,
|
UpdateEmailAddressWorkflowStepDto,
|
||||||
UpdateEmailAttendeeWorkflowStepDto,
|
UpdateEmailAttendeeWorkflowStepDto,
|
||||||
UpdateEmailHostWorkflowStepDto,
|
UpdateEmailHostWorkflowStepDto,
|
||||||
@@ -75,6 +77,8 @@ export class UpdateFormWorkflowDto {
|
|||||||
oneOf: [
|
oneOf: [
|
||||||
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
|
{ $ref: getSchemaPath(UpdateEmailAddressWorkflowStepDto) },
|
||||||
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
|
{ $ref: getSchemaPath(UpdateEmailAttendeeWorkflowStepDto) },
|
||||||
|
{ $ref: getSchemaPath(UpdatePhoneAttendeeWorkflowStepDto) },
|
||||||
|
{ $ref: getSchemaPath(UpdatePhoneNumberWorkflowStepDto) },
|
||||||
],
|
],
|
||||||
type: "array",
|
type: "array",
|
||||||
})
|
})
|
||||||
@@ -90,8 +94,15 @@ export class UpdateFormWorkflowDto {
|
|||||||
subTypes: [
|
subTypes: [
|
||||||
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
|
{ value: UpdateEmailAddressWorkflowStepDto, name: EMAIL_ADDRESS },
|
||||||
{ value: UpdateEmailAttendeeWorkflowStepDto, name: EMAIL_ATTENDEE },
|
{ value: UpdateEmailAttendeeWorkflowStepDto, name: EMAIL_ATTENDEE },
|
||||||
|
{ value: UpdatePhoneAttendeeWorkflowStepDto, name: SMS_ATTENDEE },
|
||||||
|
{ value: UpdatePhoneNumberWorkflowStepDto, name: SMS_NUMBER },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
steps?: (UpdateEmailAddressWorkflowStepDto | UpdateEmailAttendeeWorkflowStepDto)[];
|
steps?: (
|
||||||
|
| UpdateEmailAddressWorkflowStepDto
|
||||||
|
| UpdateEmailAttendeeWorkflowStepDto
|
||||||
|
| UpdatePhoneNumberWorkflowStepDto
|
||||||
|
| UpdatePhoneAttendeeWorkflowStepDto
|
||||||
|
)[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const STEP_ACTIONS = [
|
|||||||
CAL_AI_PHONE_CALL,
|
CAL_AI_PHONE_CALL,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS] as const;
|
export const FORM_ALLOWED_STEP_ACTIONS = [EMAIL_ATTENDEE, EMAIL_ADDRESS, SMS_ATTENDEE, SMS_NUMBER] as const;
|
||||||
|
|
||||||
export const STEP_ACTIONS_TO_ENUM = {
|
export const STEP_ACTIONS_TO_ENUM = {
|
||||||
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,
|
[EMAIL_HOST]: WorkflowActions.EMAIL_HOST,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@prisma/client/*": ["./client/*"],
|
"@calcom/prisma/client": ["../../../packages/prisma/generated/prisma/client"],
|
||||||
"@calcom/prisma/enums": ["../../../packages/prisma/enums"],
|
"@calcom/prisma/enums": ["../../../packages/prisma/enums"],
|
||||||
"@calcom/platform-constants": ["../../../packages/platform/constants/index.ts"],
|
"@calcom/platform-constants": ["../../../packages/platform/constants/index.ts"],
|
||||||
"@calcom/platform-types": ["../../../packages/platform/types/index.ts"],
|
"@calcom/platform-types": ["../../../packages/platform/types/index.ts"],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { unstable_cache } from "next/cache";
|
|||||||
|
|
||||||
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
||||||
import { getTeamData } from "@calcom/features/ee/teams/lib/getTeamData";
|
import { getTeamData } from "@calcom/features/ee/teams/lib/getTeamData";
|
||||||
|
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
||||||
import {
|
import {
|
||||||
getEventTypeHosts,
|
getEventTypeHosts,
|
||||||
getProfileFromEvent,
|
getProfileFromEvent,
|
||||||
@@ -11,10 +12,9 @@ import {
|
|||||||
} from "@calcom/features/eventtypes/lib/getPublicEvent";
|
} from "@calcom/features/eventtypes/lib/getPublicEvent";
|
||||||
import { getTeamEventType } from "@calcom/features/eventtypes/lib/getTeamEventType";
|
import { getTeamEventType } from "@calcom/features/eventtypes/lib/getTeamEventType";
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants";
|
import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants";
|
||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||||
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import type { SchedulingType } from "@calcom/prisma/enums";
|
import type { SchedulingType } from "@calcom/prisma/enums";
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { MembershipRole } from "@calcom/prisma/enums";
|
|||||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
import { validStatuses } from "~/bookings/lib/validStatuses";
|
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
import BookingsList from "~/bookings/views/bookings-listing-view";
|
import BookingsList from "~/bookings/views/bookings-view";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
status: z.enum(validStatuses),
|
status: z.enum(validStatuses),
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
|
|||||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||||
const searchParams = await _searchParams;
|
const searchParams = await _searchParams;
|
||||||
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
||||||
const autoAccept = Array.isArray(searchParams?.autoAccept) ? searchParams.autoAccept[0] : searchParams?.autoAccept;
|
const autoAccept = Array.isArray(searchParams?.autoAccept)
|
||||||
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}${autoAccept ? `&autoAccept=${encodeURIComponent(autoAccept)}` : ''}` : null;
|
? searchParams.autoAccept[0]
|
||||||
|
: searchParams?.autoAccept;
|
||||||
|
const callbackUrl = token
|
||||||
|
? `/teams?token=${encodeURIComponent(token)}${
|
||||||
|
autoAccept ? `&autoAccept=${encodeURIComponent(autoAccept)}` : ""
|
||||||
|
}`
|
||||||
|
: null;
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
|
redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import type { Session } from "next-auth";
|
|||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
|
|
||||||
import { TeamsListing } from "@calcom/features/ee/teams/components/TeamsListing";
|
import { TeamsListing } from "@calcom/features/ee/teams/components/TeamsListing";
|
||||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
|
||||||
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
||||||
import { TeamService } from "@calcom/features/ee/teams/services/teamService";
|
import { TeamService } from "@calcom/features/ee/teams/services/teamService";
|
||||||
|
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
@@ -33,7 +33,9 @@ export const ServerTeamsListing = async ({
|
|||||||
session: Session;
|
session: Session;
|
||||||
}) => {
|
}) => {
|
||||||
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
||||||
const autoAccept = Array.isArray(searchParams?.autoAccept) ? searchParams.autoAccept[0] : searchParams?.autoAccept;
|
const autoAccept = Array.isArray(searchParams?.autoAccept)
|
||||||
|
? searchParams.autoAccept[0]
|
||||||
|
: searchParams?.autoAccept;
|
||||||
const userId = session.user.id;
|
const userId = session.user.id;
|
||||||
let invitationAccepted = false;
|
let invitationAccepted = false;
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ export const ServerTeamsListing = async ({
|
|||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
if (autoAccept === "true") {
|
if (autoAccept === "true") {
|
||||||
await TeamService.acceptInvitationByToken(token, userId);
|
await TeamService.acceptInvitationByToken(token, userId);
|
||||||
invitationAccepted = true;
|
invitationAccepted = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ const WeightedAttributesSelector = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-muted mt-1 rounded-xl p-2">
|
<div className="bg-muted mt-1 rounded-xl p-2">
|
||||||
{!!attributeIdForWeights ? (
|
{attributeIdForWeights ? (
|
||||||
<SelectField
|
<SelectField
|
||||||
size="sm"
|
size="sm"
|
||||||
containerClassName="data-testid-select-router"
|
containerClassName="data-testid-select-router"
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export default function RoutingForms({ appUrl }: { appUrl: string }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
hookForm.reset({});
|
hookForm.reset({});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
const filters = getTeamsFiltersFromQuery(routerQuery);
|
const filters = getTeamsFiltersFromQuery(routerQuery);
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { cookies, headers } from "next/headers";
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router";
|
import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router";
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,8 +4,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||||
import { OrgForm } from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgEditPage";
|
import { OrgForm } from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgEditPage";
|
||||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
|
||||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||||
|
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||||
|
|
||||||
const orgIdSchema = z.object({ id: z.coerce.number() });
|
const orgIdSchema = z.object({ id: z.coerce.number() });
|
||||||
|
|
||||||
|
|||||||
-2
@@ -369,7 +369,6 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
|
|||||||
const tabMembers = Array.from(document.getElementsByTagName("a")).filter(
|
const tabMembers = Array.from(document.getElementsByTagName("a")).filter(
|
||||||
(bottom) => bottom.dataset.testid === "vertical-tab-Members"
|
(bottom) => bottom.dataset.testid === "vertical-tab-Members"
|
||||||
)[1];
|
)[1];
|
||||||
// eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- Settings layout isn't embedded
|
|
||||||
tabMembers?.scrollIntoView({ behavior: "smooth" });
|
tabMembers?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -568,7 +567,6 @@ const SettingsSidebarContainer = ({
|
|||||||
const tabMembers = Array.from(document.getElementsByTagName("a")).filter(
|
const tabMembers = Array.from(document.getElementsByTagName("a")).filter(
|
||||||
(bottom) => bottom.dataset.testid === "vertical-tab-Members"
|
(bottom) => bottom.dataset.testid === "vertical-tab-Members"
|
||||||
)[1];
|
)[1];
|
||||||
// eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- Settings layout isn't embedded
|
|
||||||
tabMembers?.scrollIntoView({ behavior: "smooth" });
|
tabMembers?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,9 +2,9 @@ import type { PageProps } from "app/_types";
|
|||||||
import { getTranslate, _generateMetadata } from "app/_utils";
|
import { getTranslate, _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||||
|
import { WebhookRepository } from "@calcom/features/webhooks/lib/repository/WebhookRepository";
|
||||||
import { EditWebhookView } from "@calcom/features/webhooks/pages/webhook-edit-view";
|
import { EditWebhookView } from "@calcom/features/webhooks/pages/webhook-edit-view";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { WebhookRepository } from "@calcom/features/webhooks/lib/repository/WebhookRepository";
|
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import type { AppFlags } from "@calcom/features/flags/config";
|
import type { AppFlags } from "@calcom/features/flags/config";
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
|
|||||||
+1
-1
@@ -2,8 +2,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { OtherTeamsListing } from "@calcom/features/ee/organizations/pages/components/OtherTeamsListing";
|
import { OtherTeamsListing } from "@calcom/features/ee/organizations/pages/components/OtherTeamsListing";
|
||||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
|
||||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||||
|
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||||
|
|
||||||
import { validateUserHasOrg } from "../../../actions/validateUserHasOrg";
|
import { validateUserHasOrg } from "../../../actions/validateUserHasOrg";
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const resolveEndpoint = (links: any) => {
|
|||||||
// to the correct API endpoints.
|
// to the correct API endpoints.
|
||||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
// - viewer.public.i18n - 3 segments paths can be public or authed
|
||||||
// - viewer.eventTypes.heavy.create - 4 segments paths for heavy sub-router
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (ctx: any) => {
|
return (ctx: any) => {
|
||||||
const parts = ctx.op.path.split(".");
|
const parts = ctx.op.path.split(".");
|
||||||
@@ -24,11 +23,7 @@ const resolveEndpoint = (links: any) => {
|
|||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
endpoint = parts[0] as keyof typeof links;
|
endpoint = parts[0] as keyof typeof links;
|
||||||
path = parts[1];
|
path = parts[1];
|
||||||
} else if (parts.length >= 3 && parts[2] === "heavy") {
|
} else {
|
||||||
endpoint = parts[1] + "/heavy" as keyof typeof links;
|
|
||||||
path = parts[3];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
endpoint = parts[1] as keyof typeof links;
|
endpoint = parts[1] as keyof typeof links;
|
||||||
path = parts.splice(2, parts.length - 2).join(".");
|
path = parts.splice(2, parts.length - 2).join(".");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
|
import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/features/calendars/lib/CalendarManager";
|
import {
|
||||||
|
getCalendarCredentials,
|
||||||
|
getConnectedCalendars,
|
||||||
|
} from "@calcom/features/calendars/lib/CalendarManager";
|
||||||
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import notEmpty from "@calcom/lib/notEmpty";
|
import notEmpty from "@calcom/lib/notEmpty";
|
||||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ async function handler(req: NextRequest) {
|
|||||||
let appDirRequestBody;
|
let appDirRequestBody;
|
||||||
try {
|
try {
|
||||||
appDirRequestBody = await req.json();
|
appDirRequestBody = await req.json();
|
||||||
} catch (error) {
|
} catch {
|
||||||
return NextResponse.json({ success: false, message: "Invalid JSON" }, { status: 400 });
|
return NextResponse.json({ success: false, message: "Invalid JSON" }, { status: 400 });
|
||||||
}
|
}
|
||||||
const bookingData = bookingCancelWithCsrfSchema.parse(appDirRequestBody);
|
const bookingData = bookingCancelWithCsrfSchema.parse(appDirRequestBody);
|
||||||
@@ -26,11 +26,20 @@ async function handler(req: NextRequest) {
|
|||||||
cookieStore.delete("calcom.csrf_token");
|
cookieStore.delete("calcom.csrf_token");
|
||||||
|
|
||||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||||
|
|
||||||
const result = await handleCancelBooking({
|
const result = await handleCancelBooking({
|
||||||
bookingData,
|
bookingData,
|
||||||
userId: session?.user?.id || -1,
|
userId: session?.user?.id || -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const bookingCancelService = getBookingCancelService();
|
||||||
|
// const result = await bookingCancelService.cancelBooking({
|
||||||
|
// bookingData: bookingData,
|
||||||
|
// bookingMeta: {
|
||||||
|
// userId: session?.user?.id || -1,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
const statusCode = result.success ? 200 : 400;
|
const statusCode = result.success ? 200 : 400;
|
||||||
|
|
||||||
return NextResponse.json(result, { status: statusCode });
|
return NextResponse.json(result, { status: statusCode });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||||
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
||||||
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
|
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
|
||||||
import { CalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository";
|
import { CalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository";
|
||||||
@@ -8,7 +9,6 @@ import { CalendarCacheEventService } from "@calcom/features/calendar-subscriptio
|
|||||||
import { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/sync/CalendarSyncService";
|
import { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/sync/CalendarSyncService";
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
|
||||||
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
|
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
|
|
||||||
import { CalendarAppDelegationCredentialInvalidGrantError } from "@calcom/lib/CalendarAppError";
|
|
||||||
import { findUniqueDelegationCalendarCredential } from "@calcom/app-store/delegationCredential";
|
import { findUniqueDelegationCalendarCredential } from "@calcom/app-store/delegationCredential";
|
||||||
|
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
|
||||||
|
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
|
||||||
|
import { CalendarAppDelegationCredentialInvalidGrantError } from "@calcom/lib/CalendarAppError";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
|
|
||||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
|
import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
|
||||||
import type { Ensure } from "@calcom/types/utils";
|
import type { Ensure } from "@calcom/types/utils";
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import {
|
|||||||
sendDailyVideoRecordingEmails,
|
sendDailyVideoRecordingEmails,
|
||||||
sendDailyVideoTranscriptEmails,
|
sendDailyVideoTranscriptEmails,
|
||||||
} from "@calcom/emails/daily-video-emails";
|
} from "@calcom/emails/daily-video-emails";
|
||||||
|
import {
|
||||||
|
getAllTranscriptsAccessLinkFromMeetingId,
|
||||||
|
submitBatchProcessorTranscriptionJob,
|
||||||
|
} from "@calcom/features/conferencing/lib/videoClient";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
import {
|
|
||||||
getAllTranscriptsAccessLinkFromMeetingId,
|
|
||||||
submitBatchProcessorTranscriptionJob,
|
|
||||||
} from "@calcom/features/conferencing/lib/videoClient";
|
|
||||||
import { generateVideoToken } from "@calcom/lib/videoTokens";
|
import { generateVideoToken } from "@calcom/lib/videoTokens";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { getBooking } from "@calcom/web/lib/daily-webhook/getBooking";
|
import { getBooking } from "@calcom/web/lib/daily-webhook/getBooking";
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { BillingPlanService } from "@calcom/features/ee/billing/domain/billing-plans";
|
import { BillingPlanService } from "@calcom/features/ee/billing/domain/billing-plans";
|
||||||
import type { Contact } from "@calcom/features/ee/support/lib/intercom/intercom";
|
import type { Contact } from "@calcom/features/ee/support/lib/intercom/intercom";
|
||||||
import { intercom } from "@calcom/features/ee/support/lib/intercom/intercom";
|
import { intercom } from "@calcom/features/ee/support/lib/intercom/intercom";
|
||||||
|
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
||||||
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
|
|
||||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Params } from "app/_types";
|
|||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||||
import type { CalendarSubscriptionProvider } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
import type { CalendarSubscriptionProvider } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
||||||
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
import { DefaultAdapterFactory } from "@calcom/features/calendar-subscription/adapters/AdaptersFactory";
|
||||||
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
|
import { CalendarSubscriptionService } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionService";
|
||||||
@@ -11,7 +12,6 @@ import { CalendarSyncService } from "@calcom/features/calendar-subscription/lib/
|
|||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/SelectedCalendarRepository";
|
||||||
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
|
import { defaultResponderForAppDir } from "@calcom/web/app/api/defaultResponderForAppDir";
|
||||||
|
|
||||||
|
|||||||
@@ -696,7 +696,15 @@ describe("Retell AI Webhook Handler", () => {
|
|||||||
describe("Web Call Tests", () => {
|
describe("Web Call Tests", () => {
|
||||||
const mockAgent: Pick<
|
const mockAgent: Pick<
|
||||||
Agent,
|
Agent,
|
||||||
"id" | "name" | "providerAgentId" | "enabled" | "userId" | "teamId" | "createdAt" | "updatedAt" | "inboundEventTypeId"
|
| "id"
|
||||||
|
| "name"
|
||||||
|
| "providerAgentId"
|
||||||
|
| "enabled"
|
||||||
|
| "userId"
|
||||||
|
| "teamId"
|
||||||
|
| "createdAt"
|
||||||
|
| "updatedAt"
|
||||||
|
| "inboundEventTypeId"
|
||||||
> = {
|
> = {
|
||||||
id: "agent-123",
|
id: "agent-123",
|
||||||
name: "Test Agent",
|
name: "Test Agent",
|
||||||
|
|||||||
Vendored
+1
-1
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import { revalidateTag, unstable_cache } from "next/cache";
|
import { revalidateTag, unstable_cache } from "next/cache";
|
||||||
|
|
||||||
import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants";
|
|
||||||
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
||||||
|
import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
const CACHE_TAGS = {
|
const CACHE_TAGS = {
|
||||||
HAS_TEAM_PLAN: "MembershipRepository.findFirstAcceptedMembershipByUserId",
|
HAS_TEAM_PLAN: "MembershipRepository.findFirstAcceptedMembershipByUserId",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/payment";
|
|
||||||
import { useBookingSuccessRedirect } from "@calcom/features/bookings/lib/bookingSuccessRedirect";
|
import { useBookingSuccessRedirect } from "@calcom/features/bookings/lib/bookingSuccessRedirect";
|
||||||
|
import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/payment";
|
||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|||||||
@@ -115,7 +115,9 @@ export function getEditEventActions(context: BookingActionContext): ActionType[]
|
|||||||
icon: "clock",
|
icon: "clock",
|
||||||
label: t("reschedule_booking"),
|
label: t("reschedule_booking"),
|
||||||
href: `/reschedule/${booking.uid}${
|
href: `/reschedule/${booking.uid}${
|
||||||
booking.seatsReferences.length && isAttendee && seatReferenceUid ? `?seatReferenceUid=${seatReferenceUid}` : ""
|
booking.seatsReferences.length && isAttendee && seatReferenceUid
|
||||||
|
? `?seatReferenceUid=${seatReferenceUid}`
|
||||||
|
: ""
|
||||||
}`,
|
}`,
|
||||||
disabled:
|
disabled:
|
||||||
(isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling,
|
(isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling,
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ const useReroutingState = ({ isOpenDialog }: Pick<RerouteDialogProps, "isOpenDia
|
|||||||
const status = (() => {
|
const status = (() => {
|
||||||
if (!value) return ReroutingStatusEnum.REROUTING_NOT_INITIATED;
|
if (!value) return ReroutingStatusEnum.REROUTING_NOT_INITIATED;
|
||||||
if (value.error) return ReroutingStatusEnum.REROUTING_FAILED;
|
if (value.error) return ReroutingStatusEnum.REROUTING_FAILED;
|
||||||
if (!!value.newBooking) return ReroutingStatusEnum.REROUTING_COMPLETE;
|
if (value.newBooking) return ReroutingStatusEnum.REROUTING_COMPLETE;
|
||||||
return ReroutingStatusEnum.REROUTING_IN_PROGRESS;
|
return ReroutingStatusEnum.REROUTING_IN_PROGRESS;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const UserProfile = ({ user }: UserProfileProps) => {
|
|||||||
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const createEventType = trpc.viewer.eventTypes.heavy.create.useMutation();
|
const createEventType = trpc.viewer.eventTypesHeavy.create.useMutation();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import type { LocationObject } from "@calcom/app-store/locations";
|
|||||||
import { isConferencing as isConferencingApp } from "@calcom/app-store/utils";
|
import { isConferencing as isConferencingApp } from "@calcom/app-store/utils";
|
||||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||||
@@ -281,7 +281,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarUrl,
|
||||||
alreadyInstalled: appInstalls.some((install) => !Boolean(install.teamId) && install.userId === user.id),
|
alreadyInstalled: appInstalls.some((install) => !install.teamId && install.userId === user.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
const teamsWithIsAppInstalled = hasTeams
|
const teamsWithIsAppInstalled = hasTeams
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
|
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
|
||||||
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
||||||
|
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
||||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||||
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import type { AppGetServerSidePropsContext, AppPrisma, AppUser } from "@calcom/types/AppGetServerSideProps";
|
import type { AppGetServerSidePropsContext, AppPrisma, AppUser } from "@calcom/types/AppGetServerSideProps";
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
import { getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
||||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { shouldHideBrandingForTeamEvent } from "@calcom/features/profile/lib/hideBranding";
|
|
||||||
import { EventRepository } from "@calcom/features/eventtypes/repositories/EventRepository";
|
import { EventRepository } from "@calcom/features/eventtypes/repositories/EventRepository";
|
||||||
|
import { shouldHideBrandingForTeamEvent } from "@calcom/features/profile/lib/hideBranding";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||||
import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service";
|
import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { IS_STRIPE_ENABLED } from "@calcom/lib/constants";
|
import { IS_STRIPE_ENABLED } from "@calcom/lib/constants";
|
||||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import { CreationSource } from "@calcom/prisma/enums";
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
|
import { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync";
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import {
|
import {
|
||||||
getOrganizationSettings,
|
getOrganizationSettings,
|
||||||
@@ -9,7 +10,6 @@ import { getTeamWithMembers } from "@calcom/features/ee/teams/lib/queries";
|
|||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import { IS_CALCOM } from "@calcom/lib/constants";
|
import { IS_CALCOM } from "@calcom/lib/constants";
|
||||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||||
import { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync";
|
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import type { RegularBookingCreateResult } from "@calcom/features/bookings/lib/dto/types";
|
|
||||||
|
|
||||||
export type BookingResponse = RegularBookingCreateResult;
|
|
||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
updateMeetingTokenIfExpired,
|
updateMeetingTokenIfExpired,
|
||||||
} from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
} from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||||
|
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||||
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
|
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
|
||||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||||
import { CAL_VIDEO_MEETING_LINK_FOR_TESTING } from "@calcom/lib/constants";
|
import { CAL_VIDEO_MEETING_LINK_FOR_TESTING } from "@calcom/lib/constants";
|
||||||
import { isENVDev } from "@calcom/lib/env";
|
import { isENVDev } from "@calcom/lib/env";
|
||||||
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
|
||||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||||
@@ -210,7 +210,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
exp: epochTimeFourteenDaysAfter,
|
exp: epochTimeFourteenDaysAfter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionUserId = !!session?.user?.impersonatedBy ? session.user.impersonatedBy.id : session?.user.id;
|
const sessionUserId = session?.user?.impersonatedBy ? session.user.impersonatedBy.id : session?.user.id;
|
||||||
const isOrganizer = await checkIfUserIsHost({
|
const isOrganizer = await checkIfUserIsHost({
|
||||||
booking: {
|
booking: {
|
||||||
eventTypeId: bookingObj.eventType?.id,
|
eventTypeId: bookingObj.eventType?.id,
|
||||||
@@ -238,7 +238,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
videoReferencePassword,
|
videoReferencePassword,
|
||||||
sessionUserId
|
sessionUserId
|
||||||
);
|
);
|
||||||
if (!!meetingPassword) {
|
if (meetingPassword) {
|
||||||
bookingObj.references.forEach((bookRef) => {
|
bookingObj.references.forEach((bookRef) => {
|
||||||
bookRef.meetingPassword = meetingPassword;
|
bookRef.meetingPassword = meetingPassword;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { z } from "zod";
|
|||||||
|
|
||||||
import checkForMultiplePaymentApps from "@calcom/app-store/_utils/payments/checkForMultiplePaymentApps";
|
import checkForMultiplePaymentApps from "@calcom/app-store/_utils/payments/checkForMultiplePaymentApps";
|
||||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||||
|
import type { LocationObject } from "@calcom/app-store/locations";
|
||||||
import type { EventTypeAppSettingsComponentProps, EventTypeModel } from "@calcom/app-store/types";
|
import type { EventTypeAppSettingsComponentProps, EventTypeModel } from "@calcom/app-store/types";
|
||||||
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
||||||
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
||||||
@@ -15,7 +16,6 @@ import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
|||||||
import { getAppOnboardingUrl } from "@calcom/lib/apps/getAppOnboardingUrl";
|
import { getAppOnboardingUrl } from "@calcom/lib/apps/getAppOnboardingUrl";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { LocationObject } from "@calcom/app-store/locations";
|
|
||||||
import type { Team } from "@calcom/prisma/client";
|
import type { Team } from "@calcom/prisma/client";
|
||||||
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
@@ -177,7 +177,7 @@ const OnboardingPage = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [eventTypeGroups]);
|
}, [eventTypeGroups]);
|
||||||
|
|
||||||
const updateMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
const updateMutation = trpc.viewer.eventTypesHeavy.update.useMutation({
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
showToast(t("event_type_updated_successfully", { eventTypeTitle: data.eventType?.title }), "success");
|
showToast(t("event_type_updated_successfully", { eventTypeTitle: data.eventType?.title }), "success");
|
||||||
},
|
},
|
||||||
@@ -309,21 +309,20 @@ const OnboardingPage = ({
|
|||||||
handleSetUpLater={handleSetUpLater}
|
handleSetUpLater={handleSetUpLater}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentStep === AppOnboardingSteps.CONFIGURE_STEP &&
|
{currentStep === AppOnboardingSteps.CONFIGURE_STEP && eventTypeGroups && (
|
||||||
eventTypeGroups && (
|
<ConfigureStepCard
|
||||||
<ConfigureStepCard
|
slug={appMetadata.slug}
|
||||||
slug={appMetadata.slug}
|
categories={appMetadata.categories}
|
||||||
categories={appMetadata.categories}
|
credentialId={credentialId}
|
||||||
credentialId={credentialId}
|
userName={userName}
|
||||||
userName={userName}
|
loading={updateMutation.isPending}
|
||||||
loading={updateMutation.isPending}
|
formPortalRef={formPortalRef}
|
||||||
formPortalRef={formPortalRef}
|
setConfigureStep={setConfigureStep}
|
||||||
setConfigureStep={setConfigureStep}
|
eventTypeGroups={eventTypeGroups}
|
||||||
eventTypeGroups={eventTypeGroups}
|
handleSetUpLater={handleSetUpLater}
|
||||||
handleSetUpLater={handleSetUpLater}
|
isConferencing={isConferencing}
|
||||||
isConferencing={isConferencing}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||||
|
import { LastUsed, useLastUsed } from "@calcom/features/auth/lib/hooks/useLastUsed";
|
||||||
import { HOSTED_CAL_FEATURES, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
import { HOSTED_CAL_FEATURES, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { emailRegex } from "@calcom/lib/emailSchema";
|
import { emailRegex } from "@calcom/lib/emailSchema";
|
||||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
||||||
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
||||||
@@ -62,7 +62,7 @@ export default function Login({
|
|||||||
.string()
|
.string()
|
||||||
.min(1, `${t("error_required_field")}`)
|
.min(1, `${t("error_required_field")}`)
|
||||||
.regex(emailRegex, `${t("enter_valid_email")}`),
|
.regex(emailRegex, `${t("enter_valid_email")}`),
|
||||||
...(!!totpEmail ? {} : { password: z.string().min(1, `${t("error_required_field")}`) }),
|
...(totpEmail ? {} : { password: z.string().min(1, `${t("error_required_field")}`) }),
|
||||||
})
|
})
|
||||||
// Passthrough other fields like totpCode
|
// Passthrough other fields like totpCode
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { DataTableFilters, DataTableSegment } from "@calcom/features/data-table";
|
||||||
|
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||||
|
|
||||||
|
import type { RowData, BookingListingStatus } from "../types";
|
||||||
|
|
||||||
|
type BookingsCalendarViewProps = {
|
||||||
|
status: BookingListingStatus;
|
||||||
|
table: ReactTable<RowData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BookingsCalendar({ table }: BookingsCalendarViewProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DataTableFilters.FilterBar table={table} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DataTableFilters.ClearFiltersButton />
|
||||||
|
<DataTableSegment.SaveButton />
|
||||||
|
<DataTableSegment.Select />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||||
|
<EmptyScreen Icon="calendar" headline="Calendar view" description="Calendar view is coming soon." />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { DataTableWrapper, DataTableFilters, DataTableSegment } from "@calcom/features/data-table";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||||
|
|
||||||
|
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||||
|
|
||||||
|
import type { RowData, BookingListingStatus } from "../types";
|
||||||
|
|
||||||
|
const descriptionByStatus: Record<BookingListingStatus, string> = {
|
||||||
|
upcoming: "upcoming_bookings",
|
||||||
|
recurring: "recurring_bookings",
|
||||||
|
past: "past_bookings",
|
||||||
|
cancelled: "cancelled_bookings",
|
||||||
|
unconfirmed: "unconfirmed_bookings",
|
||||||
|
};
|
||||||
|
|
||||||
|
type BookingsListViewProps = {
|
||||||
|
status: BookingListingStatus;
|
||||||
|
table: ReactTable<RowData>;
|
||||||
|
isPending: boolean;
|
||||||
|
totalRowCount?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BookingsList({ status, table, isPending, totalRowCount }: BookingsListViewProps) {
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTableWrapper
|
||||||
|
className="mb-6"
|
||||||
|
table={table}
|
||||||
|
testId={`${status}-bookings`}
|
||||||
|
bodyTestId="bookings"
|
||||||
|
headerClassName="hidden"
|
||||||
|
isPending={isPending}
|
||||||
|
totalRowCount={totalRowCount}
|
||||||
|
variant="compact"
|
||||||
|
paginationMode="standard"
|
||||||
|
ToolbarLeft={
|
||||||
|
<>
|
||||||
|
<DataTableFilters.FilterBar table={table} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
ToolbarRight={
|
||||||
|
<>
|
||||||
|
<DataTableFilters.ClearFiltersButton />
|
||||||
|
<DataTableSegment.SaveButton />
|
||||||
|
<DataTableSegment.Select />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
LoaderView={<SkeletonLoader />}
|
||||||
|
EmptyView={
|
||||||
|
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||||
|
<EmptyScreen
|
||||||
|
Icon="calendar"
|
||||||
|
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
||||||
|
description={t("no_status_bookings_yet_description", {
|
||||||
|
status: t(status).toLowerCase(),
|
||||||
|
description: t(descriptionByStatus[status]),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
|
|
||||||
|
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
|
||||||
|
export type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
||||||
|
|
||||||
|
export type RecurringInfo = {
|
||||||
|
recurringEventId: string | null;
|
||||||
|
count: number;
|
||||||
|
firstDate: Date | null;
|
||||||
|
bookings: { [key: string]: Date[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RowData =
|
||||||
|
| {
|
||||||
|
type: "data";
|
||||||
|
booking: BookingOutput;
|
||||||
|
isToday: boolean;
|
||||||
|
recurringInfo?: RecurringInfo;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "today" | "next";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BookingListingStatus = (typeof validStatuses)[number];
|
||||||
@@ -121,13 +121,22 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
: eventTypeRaw.users;
|
: eventTypeRaw.users;
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length) {
|
if (!eventTypeRaw.users.length) {
|
||||||
if (!eventTypeRaw.owner)
|
if (!eventTypeRaw.owner) {
|
||||||
return {
|
if (bookingInfoRaw.user) {
|
||||||
notFound: true,
|
eventTypeRaw.users.push({
|
||||||
} as const;
|
...bookingInfoRaw.user,
|
||||||
eventTypeRaw.users.push({
|
hideBranding: false,
|
||||||
...eventTypeRaw.owner,
|
theme: null,
|
||||||
});
|
brandColor: null,
|
||||||
|
darkBrandColor: null,
|
||||||
|
isPlatformManaged: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return { notFound: true } as const;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eventTypeRaw.users.push({ ...eventTypeRaw.owner });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventType = {
|
const eventType = {
|
||||||
|
|||||||
@@ -24,13 +24,9 @@ import {
|
|||||||
import { Price } from "@calcom/features/bookings/components/event-meta/Price";
|
import { Price } from "@calcom/features/bookings/components/event-meta/Price";
|
||||||
import { getCalendarLinks, CalendarLinkType } from "@calcom/features/bookings/lib/getCalendarLinks";
|
import { getCalendarLinks, CalendarLinkType } from "@calcom/features/bookings/lib/getCalendarLinks";
|
||||||
import { RATING_OPTIONS, validateRating } from "@calcom/features/bookings/lib/rating";
|
import { RATING_OPTIONS, validateRating } from "@calcom/features/bookings/lib/rating";
|
||||||
import {
|
|
||||||
SMS_REMINDER_NUMBER_FIELD,
|
|
||||||
SystemField,
|
|
||||||
TITLE_FIELD,
|
|
||||||
} from "@calcom/lib/bookings/SystemField";
|
|
||||||
import type { nameObjectSchema } from "@calcom/features/eventtypes/lib/eventNaming";
|
import type { nameObjectSchema } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||||
import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming";
|
import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming";
|
||||||
|
import { SMS_REMINDER_NUMBER_FIELD, SystemField, TITLE_FIELD } from "@calcom/lib/bookings/SystemField";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { formatToLocalizedDate, formatToLocalizedTime, formatToLocalizedTimezone } from "@calcom/lib/dayjs";
|
import { formatToLocalizedDate, formatToLocalizedTime, formatToLocalizedTimezone } from "@calcom/lib/dayjs";
|
||||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||||
@@ -771,7 +767,6 @@ export default function Success(props: PageProps) {
|
|||||||
<Fragment key={field.name}>
|
<Fragment key={field.name}>
|
||||||
<div
|
<div
|
||||||
className="text-emphasis mt-4 font-medium"
|
className="text-emphasis mt-4 font-medium"
|
||||||
|
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: markdownToSafeHTML(label),
|
__html: markdownToSafeHTML(label),
|
||||||
}}
|
}}
|
||||||
@@ -1084,7 +1079,8 @@ export default function Success(props: PageProps) {
|
|||||||
<span className="underline">
|
<span className="underline">
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://cal.com/blog/google-s-new-spam-policy-may-be-affecting-your-invitations" rel="noreferrer">
|
href="https://cal.com/blog/google-s-new-spam-policy-may-be-affecting-your-invitations"
|
||||||
|
rel="noreferrer">
|
||||||
{t("resolve")}
|
{t("resolve")}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+28
-74
@@ -2,57 +2,38 @@
|
|||||||
|
|
||||||
import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } from "@tanstack/react-table";
|
import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } from "@tanstack/react-table";
|
||||||
import { useSearchParams, usePathname } from "next/navigation";
|
import { useSearchParams, usePathname } from "next/navigation";
|
||||||
import { useMemo, useRef } from "react";
|
import { createParser, useQueryState } from "nuqs";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import {
|
import {
|
||||||
useDataTable,
|
|
||||||
DataTableProvider,
|
DataTableProvider,
|
||||||
DataTableWrapper,
|
type SystemFilterSegment,
|
||||||
DataTableFilters,
|
useDataTable,
|
||||||
DataTableSegment,
|
|
||||||
ColumnFilterType,
|
ColumnFilterType,
|
||||||
useFilterValue,
|
useFilterValue,
|
||||||
ZMultiSelectFilterValue,
|
ZMultiSelectFilterValue,
|
||||||
ZDateRangeFilterValue,
|
ZDateRangeFilterValue,
|
||||||
ZTextFilterValue,
|
ZTextFilterValue,
|
||||||
type SystemFilterSegment,
|
|
||||||
} from "@calcom/features/data-table";
|
} from "@calcom/features/data-table";
|
||||||
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
|
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||||
import { Alert } from "@calcom/ui/components/alert";
|
import { Alert } from "@calcom/ui/components/alert";
|
||||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
|
||||||
import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation";
|
import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation";
|
||||||
import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
||||||
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
|
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
|
||||||
import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton";
|
import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton";
|
||||||
|
|
||||||
import BookingListItem from "@components/booking/BookingListItem";
|
import BookingListItem from "@components/booking/BookingListItem";
|
||||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
|
||||||
|
|
||||||
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
||||||
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
import type { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
|
||||||
type BookingListingStatus = (typeof validStatuses)[number];
|
import { BookingsCalendar } from "../components/BookingsCalendar";
|
||||||
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
import { BookingsList } from "../components/BookingsList";
|
||||||
|
import type { RowData, BookingOutput } from "../types";
|
||||||
type RecurringInfo = {
|
|
||||||
recurringEventId: string | null;
|
|
||||||
count: number;
|
|
||||||
firstDate: Date | null;
|
|
||||||
bookings: { [key: string]: Date[] };
|
|
||||||
};
|
|
||||||
|
|
||||||
const descriptionByStatus: Record<BookingListingStatus, string> = {
|
|
||||||
upcoming: "upcoming_bookings",
|
|
||||||
recurring: "recurring_bookings",
|
|
||||||
past: "past_bookings",
|
|
||||||
cancelled: "cancelled_bookings",
|
|
||||||
unconfirmed: "unconfirmed_bookings",
|
|
||||||
};
|
|
||||||
|
|
||||||
type BookingsProps = {
|
type BookingsProps = {
|
||||||
status: (typeof validStatuses)[number];
|
status: (typeof validStatuses)[number];
|
||||||
@@ -103,21 +84,18 @@ export default function Bookings(props: BookingsProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowData =
|
const viewParser = createParser({
|
||||||
| {
|
parse: (value: string) => {
|
||||||
type: "data";
|
if (value === "calendar") return "calendar";
|
||||||
booking: BookingOutput;
|
return "list";
|
||||||
isToday: boolean;
|
},
|
||||||
recurringInfo?: RecurringInfo;
|
serialize: (value: "list" | "calendar") => value,
|
||||||
}
|
});
|
||||||
| {
|
|
||||||
type: "today" | "next";
|
|
||||||
};
|
|
||||||
|
|
||||||
function BookingsContent({ status, permissions }: BookingsProps) {
|
function BookingsContent({ status, permissions }: BookingsProps) {
|
||||||
|
const [view] = useQueryState("view", viewParser.withDefault("list"));
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const user = useMeQuery().data;
|
const user = useMeQuery().data;
|
||||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Generate dynamic tabs that preserve query parameters
|
// Generate dynamic tabs that preserve query parameters
|
||||||
@@ -411,6 +389,9 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
|||||||
getFacetedUniqueValues,
|
getFacetedUniqueValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPending = query.isPending;
|
||||||
|
const totalRowCount = query.data?.totalCount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row flex-wrap justify-between">
|
<div className="flex flex-row flex-wrap justify-between">
|
||||||
@@ -431,43 +412,16 @@ function BookingsContent({ status, permissions }: BookingsProps) {
|
|||||||
{!!bookingsToday.length && status === "upcoming" && (
|
{!!bookingsToday.length && status === "upcoming" && (
|
||||||
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
||||||
)}
|
)}
|
||||||
<DataTableWrapper
|
{view === "list" ? (
|
||||||
className="mb-6"
|
<BookingsList
|
||||||
tableContainerRef={tableContainerRef}
|
status={status}
|
||||||
table={table}
|
table={table}
|
||||||
testId={`${status}-bookings`}
|
isPending={isPending}
|
||||||
bodyTestId="bookings"
|
totalRowCount={totalRowCount}
|
||||||
headerClassName="hidden"
|
/>
|
||||||
isPending={query.isPending}
|
) : (
|
||||||
totalRowCount={query.data?.totalCount}
|
<BookingsCalendar status={status} table={table} />
|
||||||
variant="compact"
|
)}
|
||||||
paginationMode="standard"
|
|
||||||
ToolbarLeft={
|
|
||||||
<>
|
|
||||||
<DataTableFilters.FilterBar table={table} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
ToolbarRight={
|
|
||||||
<>
|
|
||||||
<DataTableFilters.ClearFiltersButton />
|
|
||||||
<DataTableSegment.SaveButton />
|
|
||||||
<DataTableSegment.Select />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
LoaderView={<SkeletonLoader />}
|
|
||||||
EmptyView={
|
|
||||||
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
|
||||||
<EmptyScreen
|
|
||||||
Icon="calendar"
|
|
||||||
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
|
||||||
description={t("no_status_bookings_yet_description", {
|
|
||||||
status: t(status).toLowerCase(),
|
|
||||||
description: t(descriptionByStatus[status]),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -271,7 +271,7 @@ export const InfiniteEventTypeList = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const setHiddenMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
const setHiddenMutation = trpc.viewer.eventTypesHeavy.update.useMutation({
|
||||||
onMutate: async (data) => {
|
onMutate: async (data) => {
|
||||||
await utils.viewer.eventTypes.getEventTypesFromGroup.cancel();
|
await utils.viewer.eventTypes.getEventTypesFromGroup.cancel();
|
||||||
const previousValue = utils.viewer.eventTypes.getEventTypesFromGroup.getInfiniteData({
|
const previousValue = utils.viewer.eventTypes.getEventTypesFromGroup.getInfiniteData({
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export default function Signup({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const constructCallBackIfUrlNotPresent = () => {
|
const constructCallBackIfUrlNotPresent = () => {
|
||||||
if (!!isPlatformUser) {
|
if (isPlatformUser) {
|
||||||
return `${WEBAPP_URL}/${gettingStartedWithPlatform}?from=signup`;
|
return `${WEBAPP_URL}/${gettingStartedWithPlatform}?from=signup`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ export default function Signup({
|
|||||||
const constructCallBackUrl = () => {
|
const constructCallBackUrl = () => {
|
||||||
const callbackUrlSearchParams = searchParams?.get("callbackUrl");
|
const callbackUrlSearchParams = searchParams?.get("callbackUrl");
|
||||||
|
|
||||||
return !!callbackUrlSearchParams
|
return callbackUrlSearchParams
|
||||||
? constructCallBackIfUrlPresent()
|
? constructCallBackIfUrlPresent()
|
||||||
: constructCallBackIfUrlNotPresent();
|
: constructCallBackIfUrlNotPresent();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable */
|
||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
@@ -223,8 +224,6 @@ const nextConfig = (phase) => {
|
|||||||
"@calcom/dayjs",
|
"@calcom/dayjs",
|
||||||
"@calcom/emails",
|
"@calcom/emails",
|
||||||
"@calcom/embed-core",
|
"@calcom/embed-core",
|
||||||
"@calcom/embed-react",
|
|
||||||
"@calcom/embed-snippet",
|
|
||||||
"@calcom/features",
|
"@calcom/features",
|
||||||
"@calcom/lib",
|
"@calcom/lib",
|
||||||
"@calcom/prisma",
|
"@calcom/prisma",
|
||||||
@@ -406,7 +405,7 @@ const nextConfig = (phase) => {
|
|||||||
}, */
|
}, */
|
||||||
];
|
];
|
||||||
|
|
||||||
if (Boolean(process.env.NEXT_PUBLIC_API_V2_URL)) {
|
if (process.env.NEXT_PUBLIC_API_V2_URL) {
|
||||||
afterFiles.push({
|
afterFiles.push({
|
||||||
source: "/api/v2/:path*",
|
source: "/api/v2/:path*",
|
||||||
destination: `${process.env.NEXT_PUBLIC_API_V2_URL}/:path*`,
|
destination: `${process.env.NEXT_PUBLIC_API_V2_URL}/:path*`,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.8.1",
|
"version": "5.8.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -201,6 +201,7 @@
|
|||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"turbo": "^2.5.5",
|
||||||
"typescript": "^5.9.0-beta"
|
"typescript": "^5.9.0-beta"
|
||||||
},
|
},
|
||||||
"nextBundleAnalysis": {
|
"nextBundleAnalysis": {
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import type { NextApiRequest } from "next";
|
|||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { getRegularBookingService } from "@calcom/features/bookings/di/RegularBookingService.container";
|
import { getRegularBookingService } from "@calcom/features/bookings/di/RegularBookingService.container";
|
||||||
import { BotDetectionService } from "@calcom/features/bot-detection";
|
import { BotDetectionService } from "@calcom/features/bot-detection";
|
||||||
|
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
||||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import getIP from "@calcom/lib/getIP";
|
import getIP from "@calcom/lib/getIP";
|
||||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||||
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
|
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { CreationSource } from "@calcom/prisma/enums";
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user