Merge branch 'main' into minimum-booking-notice-will-allow-hours-and-days
This commit is contained in:
commit
03f243db21
|
@ -2,6 +2,8 @@ import Link from "next/link";
|
|||
import { useRouter } from "next/router";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/core/List";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
@ -17,7 +19,9 @@ function IntegrationListItem(props: {
|
|||
logo: string;
|
||||
destination?: boolean;
|
||||
separate?: boolean;
|
||||
invalidCredential?: boolean;
|
||||
}): JSX.Element {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const { hl } = router.query;
|
||||
const [highlight, setHighlight] = useState(hl === props.slug);
|
||||
|
@ -44,6 +48,15 @@ function IntegrationListItem(props: {
|
|||
<Link href={"/apps/" + props.slug}>{props.name || title}</Link>
|
||||
</ListItemTitle>
|
||||
<ListItemText component="p">{props.description}</ListItemText>
|
||||
{/* Alert error that key stopped working. */}
|
||||
{props.invalidCredential && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Icon.FiAlertCircle className="w-8 text-red-500 sm:w-4" />
|
||||
<ListItemText component="p" className="whitespace-pre-wrap text-red-500">
|
||||
{t("invalid_credential")}
|
||||
</ListItemText>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>{props.actions}</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { InferGetServerSidePropsType } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import z from "zod";
|
||||
|
||||
|
@ -10,7 +9,6 @@ import { inferQueryOutput, trpc } from "@calcom/trpc/react";
|
|||
import { App } from "@calcom/types/App";
|
||||
import { AppGetServerSidePropsContext } from "@calcom/types/AppGetServerSideProps";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
|
||||
import { Alert } from "@calcom/ui/v2/core/Alert";
|
||||
import Button from "@calcom/ui/v2/core/Button";
|
||||
import EmptyScreen from "@calcom/ui/v2/core/EmptyScreen";
|
||||
|
@ -23,16 +21,19 @@ import { QueryCell } from "@lib/QueryCell";
|
|||
|
||||
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
|
||||
import IntegrationListItem from "@components/apps/IntegrationListItem";
|
||||
import SkeletonLoader from "@components/v2/availability/SkeletonLoader";
|
||||
|
||||
function ConnectOrDisconnectIntegrationButton(props: {
|
||||
credentialIds: number[];
|
||||
type: App["type"];
|
||||
isGlobal?: boolean;
|
||||
installed?: boolean;
|
||||
invalidCredentialIds?: number[];
|
||||
}) {
|
||||
const { type, credentialIds, isGlobal, installed, invalidCredentialIds } = props;
|
||||
const { t } = useLocale();
|
||||
const [credentialId] = props.credentialIds;
|
||||
const type = props.type;
|
||||
const [credentialId] = credentialIds;
|
||||
|
||||
const utils = trpc.useContext();
|
||||
const handleOpenChange = () => {
|
||||
utils.invalidateQueries(["viewer.integrations"]);
|
||||
|
@ -49,6 +50,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DisconnectIntegration
|
||||
credentialId={credentialId}
|
||||
|
@ -58,7 +60,8 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (!props.installed) {
|
||||
|
||||
if (!installed) {
|
||||
return (
|
||||
<div className="flex items-center truncate">
|
||||
<Alert severity="warning" title={t("not_installed")} />
|
||||
|
@ -66,7 +69,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
|||
);
|
||||
}
|
||||
/** We don't need to "Connect", just show that it's installed */
|
||||
if (props.isGlobal) {
|
||||
if (isGlobal) {
|
||||
return (
|
||||
<div className="truncate px-3 py-2">
|
||||
<h3 className="text-sm font-medium text-gray-700">{t("default")}</h3>
|
||||
|
@ -75,7 +78,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
|||
}
|
||||
return (
|
||||
<InstallAppButton
|
||||
type={props.type}
|
||||
type={type}
|
||||
render={(buttonProps) => (
|
||||
<Button color="secondary" {...buttonProps} data-testid="integration-connection-button">
|
||||
{t("install")}
|
||||
|
@ -99,28 +102,32 @@ interface IntegrationsListProps {
|
|||
const IntegrationsList = ({ data }: IntegrationsListProps) => {
|
||||
return (
|
||||
<List className="flex flex-col gap-6" noBorderTreatment>
|
||||
{data.items.map((item) => (
|
||||
<IntegrationListItem
|
||||
name={item.name}
|
||||
slug={item.slug}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
logo={item.logo}
|
||||
description={item.description}
|
||||
separate={true}
|
||||
actions={
|
||||
<div className="flex w-16 justify-end">
|
||||
<ConnectOrDisconnectIntegrationButton
|
||||
credentialIds={item.credentialIds}
|
||||
type={item.type}
|
||||
isGlobal={item.isGlobal}
|
||||
installed
|
||||
/>
|
||||
</div>
|
||||
}>
|
||||
<AppSettings slug={item.slug} />
|
||||
</IntegrationListItem>
|
||||
))}
|
||||
{data.items
|
||||
.filter((item) => item.invalidCredentialIds)
|
||||
.map((item) => (
|
||||
<IntegrationListItem
|
||||
name={item.name}
|
||||
slug={item.slug}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
logo={item.logo}
|
||||
description={item.description}
|
||||
separate={true}
|
||||
invalidCredential={item.invalidCredentialIds.length > 0}
|
||||
actions={
|
||||
<div className="flex w-16 justify-end">
|
||||
<ConnectOrDisconnectIntegrationButton
|
||||
credentialIds={item.credentialIds}
|
||||
type={item.type}
|
||||
isGlobal={item.isGlobal}
|
||||
installed
|
||||
invalidCredentialIds={item.invalidCredentialIds}
|
||||
/>
|
||||
</div>
|
||||
}>
|
||||
<AppSettings slug={item.slug} />
|
||||
</IntegrationListItem>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
"slide_zoom_drag_instructions": "Slide to zoom, drag to reposition",
|
||||
"view_notifications": "View notifications",
|
||||
"view_public_page": "View public page",
|
||||
"copy_public_page_link":"Copy public page link",
|
||||
"copy_public_page_link": "Copy public page link",
|
||||
"sign_out": "Sign out",
|
||||
"add_another": "Add another",
|
||||
"install_another": "Install another",
|
||||
|
@ -1266,9 +1266,9 @@
|
|||
"seats": "seats",
|
||||
"every_app_published": "Every app published on the Cal.com App Store is open source and thoroughly tested via peer reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are published by Cal.com. If you encounter inappropriate content or behaviour please report it.",
|
||||
"report_app": "Report app",
|
||||
"limit_booking_frequency":"Limit booking frequency",
|
||||
"limit_booking_frequency_description":"Limit how many times this event can be booked",
|
||||
"add_limit":"Add Limit",
|
||||
"limit_booking_frequency": "Limit booking frequency",
|
||||
"limit_booking_frequency_description": "Limit how many times this event can be booked",
|
||||
"add_limit": "Add Limit",
|
||||
"team_name_required": "Team name required",
|
||||
"show_attendees": "Share attendee information between guests",
|
||||
"how_additional_inputs_as_variables": "How to use Additional Inputs as Variables",
|
||||
|
@ -1332,11 +1332,12 @@
|
|||
"saml_sp_entity_id_copied": "SP Entity ID copied!",
|
||||
"saml_btn_configure": "Configure",
|
||||
"add_calendar": "Add Calendar",
|
||||
"limit_future_bookings":"Limit future bookings",
|
||||
"limit_future_bookings_description":"Limit how far in the future this event can be booked",
|
||||
"limit_future_bookings": "Limit future bookings",
|
||||
"limit_future_bookings_description": "Limit how far in the future this event can be booked",
|
||||
"no_event_types": "No event types setup",
|
||||
"no_event_types_description": "{{name}} has not setup any event types for you to book.",
|
||||
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
|
||||
"attendee_email_workflow": "Attendee email",
|
||||
"attendee_email_info": "The person booking's email"
|
||||
"attendee_email_info": "The person booking's email",
|
||||
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again."
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { Calendar } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import appStore from "..";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
|
||||
|
||||
export const getCalendar = (credential: Credential | null): Calendar | null => {
|
||||
export const getCalendar = (credential: CredentialPayload | null): Calendar | null => {
|
||||
if (!credential || !credential.key) return null;
|
||||
const { type: calendarType } = credential;
|
||||
const calendarApp = appStore[calendarType.split("_").join("") as keyof typeof appStore];
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
|
||||
import CalendarService from "@calcom/lib/CalendarService";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
export default class AppleCalendarService extends CalendarService {
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
super(credential, "apple_calendar", "https://caldav.icloud.com");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
|
||||
import CalendarService from "@calcom/lib/CalendarService";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
export default class CalDavCalendarService extends CalendarService {
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
super(credential, "caldav_calendar");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import z from "zod";
|
||||
|
||||
import CloseCom, { CloseComFieldOptions } from "@calcom/lib/CloseCom";
|
||||
|
@ -12,6 +11,7 @@ import type {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
const apiKeySchema = z.object({
|
||||
encrypted: z.string(),
|
||||
|
@ -55,7 +55,7 @@ export default class CloseComCalendarService implements Calendar {
|
|||
private closeCom: CloseCom;
|
||||
private log: typeof logger;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "closecom_other_calendar";
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
import { handleErrorsJson } from "@calcom/lib/errors";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
|
@ -53,12 +53,13 @@ const meetingTokenSchema = z.object({
|
|||
});
|
||||
|
||||
/** @deprecated use metadata on index file */
|
||||
export const FAKE_DAILY_CREDENTIAL: Credential = {
|
||||
export const FAKE_DAILY_CREDENTIAL: CredentialPayload & { invalid: boolean } = {
|
||||
id: +new Date().getTime(),
|
||||
type: "daily_video",
|
||||
key: { apikey: process.env.DAILY_API_KEY },
|
||||
userId: +new Date().getTime(),
|
||||
appId: "daily-video",
|
||||
invalid: false,
|
||||
};
|
||||
|
||||
const fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import {
|
||||
Appointment,
|
||||
Attendee,
|
||||
|
@ -32,6 +31,7 @@ import {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
export default class ExchangeCalendarService implements Calendar {
|
||||
private url = "";
|
||||
|
@ -40,7 +40,7 @@ export default class ExchangeCalendarService implements Calendar {
|
|||
private readonly exchangeVersion: ExchangeVersion;
|
||||
private credentials: Record<string, string>;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "exchange2013_calendar";
|
||||
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import {
|
||||
Appointment,
|
||||
Attendee,
|
||||
|
@ -32,6 +31,7 @@ import {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
export default class ExchangeCalendarService implements Calendar {
|
||||
private url = "";
|
||||
|
@ -40,7 +40,7 @@ export default class ExchangeCalendarService implements Calendar {
|
|||
private readonly exchangeVersion: ExchangeVersion;
|
||||
private credentials: Record<string, string>;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
// this.integrationName = CALENDAR_INTEGRATIONS_TYPES.exchange;
|
||||
this.integrationName = "exchange2016_calendar";
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { XhrApi } from "@ewsjs/xhr";
|
||||
import { Credential } from "@prisma/client";
|
||||
import {
|
||||
Appointment,
|
||||
Attendee,
|
||||
|
@ -39,6 +38,7 @@ import {
|
|||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { ExchangeAuthentication } from "../enums";
|
||||
|
||||
|
@ -47,7 +47,7 @@ export default class ExchangeCalendarService implements Calendar {
|
|||
private log: typeof logger;
|
||||
private payload;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "exchange_calendar";
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
this.payload = JSON.parse(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Credential, Prisma } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { calendar_v3, google } from "googleapis";
|
||||
|
||||
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
|
@ -12,6 +12,7 @@ import type {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { getGoogleAppKeys } from "./getGoogleAppKeys";
|
||||
import { googleCredentialSchema } from "./googleCredentialSchema";
|
||||
|
@ -25,13 +26,13 @@ export default class GoogleCalendarService implements Calendar {
|
|||
private auth: { getToken: () => Promise<MyGoogleAuth> };
|
||||
private log: typeof logger;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "google_calendar";
|
||||
this.auth = this.googleAuth(credential);
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
}
|
||||
|
||||
private googleAuth = (credential: Credential) => {
|
||||
private googleAuth = (credential: CredentialPayload) => {
|
||||
const googleCredentials = googleCredentialSchema.parse(credential.key);
|
||||
|
||||
async function getGoogleAuth() {
|
||||
|
|
|
@ -2,7 +2,6 @@ import * as hubspot from "@hubspot/api-client";
|
|||
import { BatchInputPublicAssociation } from "@hubspot/api-client/lib/codegen/crm/associations";
|
||||
import { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts";
|
||||
import { SimplePublicObjectInput } from "@hubspot/api-client/lib/codegen/crm/objects/meetings";
|
||||
import { Credential } from "@prisma/client";
|
||||
|
||||
import { getLocation } from "@calcom/lib/CalEventParser";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
@ -17,6 +16,7 @@ import type {
|
|||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import type { HubspotToken } from "../api/callback";
|
||||
|
@ -31,7 +31,7 @@ export default class HubspotOtherCalendarService implements Calendar {
|
|||
private client_id = "";
|
||||
private client_secret = "";
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "hubspot_other_calendar";
|
||||
|
||||
this.auth = this.hubspotAuth(credential).then((r) => r);
|
||||
|
@ -148,7 +148,7 @@ export default class HubspotOtherCalendarService implements Calendar {
|
|||
return hubspotClient.crm.objects.meetings.basicApi.archive(uid);
|
||||
};
|
||||
|
||||
private hubspotAuth = async (credential: Credential) => {
|
||||
private hubspotAuth = async (credential: CredentialPayload) => {
|
||||
const appKeys = await getAppKeysFromSlug("hubspot");
|
||||
if (typeof appKeys.client_id === "string") this.client_id = appKeys.client_id;
|
||||
if (typeof appKeys.client_secret === "string") this.client_secret = appKeys.client_secret;
|
||||
|
|
|
@ -17,15 +17,17 @@ const Huddle01VideoApiAdapter = (): VideoApiAdapter => {
|
|||
"https://wpss2zlpb9.execute-api.us-east-1.amazonaws.com/new-meeting?utmCampaign=cal.com&utmSource=partner&utmMedium=calendar"
|
||||
);
|
||||
|
||||
const json = await handleErrorsJson(res);
|
||||
const json = await handleErrorsJson<{ url: string }>(res);
|
||||
const { url } = huddle01Schema.parse(json);
|
||||
|
||||
return Promise.resolve({
|
||||
type: "huddle01_video",
|
||||
id: randomString(21),
|
||||
password: "",
|
||||
url,
|
||||
});
|
||||
if (url) {
|
||||
return Promise.resolve({
|
||||
type: "huddle01_video",
|
||||
id: randomString(21),
|
||||
password: "",
|
||||
url,
|
||||
});
|
||||
}
|
||||
return Promise.reject("Url was not received in response body.");
|
||||
},
|
||||
deleteMeeting: async (): Promise<void> => {
|
||||
Promise.resolve();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
|
||||
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
@ -11,6 +9,7 @@ import type {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { handleLarkError, isExpired, LARK_HOST } from "../common";
|
||||
import type {
|
||||
|
@ -36,13 +35,13 @@ export default class LarkCalendarService implements Calendar {
|
|||
private log: typeof logger;
|
||||
auth: { getToken: () => Promise<string> };
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "lark_calendar";
|
||||
this.auth = this.larkAuth(credential);
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
}
|
||||
|
||||
private larkAuth = (credential: Credential) => {
|
||||
private larkAuth = (credential: CredentialPayload) => {
|
||||
const larkAuthCredentials = credential.key as LarkAuthCredentials;
|
||||
return {
|
||||
getToken: () =>
|
||||
|
@ -52,7 +51,7 @@ export default class LarkCalendarService implements Calendar {
|
|||
};
|
||||
};
|
||||
|
||||
private refreshAccessToken = async (credential: Credential) => {
|
||||
private refreshAccessToken = async (credential: CredentialPayload) => {
|
||||
const larkAuthCredentials = credential.key as LarkAuthCredentials;
|
||||
const refreshExpireDate = larkAuthCredentials.refresh_expires_date;
|
||||
const refreshToken = larkAuthCredentials.refresh_token;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Calendar as OfficeCalendar } from "@microsoft/microsoft-graph-types-beta";
|
||||
import { Credential } from "@prisma/client";
|
||||
|
||||
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||
|
@ -13,6 +12,7 @@ import type {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { O365AuthCredentials } from "../types/Office365Calendar";
|
||||
import { getOfficeAppKeys } from "./getOfficeAppKeys";
|
||||
|
@ -45,7 +45,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
auth: { getToken: () => Promise<string> };
|
||||
private apiGraphUrl = "https://graph.microsoft.com/v1.0";
|
||||
|
||||
constructor(credential: Credential) {
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.integrationName = "office365_calendar";
|
||||
this.auth = this.o365Auth(credential);
|
||||
|
||||
|
@ -131,7 +131,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
url: `/me/calendars/${calendarId}/calendarView${filter}`,
|
||||
}));
|
||||
const response = await this.apiGraphBatchCall(requests);
|
||||
const responseBody = await handleErrorsJson(response);
|
||||
const responseBody = await this.handleErrorJsonOffice365Calendar(response);
|
||||
let responseBatchApi: IBatchResponse = { responses: [] };
|
||||
if (typeof responseBody === "string") {
|
||||
responseBatchApi = this.handleTextJsonResponseWithHtmlInBody(responseBody);
|
||||
|
@ -158,12 +158,12 @@ export default class Office365CalendarService implements Calendar {
|
|||
|
||||
async listCalendars(): Promise<IntegrationCalendar[]> {
|
||||
const response = await this.fetcher(`/me/calendars`);
|
||||
let responseBody = await handleErrorsJson(response);
|
||||
let responseBody = await handleErrorsJson<{ value: OfficeCalendar[] }>(response);
|
||||
// If responseBody is valid then parse the JSON text
|
||||
if (typeof responseBody === "string") {
|
||||
responseBody = JSON.parse(responseBody) as { value: OfficeCalendar[] };
|
||||
}
|
||||
return responseBody.value.map((cal: OfficeCalendar) => {
|
||||
return responseBody?.value.map((cal: OfficeCalendar) => {
|
||||
const calendar: IntegrationCalendar = {
|
||||
externalId: cal.id ?? "No Id",
|
||||
integration: this.integrationName,
|
||||
|
@ -175,7 +175,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
});
|
||||
}
|
||||
|
||||
private o365Auth = (credential: Credential) => {
|
||||
private o365Auth = (credential: CredentialPayload) => {
|
||||
const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
|
||||
const o365AuthCredentials = credential.key as O365AuthCredentials;
|
||||
|
||||
|
@ -192,7 +192,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
client_secret,
|
||||
}),
|
||||
});
|
||||
const responseBody = await handleErrorsJson(response);
|
||||
const responseBody = await handleErrorsJson<{ access_token: string; expires_in: number }>(response);
|
||||
o365AuthCredentials.access_token = responseBody.access_token;
|
||||
o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in);
|
||||
await prisma.credential.update({
|
||||
|
@ -283,7 +283,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
}
|
||||
|
||||
const newResponse = await this.apiGraphBatchCall(newLinkRequest);
|
||||
let newResponseBody = await handleErrorsJson(newResponse);
|
||||
let newResponseBody = await handleErrorsJson<IBatchResponse | string>(newResponse);
|
||||
|
||||
if (typeof newResponseBody === "string") {
|
||||
newResponseBody = this.handleTextJsonResponseWithHtmlInBody(newResponseBody);
|
||||
|
@ -324,7 +324,7 @@ export default class Office365CalendarService implements Calendar {
|
|||
await new Promise((r) => setTimeout(r, retryAfterTimeout));
|
||||
|
||||
const newResponses = await this.apiGraphBatchCall(failedRequest);
|
||||
let newResponseBody = await handleErrorsJson(newResponses);
|
||||
let newResponseBody = await handleErrorsJson<IBatchResponse | string>(newResponses);
|
||||
if (typeof newResponseBody === "string") {
|
||||
newResponseBody = this.handleTextJsonResponseWithHtmlInBody(newResponseBody);
|
||||
}
|
||||
|
@ -393,4 +393,21 @@ export default class Office365CalendarService implements Calendar {
|
|||
[]
|
||||
);
|
||||
};
|
||||
|
||||
private handleErrorJsonOffice365Calendar = <Type>(response: Response): Promise<Type | string> => {
|
||||
if (response.headers.get("content-encoding") === "gzip") {
|
||||
return response.text();
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return new Promise((resolve) => resolve({} as Type));
|
||||
}
|
||||
|
||||
if (!response.ok && response.status < 200 && response.status >= 300) {
|
||||
response.json().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
|
@ -32,8 +33,19 @@ interface O365AuthCredentials {
|
|||
ext_expires_in: number;
|
||||
}
|
||||
|
||||
interface ITokenResponse {
|
||||
expiry_date: number;
|
||||
expires_in?: number;
|
||||
token_type: string;
|
||||
scope: string;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
error?: string;
|
||||
error_description?: string;
|
||||
}
|
||||
|
||||
// Checks to see if our O365 user token is valid or if we need to refresh
|
||||
const o365Auth = async (credential: Credential) => {
|
||||
const o365Auth = async (credential: CredentialPayload) => {
|
||||
const appKeys = await getAppKeysFromSlug("msteams");
|
||||
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||
|
@ -55,13 +67,14 @@ const o365Auth = async (credential: Credential) => {
|
|||
client_secret,
|
||||
}),
|
||||
});
|
||||
const responseBody = await handleErrorsJson(response);
|
||||
if (responseBody.error) {
|
||||
|
||||
const responseBody = await handleErrorsJson<ITokenResponse>(response);
|
||||
if (responseBody?.error) {
|
||||
console.error(responseBody);
|
||||
throw new HttpError({ statusCode: 500, message: "Error contacting MS Teams" });
|
||||
}
|
||||
// set expiry date as offset from current time.
|
||||
responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000);
|
||||
responseBody.expiry_date = Math.round(Date.now() + (responseBody?.expires_in || 0) * 1000);
|
||||
delete responseBody.expires_in;
|
||||
// Store new tokens in database.
|
||||
await prisma.credential.update({
|
||||
|
@ -69,7 +82,8 @@ const o365Auth = async (credential: Credential) => {
|
|||
id: credential.id,
|
||||
},
|
||||
data: {
|
||||
key: responseBody,
|
||||
// @NOTE: prisma doesn't know key its a JSON so do as responseBody
|
||||
key: responseBody as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
o365AuthCredentials.expiry_date = responseBody.expiry_date;
|
||||
|
@ -85,7 +99,7 @@ const o365Auth = async (credential: Credential) => {
|
|||
};
|
||||
};
|
||||
|
||||
const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
||||
const TeamsVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => {
|
||||
const auth = o365Auth(credential);
|
||||
|
||||
const translateEvent = (event: CalendarEvent) => {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
|
@ -17,11 +18,25 @@ interface TandemToken {
|
|||
access_token: string;
|
||||
}
|
||||
|
||||
interface ITandemRefreshToken {
|
||||
expires_in?: number;
|
||||
expiry_date?: number;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
interface ITandemCreateMeetingResponse {
|
||||
data: {
|
||||
id: string;
|
||||
event_link: string;
|
||||
};
|
||||
}
|
||||
|
||||
let client_id = "";
|
||||
let client_secret = "";
|
||||
let base_url = "";
|
||||
|
||||
const tandemAuth = async (credential: Credential) => {
|
||||
const tandemAuth = async (credential: CredentialPayload) => {
|
||||
const appKeys = await getAppKeysFromSlug("tandem");
|
||||
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||
|
@ -33,34 +48,33 @@ const tandemAuth = async (credential: Credential) => {
|
|||
const credentialKey = credential.key as unknown as TandemToken;
|
||||
const isTokenValid = (token: TandemToken) => token && token.access_token && token.expiry_date < Date.now();
|
||||
|
||||
const refreshAccessToken = (refreshToken: string) => {
|
||||
fetch(`${base_url}/api/v1/oauth/v2/token`, {
|
||||
const refreshAccessToken = async (refreshToken: string) => {
|
||||
const result = await fetch(`${base_url}/api/v1/oauth/v2/token`, {
|
||||
method: "POST",
|
||||
body: new URLSearchParams({
|
||||
client_id,
|
||||
client_secret,
|
||||
code: refreshToken,
|
||||
}),
|
||||
})
|
||||
.then(handleErrorsJson)
|
||||
.then(async (responseBody) => {
|
||||
// set expiry date as offset from current time.
|
||||
responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000);
|
||||
delete responseBody.expires_in;
|
||||
// Store new tokens in database.
|
||||
await prisma.credential.update({
|
||||
where: {
|
||||
id: credential.id,
|
||||
},
|
||||
data: {
|
||||
key: responseBody,
|
||||
},
|
||||
});
|
||||
credentialKey.expiry_date = responseBody.expiry_date;
|
||||
credentialKey.access_token = responseBody.access_token;
|
||||
credentialKey.refresh_token = responseBody.refresh_token;
|
||||
return credentialKey.access_token;
|
||||
});
|
||||
});
|
||||
const responseBody = await handleErrorsJson<ITandemRefreshToken>(result);
|
||||
|
||||
// set expiry date as offset from current time.
|
||||
responseBody.expiry_date = Math.round(Date.now() + (responseBody.expires_in || 0) * 1000);
|
||||
delete responseBody.expires_in;
|
||||
// Store new tokens in database.
|
||||
await prisma.credential.update({
|
||||
where: {
|
||||
id: credential.id,
|
||||
},
|
||||
data: {
|
||||
key: responseBody as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
credentialKey.expiry_date = responseBody.expiry_date;
|
||||
credentialKey.access_token = responseBody.access_token;
|
||||
credentialKey.refresh_token = responseBody.refresh_token;
|
||||
return credentialKey.access_token;
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -71,7 +85,7 @@ const tandemAuth = async (credential: Credential) => {
|
|||
};
|
||||
};
|
||||
|
||||
const TandemVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
||||
const TandemVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => {
|
||||
const auth = tandemAuth(credential);
|
||||
|
||||
const _parseDate = (date: string) => {
|
||||
|
@ -91,7 +105,7 @@ const TandemVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
});
|
||||
};
|
||||
|
||||
const _translateResult = (result: { data: { id: string; event_link: string } }) => {
|
||||
const _translateResult = (result: ITandemCreateMeetingResponse) => {
|
||||
return {
|
||||
type: "tandem_video",
|
||||
id: result.data.id as string,
|
||||
|
@ -115,9 +129,10 @@ const TandemVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
"Content-Type": "application/json",
|
||||
},
|
||||
body: _translateEvent(event, "meeting"),
|
||||
}).then(handleErrorsJson);
|
||||
});
|
||||
const responseBody = await handleErrorsJson<ITandemCreateMeetingResponse>(result);
|
||||
|
||||
return Promise.resolve(_translateResult(result));
|
||||
return Promise.resolve(_translateResult(responseBody));
|
||||
},
|
||||
|
||||
deleteMeeting: async (uid: string): Promise<void> => {
|
||||
|
@ -143,9 +158,10 @@ const TandemVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
"Content-Type": "application/json",
|
||||
},
|
||||
body: _translateEvent(event, "updates"),
|
||||
}).then(handleErrorsJson);
|
||||
});
|
||||
const responseBody = await handleErrorsJson<ITandemCreateMeetingResponse>(result);
|
||||
|
||||
return Promise.resolve(_translateResult(result));
|
||||
return Promise.resolve(_translateResult(responseBody));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { handleErrorsJson } from "@calcom/lib/errors";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Credential } from "@calcom/prisma/client";
|
||||
import { Frequency } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
|
@ -69,7 +69,7 @@ const zoomRefreshedTokenSchema = z.object({
|
|||
scope: z.string(),
|
||||
});
|
||||
|
||||
const zoomAuth = (credential: Credential) => {
|
||||
const zoomAuth = (credential: CredentialPayload) => {
|
||||
const refreshAccessToken = async (refreshToken: string) => {
|
||||
const { client_id, client_secret } = await getZoomAppKeys();
|
||||
const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
|
||||
|
@ -86,8 +86,13 @@ const zoomAuth = (credential: Credential) => {
|
|||
}),
|
||||
});
|
||||
|
||||
const responseBody = await handleZoomResponse(response);
|
||||
const responseBody = await handleZoomResponse(response, credential.id);
|
||||
|
||||
if (responseBody.error) {
|
||||
if (responseBody.error === "invalid_grant") {
|
||||
return Promise.reject(new Error("Invalid grant for Cal.com zoom app"));
|
||||
}
|
||||
}
|
||||
// We check the if the new credentials matches the expected response structure
|
||||
const parsedToken = zoomRefreshedTokenSchema.safeParse(responseBody);
|
||||
|
||||
|
@ -138,7 +143,7 @@ type ZoomRecurrence = {
|
|||
monthly_day?: number; // 1-31
|
||||
};
|
||||
|
||||
const ZoomVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
||||
const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => {
|
||||
const translateEvent = (event: CalendarEvent) => {
|
||||
const getRecurrence = ({
|
||||
recurringEvent,
|
||||
|
@ -232,7 +237,7 @@ const ZoomVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
...options?.headers,
|
||||
},
|
||||
});
|
||||
const responseBody = await handleErrorsJson(response);
|
||||
const responseBody = await handleZoomResponse(response, credential.id);
|
||||
return responseBody;
|
||||
};
|
||||
|
||||
|
@ -241,6 +246,7 @@ const ZoomVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
try {
|
||||
// TODO Possibly implement pagination for cases when there are more than 300 meetings already scheduled.
|
||||
const responseBody = await fetchZoomApi("users/me/meetings?type=scheduled&page_size=300");
|
||||
|
||||
const data = zoomMeetingsSchema.parse(responseBody);
|
||||
return data.meetings.map((meeting) => ({
|
||||
start: meeting.start_time,
|
||||
|
@ -254,13 +260,19 @@ const ZoomVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
},
|
||||
createMeeting: async (event: CalendarEvent): Promise<VideoCallData> => {
|
||||
try {
|
||||
const response: ZoomEventResult = await fetchZoomApi("users/me/meetings", {
|
||||
const response = await fetchZoomApi("users/me/meetings", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(translateEvent(event)),
|
||||
});
|
||||
if (response.error) {
|
||||
if (response.error === "invalid_grant") {
|
||||
await invalidateCredential(credential.id);
|
||||
return Promise.reject(new Error("Invalid grant for Cal.com zoom app"));
|
||||
}
|
||||
}
|
||||
|
||||
const result = zoomEventResultSchema.parse(response);
|
||||
|
||||
|
@ -280,42 +292,73 @@ const ZoomVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
|||
}
|
||||
},
|
||||
deleteMeeting: async (uid: string): Promise<void> => {
|
||||
await fetchZoomApi(`meetings/${uid}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
try {
|
||||
await fetchZoomApi(`meetings/${uid}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error("Failed to delete meeting"));
|
||||
}
|
||||
},
|
||||
updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent): Promise<VideoCallData> => {
|
||||
await fetchZoomApi(`meetings/${bookingRef.uid}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(translateEvent(event)),
|
||||
});
|
||||
try {
|
||||
await fetchZoomApi(`meetings/${bookingRef.uid}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(translateEvent(event)),
|
||||
});
|
||||
|
||||
return Promise.resolve({
|
||||
type: "zoom_video",
|
||||
id: bookingRef.meetingId as string,
|
||||
password: bookingRef.meetingPassword as string,
|
||||
url: bookingRef.meetingUrl as string,
|
||||
});
|
||||
return Promise.resolve({
|
||||
type: "zoom_video",
|
||||
id: bookingRef.meetingId as string,
|
||||
password: bookingRef.meetingPassword as string,
|
||||
url: bookingRef.meetingUrl as string,
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error("Failed to update meeting"));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const handleZoomResponse = async (response: Response) => {
|
||||
if (response.headers.get("content-encoding") === "gzip") {
|
||||
const handleZoomResponse = async (response: Response, credentialId: Credential["id"]) => {
|
||||
let _response = response.clone();
|
||||
if (_response.headers.get("content-encoding") === "gzip") {
|
||||
const responseString = await response.text();
|
||||
return JSON.parse(responseString);
|
||||
_response = JSON.parse(responseString);
|
||||
}
|
||||
if (!response.ok && response.status < 200 && response.status >= 300) {
|
||||
response.json().then(console.log);
|
||||
if (!response.ok || (response.status < 200 && response.status >= 300)) {
|
||||
const responseBody = await _response.json();
|
||||
|
||||
if ((response && response.status === 124) || responseBody.error === "invalid_grant") {
|
||||
await invalidateCredential(credentialId);
|
||||
}
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const invalidateCredential = async (credentialId: Credential["id"]) => {
|
||||
const credential = await prisma.credential.findUnique({
|
||||
where: {
|
||||
id: credentialId,
|
||||
},
|
||||
});
|
||||
|
||||
if (credential) {
|
||||
await prisma.credential.update({
|
||||
where: {
|
||||
id: credentialId,
|
||||
},
|
||||
data: {
|
||||
invalid: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default ZoomVideoApiAdapter;
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import { Credential, SelectedCalendar } from "@prisma/client";
|
||||
import { SelectedCalendar } from "@prisma/client";
|
||||
import { createHash } from "crypto";
|
||||
import _ from "lodash";
|
||||
import cache from "memory-cache";
|
||||
|
||||
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import type { ExtendedCredential } from "@calcom/core/EventManager";
|
||||
import { getUid } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import { App } from "@calcom/types/App";
|
||||
import type { CalendarEvent, EventBusyDate, NewCalendarEventType } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { EventResult } from "@calcom/types/EventManager";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
|
||||
|
||||
export const getCalendarCredentials = (credentials: Array<Credential>) => {
|
||||
export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
|
||||
const calendarCredentials = getApps(credentials)
|
||||
.filter((app) => app.type.endsWith("_calendar"))
|
||||
.flatMap((app) => {
|
||||
|
@ -105,8 +104,8 @@ export const getConnectedCalendars = async (
|
|||
*/
|
||||
const cleanIntegrationKeys = (
|
||||
appIntegration: ReturnType<typeof getCalendarCredentials>[number]["integration"] & {
|
||||
credentials?: Array<Credential>;
|
||||
credential: Credential;
|
||||
credentials?: Array<CredentialPayload>;
|
||||
credential: CredentialPayload;
|
||||
}
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
@ -117,7 +116,7 @@ const cleanIntegrationKeys = (
|
|||
const CACHING_TIME = 30_000; // 30 seconds
|
||||
|
||||
const getCachedResults = async (
|
||||
withCredentials: Credential[],
|
||||
withCredentials: CredentialPayload[],
|
||||
dateFrom: string,
|
||||
dateTo: string,
|
||||
selectedCalendars: SelectedCalendar[]
|
||||
|
@ -170,7 +169,7 @@ const getCachedResults = async (
|
|||
};
|
||||
|
||||
export const getBusyCalendarTimes = async (
|
||||
withCredentials: Credential[],
|
||||
withCredentials: CredentialPayload[],
|
||||
dateFrom: string,
|
||||
dateTo: string,
|
||||
selectedCalendars: SelectedCalendar[]
|
||||
|
@ -185,7 +184,7 @@ export const getBusyCalendarTimes = async (
|
|||
};
|
||||
|
||||
export const createEvent = async (
|
||||
credential: ExtendedCredential,
|
||||
credential: CredentialWithAppName,
|
||||
calEvent: CalendarEvent
|
||||
): Promise<EventResult<NewCalendarEventType>> => {
|
||||
const uid: string = getUid(calEvent);
|
||||
|
@ -228,7 +227,7 @@ export const createEvent = async (
|
|||
};
|
||||
|
||||
export const updateEvent = async (
|
||||
credential: ExtendedCredential,
|
||||
credential: CredentialWithAppName,
|
||||
calEvent: CalendarEvent,
|
||||
bookingRefUid: string | null,
|
||||
externalCalendarId: string | null
|
||||
|
@ -266,7 +265,11 @@ export const updateEvent = async (
|
|||
};
|
||||
};
|
||||
|
||||
export const deleteEvent = (credential: Credential, uid: string, event: CalendarEvent): Promise<unknown> => {
|
||||
export const deleteEvent = (
|
||||
credential: CredentialPayload,
|
||||
uid: string,
|
||||
event: CalendarEvent
|
||||
): Promise<unknown> => {
|
||||
const calendar = getCalendar(credential);
|
||||
if (calendar) {
|
||||
return calendar.deleteEvent(uid, event);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Credential, DestinationCalendar } from "@prisma/client";
|
||||
import { DestinationCalendar } from "@prisma/client";
|
||||
import merge from "lodash/merge";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
@ -9,6 +9,7 @@ import getApps from "@calcom/app-store/utils";
|
|||
import prisma from "@calcom/prisma";
|
||||
import { createdEventSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { Event } from "@calcom/types/Event";
|
||||
import type {
|
||||
CreateUpdateResult,
|
||||
|
@ -58,17 +59,15 @@ export const processLocation = (event: CalendarEvent): CalendarEvent => {
|
|||
};
|
||||
|
||||
type EventManagerUser = {
|
||||
credentials: Credential[];
|
||||
credentials: CredentialPayload[];
|
||||
destinationCalendar: DestinationCalendar | null;
|
||||
};
|
||||
|
||||
type createdEventSchema = z.infer<typeof createdEventSchema>;
|
||||
|
||||
export type ExtendedCredential = Credential & { appName: string };
|
||||
|
||||
export default class EventManager {
|
||||
calendarCredentials: ExtendedCredential[];
|
||||
videoCredentials: ExtendedCredential[];
|
||||
calendarCredentials: CredentialWithAppName[];
|
||||
videoCredentials: CredentialWithAppName[];
|
||||
|
||||
/**
|
||||
* Takes an array of credentials and initializes a new instance of the EventManager.
|
||||
|
@ -359,7 +358,7 @@ export default class EventManager {
|
|||
* @private
|
||||
*/
|
||||
|
||||
private getVideoCredential(event: CalendarEvent): ExtendedCredential | undefined {
|
||||
private getVideoCredential(event: CalendarEvent): CredentialWithAppName | undefined {
|
||||
if (!event.location) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -373,7 +372,7 @@ export default class EventManager {
|
|||
.sort((a, b) => {
|
||||
return b.id - a.id;
|
||||
})
|
||||
.find((credential: Credential) => credential.type.includes(integrationName));
|
||||
.find((credential: CredentialPayload) => credential.type.includes(integrationName));
|
||||
|
||||
/**
|
||||
* This might happen if someone tries to use a location with a missing credential, so we fallback to Cal Video.
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { Credential } from "@prisma/client";
|
||||
import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import appStore from "@calcom/app-store";
|
||||
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
|
||||
import type { ExtendedCredential } from "@calcom/core/EventManager";
|
||||
import { sendBrokenIntegrationEmail } from "@calcom/emails";
|
||||
import { getUid } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoApiAdapterFactory, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
|
@ -17,7 +16,7 @@ const log = logger.getChildLogger({ prefix: ["[lib] videoClient"] });
|
|||
const translator = short();
|
||||
|
||||
// factory
|
||||
const getVideoAdapters = (withCredentials: Credential[]): VideoApiAdapter[] =>
|
||||
const getVideoAdapters = (withCredentials: CredentialPayload[]): VideoApiAdapter[] =>
|
||||
withCredentials.reduce<VideoApiAdapter[]>((acc, cred) => {
|
||||
const appName = cred.type.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
|
||||
const app = appStore[appName as keyof typeof appStore];
|
||||
|
@ -30,12 +29,12 @@ const getVideoAdapters = (withCredentials: Credential[]): VideoApiAdapter[] =>
|
|||
return acc;
|
||||
}, []);
|
||||
|
||||
const getBusyVideoTimes = (withCredentials: Credential[]) =>
|
||||
const getBusyVideoTimes = (withCredentials: CredentialPayload[]) =>
|
||||
Promise.all(getVideoAdapters(withCredentials).map((c) => c?.getAvailability())).then((results) =>
|
||||
results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[])
|
||||
);
|
||||
|
||||
const createMeeting = async (credential: ExtendedCredential, calEvent: CalendarEvent) => {
|
||||
const createMeeting = async (credential: CredentialWithAppName, calEvent: CalendarEvent) => {
|
||||
const uid: string = getUid(calEvent);
|
||||
|
||||
if (!credential) {
|
||||
|
@ -82,7 +81,7 @@ const createMeeting = async (credential: ExtendedCredential, calEvent: CalendarE
|
|||
};
|
||||
|
||||
const updateMeeting = async (
|
||||
credential: ExtendedCredential,
|
||||
credential: CredentialWithAppName,
|
||||
calEvent: CalendarEvent,
|
||||
bookingRef: PartialReference | null
|
||||
): Promise<EventResult<VideoCallData>> => {
|
||||
|
@ -121,7 +120,7 @@ const updateMeeting = async (
|
|||
};
|
||||
};
|
||||
|
||||
const deleteMeeting = (credential: Credential, uid: string): Promise<unknown> => {
|
||||
const deleteMeeting = (credential: CredentialPayload, uid: string): Promise<unknown> => {
|
||||
if (credential) {
|
||||
const videoAdapter = getVideoAdapters([credential])[0];
|
||||
// There are certain video apps with no video adapter defined. e.g. riverby,whereby
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/triple-slash-reference */
|
||||
/// <reference path="../types/ical.d.ts"/>
|
||||
import { Credential, Prisma } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import ICAL from "ical.js";
|
||||
import type { Attendee, DateArray, DurationObject, Person } from "ics";
|
||||
import { createEvent } from "ics";
|
||||
|
@ -26,6 +26,7 @@ import type {
|
|||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import { getLocation, getRichDescription } from "./CalEventParser";
|
||||
import { symmetricDecrypt } from "./crypto";
|
||||
|
@ -68,7 +69,7 @@ export default abstract class BaseCalendarService implements Calendar {
|
|||
protected integrationName = "";
|
||||
private log: typeof logger;
|
||||
|
||||
constructor(credential: Credential, integrationName: string, url?: string) {
|
||||
constructor(credential: CredentialPayload, integrationName: string, url?: string) {
|
||||
this.integrationName = integrationName;
|
||||
|
||||
const {
|
||||
|
|
|
@ -10,13 +10,16 @@ export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: numb
|
|||
return new Error(`Unhandled error of type '${typeof cause}''`);
|
||||
}
|
||||
|
||||
export function handleErrorsJson(response: Response) {
|
||||
export async function handleErrorsJson<Type>(response: Response): Promise<Type> {
|
||||
if (response.headers.get("content-encoding") === "gzip") {
|
||||
return response.text();
|
||||
const responseText = await response.text();
|
||||
return new Promise((resolve) => resolve(JSON.parse(responseText)));
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return new Promise((resolve) => resolve({}));
|
||||
return new Promise((resolve) => resolve({} as Type));
|
||||
}
|
||||
|
||||
if (!response.ok && response.status < 200 && response.status >= 300) {
|
||||
response.json().then(console.log);
|
||||
throw Error(response.statusText);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Credential" ADD COLUMN "invalid" BOOLEAN;
|
|
@ -100,6 +100,7 @@ model Credential {
|
|||
// How to make it a required column?
|
||||
appId String?
|
||||
destinationCalendars DestinationCalendar[]
|
||||
invalid Boolean?
|
||||
}
|
||||
|
||||
enum UserPlan {
|
||||
|
|
|
@ -58,6 +58,7 @@ async function getUserFromSession({
|
|||
key: true,
|
||||
userId: true,
|
||||
appId: true,
|
||||
invalid: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "asc",
|
||||
|
|
|
@ -772,11 +772,19 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
const { variant, exclude, onlyInstalled } = input;
|
||||
const { credentials } = user;
|
||||
let apps = getApps(credentials).map(
|
||||
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => ({
|
||||
...app,
|
||||
credentialIds: credentials.filter((c) => c.type === app.type).map((c) => c.id),
|
||||
})
|
||||
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => {
|
||||
const credentialIds = credentials.filter((c) => c.type === app.type).map((c) => c.id);
|
||||
const invalidCredentialIds = credentials
|
||||
.filter((c) => c.type === app.type && c.invalid)
|
||||
.map((c) => c.id);
|
||||
return {
|
||||
...app,
|
||||
credentialIds,
|
||||
invalidCredentialIds,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
if (exclude) {
|
||||
// exclusion filter
|
||||
apps = apps.filter((item) => (exclude ? !exclude.includes(item.variant) : true));
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { Prisma } from ".prisma/client";
|
||||
|
||||
/*
|
||||
* The logic on this it's just using Credential Type doesn't reflect that some fields can be
|
||||
* null sometimes, so with this we should get correct type.
|
||||
* Also there may be a better place to save this.
|
||||
*/
|
||||
export type CredentialPayload = Prisma.CredentialGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
appId: true;
|
||||
type: true;
|
||||
userId: true;
|
||||
key: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type CredentialWithAppName = CredentialPayload & { appName: string };
|
|
@ -1,6 +1,5 @@
|
|||
import type { Credential } from "@prisma/client";
|
||||
|
||||
import type { EventBusyDate } from "./Calendar";
|
||||
import { CredentialPayload } from "./Credential";
|
||||
|
||||
export interface VideoCallData {
|
||||
type: string;
|
||||
|
@ -22,4 +21,4 @@ export type VideoApiAdapter =
|
|||
}
|
||||
| undefined;
|
||||
|
||||
export type VideoApiAdapterFactory = (credential: Credential) => VideoApiAdapter;
|
||||
export type VideoApiAdapterFactory = (credential: CredentialPayload) => VideoApiAdapter;
|
||||
|
|
|
@ -30,8 +30,8 @@ import Dropdown, {
|
|||
import { Icon } from "@calcom/ui/Icon";
|
||||
import TimezoneChangeDialog from "@calcom/ui/TimezoneChangeDialog";
|
||||
import Button from "@calcom/ui/v2/core/Button";
|
||||
import Tips from "@calcom/ui/v2/modules/tips/Tips";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import Tips from "@calcom/ui/v2/modules/tips/Tips";
|
||||
|
||||
/* TODO: Get this from endpoint */
|
||||
import pkg from "../../../../apps/web/package.json";
|
||||
|
|
Loading…
Reference in New Issue
Block a user