Files
calendar/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts
T
Benny JooGitHubbenny@cal.com <sldisek783@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

286 lines
8.4 KiB
TypeScript

import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { scheduleNoShowTriggers } from "@calcom/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers";
import {
type EventTypeBrandingData,
getEventTypeService,
} from "@calcom/features/eventtypes/di/EventTypeService.container";
import { getTranslation } from "@calcom/i18n/server";
import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj";
import logger from "@calcom/lib/logger";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { bookingMetadataSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { TRPCError } from "@trpc/server";
import type { TConnectAndJoinInputSchema } from "./connectAndJoin.schema";
type Options = {
ctx: {
user: NonNullable<TrpcSessionUser>;
};
input: TConnectAndJoinInputSchema;
};
export const Handler = async ({ ctx, input }: Options) => {
const { token } = input;
const { user } = ctx;
const isLoggedInUserPartOfOrg = !!user.organization.id;
if (!isLoggedInUserPartOfOrg) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Logged in user is not member of Organization" });
}
const tOrganizer = await getTranslation(user?.locale ?? "en", "common");
const userBrandingInfo = await prisma.user.findUnique({
where: { id: user.id },
select: {
hideBranding: true,
profiles: {
select: {
organization: { select: { hideBranding: true } },
},
},
},
});
const instantMeetingToken = await prisma.instantMeetingToken.findUnique({
select: {
expires: true,
teamId: true,
booking: {
select: {
id: true,
status: true,
user: {
select: {
id: true,
},
},
},
},
},
where: {
token,
team: {
members: {
some: {
userId: user.id,
accepted: true,
},
},
},
},
});
// Check if logged in user belong to current team
if (!instantMeetingToken) {
throw new TRPCError({ code: "BAD_REQUEST", message: "token_not_found" });
}
if (!instantMeetingToken.booking?.id) {
throw new TRPCError({ code: "FORBIDDEN", message: "token_invalid_expired" });
}
// Check if token has not expired
if (instantMeetingToken.expires < new Date()) {
throw new TRPCError({ code: "BAD_REQUEST", message: "token_invalid_expired" });
}
// Check if Booking is already accepted by any other user
let isBookingAlreadyAcceptedBySomeoneElse = false;
if (
instantMeetingToken.booking.status === BookingStatus.ACCEPTED &&
instantMeetingToken.booking?.user?.id !== user.id
) {
isBookingAlreadyAcceptedBySomeoneElse = true;
}
// Update User in Booking
const updatedBooking = await prisma.booking.update({
where: {
id: instantMeetingToken.booking.id,
},
data: {
...(isBookingAlreadyAcceptedBySomeoneElse
? { status: BookingStatus.ACCEPTED }
: {
status: BookingStatus.ACCEPTED,
user: {
connect: {
id: user.id,
},
},
}),
},
select: {
title: true,
description: true,
customInputs: true,
startTime: true,
references: true,
endTime: true,
attendees: true,
eventTypeId: true,
responses: true,
metadata: true,
eventType: {
select: {
id: true,
owner: true,
teamId: true,
title: true,
slug: true,
requiresConfirmation: true,
currency: true,
length: true,
description: true,
price: true,
bookingFields: true,
disableGuests: true,
metadata: true,
hideOrganizerEmail: true,
customInputs: true,
parentId: true,
customReplyToEmail: true,
team: {
select: {
id: true,
name: true,
hideBranding: true,
parent: { select: { hideBranding: true } },
},
},
},
},
location: true,
userId: true,
id: true,
uid: true,
status: true,
},
});
const locationVideoCallUrl = bookingMetadataSchema.parse(updatedBooking.metadata || {})?.videoCallUrl;
if (!locationVideoCallUrl) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "meeting_url_not_found" });
}
const videoCallReference = updatedBooking.references.find((reference) => reference.type.includes("_video"));
const videoCallData = {
type: videoCallReference?.type,
id: videoCallReference?.meetingId,
password: videoCallReference?.meetingPassword,
url: videoCallReference?.meetingUrl,
};
const { eventType } = updatedBooking;
// Send Scheduled Email to Organizer and Attendees
const translations = new Map();
const attendeesListPromises = updatedBooking.attendees.map(async (attendee) => {
const locale = attendee.locale ?? "en";
let translate = translations.get(locale);
if (!translate) {
translate = await getTranslation(locale, "common");
translations.set(locale, translate);
}
return {
name: attendee.name,
email: attendee.email,
timeZone: attendee.timeZone,
language: {
translate,
locale,
},
};
});
const attendeesList = await Promise.all(attendeesListPromises);
const evt: CalendarEvent = {
type: updatedBooking?.eventType?.slug as string,
title: updatedBooking.title,
description: updatedBooking.description,
...getCalEventResponses({
bookingFields: eventType?.bookingFields ?? null,
booking: updatedBooking,
}),
customInputs: isPrismaObjOrUndefined(updatedBooking.customInputs),
startTime: updatedBooking.startTime.toISOString(),
endTime: updatedBooking.endTime.toISOString(),
organizer: {
email: user.email,
name: user.name || "Unnamed",
username: user.username || undefined,
timeZone: user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat),
language: { translate: tOrganizer, locale: user.locale ?? "en" },
},
hideOrganizerEmail: updatedBooking.eventType?.hideOrganizerEmail,
attendees: attendeesList,
location: updatedBooking.location ?? "",
uid: updatedBooking.uid,
requiresConfirmation: false,
eventTypeId: eventType?.id,
videoCallData,
customReplyToEmail: eventType?.customReplyToEmail,
organizationId: user?.organizationId ?? null,
team: updatedBooking.eventType?.team
? {
name: updatedBooking.eventType.team.name,
id: updatedBooking.eventType.team.id,
members: [],
}
: undefined,
hideBranding: updatedBooking.eventTypeId
? await getEventTypeService().shouldHideBrandingForEventType(updatedBooking.eventTypeId, {
team: updatedBooking.eventType?.team
? {
hideBranding: updatedBooking.eventType.team.hideBranding,
parent: updatedBooking.eventType.team.parent,
}
: null,
owner: {
id: user.id,
hideBranding: userBrandingInfo?.hideBranding ?? null,
profiles: userBrandingInfo?.profiles ?? [],
},
} satisfies EventTypeBrandingData)
: false,
};
const eventTypeMetadata = EventTypeMetaDataSchema.parse(updatedBooking?.eventType?.metadata);
await sendScheduledEmailsAndSMS(
{
...evt,
},
undefined,
false,
false,
eventTypeMetadata
);
await scheduleNoShowTriggers({
booking: {
startTime: updatedBooking.startTime,
id: updatedBooking.id,
location: updatedBooking.location,
uid: updatedBooking.uid,
},
triggerForUser: !eventType?.teamId || (eventType?.teamId && eventType?.parentId),
organizerUser: { id: user.id },
eventTypeId: eventType?.id ?? null,
teamId: eventType?.teamId,
orgId: user.organizationId,
});
return { isBookingAlreadyAcceptedBySomeoneElse, meetingUrl: locationVideoCallUrl };
};