diff --git a/.env.appStore.example b/.env.appStore.example index 10da881498..fe1f1038a9 100644 --- a/.env.appStore.example +++ b/.env.appStore.example @@ -16,11 +16,12 @@ # - WEB3 # - SALESFORCE # - ZOHOCRM +# - ZOHO_BIGIN # - APP STORE ********************************************************************************************** # ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️ # - DAILY.CO VIDEO -# Enables Cal Video. to get your key +# Enables Cal Video. to get your key # 1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information # 2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab. # @see https://github.com/calcom/cal.com#obtaining-daily-api-credentials diff --git a/README.md b/README.md index af95d8db42..3ef368a738 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,8 @@ following 9. Click the "Save"/ "UPDATE" button at the bottom footer. 10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings. +### Obtaining Zoho Bigin Client ID and Secret +[Follow these steps](./packages/app-store/zoho-bigin/) ## Workflows ### Setting up SendGrid for Email reminders diff --git a/packages/app-store/apps.keys-schemas.generated.ts b/packages/app-store/apps.keys-schemas.generated.ts index 4628d82620..fe57bb2d9d 100644 --- a/packages/app-store/apps.keys-schemas.generated.ts +++ b/packages/app-store/apps.keys-schemas.generated.ts @@ -25,6 +25,7 @@ import { appKeysSchema as event_type_app_card_zod_ts } from "./templates/event-t import { appKeysSchema as vital_zod_ts } from "./vital/zod"; import { appKeysSchema as wordpress_zod_ts } from "./wordpress/zod"; import { appKeysSchema as zapier_zod_ts } from "./zapier/zod"; +import { appKeysSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod"; import { appKeysSchema as zohocrm_zod_ts } from "./zohocrm/zod"; import { appKeysSchema as zoomvideo_zod_ts } from "./zoomvideo/zod"; @@ -52,6 +53,7 @@ export const appKeysSchemas = { vital: vital_zod_ts, wordpress: wordpress_zod_ts, zapier: zapier_zod_ts, + "zoho-bigin": zoho_bigin_zod_ts, zohocrm: zohocrm_zod_ts, zoomvideo: zoomvideo_zod_ts, }; diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts index e1e62f06ce..bb402e60a3 100644 --- a/packages/app-store/apps.metadata.generated.ts +++ b/packages/app-store/apps.metadata.generated.ts @@ -60,6 +60,7 @@ import whereby_config_json from "./whereby/config.json"; import { metadata as wipemycalother__metadata_ts } from "./wipemycalother/_metadata"; import wordpress_config_json from "./wordpress/config.json"; import { metadata as zapier__metadata_ts } from "./zapier/_metadata"; +import zoho_bigin_config_json from "./zoho-bigin/config.json"; import zohocrm_config_json from "./zohocrm/config.json"; import { metadata as zoomvideo__metadata_ts } from "./zoomvideo/_metadata"; @@ -122,6 +123,7 @@ export const appStoreMetadata = { wipemycalother: wipemycalother__metadata_ts, wordpress: wordpress_config_json, zapier: zapier__metadata_ts, + "zoho-bigin": zoho_bigin_config_json, zohocrm: zohocrm_config_json, zoomvideo: zoomvideo__metadata_ts, }; diff --git a/packages/app-store/apps.schemas.generated.ts b/packages/app-store/apps.schemas.generated.ts index 005d0e6790..4362a31744 100644 --- a/packages/app-store/apps.schemas.generated.ts +++ b/packages/app-store/apps.schemas.generated.ts @@ -25,6 +25,7 @@ import { appDataSchema as event_type_app_card_zod_ts } from "./templates/event-t import { appDataSchema as vital_zod_ts } from "./vital/zod"; import { appDataSchema as wordpress_zod_ts } from "./wordpress/zod"; import { appDataSchema as zapier_zod_ts } from "./zapier/zod"; +import { appDataSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod"; import { appDataSchema as zohocrm_zod_ts } from "./zohocrm/zod"; import { appDataSchema as zoomvideo_zod_ts } from "./zoomvideo/zod"; @@ -52,6 +53,7 @@ export const appDataSchemas = { vital: vital_zod_ts, wordpress: wordpress_zod_ts, zapier: zapier_zod_ts, + "zoho-bigin": zoho_bigin_zod_ts, zohocrm: zohocrm_zod_ts, zoomvideo: zoomvideo_zod_ts, }; diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index a0c37f7925..7b35ab5809 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -60,6 +60,7 @@ export const apiHandlers = { wipemycalother: import("./wipemycalother/api"), wordpress: import("./wordpress/api"), zapier: import("./zapier/api"), + "zoho-bigin": import("./zoho-bigin/api"), zohocrm: import("./zohocrm/api"), zoomvideo: import("./zoomvideo/api"), }; diff --git a/packages/app-store/index.ts b/packages/app-store/index.ts index e232b9fa4f..b7d5393644 100644 --- a/packages/app-store/index.ts +++ b/packages/app-store/index.ts @@ -15,6 +15,7 @@ const appStore = { plausible: import("./plausible"), salesforce: import("./salesforce"), zohocrm: import("./zohocrm"), + "zoho-bigin": import("./zoho-bigin"), sendgrid: import("./sendgrid"), stripepayment: import("./stripepayment"), tandemvideo: import("./tandemvideo"), diff --git a/packages/app-store/zoho-bigin/DESCRIPTION.md b/packages/app-store/zoho-bigin/DESCRIPTION.md new file mode 100644 index 0000000000..d07280ea4c --- /dev/null +++ b/packages/app-store/zoho-bigin/DESCRIPTION.md @@ -0,0 +1,6 @@ +--- +items: + - 1.png +--- + +{DESCRIPTION} diff --git a/packages/app-store/zoho-bigin/README.md b/packages/app-store/zoho-bigin/README.md new file mode 100644 index 0000000000..11ddc9f3a2 --- /dev/null +++ b/packages/app-store/zoho-bigin/README.md @@ -0,0 +1,9 @@ +### Obtaining Zoho Bigin Client ID and Secret + +1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one. +2. Click "ADD CLIENT" button top right and select "Server-based Applications". +3. Set the Redirect URL for OAuth `/api/integrations/zoho-bigin/callback` replacing Cal.com URL with the URI at which your application runs. +4. Go to tab "Client Secret" tab. +5. Now copy the Client ID and Client Secret to your .env.appStore file into the `ZOHO_BIGIN_CLIENT_ID` and `ZOHO_BIGIN_CLIENT_SECRET` fields. +6. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers. +7. You're good to go. Now you can easily add Zoho Bigin from the Cal.com app store. diff --git a/packages/app-store/zoho-bigin/api/add.ts b/packages/app-store/zoho-bigin/api/add.ts new file mode 100644 index 0000000000..bb582ed545 --- /dev/null +++ b/packages/app-store/zoho-bigin/api/add.ts @@ -0,0 +1,32 @@ +import axios from "axios"; +import type { NextApiRequest, NextApiResponse } from "next"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; +import appConfig from "../config.json"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "GET") { + const appKeys = await getAppKeysFromSlug(appConfig.slug); + + const clientId = typeof appKeys.client_id === "string" ? appKeys.client_id : ""; + if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." }); + + const redirectUri = WEBAPP_URL + `/api/integrations/${appConfig.slug}/callback`; + const authUrl = axios.getUri({ + url: "https://accounts.zoho.com/oauth/v2/auth", + params: { + scope: appConfig.scope, + client_id: clientId, + response_type: "code", + redirect_uri: redirectUri, + access_type: "offline", + }, + }); + + res.status(200).json({ url: authUrl }); + return; + } + res.status(400).json({ message: "Invalid request method." }); +} diff --git a/packages/app-store/zoho-bigin/api/callback.ts b/packages/app-store/zoho-bigin/api/callback.ts new file mode 100644 index 0000000000..c68d2f35d5 --- /dev/null +++ b/packages/app-store/zoho-bigin/api/callback.ts @@ -0,0 +1,69 @@ +import axios from "axios"; +import type { NextApiRequest, NextApiResponse } from "next"; +import qs from "qs"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import prisma from "@calcom/prisma"; + +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; +import getInstalledAppPath from "../../_utils/getInstalledAppPath"; +import appConfig from "../config.json"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { code, "accounts-server": accountsServer } = req.query; + + if (code && typeof code !== "string") { + res.status(400).json({ message: "`code` must be a string" }); + return; + } + + if (!req.session?.user?.id) { + res.status(401).json({ message: "You must be logged in to do this" }); + return; + } + + const appKeys = await getAppKeysFromSlug(appConfig.slug); + + const clientId = typeof appKeys.client_id === "string" ? appKeys.client_id : ""; + const clientSecret = typeof appKeys.client_secret === "string" ? appKeys.client_secret : ""; + + if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." }); + if (!clientSecret) return res.status(400).json({ message: "Zoho Bigin client_secret missing." }); + + const accountsUrl = `${accountsServer}/oauth/v2/token`; + const redirectUri = WEBAPP_URL + `/api/integrations/${appConfig.slug}/callback`; + + const formData = { + client_id: clientId, + client_secret: clientSecret, + code: code, + redirect_uri: redirectUri, + grant_type: "authorization_code", + }; + + const tokenInfo = await axios.post(accountsUrl, qs.stringify(formData), { + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + }, + }); + + tokenInfo.data.expiryDate = Math.round(Date.now() + tokenInfo.data.expires_in); + tokenInfo.data.accountServer = accountsServer; + + await prisma.credential.create({ + data: { + type: appConfig.type, + key: tokenInfo.data, + userId: req.session.user.id, + appId: appConfig.slug, + }, + }); + + const state = decodeOAuthState(req); + res.redirect( + getSafeRedirectUrl(state?.returnTo) ?? + getInstalledAppPath({ variant: appConfig.variant, slug: appConfig.slug }) + ); +} diff --git a/packages/app-store/zoho-bigin/api/index.ts b/packages/app-store/zoho-bigin/api/index.ts new file mode 100644 index 0000000000..eb12c1b4ed --- /dev/null +++ b/packages/app-store/zoho-bigin/api/index.ts @@ -0,0 +1,2 @@ +export { default as add } from "./add"; +export { default as callback } from "./callback"; diff --git a/packages/app-store/zoho-bigin/config.json b/packages/app-store/zoho-bigin/config.json new file mode 100644 index 0000000000..3425e1efaf --- /dev/null +++ b/packages/app-store/zoho-bigin/config.json @@ -0,0 +1,17 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Zoho Bigin", + "slug": "zoho-bigin", + "type": "zoho-bigin_other_calendar", + "logo": "zohobigin.svg", + "url": "https://cal.com/apps/zoho-bigin", + "variant": "other", + "categories": ["other"], + "publisher": "Cal.com", + "email": "help@cal.com", + "description": "Bigin easily transforms your day-to-day customer processes into actionable pipelines. From qualifying leads to closing deals to managing important after-sales operations—Bigin connects your different teams to work together so that you can offer the best possible experience to your customers. Say goodbye to missing follow-ups, manual data entry, lack of team communication, and information silos.", + "isTemplate": false, + "__createdUsingCli": true, + "__template": "basic", + "scope": "ZohoBigin.modules.events.ALL,ZohoBigin.modules.contacts.ALL" +} diff --git a/packages/app-store/zoho-bigin/index.ts b/packages/app-store/zoho-bigin/index.ts new file mode 100644 index 0000000000..e2e9d7b029 --- /dev/null +++ b/packages/app-store/zoho-bigin/index.ts @@ -0,0 +1,2 @@ +export * as api from "./api"; +export * as lib from "./lib"; diff --git a/packages/app-store/zoho-bigin/lib/CalendarService.ts b/packages/app-store/zoho-bigin/lib/CalendarService.ts new file mode 100644 index 0000000000..7f1ba1625b --- /dev/null +++ b/packages/app-store/zoho-bigin/lib/CalendarService.ts @@ -0,0 +1,312 @@ +import axios from "axios"; +import qs from "qs"; + +import { getLocation } from "@calcom/lib/CalEventParser"; +import logger from "@calcom/lib/logger"; +import prisma from "@calcom/prisma"; +import type { + Calendar, + CalendarEvent, + EventBusyDate, + IntegrationCalendar, + NewCalendarEventType, + Person, +} from "@calcom/types/Calendar"; +import type { CredentialPayload } from "@calcom/types/Credential"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; +import { appKeysSchema } from "../zod"; + +export type BiginToken = { + scope: string; + api_domain: string; + expires_in: number; + expiryDate: number; + token_type: string; + access_token: string; + accountServer: string; + refresh_token: string; +}; + +export type BiginContact = { + email: string; +}; + +export default class BiginCalendarService implements Calendar { + private readonly integrationName = "zoho-bigin"; + private readonly auth: { getToken: () => Promise }; + private log: typeof logger; + private eventsSlug = "/bigin/v1/Events"; + private contactsSlug = "/bigin/v1/Contacts"; + + constructor(credential: CredentialPayload) { + this.auth = this.biginAuth(credential); + this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] }); + } + + /*** + * Authenticate calendar service with Zoho Bigin provided credentials. + */ + private biginAuth(credential: CredentialPayload) { + const credentialKey = credential.key as unknown as BiginToken; + const credentialId = credential.id; + + const isTokenValid = (token: BiginToken) => + token.access_token && token.expiryDate && token.expiryDate > Date.now(); + + return { + getToken: () => + isTokenValid(credentialKey) + ? Promise.resolve(credentialKey) + : this.refreshAccessToken(credentialId, credentialKey), + }; + } + + /*** + * Fetches a new access token if stored token is expired. + */ + private async refreshAccessToken(credentialId: number, credentialKey: BiginToken) { + this.log.debug("Refreshing token as it's invalid"); + const grantType = "refresh_token"; + const accountsUrl = `${credentialKey.accountServer}/oauth/v2/token`; + + const appKeys = await getAppKeysFromSlug(this.integrationName); + + const { client_id: clientId, client_secret: clientSecret } = appKeysSchema.parse(appKeys); + + const formData = { + grant_type: grantType, + client_id: clientId, + client_secret: clientSecret, + refresh_token: credentialKey.refresh_token, + }; + + const tokenInfo = await axios.post(accountsUrl, qs.stringify(formData), { + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + }, + }); + + if (!tokenInfo.data.error) { + // set expiry date as offset from current time. + tokenInfo.data.expiryDate = Math.round(Date.now() + tokenInfo.data.expires_in); + tokenInfo.data.accountServer = credentialKey.accountServer; + tokenInfo.data.refresh_token = credentialKey.refresh_token; + + await prisma.credential.update({ + where: { + id: credentialId, + }, + data: { + key: tokenInfo.data as BiginToken, + }, + }); + this.log.debug("Fetched token", tokenInfo.data.access_token); + } else { + this.log.error(tokenInfo.data); + } + + return tokenInfo.data as BiginToken; + } + + /*** + * Creates Zoho Bigin Contact records for every attendee added in event bookings. + * Returns the results of all contact creation operations. + */ + private async createContacts(attendees: Person[]) { + const token = await this.auth.getToken(); + const contacts = attendees.map((attendee) => { + const nameParts = attendee.name.split(" "); + const firstName = nameParts[0]; + const lastName = nameParts.length > 1 ? nameParts.slice(1).join(" ") : "-"; + return { + First_Name: firstName, + Last_Name: lastName, + Email: attendee.email, + }; + }); + + return axios({ + method: "post", + url: token.api_domain + this.contactsSlug, + headers: { + "content-type": "application/json", + authorization: `Zoho-oauthtoken ${token.access_token}`, + }, + data: JSON.stringify({ data: contacts }), + }); + } + + /*** + * Finds existing Zoho Bigin Contact record based on email address. Returns a list of contacts objects that matched. + */ + private async contactSearch(event: CalendarEvent) { + const token = await this.auth.getToken(); + const searchCriteria = + "(" + event.attendees.map((attendee) => `(Email:equals:${encodeURI(attendee.email)})`).join("or") + ")"; + + return await axios({ + method: "get", + url: `${token.api_domain}${this.contactsSlug}/search?criteria=${searchCriteria}`, + headers: { + authorization: `Zoho-oauthtoken ${token.access_token}`, + }, + }) + .then((data) => data.data) + .catch((e) => this.log.error("Error searching contact:", JSON.stringify(e), e.response?.data)); + } + + /*** + * Sends request to Zoho Bigin API to add new Events. + */ + private async createBiginEvent(event: CalendarEvent) { + const token = await this.auth.getToken(); + const biginEvent = { + Event_Title: event.title, + Start_DateTime: toISO8601String(new Date(event.startTime)), + End_DateTime: toISO8601String(new Date(event.endTime)), + Description: event.additionalNotes, + Location: getLocation(event), + }; + + return axios({ + method: "post", + url: token.api_domain + this.eventsSlug, + headers: { + "content-type": "application/json", + authorization: `Zoho-oauthtoken ${token.access_token}`, + }, + data: JSON.stringify({ data: [biginEvent] }), + }) + .then((data) => data.data) + .catch((e) => this.log.error("Error creating bigin event", JSON.stringify(e), e.response?.data)); + } + + /*** + * Handles orchestrating the creation of new events in Zoho Bigin. + */ + async handleEventCreation(event: CalendarEvent, contacts: CalendarEvent["attendees"]) { + const meetingEvent = await this.createBiginEvent(event); + if (meetingEvent.data && meetingEvent.data.length && meetingEvent.data[0].status === "success") { + this.log.debug("event:creation:ok", { meetingEvent }); + return Promise.resolve({ + uid: meetingEvent.data[0].details.id, + id: meetingEvent.data[0].details.id, + //FIXME: `externalCalendarId` is required by the `updateAllCalendarEvents` method, but is not used by zoho-bigin App. Not setting this property actually skips calling updateEvent.. + // Here the value doesn't matter. We just need to set it to something. + externalCalendarId: "NO_CALENDAR_ID_NEEDED", + type: this.integrationName, + password: "", + url: "", + additionalInfo: { contacts, meetingEvent }, + }); + } + this.log.debug("meeting:creation:notOk", { meetingEvent, event, contacts }); + return Promise.reject("Something went wrong when creating a meeting in Zoho Bigin"); + } + + /*** + * Creates contacts and event records for new bookings. + * Initially creates all new attendees as contacts, then creates the event. + */ + async createEvent(event: CalendarEvent): Promise { + const contacts = (await this.contactSearch(event))?.data || []; + + const existingContacts = contacts.map((contact: BiginContact) => contact.email); + const newContacts: Person[] = event.attendees.filter( + (attendee) => !existingContacts.includes(attendee.email) + ); + + if (newContacts.length === 0) { + return await this.handleEventCreation(event, event.attendees); + } + + const createContacts = await this.createContacts(newContacts); + if (createContacts.data?.data[0].status === "success") { + return await this.handleEventCreation(event, event.attendees); + } + + return Promise.reject({ + calError: "Something went wrong when creating non-existing attendees in Zoho Bigin", + }); + } + + /*** + * Updates an existing event in Zoho Bigin. + */ + async updateEvent(uid: string, event: CalendarEvent): Promise { + this.log.debug(`Updating Event with uid ${uid}`); + const token = await this.auth.getToken(); + const biginEvent = { + id: uid, + Event_Title: event.title, + Start_DateTime: toISO8601String(new Date(event.startTime)), + End_DateTime: toISO8601String(new Date(event.endTime)), + Description: event.additionalNotes, + Location: getLocation(event), + }; + + return axios + .put(token.api_domain + this.eventsSlug, JSON.stringify({ data: [biginEvent] }), { + headers: { + "content-type": "application/json", + authorization: `Zoho-oauthtoken ${token.access_token}`, + }, + }) + .then((data) => data.data) + .catch((e) => { + this.log.error("Error in updating bigin event", JSON.stringify(e), e.response?.data); + }); + } + + async deleteEvent(uid: string): Promise { + const token = await this.auth.getToken(); + return axios + .delete(`${token.api_domain}${this.eventsSlug}?ids=${uid}`, { + headers: { + "content-type": "application/json", + authorization: `Zoho-oauthtoken ${token.access_token}`, + }, + }) + .then((data) => data.data) + .catch((e) => this.log.error("Error deleting bigin event", JSON.stringify(e), e.response?.data)); + } + + async getAvailability( + _dateFrom: string, + _dateTo: string, + _selectedCalendars: IntegrationCalendar[] + ): Promise { + return Promise.resolve([]); + } + + async listCalendars(_event?: CalendarEvent): Promise { + return Promise.resolve([]); + } +} + +const toISO8601String = (date: Date) => { + const tzo = -date.getTimezoneOffset(), + dif = tzo >= 0 ? "+" : "-", + pad = function (num: number) { + return (num < 10 ? "0" : "") + num; + }; + + return ( + date.getFullYear() + + "-" + + pad(date.getMonth() + 1) + + "-" + + pad(date.getDate()) + + "T" + + pad(date.getHours()) + + ":" + + pad(date.getMinutes()) + + ":" + + pad(date.getSeconds()) + + dif + + pad(Math.floor(Math.abs(tzo) / 60)) + + ":" + + pad(Math.abs(tzo) % 60) + ); +}; diff --git a/packages/app-store/zoho-bigin/lib/index.ts b/packages/app-store/zoho-bigin/lib/index.ts new file mode 100644 index 0000000000..e168c149df --- /dev/null +++ b/packages/app-store/zoho-bigin/lib/index.ts @@ -0,0 +1 @@ +export { default as CalendarService } from "./CalendarService"; diff --git a/packages/app-store/zoho-bigin/package.json b/packages/app-store/zoho-bigin/package.json new file mode 100644 index 0000000000..fa2e550751 --- /dev/null +++ b/packages/app-store/zoho-bigin/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/zoho-bigin", + "version": "1.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Bigin easily transforms your day-to-day customer processes into actionable pipelines. From qualifying leads to closing deals to managing important after-sales operations—Bigin connects your different teams to work together so that you can offer the best possible experience to your customers. Say goodbye to missing follow-ups, manual data entry, lack of team communication, and information silos." +} diff --git a/packages/app-store/zoho-bigin/static/1.png b/packages/app-store/zoho-bigin/static/1.png new file mode 100644 index 0000000000..50d0a78968 Binary files /dev/null and b/packages/app-store/zoho-bigin/static/1.png differ diff --git a/packages/app-store/zoho-bigin/static/zohobigin.svg b/packages/app-store/zoho-bigin/static/zohobigin.svg new file mode 100644 index 0000000000..c90f0946c0 --- /dev/null +++ b/packages/app-store/zoho-bigin/static/zohobigin.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/app-store/zoho-bigin/zod.ts b/packages/app-store/zoho-bigin/zod.ts new file mode 100644 index 0000000000..b2621bfca9 --- /dev/null +++ b/packages/app-store/zoho-bigin/zod.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const appKeysSchema = z.object({ + client_id: z.string().min(1), + client_secret: z.string().min(1), +}); +export const appDataSchema = z.object({}); diff --git a/packages/core/CalendarManager.ts b/packages/core/CalendarManager.ts index af2c845655..075e7a203a 100644 --- a/packages/core/CalendarManager.ts +++ b/packages/core/CalendarManager.ts @@ -280,7 +280,7 @@ export const createEvent = async ( if (error?.calError) { calError = error.calError; } - log.error("createEvent failed", error, calEvent); + log.error("createEvent failed", JSON.stringify(error), calEvent); // @TODO: This code will be off till we can investigate an error with it //https://github.com/calcom/cal.com/issues/3949 // await sendBrokenIntegrationEmail(calEvent, "calendar"); diff --git a/yarn.lock b/yarn.lock index 33cbdb56c8..3075c559e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5165,6 +5165,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/zoho-bigin@workspace:packages/app-store/zoho-bigin": + version: 0.0.0-use.local + resolution: "@calcom/zoho-bigin@workspace:packages/app-store/zoho-bigin" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/zohocrm@workspace:packages/app-store/zohocrm": version: 0.0.0-use.local resolution: "@calcom/zohocrm@workspace:packages/app-store/zohocrm"