Synchronize Directus and Google Calendar

Gabrio Tognozzi
4 min readDec 4, 2024

--

Photo by Leeloo The First

I was trying to sell to my customer an Integration between the CRM I’ve implemented ( based on Directus ) and Google Calendar. I’ve found on the internet this Directus blog post that shows how to somewhat sync Directus with Google Calendar but I wasn’t satisfied. After some work I was able to came out with an (almost) well-designed integration (it can be improved but it works) to provide the functionality of writing a Directus Booking entity, and having it registered in the associated Google Calendar.

The required components for this integration follow:

  • GCP OAuth Singleton:
    - Allows the Directus UI to configure GCP OAuth2 Parameters
    - Generate the Login Link
    - Store the generated AccessToken and RefreshToken
  • Google OAuth Callback Endpoint:
    - Handles the OAuth2 callback and stores the AccessToken and RefreshToken inside the Singleton
  • Bookings Hook:
    -
    The hook will be responsible to create the Google Calendar event
  • [TODO] Booking Schedule:
    -
    Import existing events from Google Calendar and align Directus

GCP OAuth Singleton

I’ve created a GCP Google Calendar Singleton to store all the relevant information about the Google OAuth flow. I’ve also inserted in the Singleton the accessToken and refreshToken information, while it may be the case that this information is associated to each different User.

In the Singleton the “Button Link” field generates a Button that triggers the OAuth2 Login flow.

In order to generate a valid clientId and clientSecret pair, you will have to create a Project and generate some credentials. Also, under “API & Services -> OAuth consent screen” you will have to add test users.

I’ve configured the “Button Link” field to have the following value. Most important parts here are:

  • redirect_uri should be correctly configured in the Google OAuth configuration. It will need to be a valid HTTPS endpoint, I wasn’t able to make it work w/ an HTTP endpoint. (consider using a VPS w/ LetsEncrypt or ngrok)
  • response_type=code will provide us in the callback a “code” that we will be able to exchange for an accessToken and refreshToken using our clientId and clientSecret
  • access_type=offline that will instruct Google OAuth2 server to return a refreshToken.
  • prompt=consent will force the user to confirm again the scope of the token, if the consent is not explicitly granted, the refreshToken would not be provided
https://accounts.google.com/o/oauth2/auth?client_id={{client_id}}&redirect_uri={{redirect_uri}}&scope={{scope}}&response_type=code&state=randomstate&access_type=offline&prompt=consent

Google OAuth Callback Endpoint

Once the User grants the permission to the app, you will receive the below callback containing the “code” authorization grant



https://your-domain.directus.com/calendar-endpoint/gcp-oauth-callback?state=randomstate&code=<AUTH_CODE>&scope=https://www.googleapis.com/auth/calendar

Now, in our Directus custom endpoint we will have to exchange the “code” authorization grant, with the pair of accessToken and refreshToken.

import { defineEndpoint } from "@directus/extensions-sdk";
import axios from "axios";
import { Router } from "express";
import type { EndpointExtensionContext } from "@cyberleap/beauty-manager-orm/types/directus";
import { useEnv } from "@directus/env";

const handler = (
router: Router,
{ services, database, logger, getSchema }: EndpointExtensionContext
) => {
router.get("/hello", async (req, res) => {
res.send("Hello, World!");
});

router.get("/gcp-oauth-callback", async (req, res) => {
const tokenEndpoint = "https://oauth2.googleapis.com/token";
// @ts-ignore
const accountability = req.accountability;
const schema = await getSchema({ accountability, database });
const { ItemsService } = services;

const gcpAuthService = new ItemsService("gcp_google_calendar", {
schema: schema,
accountability,
knex: database,
});

const gcpAuth = await gcpAuthService.readSingleton({});

const response = await axios.post(tokenEndpoint, {
code: req.query.code,
client_id: gcpAuth.client_id,
client_secret: gcpAuth.client_secret,
redirect_uri: gcpAuth.redirect_uri,
grant_type: "authorization_code",
});

await gcpAuthService.upsertSingleton({
access_token: response.data.access_token,
refresh_token: response.data.refresh_token,
});

const env = useEnv();
const baseUrl = env["PUBLIC_URL"] as string;
res.redirect(baseUrl);
});
};

export default defineEndpoint({ id: "calendar-endpoint", handler

Now we can test our GCP Singleton Login Link, and check that the acessToken and refreshToken are correctly added to the config information

Bookings Hook

The remaining part is to react to the Directus “bookings.items.create” filter, and use this hook to call the google calendar APIs to create an event.

The google calendar oauth2Client will refresh the accessToken when it expires on its own.

Also, note that I’ve imported only the specific sections from “googleapis” library: importing the whole googleapis module was causing my laptop to go OOM.

import { auth } from "googleapis/build/src/apis/oauth2";
import { calendar_v3 } from "googleapis/build/src/apis/calendar";

export class BookingService {

async filterBookingCreate(
payload: Partial<bookings>
): Promise<bookings["id"]> {

const oauth2Client = new auth.OAuth2({
clientId: client_id,
clientSecret: client_secret,
});
oauth2Client.setCredentials({ refresh_token, access_token });

const calendar = new calendar_v3.Calendar({ auth });

const itemUrl = new URL(
`admin/content/bookings/${createdBooking.id}`,
this.env["PUBLIC_URL"] as string
);

const response = await calendar.events.insert({
calendarId: calendar_id,
requestBody: {
summary,
description: itemUrl.href,
start: {
dateTime: createdBooking.start_datetime,
timeZone: "Europe/Rome",
},
end: {
dateTime: createdBooking.end_datetime,
timeZone: "Europe/Rome",
},
},
});

const googleCalendarId = response.data.id!

It is straight forward to also implement the delete() filter.

Conclusion

In the next episode, we will look at how to configure a schedule that will Import Google Calendar events. It is awesome to manage the google calendar events, and hook them to my customer CRM.

--

--

Gabrio Tognozzi
Gabrio Tognozzi

Written by Gabrio Tognozzi

Software Engineer with a strong passion for Cybersecurity and Cryptography

No responses yet