Skip to content

Commit 70461b2

Browse files
murtajaziadhariombalharaalannncjoeauyeungPeerRich
authored
feat: Zoho Calendar (#10429)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: aar2dee2 <85004512+aar2dee2@users.noreply.github.com>
1 parent efb04d0 commit 70461b2

26 files changed

+663
-0
lines changed

.env.appStore.example

+1
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,5 @@ SALESFORCE_CONSUMER_SECRET=""
125125
ZOHOCRM_CLIENT_ID=""
126126
ZOHOCRM_CLIENT_SECRET=""
127127

128+
128129
# *********************************************************************************************************

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ For example, `Cal.com (support@cal.com)`.
504504
9. Click the "Save"/ "UPDATE" button at the bottom footer.
505505
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
506506

507+
### Obtaining Zoho Calendar Client ID and Secret
508+
509+
[Follow these steps](./packages/app-store/zohocalendar/)
507510
### Obtaining Zoho Bigin Client ID and Secret
508511

509512
[Follow these steps](./packages/app-store/zoho-bigin/)

packages/app-store/apps.keys-schemas.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { appKeysSchema as webex_zod_ts } from "./webex/zod";
3030
import { appKeysSchema as wordpress_zod_ts } from "./wordpress/zod";
3131
import { appKeysSchema as zapier_zod_ts } from "./zapier/zod";
3232
import { appKeysSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod";
33+
import { appKeysSchema as zohocalendar_zod_ts } from "./zohocalendar/zod";
3334
import { appKeysSchema as zohocrm_zod_ts } from "./zohocrm/zod";
3435
import { appKeysSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
3536

@@ -62,6 +63,7 @@ export const appKeysSchemas = {
6263
wordpress: wordpress_zod_ts,
6364
zapier: zapier_zod_ts,
6465
"zoho-bigin": zoho_bigin_zod_ts,
66+
zohocalendar: zohocalendar_zod_ts,
6567
zohocrm: zohocrm_zod_ts,
6668
zoomvideo: zoomvideo_zod_ts,
6769
};

packages/app-store/apps.metadata.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { metadata as wipemycalother__metadata_ts } from "./wipemycalother/_metad
7070
import wordpress_config_json from "./wordpress/config.json";
7171
import { metadata as zapier__metadata_ts } from "./zapier/_metadata";
7272
import zoho_bigin_config_json from "./zoho-bigin/config.json";
73+
import zohocalendar_config_json from "./zohocalendar/config.json";
7374
import zohocrm_config_json from "./zohocrm/config.json";
7475
import { metadata as zoomvideo__metadata_ts } from "./zoomvideo/_metadata";
7576

@@ -142,6 +143,7 @@ export const appStoreMetadata = {
142143
wordpress: wordpress_config_json,
143144
zapier: zapier__metadata_ts,
144145
"zoho-bigin": zoho_bigin_config_json,
146+
zohocalendar: zohocalendar_config_json,
145147
zohocrm: zohocrm_config_json,
146148
zoomvideo: zoomvideo__metadata_ts,
147149
};

packages/app-store/apps.schemas.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { appDataSchema as webex_zod_ts } from "./webex/zod";
3030
import { appDataSchema as wordpress_zod_ts } from "./wordpress/zod";
3131
import { appDataSchema as zapier_zod_ts } from "./zapier/zod";
3232
import { appDataSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod";
33+
import { appDataSchema as zohocalendar_zod_ts } from "./zohocalendar/zod";
3334
import { appDataSchema as zohocrm_zod_ts } from "./zohocrm/zod";
3435
import { appDataSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
3536

@@ -62,6 +63,7 @@ export const appDataSchemas = {
6263
wordpress: wordpress_zod_ts,
6364
zapier: zapier_zod_ts,
6465
"zoho-bigin": zoho_bigin_zod_ts,
66+
zohocalendar: zohocalendar_zod_ts,
6567
zohocrm: zohocrm_zod_ts,
6668
zoomvideo: zoomvideo_zod_ts,
6769
};

packages/app-store/apps.server.generated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const apiHandlers = {
7070
wordpress: import("./wordpress/api"),
7171
zapier: import("./zapier/api"),
7272
"zoho-bigin": import("./zoho-bigin/api"),
73+
zohocalendar: import("./zohocalendar/api"),
7374
zohocrm: import("./zohocrm/api"),
7475
zoomvideo: import("./zoomvideo/api"),
7576
};

packages/app-store/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const appStore = {
3232
exchangecalendar: () => import("./exchangecalendar"),
3333
facetime: () => import("./facetime"),
3434
sylapsvideo: () => import("./sylapsvideo"),
35+
zohocalendar: () => import("./zohocalendar"),
3536
"zoho-bigin": () => import("./zoho-bigin"),
3637
basecamp3: () => import("./basecamp3"),
3738
telegramvideo: () => import("./telegram"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
items:
3+
- ZCal1.jpg
4+
- ZCal2.jpg
5+
- ZCal3.jpg
6+
- ZCal4.jpg
7+
---
8+
9+
Zoho Calendar is an online business calendar that makes scheduling easy for you. Use this app to sync your Cal bookings with your Zoho Calendar.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Zoho Calendar
2+
3+
### Obtaining Zoho Calendar Client ID and Secret
4+
5+
1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
6+
2. Create a "Server-based Applications", set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zohocalendar/callback` replacing Cal.com URL with the URI at which your application runs.
7+
4. Fill in any information you want in the "Client Details" tab
8+
5. Go to tab "Client Secret" tab.
9+
6. Now copy the Client ID and Client Secret into your app keys in the Cal.com admin panel (`<Cal.com>/settings/admin/apps`).
10+
7. Back in Zoho API Console,
11+
8. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
12+
9. Click the "Save"/ "UPDATE" button at the bottom footer.
13+
10. You're good to go. Now you can easily add your Zoho Calendar integration in the Cal.com settings at `/settings/my-account/calendars`.
14+
11. You can access your Zoho calendar at [https://calendar.zoho.com/](https://calendar.zoho.com/)
15+
16+
NOTE: If you use multiple calendars with Cal, make sure you enable the toggle to prevent double-bookings across calendar. This is in `/settings/my-account/calendars`.
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { NextApiRequest, NextApiResponse } from "next";
2+
import { stringify } from "querystring";
3+
4+
import { WEBAPP_URL } from "@calcom/lib/constants";
5+
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
6+
7+
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
8+
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
9+
import config from "../config.json";
10+
import { appKeysSchema as zohoKeysSchema } from "../zod";
11+
12+
const OAUTH_BASE_URL = "https://accounts.zoho.com/oauth/v2";
13+
14+
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
15+
const appKeys = await getAppKeysFromSlug(config.slug);
16+
const { client_id } = zohoKeysSchema.parse(appKeys);
17+
18+
const state = encodeOAuthState(req);
19+
20+
const params = {
21+
client_id,
22+
response_type: "code",
23+
redirect_uri: WEBAPP_URL + "/api/integrations/zohocalendar/callback",
24+
scope: [
25+
"ZohoCalendar.calendar.ALL",
26+
"ZohoCalendar.event.ALL",
27+
"ZohoCalendar.freebusy.READ",
28+
"AaaServer.profile.READ",
29+
],
30+
access_type: "offline",
31+
state,
32+
prompt: "consent",
33+
};
34+
35+
const query = stringify(params);
36+
37+
res.status(200).json({ url: `${OAUTH_BASE_URL}/auth?${query}` });
38+
}
39+
40+
export default defaultHandler({
41+
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { NextApiRequest, NextApiResponse } from "next";
2+
import { stringify } from "querystring";
3+
4+
import { WEBAPP_URL } from "@calcom/lib/constants";
5+
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
6+
import logger from "@calcom/lib/logger";
7+
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
8+
9+
import createOAuthAppCredential from "../../_utils/createOAuthAppCredential";
10+
import { decodeOAuthState } from "../../_utils/decodeOAuthState";
11+
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
12+
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
13+
import config from "../config.json";
14+
import type { ZohoAuthCredentials } from "../types/ZohoCalendar";
15+
import { appKeysSchema as zohoKeysSchema } from "../zod";
16+
17+
const log = logger.getChildLogger({ prefix: [`[[zohocalendar/api/callback]`] });
18+
19+
const OAUTH_BASE_URL = "https://accounts.zoho.com/oauth/v2";
20+
21+
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
22+
const { code } = req.query;
23+
const state = decodeOAuthState(req);
24+
25+
if (code && typeof code !== "string") {
26+
res.status(400).json({ message: "`code` must be a string" });
27+
return;
28+
}
29+
30+
if (!req.session?.user?.id) {
31+
return res.status(401).json({ message: "You must be logged in to do this" });
32+
}
33+
34+
const appKeys = await getAppKeysFromSlug(config.slug);
35+
const { client_id, client_secret } = zohoKeysSchema.parse(appKeys);
36+
37+
const params = {
38+
client_id,
39+
grant_type: "authorization_code",
40+
client_secret,
41+
redirect_uri: `${WEBAPP_URL}/api/integrations/${config.slug}/callback`,
42+
code,
43+
};
44+
45+
const query = stringify(params);
46+
47+
const response = await fetch(`${OAUTH_BASE_URL}/token?${query}`, {
48+
method: "POST",
49+
headers: {
50+
"Content-Type": "application/json; charset=utf-8",
51+
},
52+
});
53+
54+
const responseBody = await response.json();
55+
56+
if (!response.ok || responseBody.error) {
57+
log.error("get access_token failed", responseBody);
58+
return res.redirect("/apps/installed?error=" + JSON.stringify(responseBody));
59+
}
60+
61+
const key: ZohoAuthCredentials = {
62+
access_token: responseBody.access_token,
63+
refresh_token: responseBody.refresh_token,
64+
expires_in: Math.round(+new Date() / 1000 + responseBody.expires_in),
65+
};
66+
67+
await createOAuthAppCredential({ appId: config.slug, type: config.type }, key, req);
68+
69+
res.redirect(
70+
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: config.variant, slug: config.slug })
71+
);
72+
}
73+
74+
export default defaultHandler({
75+
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
76+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as add } from "./add";
2+
export { default as callback } from "./callback";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "Zoho Calendar",
3+
"description": "Zoho Calendar is an online business calendar that makes scheduling easy for you. You can use it to stay on top of your schedule and also share calendars with your team to keep everyone on the same page.",
4+
"slug": "zohocalendar",
5+
"type": "zoho_calendar",
6+
"title": "Zoho Calendar",
7+
"variant": "calendar",
8+
"category": "calendar",
9+
"categories": [
10+
"calendar"
11+
],
12+
"logo": "icon.svg",
13+
"publisher": "Cal.com",
14+
"url": "https://cal.com/",
15+
"email": "help@cal.com"
16+
}
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * as api from "./api";
2+
export * as lib from "./lib";

0 commit comments

Comments
 (0)