mirror of
https://github.com/hanzoai/calendar.git
synced 2026-09-24 22:42:24 +00:00
* 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: [email protected] <[email protected]> * 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>
312 lines
9.6 KiB
TypeScript
312 lines
9.6 KiB
TypeScript
import {
|
|
generateGuestMeetingTokenFromOwnerMeetingToken,
|
|
setEnableRecordingUIAndUserIdForOrganizer,
|
|
updateMeetingTokenIfExpired,
|
|
} from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
|
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
|
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
|
|
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
|
import { CAL_VIDEO_MEETING_LINK_FOR_TESTING } from "@calcom/lib/constants";
|
|
import { isENVDev } from "@calcom/lib/env";
|
|
import prisma from "@calcom/prisma";
|
|
import MarkdownIt from "markdown-it";
|
|
import type { GetServerSidePropsContext } from "next";
|
|
|
|
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
|
|
|
type CalVideoSettings = {
|
|
disableRecordingForGuests: boolean;
|
|
disableRecordingForOrganizer: boolean;
|
|
enableAutomaticTranscription: boolean;
|
|
enableAutomaticRecordingForOrganizer: boolean;
|
|
disableTranscriptionForGuests: boolean;
|
|
disableTranscriptionForOrganizer: boolean;
|
|
requireEmailForGuests: boolean;
|
|
};
|
|
|
|
const shouldEnableRecordButton = ({
|
|
hasTeamPlan,
|
|
calVideoSettings,
|
|
isOrganizer,
|
|
}: {
|
|
hasTeamPlan: boolean;
|
|
calVideoSettings?: CalVideoSettings | null;
|
|
isOrganizer: boolean;
|
|
}) => {
|
|
if (!hasTeamPlan) return false;
|
|
if (!calVideoSettings) return true;
|
|
|
|
if (isOrganizer) {
|
|
return !calVideoSettings.disableRecordingForOrganizer;
|
|
}
|
|
|
|
return !calVideoSettings.disableRecordingForGuests;
|
|
};
|
|
|
|
const shouldEnableAutomaticTranscription = ({
|
|
hasTeamPlan,
|
|
calVideoSettings,
|
|
}: {
|
|
hasTeamPlan: boolean;
|
|
calVideoSettings?: CalVideoSettings | null;
|
|
}) => {
|
|
if (!hasTeamPlan) return false;
|
|
if (!calVideoSettings) return false;
|
|
|
|
return !!calVideoSettings.enableAutomaticTranscription;
|
|
};
|
|
|
|
const shouldEnableAutomaticRecording = ({
|
|
hasTeamPlan,
|
|
calVideoSettings,
|
|
isOrganizer,
|
|
}: {
|
|
hasTeamPlan: boolean;
|
|
calVideoSettings?: CalVideoSettings | null;
|
|
isOrganizer: boolean;
|
|
}) => {
|
|
if (!hasTeamPlan || !isOrganizer) return false;
|
|
if (!calVideoSettings) return false;
|
|
|
|
return !!calVideoSettings.enableAutomaticRecordingForOrganizer;
|
|
};
|
|
|
|
const shouldEnableTranscriptionButton = ({
|
|
hasTeamPlan,
|
|
calVideoSettings,
|
|
isOrganizer,
|
|
}: {
|
|
hasTeamPlan: boolean;
|
|
calVideoSettings?: CalVideoSettings | null;
|
|
isOrganizer: boolean;
|
|
}) => {
|
|
if (!hasTeamPlan) return false;
|
|
if (!calVideoSettings) return true;
|
|
|
|
if (isOrganizer) {
|
|
return !calVideoSettings.disableTranscriptionForOrganizer;
|
|
}
|
|
|
|
return !calVideoSettings.disableTranscriptionForGuests;
|
|
};
|
|
|
|
const checkIfUserIsHost = async ({
|
|
booking,
|
|
sessionUserId,
|
|
}: {
|
|
booking: {
|
|
user: { id: number } | null;
|
|
eventTypeId: number | undefined;
|
|
};
|
|
sessionUserId?: number;
|
|
}) => {
|
|
if (!sessionUserId) return false;
|
|
|
|
if (!booking.eventTypeId) {
|
|
return booking.user?.id === sessionUserId;
|
|
}
|
|
|
|
const eventTypeRepo = new EventTypeRepository(prisma);
|
|
const eventType = await eventTypeRepo.findByIdWithUserAccess({
|
|
id: booking.eventTypeId,
|
|
userId: sessionUserId,
|
|
});
|
|
|
|
// If eventType exists, it means user is either owner, host or user
|
|
return !!eventType;
|
|
};
|
|
|
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|
const { req } = context;
|
|
|
|
const bookingRepo = new BookingRepository(prisma);
|
|
const booking = await bookingRepo.findBookingIncludeCalVideoSettingsAndReferences({
|
|
bookingUid: context.query.uid as string,
|
|
});
|
|
|
|
// Below if block is for local testing purposes only
|
|
// STARTS------------------------------------------------------------------------------
|
|
if (booking?.references[0] && isENVDev && CAL_VIDEO_MEETING_LINK_FOR_TESTING) {
|
|
// meetingUrl is `null` in dev env, so setting a dummy meetingUrl (it's a past but real meeting link in production env)
|
|
booking.references[0].meetingUrl = CAL_VIDEO_MEETING_LINK_FOR_TESTING;
|
|
}
|
|
// ENDS--------------------------------------------------------------------------------
|
|
|
|
if (!booking || booking.references.length === 0 || !booking.references[0].meetingUrl) {
|
|
return {
|
|
redirect: {
|
|
destination: "/video/no-meeting-found",
|
|
permanent: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
const hasTeamPlan = booking.user?.id
|
|
? await prisma.membership.findFirst({
|
|
where: {
|
|
userId: booking.user.id,
|
|
team: {
|
|
slug: {
|
|
not: null,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
: false;
|
|
|
|
const userRepo = new UserRepository(prisma);
|
|
const profile = booking.user
|
|
? (
|
|
await userRepo.enrichUserWithItsProfile({
|
|
user: booking.user,
|
|
})
|
|
).profile
|
|
: null;
|
|
|
|
const organizationRepository = { findCalVideoLogoByOrgId: async (_args: { id: number }) => null as string | null };
|
|
|
|
const calVideoLogo = profile?.organization
|
|
? await organizationRepository.findCalVideoLogoByOrgId({ id: profile.organization.id })
|
|
: null;
|
|
|
|
//daily.co calls have a 14 days exit buffer when a user enters a call when it's not available it will trigger the modals
|
|
const now = new Date();
|
|
const exitDate = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
|
|
//find out if the meeting is in the past
|
|
const isPast = booking?.endTime <= exitDate;
|
|
const testingUid = CAL_VIDEO_MEETING_LINK_FOR_TESTING?.split("/").pop();
|
|
const isTestingLink = booking?.uid === testingUid;
|
|
|
|
if (isPast && !isTestingLink) {
|
|
return {
|
|
redirect: {
|
|
destination: `/video/meeting-ended/${booking?.uid}`,
|
|
permanent: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
const bookingObj = Object.assign({}, booking, {
|
|
startTime: booking.startTime.toString(),
|
|
endTime: booking.endTime.toString(),
|
|
});
|
|
|
|
const session = await getServerSession({ req });
|
|
|
|
const oldVideoReference = getCalVideoReference(bookingObj.references);
|
|
|
|
const endTime = new Date(booking.endTime);
|
|
const fourteenDaysAfter = new Date(endTime.getTime() + 14 * 24 * 60 * 60 * 1000);
|
|
const epochTimeFourteenDaysAfter = Math.floor(fourteenDaysAfter.getTime() / 1000);
|
|
|
|
const videoReferencePassword = await updateMeetingTokenIfExpired({
|
|
bookingReferenceId: oldVideoReference.id,
|
|
roomName: oldVideoReference.uid,
|
|
meetingToken: oldVideoReference.meetingPassword,
|
|
exp: epochTimeFourteenDaysAfter,
|
|
});
|
|
|
|
const sessionUserId = session?.user?.impersonatedBy ? session.user.impersonatedBy.id : session?.user.id;
|
|
const sessionUserEmail = session?.user?.email;
|
|
const isOrganizer = await checkIfUserIsHost({
|
|
booking: {
|
|
eventTypeId: bookingObj.eventType?.id,
|
|
user: bookingObj.user,
|
|
},
|
|
sessionUserId,
|
|
});
|
|
|
|
const isAttendee = sessionUserEmail
|
|
? (bookingObj.attendees?.some(
|
|
(attendee) => attendee.email.toLowerCase() === sessionUserEmail.toLowerCase()
|
|
) ?? false)
|
|
: false;
|
|
|
|
// set meetingPassword for guests
|
|
if (!isOrganizer) {
|
|
const userIdForToken = sessionUserId;
|
|
|
|
const guestMeetingPassword = await generateGuestMeetingTokenFromOwnerMeetingToken({
|
|
meetingToken: videoReferencePassword,
|
|
userId: userIdForToken,
|
|
});
|
|
|
|
bookingObj.references.forEach((bookRef) => {
|
|
bookRef.meetingPassword = guestMeetingPassword;
|
|
});
|
|
}
|
|
// Only for backward compatibility and setting user id in participants for organizer
|
|
else {
|
|
const meetingPassword = await setEnableRecordingUIAndUserIdForOrganizer(
|
|
oldVideoReference.id,
|
|
videoReferencePassword,
|
|
sessionUserId
|
|
);
|
|
if (meetingPassword) {
|
|
bookingObj.references.forEach((bookRef) => {
|
|
bookRef.meetingPassword = meetingPassword;
|
|
});
|
|
}
|
|
}
|
|
|
|
const videoReference = getCalVideoReference(bookingObj.references);
|
|
|
|
const showRecordingButton = shouldEnableRecordButton({
|
|
hasTeamPlan: !!hasTeamPlan,
|
|
calVideoSettings: bookingObj.eventType?.calVideoSettings,
|
|
isOrganizer,
|
|
});
|
|
|
|
const enableAutomaticTranscription = shouldEnableAutomaticTranscription({
|
|
hasTeamPlan: !!hasTeamPlan,
|
|
calVideoSettings: bookingObj.eventType?.calVideoSettings,
|
|
});
|
|
|
|
const enableAutomaticRecordingForOrganizer = shouldEnableAutomaticRecording({
|
|
hasTeamPlan: !!hasTeamPlan,
|
|
calVideoSettings: bookingObj.eventType?.calVideoSettings,
|
|
isOrganizer,
|
|
});
|
|
|
|
const showTranscriptionButton = shouldEnableTranscriptionButton({
|
|
hasTeamPlan: !!hasTeamPlan,
|
|
calVideoSettings: bookingObj.eventType?.calVideoSettings,
|
|
isOrganizer,
|
|
});
|
|
|
|
return {
|
|
props: {
|
|
meetingUrl: videoReference.meetingUrl ?? "",
|
|
...(typeof videoReference.meetingPassword === "string" && {
|
|
meetingPassword: videoReference.meetingPassword,
|
|
}),
|
|
booking: {
|
|
...bookingObj,
|
|
...(bookingObj.description && { description: md.render(bookingObj.description) }),
|
|
user: bookingObj.user
|
|
? {
|
|
...bookingObj.user,
|
|
organization: profile?.organization,
|
|
}
|
|
: bookingObj.user,
|
|
},
|
|
hasTeamPlan: !!hasTeamPlan,
|
|
calVideoLogo,
|
|
loggedInUserName: sessionUserId ? session?.user?.name : undefined,
|
|
showRecordingButton,
|
|
enableAutomaticTranscription,
|
|
enableAutomaticRecordingForOrganizer,
|
|
showTranscriptionButton,
|
|
rediectAttendeeToOnExit: isOrganizer
|
|
? undefined
|
|
: bookingObj.eventType?.calVideoSettings?.redirectUrlOnExit,
|
|
overrideName: Array.isArray(context.query.name) ? context.query.name[0] : context.query.name,
|
|
requireEmailForGuests: bookingObj.eventType?.calVideoSettings?.requireEmailForGuests ?? false,
|
|
isLoggedInUserPartOfMeeting: isAttendee || isOrganizer,
|
|
},
|
|
};
|
|
}
|