WIP
parent
ed40b09430
commit
355737a86f
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "zoom",
|
||||
"label": "Zoom",
|
||||
"⬇️ needs to be the same as the folder name": 1,
|
||||
"slug": "zoom",
|
||||
"category": "Video Conferencing",
|
||||
"description": "Zoom is the most popular video conferencing platform, joinable on the web or via desktop/mobile apps.",
|
||||
"logo": "/apps/zoom.svg",
|
||||
"publisher": "Cal.com",
|
||||
"url": "https://zoom.us/",
|
||||
"verified": true,
|
||||
"⬇️ TODO: placeholder for now, pull this from TrustPilot or G2": 1,
|
||||
"rating": 4.3,
|
||||
"⬇️ TODO 2: placeholder for now, pull this from TrustPilot or G2": 1,
|
||||
"reviews": 69
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"index": "Home",
|
||||
"self-hosting": "Self-hosting",
|
||||
"availability": "Availability",
|
||||
"bookings": "Bookings",
|
||||
"event-types": "Event Types",
|
||||
"teams": "Teams",
|
||||
"integrations": "Integrations",
|
||||
"webhooks": "Webhooks",
|
||||
"settings": "Settings",
|
||||
"import": "Import",
|
||||
"billing": "Billing",
|
||||
"developer": "Developer",
|
||||
"faq": "FAQs"
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
import { DestinationCalendar, SelectedCalendar } from "@prisma/client";
|
||||
import { SelectedCalendar } from "@prisma/client";
|
||||
|
||||
import { PaymentInfo } from "@ee/lib/stripe/server";
|
||||
import type { CalendarEvent, ConferenceData } from "@calcom/types/CalendarEvent";
|
||||
|
||||
import type { Event } from "@lib/events/EventManager";
|
||||
import { Ensure } from "@lib/types/utils";
|
||||
import { VideoCallData } from "@lib/videoClient";
|
||||
|
||||
import { ConferenceData } from "../../google_calendar/interfaces/GoogleCalendar";
|
||||
import { NewCalendarEventType, Person } from "../types/CalendarTypes";
|
||||
import { NewCalendarEventType } from "../types/CalendarTypes";
|
||||
|
||||
export interface EntryPoint {
|
||||
entryPointType?: string;
|
||||
|
@ -26,29 +24,6 @@ export interface AdditionInformation {
|
|||
hangoutLink?: string;
|
||||
}
|
||||
|
||||
export interface CalendarEvent {
|
||||
type: string;
|
||||
title: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
description?: string | null;
|
||||
team?: {
|
||||
name: string;
|
||||
members: string[];
|
||||
};
|
||||
location?: string | null;
|
||||
organizer: Person;
|
||||
attendees: Person[];
|
||||
conferenceData?: ConferenceData;
|
||||
additionInformation?: AdditionInformation;
|
||||
uid?: string | null;
|
||||
videoCallData?: VideoCallData;
|
||||
paymentInfo?: PaymentInfo | null;
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
cancellationReason?: string | null;
|
||||
rejectionReason?: string | null;
|
||||
}
|
||||
|
||||
export interface IntegrationCalendar extends Ensure<Partial<SelectedCalendar>, "externalId"> {
|
||||
primary?: boolean;
|
||||
name?: string;
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { calendar_v3 } from "googleapis";
|
||||
|
||||
export interface ConferenceData {
|
||||
createRequest?: calendar_v3.Schema$CreateConferenceRequest;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"index": 1,
|
||||
"self-hosting": 1,
|
||||
"availability": 1,
|
||||
"bookings": 1,
|
||||
"event-types": 1,
|
||||
"teams": 1,
|
||||
"integrations": 1,
|
||||
"webhooks": 1,
|
||||
"settings": 1,
|
||||
"import": 1,
|
||||
"billing": 1,
|
||||
"developer": 1
|
||||
}
|
|
@ -1,32 +1,2 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: number; code?: string } {
|
||||
if (cause instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return cause;
|
||||
}
|
||||
if (cause instanceof Error) {
|
||||
return cause;
|
||||
}
|
||||
if (typeof cause === "string") {
|
||||
// @ts-expect-error https://github.com/tc39/proposal-error-cause
|
||||
return new Error(cause, { cause });
|
||||
}
|
||||
|
||||
return new Error(`Unhandled error of type '${typeof cause}''`);
|
||||
}
|
||||
|
||||
export function handleErrorsJson(response: Response) {
|
||||
if (!response.ok) {
|
||||
response.json().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function handleErrorsRaw(response: Response) {
|
||||
if (!response.ok) {
|
||||
response.text().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
// TODO: Remove this file once everything is imported from `@calcom/lib`
|
||||
export * from "@calcom/lib/errors";
|
||||
|
|
|
@ -2,48 +2,36 @@ import { Credential } from "@prisma/client";
|
|||
import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import appStore from "@calcom/app-store";
|
||||
import type { CalendarEvent } from "@calcom/types/CalendarEvent";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoApiAdapterFactory } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
import { getUid } from "@lib/CalEventParser";
|
||||
import { EventResult } from "@lib/events/EventManager";
|
||||
import { PartialReference } from "@lib/events/EventManager";
|
||||
import Huddle01VideoApiAdapter from "@lib/integrations/Huddle01/Huddle01VideoApiAdapter";
|
||||
import JitsiVideoApiAdapter from "@lib/integrations/Jitsi/JitsiVideoApiAdapter";
|
||||
import logger from "@lib/logger";
|
||||
|
||||
import DailyVideoApiAdapter from "./integrations/Daily/DailyVideoApiAdapter";
|
||||
import TandemVideoApiAdapter from "./integrations/Tandem/TandemVideoApiAdapter";
|
||||
import ZoomVideoApiAdapter from "./integrations/Zoom/ZoomVideoApiAdapter";
|
||||
import { CalendarEvent } from "./integrations/calendar/interfaces/Calendar";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["[lib] videoClient"] });
|
||||
|
||||
const translator = short();
|
||||
|
||||
export interface VideoCallData {
|
||||
type: string;
|
||||
id: string;
|
||||
password: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type EventBusyDate = Record<"start" | "end", Date>;
|
||||
|
||||
export interface VideoApiAdapter {
|
||||
createMeeting(event: CalendarEvent): Promise<VideoCallData>;
|
||||
|
||||
updateMeeting(bookingRef: PartialReference, event: CalendarEvent): Promise<VideoCallData>;
|
||||
|
||||
deleteMeeting(uid: string): Promise<unknown>;
|
||||
|
||||
getAvailability(dateFrom?: string, dateTo?: string): Promise<EventBusyDate[]>;
|
||||
}
|
||||
|
||||
// factory
|
||||
const getVideoAdapters = (withCredentials: Credential[]): VideoApiAdapter[] =>
|
||||
withCredentials.reduce<VideoApiAdapter[]>((acc, cred) => {
|
||||
const appName = cred.type.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
|
||||
const makeVideoApiAdapter = appStore[appName].lib?.VideoApiAdapter as VideoApiAdapterFactory;
|
||||
if (typeof makeVideoApiAdapter !== "undefined") {
|
||||
const videoAdapter = makeVideoApiAdapter(cred);
|
||||
acc.push(videoAdapter);
|
||||
return acc;
|
||||
}
|
||||
|
||||
switch (cred.type) {
|
||||
case "zoom_video":
|
||||
acc.push(ZoomVideoApiAdapter(cred));
|
||||
break;
|
||||
case "daily_video":
|
||||
acc.push(DailyVideoApiAdapter(cred));
|
||||
break;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const withTM = require("@vercel/edge-functions-ui/transpile")([
|
||||
"@calcom/app-store",
|
||||
"@calcom/lib",
|
||||
"@calcom/prisma",
|
||||
"@calcom/ui",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@boxyhq/saml-jackson": "0.3.6",
|
||||
"@calcom/app-store": "*",
|
||||
"@calcom/lib": "*",
|
||||
"@calcom/prisma": "*",
|
||||
"@calcom/tsconfig": "*",
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import appStore from "@calcom/app-store";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
// Check that user is authenticated
|
||||
req.session = await getSession({ req });
|
||||
|
||||
if (!req.session?.user?.id) {
|
||||
res.status(401).json({ message: "You must be logged in to do this" });
|
||||
return;
|
||||
}
|
||||
|
||||
const { args } = req.query;
|
||||
|
||||
if (!Array.isArray(args)) {
|
||||
return res.status(404).json({ message: `API route not found` });
|
||||
}
|
||||
|
||||
const [appName, apiEndpoint] = args;
|
||||
|
||||
try {
|
||||
// TODO: Find a way to dynamically import these modules
|
||||
// const app = (await import(`@calcom/${appName}`)).default;
|
||||
const handler = appStore[appName].api[apiEndpoint];
|
||||
if (typeof handler !== "function")
|
||||
throw new HttpError({ statusCode: 404, message: `API handler not found` });
|
||||
|
||||
const response = await handler(req, res);
|
||||
console.log("response", response);
|
||||
|
||||
res.status(200);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof HttpError) {
|
||||
return res.status(error.statusCode).json({ message: error.message });
|
||||
}
|
||||
return res.status(404).json({ message: `API handler not found` });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
|
@ -1,4 +1,32 @@
|
|||
// TODO: maybe we wanna do this dynamically later based on folder structure
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// It won't be called on client-side.
|
||||
export async function getStaticProps() {
|
||||
const appStoreDir = path.join(process.cwd(), "packages/appStore");
|
||||
const filenames = fs.readdirSync(appStoreDir);
|
||||
|
||||
const apps = filenames.map((filename) => {
|
||||
const filePath = path.join(appStoreDir, filename);
|
||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// Generally you would parse/transform the contents
|
||||
// For example you can transform markdown to HTML here
|
||||
|
||||
return {
|
||||
filename,
|
||||
content: fileContents,
|
||||
};
|
||||
});
|
||||
// By returning { props: posts }, the Blog component
|
||||
// will receive `posts` as a prop at build time
|
||||
return {
|
||||
props: {
|
||||
posts: apps,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function appRegistry() {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*",
|
||||
"appStore/*"
|
||||
"packages/app-store/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build --scope=\"@calcom/web\" --include-dependencies",
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "zoom",
|
||||
"label": "Zoom",
|
||||
"⬇️ needs to be the same as the folder name": 1,
|
||||
"slug": "zoom",
|
||||
"category": "Video Conferencing",
|
||||
"description": "Zoom is the most popular video conferencing platform, joinable on the web or via desktop/mobile apps.",
|
||||
"logo": "/apps/zoom.svg",
|
||||
"publisher": "Cal.com",
|
||||
"url": "https://zoom.us/",
|
||||
"verified": true,
|
||||
"⬇️ TODO: placeholder for now, pull this from TrustPilot or G2": 1,
|
||||
"rating": 4.3,
|
||||
"⬇️ TODO 2: placeholder for now, pull this from TrustPilot or G2": 1,
|
||||
"reviews": 69
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import * as zoomvideo from "./zoomvideo";
|
||||
|
||||
const appStore = {
|
||||
zoomvideo,
|
||||
};
|
||||
|
||||
export default appStore;
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@calcom/app-store",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/zoomvideo": "*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@calcom/tsconfig/base.json",
|
||||
"include": [".", "@calcom/types"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
|
@ -2,7 +2,6 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import { stringify } from "querystring";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import "@calcom/types/next";
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || `https://${process.env.VERCEL_URL}`;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import "@calcom/types/next";
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || `https://${process.env.VERCEL_URL}`;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
|
@ -0,0 +1,2 @@
|
|||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
|
@ -1,11 +1,10 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
|
||||
import { handleErrorsJson, handleErrorsRaw } from "@lib/errors";
|
||||
import { PartialReference } from "@lib/events/EventManager";
|
||||
import prisma from "@lib/prisma";
|
||||
import { VideoApiAdapter, VideoCallData } from "@lib/videoClient";
|
||||
|
||||
import { CalendarEvent } from "../calendar/interfaces/Calendar";
|
||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/CalendarEvent";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
/** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */
|
||||
export interface ZoomEventResult {
|
|
@ -0,0 +1 @@
|
|||
export { default as VideoApiAdapter } from "./VideoApiAdapter";
|
|
@ -16,6 +16,9 @@
|
|||
"⬇️ TODO 2: placeholder for now, pull this from TrustPilot or G2": 1,
|
||||
"reviews": 69,
|
||||
"dependencies": {
|
||||
"@calcom/prisma": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: number; code?: string } {
|
||||
if (cause instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return cause;
|
||||
}
|
||||
if (cause instanceof Error) {
|
||||
return cause;
|
||||
}
|
||||
if (typeof cause === "string") {
|
||||
// @ts-expect-error https://github.com/tc39/proposal-error-cause
|
||||
return new Error(cause, { cause });
|
||||
}
|
||||
|
||||
return new Error(`Unhandled error of type '${typeof cause}''`);
|
||||
}
|
||||
|
||||
export function handleErrorsJson(response: Response) {
|
||||
if (!response.ok) {
|
||||
response.json().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function handleErrorsRaw(response: Response) {
|
||||
if (!response.ok) {
|
||||
response.text().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import type { DestinationCalendar } from "@prisma/client";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
|
||||
export interface ConferenceData {
|
||||
createRequest?: calendar_v3.Schema$CreateConferenceRequest;
|
||||
}
|
||||
|
||||
export interface AdditionInformation {
|
||||
conferenceData?: ConferenceData;
|
||||
entryPoints?: EntryPoint[];
|
||||
hangoutLink?: string;
|
||||
}
|
||||
|
||||
export interface CalendarEvent {
|
||||
type: string;
|
||||
title: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
description?: string | null;
|
||||
team?: {
|
||||
name: string;
|
||||
members: string[];
|
||||
};
|
||||
location?: string | null;
|
||||
organizer: Person;
|
||||
attendees: Person[];
|
||||
conferenceData?: ConferenceData;
|
||||
additionInformation?: AdditionInformation;
|
||||
uid?: string | null;
|
||||
videoCallData?: VideoCallData;
|
||||
paymentInfo?: PaymentInfo | null;
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
cancellationReason?: string | null;
|
||||
rejectionReason?: string | null;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import type { Credential } from "@prisma/client";
|
||||
|
||||
export interface VideoCallData {
|
||||
type: string;
|
||||
id: string;
|
||||
password: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type EventBusyDate = Record<"start" | "end", Date>;
|
||||
|
||||
export interface VideoApiAdapter {
|
||||
createMeeting(event: CalendarEvent): Promise<VideoCallData>;
|
||||
|
||||
updateMeeting(bookingRef: PartialReference, event: CalendarEvent): Promise<VideoCallData>;
|
||||
|
||||
deleteMeeting(uid: string): Promise<unknown>;
|
||||
|
||||
getAvailability(dateFrom?: string, dateTo?: string): Promise<EventBusyDate[]>;
|
||||
}
|
||||
|
||||
export type VideoApiAdapterFactory = (credential: Credential) => VideoApiAdapter;
|
|
@ -1,6 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import type { IncomingMessage } from "http";
|
||||
import { Session } from "next-auth";
|
||||
import type { Session } from "next-auth";
|
||||
|
||||
import "./next-auth";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "@calcom/tsconfig/base.json",
|
||||
"include": ["", "@calcom/types"],
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
Loading…
Reference in New Issue