diff --git a/apps/web/pages/video/[uid].tsx b/apps/web/pages/video/[uid].tsx index c3e84c705f..5075088728 100644 --- a/apps/web/pages/video/[uid].tsx +++ b/apps/web/pages/video/[uid].tsx @@ -3,10 +3,9 @@ import { NextPageContext } from "next"; import { getSession } from "next-auth/react"; import Head from "next/head"; import Link from "next/link"; -import { useRouter } from "next/router"; import { useEffect } from "react"; -import { WEBSITE_URL, SEO_IMG_OGIMG_VIDEO } from "@calcom/lib/constants"; +import { SEO_IMG_OGIMG_VIDEO, WEBSITE_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import { inferSSRProps } from "@calcom/types/inferSSRProps"; @@ -15,96 +14,38 @@ export type JoinCallPageProps = inferSSRProps; export default function JoinCall(props: JoinCallPageProps) { const { t } = useLocale(); - const session = props.session; - const router = useRouter(); - - //if no booking redirectis to the 404 page - const emptyBooking = props.booking === null; - - //daily.co calls have a 60 minute 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() - 60 * 60 * 1000); - - //find out if the meeting is in the past - const isPast = new Date(props.booking?.endTime || "") <= exitDate; - const meetingUnavailable = isPast == true; useEffect(() => { - if (emptyBooking) { - router.push("/video/no-meeting-found"); - } - - if (isPast) { - router.push(`/video/meeting-ended/${props.booking?.uid}`); - } - }); - - useEffect(() => { - if (!meetingUnavailable && !emptyBooking && session?.userid !== props.booking.user?.id) { - const callFrame = DailyIframe.createFrame({ - theme: { - colors: { - accent: "#FFF", - accentText: "#111111", - background: "#111111", - backgroundAccent: "#111111", - baseText: "#FFF", - border: "#292929", - mainAreaBg: "#111111", - mainAreaBgAccent: "#111111", - mainAreaText: "#FFF", - supportiveText: "#FFF", - }, + const callFrame = DailyIframe.createFrame({ + theme: { + colors: { + accent: "#FFF", + accentText: "#111111", + background: "#111111", + backgroundAccent: "#111111", + baseText: "#FFF", + border: "#292929", + mainAreaBg: "#111111", + mainAreaBgAccent: "#111111", + mainAreaText: "#FFF", + supportiveText: "#FFF", }, - showLeaveButton: true, - iframeStyle: { - position: "fixed", - width: "100%", - height: "100%", - }, - }); - callFrame.join({ - url: props.booking.dailyRef?.dailyurl, - showLeaveButton: true, - }); - } - if (!meetingUnavailable && !emptyBooking && session?.userid === props.booking.user?.id) { - const callFrame = DailyIframe.createFrame({ - theme: { - colors: { - accent: "#FFF", - accentText: "#111111", - background: "#111111", - backgroundAccent: "#111111", - baseText: "#FFF", - border: "#292929", - mainAreaBg: "#111111", - mainAreaBgAccent: "#111111", - mainAreaText: "#FFF", - supportiveText: "#FFF", - }, - }, - showLeaveButton: true, - iframeStyle: { - position: "fixed", - width: "100%", - height: "100%", - }, - }); - callFrame.join({ - url: props.booking.dailyRef?.dailyurl, - showLeaveButton: true, - token: props.booking.dailyRef?.dailytoken, - }); - } - }, [ - emptyBooking, - meetingUnavailable, - props.booking?.dailyRef?.dailytoken, - props.booking?.dailyRef?.dailyurl, - props.booking?.user?.id, - session?.userid, - ]); + }, + showLeaveButton: true, + iframeStyle: { + position: "fixed", + width: "100%", + height: "100%", + }, + }); + callFrame.join({ + url: props.booking.references[0].meetingUrl ?? "", + showLeaveButton: true, + ...(props.booking.references[0].meetingPassword + ? { token: props.booking.references[0].meetingPassword } + : null), + }); + }, [props.booking?.references]); return ( <> @@ -123,23 +64,20 @@ export default function JoinCall(props: JoinCallPageProps) {
- <> - - { - // eslint-disable-next-line @next/next/no-img-element - Cal.com Logo - } - - {JoinCall} - + + { + // eslint-disable-next-line @next/next/no-img-element + Cal.com Logo + } +
); @@ -159,25 +97,41 @@ export async function getServerSideProps(context: NextPageContext) { credentials: true, }, }, - dailyRef: { - select: { - dailyurl: true, - dailytoken: true, - }, - }, references: { select: { uid: true, type: true, + meetingUrl: true, + meetingPassword: true, + }, + where: { + type: "daily_video", }, }, }, }); - if (!booking) { - // TODO: Booking is already cancelled + if (!booking || booking.references.length === 0 || !booking.references[0].meetingUrl) { return { - props: { booking: null }, + redirect: { + destination: "/video/no-meeting-found", + permanent: false, + }, + }; + } + + //daily.co calls have a 60 minute 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() - 60 * 60 * 1000); + + //find out if the meeting is in the past + const isPast = booking?.endTime <= exitDate; + if (isPast) { + return { + redirect: { + destination: `/video/meeting-ended/${booking?.uid}`, + permanent: false, + }, }; } @@ -187,10 +141,16 @@ export async function getServerSideProps(context: NextPageContext) { }); const session = await getSession(); + // set meetingPassword to null for guests + if (session?.userid !== bookingObj.user?.id) { + bookingObj.references.forEach((bookRef) => { + bookRef.meetingPassword = null; + }); + } + return { props: { booking: bookingObj, - session: session, }, }; } diff --git a/apps/web/pages/video/meeting-ended/[uid].tsx b/apps/web/pages/video/meeting-ended/[uid].tsx index 325b1cba67..e3c8d2d35c 100644 --- a/apps/web/pages/video/meeting-ended/[uid].tsx +++ b/apps/web/pages/video/meeting-ended/[uid].tsx @@ -1,7 +1,4 @@ import { NextPageContext } from "next"; -import { getSession } from "next-auth/react"; -import { useRouter } from "next/router"; -import { useEffect } from "react"; import dayjs from "@calcom/dayjs"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -16,67 +13,55 @@ import { HeadSeo } from "@components/seo/head-seo"; export default function MeetingUnavailable(props: inferSSRProps) { const { t } = useLocale(); - const router = useRouter(); - // if no booking redirectis to the 404 page - const emptyBooking = props.booking === null; - useEffect(() => { - if (emptyBooking) { - router.push("/video/no-meeting-found"); - } - }); - if (!emptyBooking) { - return ( -
- -
-
-
-
+
+ ); } export async function getServerSideProps(context: NextPageContext) { @@ -92,25 +77,22 @@ export async function getServerSideProps(context: NextPageContext) { credentials: true, }, }, - dailyRef: { - select: { - dailyurl: true, - dailytoken: true, - }, - }, references: { select: { uid: true, type: true, + meetingUrl: true, }, }, }, }); if (!booking) { - // TODO: Booking is already cancelled return { - props: { booking: null }, + redirect: { + destination: "/video/no-meeting-found", + permanent: false, + }, }; } @@ -118,12 +100,10 @@ export async function getServerSideProps(context: NextPageContext) { startTime: booking.startTime.toString(), endTime: booking.endTime.toString(), }); - const session = await getSession(); return { props: { booking: bookingObj, - session: session, }, }; } diff --git a/apps/web/pages/video/meeting-not-started/[uid].tsx b/apps/web/pages/video/meeting-not-started/[uid].tsx index be8d8a366f..59c088aae7 100644 --- a/apps/web/pages/video/meeting-not-started/[uid].tsx +++ b/apps/web/pages/video/meeting-not-started/[uid].tsx @@ -1,7 +1,4 @@ import { NextPageContext } from "next"; -import { getSession } from "next-auth/react"; -import { useRouter } from "next/router"; -import { useEffect } from "react"; import dayjs from "@calcom/dayjs"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -15,73 +12,59 @@ import { HeadSeo } from "@components/seo/head-seo"; export default function MeetingNotStarted(props: inferSSRProps) { const { t } = useLocale(); - const router = useRouter(); - - //if no booking redirectis to the 404 page - const emptyBooking = props.booking === null; - useEffect(() => { - if (emptyBooking) { - router.push("/video/no-meeting-found"); - } - }); - if (!emptyBooking) { - return ( -
- -
-
-
-
+
+ ); } export async function getServerSideProps(context: NextPageContext) { @@ -89,33 +72,15 @@ export async function getServerSideProps(context: NextPageContext) { where: { uid: context.query.uid as string, }, - select: { - ...bookingMinimalSelect, - uid: true, - user: { - select: { - credentials: true, - }, - }, - dailyRef: { - select: { - dailyurl: true, - dailytoken: true, - }, - }, - references: { - select: { - uid: true, - type: true, - }, - }, - }, + select: bookingMinimalSelect, }); if (!booking) { - // TODO: Booking is already cancelled return { - props: { booking: null }, + redirect: { + destination: "/video/no-meeting-found", + permanent: false, + }, }; } @@ -123,12 +88,10 @@ export async function getServerSideProps(context: NextPageContext) { startTime: booking.startTime.toString(), endTime: booking.endTime.toString(), }); - const session = await getSession(); return { props: { booking: bookingObj, - session: session, }, }; } diff --git a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts index 7feb68b348..4c6f503a1d 100644 --- a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts @@ -1,34 +1,35 @@ import { Credential } from "@prisma/client"; +import { z } from "zod"; -import { WEBAPP_URL } from "@calcom/lib/constants"; import { handleErrorsJson } from "@calcom/lib/errors"; -import prisma from "@calcom/prisma"; import type { CalendarEvent } from "@calcom/types/Calendar"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; +import { getDailyAppKeys } from "./getDailyAppKeys"; + /** @link https://docs.daily.co/reference/rest-api/rooms/create-room */ -export interface DailyReturnType { +const dailyReturnTypeSchema = z.object({ /** Long UID string ie: 987b5eb5-d116-4a4e-8e2c-14fcb5710966 */ - id: string; + id: z.string(), /** Not a real name, just a random generated string ie: "ePR84NQ1bPigp79dDezz" */ - name: string; - api_created: boolean; - privacy: "private" | "public"; + name: z.string(), + api_created: z.boolean(), + privacy: z.union([z.literal("private"), z.literal("public")]), /** https://api-demo.daily.co/ePR84NQ1bPigp79dDezz */ - url: string; - created_at: string; - config: { + url: z.string(), + created_at: z.string(), + config: z.object({ /** Timestamps expressed in seconds, not in milliseconds */ - nbf: number; + nbf: z.number().optional(), /** Timestamps expressed in seconds, not in milliseconds */ - exp: number; - enable_chat: boolean; - enable_knocking: boolean; - enable_prejoin_ui: boolean; - enable_new_call_ui: boolean; - }; -} + exp: z.number(), + enable_chat: z.boolean(), + enable_knocking: z.boolean(), + enable_prejoin_ui: z.boolean(), + enable_new_call_ui: z.boolean(), + }), +}); export interface DailyEventResult { id: string; @@ -47,9 +48,9 @@ export interface DailyVideoCallData { url: string; } -type DailyKey = { - apikey: string; -}; +const meetingTokenSchema = z.object({ + token: z.string(), +}); /** @deprecated use metadata on index file */ export const FAKE_DAILY_CREDENTIAL: Credential = { @@ -60,47 +61,43 @@ export const FAKE_DAILY_CREDENTIAL: Credential = { appId: "daily-video", }; -const DailyVideoApiAdapter = (credential: Credential): VideoApiAdapter => { - const dailyApiToken = (credential.key as DailyKey).apikey; +const fetcher = async (endpoint: string, init?: RequestInit | undefined) => { + const { api_key } = await getDailyAppKeys(); + return fetch(`https://api.daily.co/v1${endpoint}`, { + method: "GET", + headers: { + Authorization: "Bearer " + api_key, + "Content-Type": "application/json", + ...init?.headers, + }, + ...init, + }).then(handleErrorsJson); +}; - function postToDailyAPI(endpoint: string, body: Record) { - return fetch("https://api.daily.co/v1" + endpoint, { - method: "POST", - headers: { - Authorization: "Bearer " + dailyApiToken, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - } +function postToDailyAPI(endpoint: string, body: Record) { + return fetcher(endpoint, { + method: "POST", + body: JSON.stringify(body), + }); +} +const DailyVideoApiAdapter = (): VideoApiAdapter => { async function createOrUpdateMeeting(endpoint: string, event: CalendarEvent): Promise { if (!event.uid) { throw new Error("We need need the booking uid to create the Daily reference in DB"); } - const response = await postToDailyAPI(endpoint, translateEvent(event)); - const dailyEvent = (await handleErrorsJson(response)) as DailyReturnType; - const res = await postToDailyAPI("/meeting-tokens", { + const dailyEvent = await postToDailyAPI(endpoint, translateEvent(event)).then( + dailyReturnTypeSchema.parse + ); + const meetingToken = await postToDailyAPI("/meeting-tokens", { properties: { room_name: dailyEvent.name, is_owner: true }, - }); - const meetingToken = (await handleErrorsJson(res)) as { token: string }; - await prisma.dailyEventReference.create({ - data: { - dailyurl: dailyEvent.url, - dailytoken: meetingToken.token, - booking: { - connect: { - uid: event.uid, - }, - }, - }, - }); + }).then(meetingTokenSchema.parse); return Promise.resolve({ type: "daily_video", id: dailyEvent.name, - password: "", - url: WEBAPP_URL + "/video/" + event.uid, + password: meetingToken.token, + url: dailyEvent.url, }); } @@ -145,17 +142,11 @@ const DailyVideoApiAdapter = (credential: Credential): VideoApiAdapter => { createMeeting: async (event: CalendarEvent): Promise => createOrUpdateMeeting("/rooms", event), deleteMeeting: async (uid: string): Promise => { - await fetch("https://api.daily.co/v1/rooms/" + uid, { - method: "DELETE", - headers: { - Authorization: "Bearer " + dailyApiToken, - }, - }).then(handleErrorsJson); - + await fetcher(`/rooms/${uid}`, { method: "DELETE" }); return Promise.resolve(); }, updateMeeting: (bookingRef: PartialReference, event: CalendarEvent): Promise => - createOrUpdateMeeting("/rooms/" + bookingRef.uid, event), + createOrUpdateMeeting(`/rooms/${bookingRef.uid}`, event), }; }; diff --git a/packages/app-store/dailyvideo/lib/getDailyAppKeys.ts b/packages/app-store/dailyvideo/lib/getDailyAppKeys.ts new file mode 100644 index 0000000000..adc04f7a36 --- /dev/null +++ b/packages/app-store/dailyvideo/lib/getDailyAppKeys.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +const dailyAppKeysSchema = z.object({ + api_key: z.string(), +}); + +export const getDailyAppKeys = async () => { + const appKeys = await getAppKeysFromSlug("daily-video"); + return dailyAppKeysSchema.parse(appKeys); +}; diff --git a/packages/core/builders/CalendarEvent/class.ts b/packages/core/builders/CalendarEvent/class.ts index d74d9a3739..168fb08043 100644 --- a/packages/core/builders/CalendarEvent/class.ts +++ b/packages/core/builders/CalendarEvent/class.ts @@ -1,6 +1,12 @@ import { DestinationCalendar } from "@prisma/client"; -import type { AdditionalInformation, CalendarEvent, ConferenceData, Person } from "@calcom/types/Calendar"; +import type { + AdditionalInformation, + CalendarEvent, + ConferenceData, + Person, + VideoCallData, +} from "@calcom/types/Calendar"; class CalendarEventClass implements CalendarEvent { type!: string; @@ -15,7 +21,7 @@ class CalendarEventClass implements CalendarEvent { conferenceData?: ConferenceData; additionalInformation?: AdditionalInformation; uid?: string | null; - videoCallData?: any; + videoCallData?: VideoCallData; paymentInfo?: any; destinationCalendar?: DestinationCalendar | null; cancellationReason?: string | null; diff --git a/packages/emails/src/components/LocationInfo.tsx b/packages/emails/src/components/LocationInfo.tsx index 63f6651b64..2c38e10751 100644 --- a/packages/emails/src/components/LocationInfo.tsx +++ b/packages/emails/src/components/LocationInfo.tsx @@ -1,6 +1,7 @@ import type { TFunction } from "next-i18next"; import { getAppName } from "@calcom/app-store/utils"; +import { getVideoCallPassword, getVideoCallUrl, getProviderName } from "@calcom/lib/CalEventParser"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { Info } from "./Info"; @@ -8,22 +9,13 @@ import { LinkIcon } from "./LinkIcon"; export function LocationInfo(props: { calEvent: CalendarEvent; t: TFunction }) { const { t } = props; - let providerName = props.calEvent.location && getAppName(props.calEvent.location); - - if (props.calEvent.location && props.calEvent.location.includes("integrations:")) { - const location = props.calEvent.location.split(":")[1]; - providerName = location[0].toUpperCase() + location.slice(1); - } - - // If location its a url, probably we should be validating it with a custom library - if (props.calEvent.location && /^https?:\/\//.test(props.calEvent.location)) { - providerName = props.calEvent.location; - } + const providerName = + (props.calEvent.location && getAppName(props.calEvent.location)) || getProviderName(props.calEvent); if (props.calEvent.videoCallData) { const meetingId = props.calEvent.videoCallData.id; - const meetingPassword = props.calEvent.videoCallData.password; - const meetingUrl = props.calEvent.videoCallData.url; + const meetingPassword = getVideoCallPassword(props.calEvent); + const meetingUrl = getVideoCallUrl(props.calEvent); return ( { `; }; export const getLocation = (calEvent: CalendarEvent) => { - let providerName = ""; + const meetingUrl = getVideoCallUrl(calEvent); + if (meetingUrl) { + return meetingUrl; + } + const providerName = getProviderName(calEvent); + return providerName || calEvent.location || ""; +}; +export const getProviderName = (calEvent: CalendarEvent): string => { + // TODO: use getAppName from @calcom/app-store if (calEvent.location && calEvent.location.includes("integrations:")) { const location = calEvent.location.split(":")[1]; - providerName = location[0].toUpperCase() + location.slice(1); + return location[0].toUpperCase() + location.slice(1); } - - if (calEvent.videoCallData) { - return calEvent.videoCallData.url; + // If location its a url, probably we should be validating it with a custom library + if (calEvent.location && /^https?:\/\//.test(calEvent.location)) { + return calEvent.location; } - - if (calEvent.additionalInformation?.hangoutLink) { - return calEvent.additionalInformation.hangoutLink; - } - - return providerName || calEvent.location || ""; + return ""; }; export const getManageLink = (calEvent: CalendarEvent) => { @@ -150,3 +152,28 @@ ${calEvent.organizer.language.translate("cancellation_reason")}: ${calEvent.cancellationReason} `; }; + +export const isDailyVideoCall = (calEvent: CalendarEvent): boolean => { + return calEvent?.videoCallData?.type === "daily_video"; +}; + +export const getPublicVideoCallUrl = (calEvent: CalendarEvent): string => { + return WEBAPP_URL + "/video/" + getUid(calEvent); +}; + +export const getVideoCallUrl = (calEvent: CalendarEvent): string => { + if (calEvent.videoCallData) { + if (isDailyVideoCall(calEvent)) { + return getPublicVideoCallUrl(calEvent); + } + return calEvent.videoCallData.url; + } + if (calEvent.additionalInformation?.hangoutLink) { + return calEvent.additionalInformation.hangoutLink; + } + return ""; +}; + +export const getVideoCallPassword = (calEvent: CalendarEvent): string => { + return isDailyVideoCall(calEvent) ? "" : calEvent?.videoCallData?.password ?? ""; +}; diff --git a/packages/lib/jest.config.js b/packages/lib/jest.config.js new file mode 100644 index 0000000000..a93551fca2 --- /dev/null +++ b/packages/lib/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/packages/lib/package.json b/packages/lib/package.json index 2963e09239..74431fc664 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -5,13 +5,14 @@ "types": "./index.ts", "license": "MIT", "scripts": { + "test": "dotenv -e ./test/.env.test -- jest", "lint": "eslint . --ext .ts,.js,.tsx,.jsx", "lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix", "lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json" }, "dependencies": { - "@calcom/dayjs": "*", "@calcom/config": "*", + "@calcom/dayjs": "*", "@prisma/client": "^4.1.0", "bcryptjs": "^2.4.3", "ical.js": "^1.4.0", @@ -26,6 +27,9 @@ "devDependencies": { "@calcom/tsconfig": "*", "@calcom/types": "*", + "@faker-js/faker": "^7.3.0", + "jest": "^26.0.0", + "ts-jest": "^26.0.0", "typescript": "^4.6.4" } } diff --git a/packages/lib/test/.env.test b/packages/lib/test/.env.test new file mode 100644 index 0000000000..a0649dd7d7 --- /dev/null +++ b/packages/lib/test/.env.test @@ -0,0 +1 @@ +NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000 diff --git a/packages/lib/test/CalEventParser.test.ts b/packages/lib/test/CalEventParser.test.ts new file mode 100644 index 0000000000..28fa758301 --- /dev/null +++ b/packages/lib/test/CalEventParser.test.ts @@ -0,0 +1,63 @@ +import { faker } from "@faker-js/faker"; + +import { getPublicVideoCallUrl, getLocation, getVideoCallPassword, getVideoCallUrl } from "../CalEventParser"; +import { buildCalendarEvent, buildVideoCallData } from "./builder"; + +describe("getLocation", () => { + it("should return a meetingUrl for video call meetings", () => { + const calEvent = buildCalendarEvent({ + videoCallData: buildVideoCallData({ + type: "daily_video", + }), + }); + + expect(getLocation(calEvent)).toEqual(getVideoCallUrl(calEvent)); + }); + it("should return an integration provider name from event", () => { + const provideName = "Cal.com"; + const calEvent = buildCalendarEvent({ + videoCallData: undefined, + location: `integrations:${provideName}`, + }); + + expect(getLocation(calEvent)).toEqual(provideName); + }); + it("should return a real-world location from event", () => { + const calEvent = buildCalendarEvent({ + videoCallData: undefined, + location: faker.address.streetAddress(true), + }); + + expect(getLocation(calEvent)).toEqual(calEvent.location); + }); +}); + +describe("getVideoCallUrl", () => { + it("should return an app public url instead of meeting url for daily call meetings", () => { + const calEvent = buildCalendarEvent({ + videoCallData: buildVideoCallData({ + type: "daily_video", + }), + }); + + expect(getVideoCallUrl(calEvent)).toEqual(getPublicVideoCallUrl(calEvent)); + }); +}); + +describe("getVideoCallPassword", () => { + it("should return an empty password for daily call meetings", () => { + const calEvent = buildCalendarEvent({ + videoCallData: buildVideoCallData({ + type: "daily_video", + }), + }); + + expect(getVideoCallPassword(calEvent)).toEqual(""); + }); + it("should return original password for other video call meetings", () => { + const calEvent = buildCalendarEvent(); + + expect(calEvent?.videoCallData?.type).not.toBe("daily_video"); + expect(getVideoCallPassword(calEvent)).toEqual(calEvent?.videoCallData.password); + }); +}); diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts new file mode 100644 index 0000000000..ce0f94d149 --- /dev/null +++ b/packages/lib/test/builder.ts @@ -0,0 +1,46 @@ +import { faker } from "@faker-js/faker"; + +import { CalendarEvent, Person, VideoCallData } from "@calcom/types/Calendar"; + +export const buildVideoCallData = (callData?: Partial): VideoCallData => { + return { + type: faker.helpers.arrayElement(["zoom_video", "stream_video"]), + id: faker.datatype.uuid(), + password: faker.internet.password(), + url: faker.internet.url(), + ...callData, + }; +}; + +export const buildPerson = (person?: Partial): Person => { + return { + name: faker.name.firstName(), + email: faker.internet.email(), + timeZone: faker.address.timeZone(), + username: faker.internet.userName(), + id: faker.datatype.uuid(), + language: { + locale: faker.random.locale(), + translate: (key: string) => key, + }, + ...person, + }; +}; + +export const buildCalendarEvent = (event?: Partial): CalendarEvent => { + return { + uid: faker.datatype.uuid(), + type: faker.helpers.arrayElement(["event", "meeting"]), + title: faker.lorem.sentence(), + startTime: faker.date.future().toISOString(), + endTime: faker.date.future().toISOString(), + location: faker.address.city(), + description: faker.lorem.paragraph(), + attendees: [], + customInputs: {}, + additionalNotes: faker.lorem.paragraph(), + organizer: buildPerson(), + videoCallData: buildVideoCallData(), + ...event, + }; +}; diff --git a/packages/prisma/migrations/20220803090845_migrate_daily_event_reference_to_booking_reference/migration.sql b/packages/prisma/migrations/20220803090845_migrate_daily_event_reference_to_booking_reference/migration.sql new file mode 100644 index 0000000000..c384e45e53 --- /dev/null +++ b/packages/prisma/migrations/20220803090845_migrate_daily_event_reference_to_booking_reference/migration.sql @@ -0,0 +1,4 @@ +UPDATE "BookingReference" +SET "meetingUrl" = "dailyurl", "meetingPassword" = "dailytoken" +FROM "DailyEventReference" +WHERE "DailyEventReference"."bookingId" = "BookingReference"."bookingId" AND "BookingReference"."type" = 'daily_video' diff --git a/packages/prisma/migrations/20220803091114_drop_daily_event_reference/migration.sql b/packages/prisma/migrations/20220803091114_drop_daily_event_reference/migration.sql new file mode 100644 index 0000000000..a8947d0810 --- /dev/null +++ b/packages/prisma/migrations/20220803091114_drop_daily_event_reference/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the `DailyEventReference` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "DailyEventReference" DROP CONSTRAINT "DailyEventReference_bookingId_fkey"; + +-- DropTable +DROP TABLE "DailyEventReference"; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 5ebd5b72ee..be8fd2d2f7 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -260,14 +260,6 @@ enum BookingStatus { PENDING @map("pending") } -model DailyEventReference { - id Int @id @default(autoincrement()) - dailyurl String @default("dailycallurl") - dailytoken String @default("dailytoken") - booking Booking? @relation(fields: [bookingId], references: [id]) - bookingId Int? @unique -} - model Booking { id Int @id @default(autoincrement()) uid String @unique @@ -283,7 +275,6 @@ model Booking { endTime DateTime attendees Attendee[] location String? - dailyRef DailyEventReference? createdAt DateTime @default(now()) updatedAt DateTime? status BookingStatus @default(ACCEPTED) diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index b4b7333eb4..382d09432d 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -6,9 +6,10 @@ import type { TFunction } from "next-i18next"; import type { Frequency } from "@calcom/prisma/zod-utils"; -import type { Event } from "./Event"; import type { Ensure } from "./utils"; +export type { VideoCallData } from "./VideoApiAdapter"; + type PaymentInfo = { link?: string | null; reason?: string | null;