App env fixes
This commit is contained in:
parent
de4b3c186e
commit
6011b440a8
17 changed files with 169 additions and 138 deletions
|
@ -112,7 +112,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
||||||
credentialIds: number[];
|
credentialIds: number[];
|
||||||
type: App["type"];
|
type: App["type"];
|
||||||
isGlobal?: boolean;
|
isGlobal?: boolean;
|
||||||
installed: boolean;
|
installed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const [credentialId] = props.credentialIds;
|
const [credentialId] = props.credentialIds;
|
||||||
|
|
|
@ -2137,8 +2137,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
const t = await getTranslation(currentUser?.locale ?? "en", "common");
|
const t = await getTranslation(currentUser?.locale ?? "en", "common");
|
||||||
const integrations = getApps(credentials);
|
const integrations = getApps(credentials);
|
||||||
const locationOptions = getLocationOptions(integrations, t);
|
const locationOptions = getLocationOptions(integrations, t);
|
||||||
|
const hasPaymentIntegration = !!credentials.find((credential) => credential.type === "stripe_payment");
|
||||||
const hasPaymentIntegration = hasIntegration(integrations, "stripe_payment");
|
|
||||||
const currency =
|
const currency =
|
||||||
(credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData)
|
(credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData)
|
||||||
?.default_currency || "usd";
|
?.default_currency || "usd";
|
||||||
|
|
|
@ -5,7 +5,6 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
name: "Giphy",
|
name: "Giphy",
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
installed: !!process.env.GIPHY_API_KEY,
|
|
||||||
category: "other",
|
category: "other",
|
||||||
// If using static next public folder, can then be referenced from the base URL (/).
|
// If using static next public folder, can then be referenced from the base URL (/).
|
||||||
imageSrc: "/api/app-store/giphy/icon.svg",
|
imageSrc: "/api/app-store/giphy/icon.svg",
|
||||||
|
|
|
@ -5,7 +5,6 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
name: "HubSpot CRM",
|
name: "HubSpot CRM",
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
installed: !!(process.env.HUBSPOT_CLIENT_ID && process.env.HUBSPOT_CLIENT_SECRET),
|
|
||||||
type: "hubspot_other_calendar",
|
type: "hubspot_other_calendar",
|
||||||
imageSrc: "/api/app-store/hubspotothercalendar/icon.svg",
|
imageSrc: "/api/app-store/hubspotothercalendar/icon.svg",
|
||||||
variant: "other_calendar",
|
variant: "other_calendar",
|
||||||
|
|
|
@ -3,20 +3,21 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"];
|
const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"];
|
||||||
|
|
||||||
const client_id = process.env.HUBSPOT_CLIENT_ID;
|
let client_id = "";
|
||||||
const hubspotClient = new hubspot.Client();
|
const hubspotClient = new hubspot.Client();
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!client_id) {
|
if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" });
|
||||||
res.status(400).json({ message: "HubSpot client id missing." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method === "GET") {
|
const appKeys = await getAppKeysFromSlug("hubspot");
|
||||||
const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback";
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" "));
|
if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." });
|
||||||
res.status(200).json({ url });
|
|
||||||
}
|
const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback";
|
||||||
|
const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" "));
|
||||||
|
res.status(200).json({ url });
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
name: "Office 365 / Outlook.com Calendar",
|
name: "Office 365 / Outlook.com Calendar",
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
|
|
||||||
type: "office365_calendar",
|
type: "office365_calendar",
|
||||||
title: "Office 365 / Outlook.com Calendar",
|
title: "Office 365 / Outlook.com Calendar",
|
||||||
imageSrc: "/api/app-store/office365calendar/icon.svg",
|
imageSrc: "/api/app-store/office365calendar/icon.svg",
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { stringify } from "querystring";
|
import { stringify } from "querystring";
|
||||||
|
|
||||||
import { BASE_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
|
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
|
||||||
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"];
|
const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"];
|
||||||
|
|
||||||
|
let client_id = "";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
|
const appKeys = await getAppKeysFromSlug("office365-calendar");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (!client_id) return res.status(400).json({ message: "Office 365 client_id missing." });
|
||||||
const state = encodeOAuthState(req);
|
const state = encodeOAuthState(req);
|
||||||
const params = {
|
const params = {
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
scope: scopes.join(" "),
|
scope: scopes.join(" "),
|
||||||
client_id: process.env.MS_GRAPH_CLIENT_ID,
|
client_id,
|
||||||
redirect_uri: BASE_URL + "/api/integrations/office365calendar/callback",
|
redirect_uri: WEBAPP_URL + "/api/integrations/office365calendar/callback",
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
const query = stringify(params);
|
const query = stringify(params);
|
||||||
|
|
|
@ -3,39 +3,40 @@ import { Credential } from "@prisma/client";
|
||||||
|
|
||||||
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
|
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
|
||||||
import type {
|
import type {
|
||||||
Calendar,
|
Calendar,
|
||||||
CalendarEvent,
|
CalendarEvent,
|
||||||
IntegrationCalendar,
|
|
||||||
BatchResponse,
|
|
||||||
EventBusyDate,
|
EventBusyDate,
|
||||||
|
IntegrationCalendar,
|
||||||
NewCalendarEventType,
|
NewCalendarEventType,
|
||||||
} from "@calcom/types/Calendar";
|
} from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
import { O365AuthCredentials } from "../types/Office365Calendar";
|
import { O365AuthCredentials } from "../types/Office365Calendar";
|
||||||
|
|
||||||
const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || "";
|
let client_id = "";
|
||||||
const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || "";
|
let client_secret = "";
|
||||||
|
|
||||||
export default class Office365CalendarService implements Calendar {
|
export default class Office365CalendarService implements Calendar {
|
||||||
private url = "";
|
private url = "";
|
||||||
private integrationName = "";
|
private integrationName = "";
|
||||||
private log: typeof logger;
|
private log: typeof logger;
|
||||||
auth: { getToken: () => Promise<string> };
|
auth: Promise<{ getToken: () => Promise<string> }>;
|
||||||
|
|
||||||
constructor(credential: Credential) {
|
constructor(credential: Credential) {
|
||||||
this.integrationName = "office365_calendar";
|
this.integrationName = "office365_calendar";
|
||||||
this.auth = this.o365Auth(credential);
|
this.auth = this.o365Auth(credential).then((t) => t);
|
||||||
|
|
||||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
||||||
try {
|
try {
|
||||||
const accessToken = await this.auth.getToken();
|
const accessToken = await (await this.auth).getToken();
|
||||||
|
|
||||||
const calendarId = event.destinationCalendar?.externalId
|
const calendarId = event.destinationCalendar?.externalId
|
||||||
? `${event.destinationCalendar.externalId}/`
|
? `${event.destinationCalendar.externalId}/`
|
||||||
|
@ -60,7 +61,7 @@ export default class Office365CalendarService implements Calendar {
|
||||||
|
|
||||||
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
|
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const accessToken = await this.auth.getToken();
|
const accessToken = await (await this.auth).getToken();
|
||||||
|
|
||||||
const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
|
const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
@ -81,7 +82,7 @@ export default class Office365CalendarService implements Calendar {
|
||||||
|
|
||||||
async deleteEvent(uid: string): Promise<void> {
|
async deleteEvent(uid: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const accessToken = await this.auth.getToken();
|
const accessToken = await (await this.auth).getToken();
|
||||||
|
|
||||||
const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
|
const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
@ -109,9 +110,9 @@ export default class Office365CalendarService implements Calendar {
|
||||||
const filter = `?startdatetime=${encodeURIComponent(
|
const filter = `?startdatetime=${encodeURIComponent(
|
||||||
dateFromParsed.toISOString()
|
dateFromParsed.toISOString()
|
||||||
)}&enddatetime=${encodeURIComponent(dateToParsed.toISOString())}`;
|
)}&enddatetime=${encodeURIComponent(dateToParsed.toISOString())}`;
|
||||||
return this.auth
|
return (await this.auth)
|
||||||
.getToken()
|
.getToken()
|
||||||
.then((accessToken) => {
|
.then(async (accessToken) => {
|
||||||
const selectedCalendarIds = selectedCalendars
|
const selectedCalendarIds = selectedCalendars
|
||||||
.filter((e) => e.integration === this.integrationName)
|
.filter((e) => e.integration === this.integrationName)
|
||||||
.map((e) => e.externalId)
|
.map((e) => e.externalId)
|
||||||
|
@ -121,50 +122,44 @@ export default class Office365CalendarService implements Calendar {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const ids = await (selectedCalendarIds.length === 0
|
||||||
selectedCalendarIds.length === 0
|
? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || [])
|
||||||
? this.listCalendars().then((cals) => cals.map((e) => e.externalId).filter(Boolean) || [])
|
: Promise.resolve(selectedCalendarIds));
|
||||||
: Promise.resolve(selectedCalendarIds)
|
const requests = ids.map((calendarId, id) => ({
|
||||||
).then((ids) => {
|
id,
|
||||||
const requests = ids.map((calendarId, id) => ({
|
method: "GET",
|
||||||
id,
|
url: `/me/calendars/${calendarId}/calendarView${filter}`,
|
||||||
method: "GET",
|
}));
|
||||||
url: `/me/calendars/${calendarId}/calendarView${filter}`,
|
const response = await fetch("https://graph.microsoft.com/v1.0/$batch", {
|
||||||
}));
|
method: "POST",
|
||||||
|
headers: {
|
||||||
return fetch("https://graph.microsoft.com/v1.0/$batch", {
|
Authorization: "Bearer " + accessToken,
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
Authorization: "Bearer " + accessToken,
|
body: JSON.stringify({ requests }),
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ requests }),
|
|
||||||
})
|
|
||||||
.then(handleErrorsJson)
|
|
||||||
.then((responseBody: BatchResponse) =>
|
|
||||||
responseBody.responses.reduce(
|
|
||||||
(acc: BufferedBusyTime[], subResponse) =>
|
|
||||||
acc.concat(
|
|
||||||
subResponse.body.value.map((evt) => {
|
|
||||||
return {
|
|
||||||
start: evt.start.dateTime + "Z",
|
|
||||||
end: evt.end.dateTime + "Z",
|
|
||||||
};
|
|
||||||
})
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
const responseBody = await handleErrorsJson(response);
|
||||||
|
return responseBody.responses.reduce(
|
||||||
|
(acc: BufferedBusyTime[], subResponse: { body: { value: any[] } }) =>
|
||||||
|
acc.concat(
|
||||||
|
subResponse.body.value.map((evt) => {
|
||||||
|
return {
|
||||||
|
start: evt.start.dateTime + "Z",
|
||||||
|
end: evt.end.dateTime + "Z",
|
||||||
|
};
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err: unknown) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return Promise.reject([]);
|
return Promise.reject([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async listCalendars(): Promise<IntegrationCalendar[]> {
|
async listCalendars(): Promise<IntegrationCalendar[]> {
|
||||||
return this.auth.getToken().then((accessToken) =>
|
return (await this.auth).getToken().then((accessToken) =>
|
||||||
fetch("https://graph.microsoft.com/v1.0/me/calendars", {
|
fetch("https://graph.microsoft.com/v1.0/me/calendars", {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -187,38 +182,41 @@ export default class Office365CalendarService implements Calendar {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private o365Auth = (credential: Credential) => {
|
private o365Auth = async (credential: Credential) => {
|
||||||
|
const appKeys = await getAppKeysFromSlug("office365-calendar");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||||
|
if (!client_id) throw new HttpError({ statusCode: 400, message: "office365 client_id missing." });
|
||||||
|
if (!client_secret) throw new HttpError({ statusCode: 400, message: "office365 client_secret missing." });
|
||||||
|
|
||||||
const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
|
const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
|
||||||
|
|
||||||
const o365AuthCredentials = credential.key as O365AuthCredentials;
|
const o365AuthCredentials = credential.key as O365AuthCredentials;
|
||||||
|
|
||||||
const refreshAccessToken = (refreshToken: string) => {
|
const refreshAccessToken = async (refreshToken: string) => {
|
||||||
return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
|
const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
scope: "User.Read Calendars.Read Calendars.ReadWrite",
|
scope: "User.Read Calendars.Read Calendars.ReadWrite",
|
||||||
client_id: MS_GRAPH_CLIENT_ID,
|
client_id,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
client_secret: MS_GRAPH_CLIENT_SECRET,
|
client_secret,
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
.then(handleErrorsJson)
|
const responseBody = await handleErrorsJson(response);
|
||||||
.then((responseBody) => {
|
o365AuthCredentials.access_token = responseBody.access_token;
|
||||||
o365AuthCredentials.access_token = responseBody.access_token;
|
o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in);
|
||||||
o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in);
|
await prisma.credential.update({
|
||||||
return prisma.credential
|
where: {
|
||||||
.update({
|
id: credential.id,
|
||||||
where: {
|
},
|
||||||
id: credential.id,
|
data: {
|
||||||
},
|
key: o365AuthCredentials,
|
||||||
data: {
|
},
|
||||||
key: o365AuthCredentials,
|
});
|
||||||
},
|
return o365AuthCredentials.access_token;
|
||||||
})
|
|
||||||
.then(() => o365AuthCredentials.access_token);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
name: "Microsoft 365/Teams",
|
name: "Microsoft 365/Teams",
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
|
|
||||||
type: "office365_video",
|
type: "office365_video",
|
||||||
imageSrc: "/api/app-store/office365video/icon.svg",
|
imageSrc: "/api/app-store/office365video/icon.svg",
|
||||||
variant: "conferencing",
|
variant: "conferencing",
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { stringify } from "querystring";
|
import { stringify } from "querystring";
|
||||||
|
|
||||||
import { BASE_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
|
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
|
||||||
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
const scopes = ["OnlineMeetings.ReadWrite"];
|
const scopes = ["OnlineMeetings.ReadWrite"];
|
||||||
|
|
||||||
|
let client_id = "";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
|
const appKeys = await getAppKeysFromSlug("office365-calendar");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (!client_id) return res.status(400).json({ message: "Office 365 client_id missing." });
|
||||||
const state = encodeOAuthState(req);
|
const state = encodeOAuthState(req);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
scope: scopes.join(" "),
|
scope: scopes.join(" "),
|
||||||
client_id: process.env.MS_GRAPH_CLIENT_ID,
|
client_id,
|
||||||
redirect_uri: BASE_URL + "/api/integrations/office365video/callback",
|
redirect_uri: WEBAPP_URL + "/api/integrations/office365video/callback",
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
const query = stringify(params);
|
const query = stringify(params);
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { Credential } from "@prisma/client";
|
import { Credential } from "@prisma/client";
|
||||||
|
|
||||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
import type { PartialReference } from "@calcom/types/EventManager";
|
import type { PartialReference } from "@calcom/types/EventManager";
|
||||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||||
|
|
||||||
const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || "";
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || "";
|
|
||||||
|
let client_id = "";
|
||||||
|
let client_secret = "";
|
||||||
|
|
||||||
/** @link https://docs.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http#response */
|
/** @link https://docs.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http#response */
|
||||||
export interface TeamsEventResult {
|
export interface TeamsEventResult {
|
||||||
|
@ -30,41 +33,45 @@ interface O365AuthCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks to see if our O365 user token is valid or if we need to refresh
|
// Checks to see if our O365 user token is valid or if we need to refresh
|
||||||
const o365Auth = (credential: Credential) => {
|
const o365Auth = async (credential: Credential) => {
|
||||||
|
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;
|
||||||
|
if (!client_id) throw new HttpError({ statusCode: 400, message: "MS teams client_id missing." });
|
||||||
|
if (!client_secret) throw new HttpError({ statusCode: 400, message: "MS teams client_secret missing." });
|
||||||
|
|
||||||
const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
|
const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
|
||||||
|
|
||||||
const o365AuthCredentials = credential.key as unknown as O365AuthCredentials;
|
const o365AuthCredentials = credential.key as unknown as O365AuthCredentials;
|
||||||
|
|
||||||
const refreshAccessToken = (refreshToken: string) => {
|
const refreshAccessToken = async (refreshToken: string) => {
|
||||||
return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
|
const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
scope: "User.Read Calendars.Read Calendars.ReadWrite",
|
scope: "User.Read Calendars.Read Calendars.ReadWrite",
|
||||||
client_id: MS_GRAPH_CLIENT_ID,
|
client_id,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
client_secret: MS_GRAPH_CLIENT_SECRET,
|
client_secret,
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
.then(handleErrorsJson)
|
const responseBody = await handleErrorsJson(response);
|
||||||
.then(async (responseBody) => {
|
// set expiry date as offset from current time.
|
||||||
// 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 * 1000);
|
delete responseBody.expires_in;
|
||||||
delete responseBody.expires_in;
|
// Store new tokens in database.
|
||||||
// Store new tokens in database.
|
await prisma.credential.update({
|
||||||
await prisma.credential.update({
|
where: {
|
||||||
where: {
|
id: credential.id,
|
||||||
id: credential.id,
|
},
|
||||||
},
|
data: {
|
||||||
data: {
|
key: responseBody,
|
||||||
key: responseBody,
|
},
|
||||||
},
|
});
|
||||||
});
|
o365AuthCredentials.expiry_date = responseBody.expiry_date;
|
||||||
o365AuthCredentials.expiry_date = responseBody.expiry_date;
|
o365AuthCredentials.access_token = responseBody.access_token;
|
||||||
o365AuthCredentials.access_token = responseBody.access_token;
|
return o365AuthCredentials.access_token;
|
||||||
return o365AuthCredentials.access_token;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -92,7 +99,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
},
|
},
|
||||||
updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent) => {
|
updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent) => {
|
||||||
const accessToken = await auth.getToken();
|
const accessToken = await (await auth).getToken();
|
||||||
|
|
||||||
const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
|
const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -116,7 +123,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
},
|
},
|
||||||
createMeeting: async (event: CalendarEvent): Promise<VideoCallData> => {
|
createMeeting: async (event: CalendarEvent): Promise<VideoCallData> => {
|
||||||
const accessToken = await auth.getToken();
|
const accessToken = await (await auth).getToken();
|
||||||
|
|
||||||
const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
|
const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
@ -97,11 +97,6 @@ function getApps(userCredentials: CredentialData[]) {
|
||||||
|
|
||||||
export type AppMeta = ReturnType<typeof getApps>;
|
export type AppMeta = ReturnType<typeof getApps>;
|
||||||
|
|
||||||
/** @deprecated use `getApps` */
|
|
||||||
export function hasIntegration(apps: AppMeta, type: string): boolean {
|
|
||||||
return !!apps.find((app) => app.type === type && !!app.installed && app.credentials.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasIntegrationInstalled(type: App["type"]): boolean {
|
export function hasIntegrationInstalled(type: App["type"]): boolean {
|
||||||
return ALL_APPS.some((app) => app.type === type && !!app.installed);
|
return ALL_APPS.some((app) => app.type === type && !!app.installed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import _package from "./package.json";
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
name: "Zoom Video",
|
name: "Zoom Video",
|
||||||
description: _package.description,
|
description: _package.description,
|
||||||
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
|
|
||||||
type: "zoom_video",
|
type: "zoom_video",
|
||||||
imageSrc: "/api/app-store/zoomvideo/icon.svg",
|
imageSrc: "/api/app-store/zoomvideo/icon.svg",
|
||||||
variant: "conferencing",
|
variant: "conferencing",
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { stringify } from "querystring";
|
import { stringify } from "querystring";
|
||||||
|
|
||||||
import { BASE_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
const client_id = process.env.ZOOM_CLIENT_ID;
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
|
let client_id = "";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
|
@ -19,10 +21,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const appKeys = await getAppKeysFromSlug("zoom");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (!client_id) return res.status(400).json({ message: "Zoom client_id missing." });
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
client_id,
|
client_id,
|
||||||
redirect_uri: BASE_URL + "/api/integrations/zoomvideo/callback",
|
redirect_uri: WEBAPP_URL + "/api/integrations/zoomvideo/callback",
|
||||||
};
|
};
|
||||||
const query = stringify(params);
|
const query = stringify(params);
|
||||||
const url = `https://zoom.us/oauth/authorize?${query}`;
|
const url = `https://zoom.us/oauth/authorize?${query}`;
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { BASE_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
const client_id = process.env.ZOOM_CLIENT_ID;
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
const client_secret = process.env.ZOOM_CLIENT_SECRET;
|
|
||||||
|
let client_id = "";
|
||||||
|
let client_secret = "";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { code } = req.query;
|
const { code } = req.query;
|
||||||
|
|
||||||
const redirectUri = encodeURI(BASE_URL + "/api/integrations/zoomvideo/callback");
|
const appKeys = await getAppKeysFromSlug("zoom");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||||
|
if (!client_id) return res.status(400).json({ message: "Zoom client_id missing." });
|
||||||
|
if (!client_secret) return res.status(400).json({ message: "Zoom client_secret missing." });
|
||||||
|
|
||||||
|
const redirectUri = encodeURI(WEBAPP_URL + "/api/integrations/zoomvideo/callback");
|
||||||
const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
|
const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
"https://zoom.us/oauth/token?grant_type=authorization_code&code=" + code + "&redirect_uri=" + redirectUri,
|
"https://zoom.us/oauth/token?grant_type=authorization_code&code=" + code + "&redirect_uri=" + redirectUri,
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import { Credential } from "@prisma/client";
|
import { Credential } from "@prisma/client";
|
||||||
|
|
||||||
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
import type { PartialReference } from "@calcom/types/EventManager";
|
import type { PartialReference } from "@calcom/types/EventManager";
|
||||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||||
|
|
||||||
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
/** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */
|
/** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */
|
||||||
export interface ZoomEventResult {
|
export interface ZoomEventResult {
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -67,13 +70,20 @@ interface ZoomToken {
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomAuth = (credential: Credential) => {
|
let client_id = "";
|
||||||
|
let client_secret = "";
|
||||||
|
|
||||||
|
const zoomAuth = async (credential: Credential) => {
|
||||||
|
const appKeys = await getAppKeysFromSlug("zoom");
|
||||||
|
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||||
|
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||||
|
if (!client_id) throw new HttpError({ statusCode: 400, message: "Zoom client_id missing." });
|
||||||
|
if (!client_secret) throw new HttpError({ statusCode: 400, message: "Zoom client_secret missing." });
|
||||||
|
|
||||||
const credentialKey = credential.key as unknown as ZoomToken;
|
const credentialKey = credential.key as unknown as ZoomToken;
|
||||||
const isTokenValid = (token: ZoomToken) =>
|
const isTokenValid = (token: ZoomToken) =>
|
||||||
token && token.token_type && token.access_token && (token.expires_in || token.expiry_date) < Date.now();
|
token && token.token_type && token.access_token && (token.expires_in || token.expiry_date) < Date.now();
|
||||||
const authHeader =
|
const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
|
||||||
"Basic " +
|
|
||||||
Buffer.from(process.env.ZOOM_CLIENT_ID + ":" + process.env.ZOOM_CLIENT_SECRET).toString("base64");
|
|
||||||
|
|
||||||
const refreshAccessToken = (refreshToken: string) =>
|
const refreshAccessToken = (refreshToken: string) =>
|
||||||
fetch("https://zoom.us/oauth/token", {
|
fetch("https://zoom.us/oauth/token", {
|
||||||
|
|
3
packages/types/App.d.ts
vendored
3
packages/types/App.d.ts
vendored
|
@ -8,10 +8,11 @@ import type { LocationType } from "@calcom/app-store/locations";
|
||||||
*/
|
*/
|
||||||
export interface App {
|
export interface App {
|
||||||
/**
|
/**
|
||||||
|
* @deprecated
|
||||||
* Wheter if the app is installed or not. Usually we check for api keys in env
|
* Wheter if the app is installed or not. Usually we check for api keys in env
|
||||||
* variables to determine if this is true or not.
|
* variables to determine if this is true or not.
|
||||||
* */
|
* */
|
||||||
installed: boolean;
|
installed?: boolean;
|
||||||
/** The app type */
|
/** The app type */
|
||||||
type:
|
type:
|
||||||
| `${string}_calendar`
|
| `${string}_calendar`
|
||||||
|
|
Loading…
Reference in a new issue