Files
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-11 15:47:14 +01:00

217 lines
7.1 KiB
TypeScript

import type { calendar_v3 } from "@googleapis/calendar";
import { v4 as uuid } from "uuid";
import { CalendarAuth } from "@calcom/app-store/googlecalendar/lib/CalendarAuth";
import dayjs from "@calcom/dayjs";
import { CalendarCacheEventService } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventService";
import logger from "@calcom/lib/logger";
import type { SelectedCalendar } from "@calcom/prisma/client";
import type {
ICalendarSubscriptionPort,
CalendarSubscriptionResult,
CalendarSubscriptionEvent,
CalendarSubscriptionEventItem,
CalendarCredential,
} from "../lib/CalendarSubscriptionPort.interface";
const log = logger.getSubLogger({ prefix: ["GoogleCalendarSubscriptionAdapter"] });
/**
* Google Calendar Subscription Adapter
*
* This adapter uses the Google Calendar API to create and manage calendar subscriptions
* @see https://developers.google.com/google-apps/calendar/quickstart/nodejs
*/
export class GoogleCalendarSubscriptionAdapter implements ICalendarSubscriptionPort {
private GOOGLE_WEBHOOK_TOKEN = process.env.GOOGLE_WEBHOOK_TOKEN;
private GOOGLE_WEBHOOK_URL = `${
process.env.GOOGLE_WEBHOOK_URL || process.env.NEXT_PUBLIC_WEBAPP_URL
}/api/webhooks/calendar-subscription/google_calendar`;
async validate(request: Request): Promise<boolean> {
const token = request?.headers?.get("X-Goog-Channel-Token");
if (!this.GOOGLE_WEBHOOK_TOKEN) {
log.warn("GOOGLE_WEBHOOK_TOKEN not configured");
return false;
}
if (token !== this.GOOGLE_WEBHOOK_TOKEN) {
log.warn("Invalid webhook token");
return false;
}
return true;
}
async extractChannelId(request: Request): Promise<string | null> {
const channelId = request?.headers?.get("X-Goog-Channel-ID");
if (!channelId) {
log.warn("Missing channel ID in webhook");
return null;
}
return channelId;
}
async subscribe(
selectedCalendar: SelectedCalendar,
credential: CalendarCredential
): Promise<CalendarSubscriptionResult> {
log.debug("Attempt to subscribe to Google Calendar", { externalId: selectedCalendar.externalId });
const MONTH_IN_SECONDS = 60 * 60 * 24 * 30;
const client = await this.getClient(credential);
const result = await client.events.watch({
calendarId: selectedCalendar.externalId,
requestBody: {
id: uuid(),
type: "web_hook",
address: this.GOOGLE_WEBHOOK_URL,
token: this.GOOGLE_WEBHOOK_TOKEN,
params: {
ttl: MONTH_IN_SECONDS.toFixed(0),
},
},
});
const e = result.data?.expiration;
const expiration = e ? new Date(/^\d+$/.test(e) ? +e : e) : null;
return {
provider: "google_calendar",
id: result.data.id,
resourceId: result.data.resourceId,
resourceUri: result.data.resourceUri,
expiration,
};
}
async unsubscribe(selectedCalendar: SelectedCalendar, credential: CalendarCredential): Promise<void> {
log.debug("Attempt to unsubscribe from Google Calendar", { externalId: selectedCalendar.externalId });
const client = await this.getClient(credential);
await client.channels
.stop({
requestBody: {
id: selectedCalendar.channelId as string,
resourceId: selectedCalendar.channelResourceId as string,
},
})
.catch((err) => {
log.error("Error unsubscribing from Google Calendar", err);
throw err;
});
}
async fetchEvents(
selectedCalendar: SelectedCalendar,
credential: CalendarCredential
): Promise<CalendarSubscriptionEvent> {
log.info("Attempt to fetch events from Google Calendar", { externalId: selectedCalendar.externalId });
const client = await this.getClient(credential);
let syncToken = selectedCalendar.syncToken || undefined;
let pageToken;
const params: calendar_v3.Params$Resource$Events$List = {
calendarId: selectedCalendar.externalId,
pageToken,
singleEvents: true,
};
if (!syncToken) {
const now = dayjs().startOf("day");
// first sync or unsync (3 months)
const monthsAhead = now.add(CalendarCacheEventService.MONTHS_AHEAD, "month").endOf("day");
const timeMinISO = now.toISOString();
const timeMaxISO = monthsAhead.toISOString();
params.timeMin = timeMinISO;
params.timeMax = timeMaxISO;
} else {
// incremental sync
params.syncToken = syncToken;
}
const events: calendar_v3.Schema$Event[] = [];
do {
const { data }: { data: calendar_v3.Schema$Events } = await client.events.list(params);
syncToken = data.nextSyncToken || syncToken;
pageToken = data.nextPageToken ?? null;
if (pageToken) {
params.pageToken = pageToken;
}
events.push(...(data.items || []));
} while (pageToken);
return {
provider: "google_calendar",
syncToken: syncToken || null,
items: this.parseEvents(events),
};
}
private parseEvents(events: calendar_v3.Schema$Event[]): CalendarSubscriptionEventItem[] {
const now = dayjs().startOf("day");
const monthsAhead = now.add(CalendarCacheEventService.MONTHS_AHEAD, "month").endOf("day");
function filterEventsWithoutId(event: calendar_v3.Schema$Event) {
return typeof event.id === "string" && !!event.id;
}
function filterEventsByDateRange(event: calendar_v3.Schema$Event) {
const start = dayjs(event.start?.dateTime || event.start?.date);
return !start.isBefore(now) && !start.isAfter(monthsAhead);
}
return events
.filter(filterEventsWithoutId)
.filter(filterEventsByDateRange)
.map((event) => {
// empty or opaque is busy
const busy = !event.transparency || event.transparency === "opaque";
const start = event.start?.dateTime
? new Date(event.start.dateTime)
: event.start?.date
? new Date(event.start.date)
: new Date();
const end = event.end?.dateTime
? new Date(event.end.dateTime)
: event.end?.date
? new Date(event.end.date)
: new Date();
return {
id: event.id as string,
iCalUID: event.iCalUID ?? null,
start,
end,
busy,
summary: event.summary ?? null,
description: event.description ?? null,
location: event.location ?? null,
kind: event.kind ?? null,
etag: event.etag ?? null,
status: event.status ?? null,
isAllDay: typeof event.start?.date === "string" && !event.start?.dateTime ? true : false,
timeZone: event.start?.timeZone ?? null,
recurringEventId: event.recurringEventId ?? null,
originalStartDate: event.originalStartTime?.dateTime
? new Date(event.originalStartTime.dateTime)
: event.originalStartTime?.date
? new Date(event.originalStartTime.date)
: null,
createdAt: event.created ? new Date(event.created) : null,
updatedAt: event.updated ? new Date(event.updated) : null,
};
});
}
private async getClient(credential: CalendarCredential) {
const auth = new CalendarAuth(credential);
return await auth.getClient();
}
}