From 62216f2db6c1c09461aec69cbe2401147858bb58 Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:20:05 +0200 Subject: [PATCH] feat: add CalendarsTasker with sync and trigger.dev versions (#26854) * feat: add CalendarsTasker with sync and trigger.dev versions This PR implements a CalendarsTasker following the same pattern as PlatformOrganizationBillingTasker to replace the logic in apps/api/v2/src/ee/calendars/processors/calendars.processor.ts New files created: - CalendarsTasker main orchestrator extending Tasker base class - CalendarsSyncTasker for sync execution - CalendarsTriggerTasker for async execution via trigger.dev - CalendarsTaskService with business logic for ensuring default calendars - trigger.dev task with queue config and retry settings - DI modules using @evyweb/ioctopus - NestJS DI modules for API v2 Co-Authored-By: morgan@cal.com * fix: import and deasync * style: format trigger.config.ts dirs array Co-Authored-By: unknown <> * refactor: move prisma query to UserRepository and set onboarding to true Co-Authored-By: morgan@cal.com * refactor: remove credential.key from UserRepository method Co-Authored-By: morgan@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../lib/modules/calendars-tasker.module.ts | 26 +++++++++ .../tasker/calendars-sync-tasker.service.ts | 15 +++++ .../services/tasker/calendars-task.service.ts | 15 +++++ .../tasker/calendars-tasker.service.ts | 21 +++++++ .../calendars-trigger-tasker.service.ts | 13 +++++ .../di/tasker/CalendarsSyncTasker.module.ts | 26 +++++++++ .../tasker/CalendarsTaskService.container.ts | 11 ++++ .../di/tasker/CalendarsTaskService.module.ts | 26 +++++++++ .../di/tasker/CalendarsTasker.container.ts | 11 ++++ .../di/tasker/CalendarsTasker.module.ts | 28 +++++++++ .../tasker/CalendarsTriggerTasker.module.ts | 24 ++++++++ .../features/calendars/di/tasker/tokens.ts | 10 ++++ .../lib/tasker/CalendarsSyncTasker.ts | 23 ++++++++ .../lib/tasker/CalendarsTaskService.ts | 58 +++++++++++++++++++ .../calendars/lib/tasker/CalendarsTasker.ts | 43 ++++++++++++++ .../lib/tasker/CalendarsTriggerTasker.ts | 16 +++++ .../calendars/lib/tasker/trigger/config.ts | 19 ++++++ .../trigger/ensure-default-calendars.ts | 24 ++++++++ .../calendars/lib/tasker/trigger/schema.ts | 5 ++ .../features/calendars/lib/tasker/types.ts | 15 +++++ packages/features/package.json | 2 + packages/features/trigger.config.ts | 17 +++++- .../users/repositories/UserRepository.ts | 12 ++++ packages/platform/libraries/calendars.ts | 4 ++ yarn.lock | 28 +++++++++ 25 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 apps/api/v2/src/lib/modules/calendars-tasker.module.ts create mode 100644 apps/api/v2/src/lib/services/tasker/calendars-sync-tasker.service.ts create mode 100644 apps/api/v2/src/lib/services/tasker/calendars-task.service.ts create mode 100644 apps/api/v2/src/lib/services/tasker/calendars-tasker.service.ts create mode 100644 apps/api/v2/src/lib/services/tasker/calendars-trigger-tasker.service.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsSyncTasker.module.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsTaskService.container.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsTaskService.module.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsTasker.container.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsTasker.module.ts create mode 100644 packages/features/calendars/di/tasker/CalendarsTriggerTasker.module.ts create mode 100644 packages/features/calendars/di/tasker/tokens.ts create mode 100644 packages/features/calendars/lib/tasker/CalendarsSyncTasker.ts create mode 100644 packages/features/calendars/lib/tasker/CalendarsTaskService.ts create mode 100644 packages/features/calendars/lib/tasker/CalendarsTasker.ts create mode 100644 packages/features/calendars/lib/tasker/CalendarsTriggerTasker.ts create mode 100644 packages/features/calendars/lib/tasker/trigger/config.ts create mode 100644 packages/features/calendars/lib/tasker/trigger/ensure-default-calendars.ts create mode 100644 packages/features/calendars/lib/tasker/trigger/schema.ts create mode 100644 packages/features/calendars/lib/tasker/types.ts create mode 100644 packages/platform/libraries/calendars.ts diff --git a/apps/api/v2/src/lib/modules/calendars-tasker.module.ts b/apps/api/v2/src/lib/modules/calendars-tasker.module.ts new file mode 100644 index 0000000000..97b3280079 --- /dev/null +++ b/apps/api/v2/src/lib/modules/calendars-tasker.module.ts @@ -0,0 +1,26 @@ +import { Module, Scope } from "@nestjs/common"; +import { Logger } from "@/lib/logger.bridge"; +import { CalendarsSyncTaskerService } from "@/lib/services/tasker/calendars-sync-tasker.service"; +import { CalendarsTaskService } from "@/lib/services/tasker/calendars-task.service"; +import { CalendarsTasker } from "@/lib/services/tasker/calendars-tasker.service"; +import { CalendarsTriggerTaskerService } from "@/lib/services/tasker/calendars-trigger-tasker.service"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; + +@Module({ + imports: [PrismaModule], + providers: [ + { + provide: Logger, + useFactory: () => { + return new Logger(); + }, + scope: Scope.TRANSIENT, + }, + CalendarsTaskService, + CalendarsSyncTaskerService, + CalendarsTriggerTaskerService, + CalendarsTasker, + ], + exports: [CalendarsTasker], +}) +export class CalendarsTaskerModule {} diff --git a/apps/api/v2/src/lib/services/tasker/calendars-sync-tasker.service.ts b/apps/api/v2/src/lib/services/tasker/calendars-sync-tasker.service.ts new file mode 100644 index 0000000000..ccb94ced52 --- /dev/null +++ b/apps/api/v2/src/lib/services/tasker/calendars-sync-tasker.service.ts @@ -0,0 +1,15 @@ +import { Logger } from "@/lib/logger.bridge"; +import { CalendarsTaskService } from "@/lib/services/tasker/calendars-task.service"; +import { Injectable } from "@nestjs/common"; + +import { CalendarsSyncTasker as BaseCalendarsSyncTasker } from "@calcom/platform-libraries/calendars"; + +@Injectable() +export class CalendarsSyncTaskerService extends BaseCalendarsSyncTasker { + constructor(calendarsTaskService: CalendarsTaskService, logger: Logger) { + super({ + logger, + calendarsTaskService, + }); + } +} diff --git a/apps/api/v2/src/lib/services/tasker/calendars-task.service.ts b/apps/api/v2/src/lib/services/tasker/calendars-task.service.ts new file mode 100644 index 0000000000..5b63f553ca --- /dev/null +++ b/apps/api/v2/src/lib/services/tasker/calendars-task.service.ts @@ -0,0 +1,15 @@ +import { Logger } from "@/lib/logger.bridge"; +import { Injectable } from "@nestjs/common"; +import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; + +import { CalendarsTaskService as BaseCalendarsTaskService } from "@calcom/platform-libraries/calendars"; + +@Injectable() +export class CalendarsTaskService extends BaseCalendarsTaskService { + constructor(prismaReadService: PrismaReadService, logger: Logger) { + super({ + logger, + prisma: prismaReadService.prisma, + }); + } +} diff --git a/apps/api/v2/src/lib/services/tasker/calendars-tasker.service.ts b/apps/api/v2/src/lib/services/tasker/calendars-tasker.service.ts new file mode 100644 index 0000000000..bb98d3cab7 --- /dev/null +++ b/apps/api/v2/src/lib/services/tasker/calendars-tasker.service.ts @@ -0,0 +1,21 @@ +import { Logger } from "@/lib/logger.bridge"; +import { CalendarsSyncTaskerService } from "@/lib/services/tasker/calendars-sync-tasker.service"; +import { CalendarsTriggerTaskerService } from "@/lib/services/tasker/calendars-trigger-tasker.service"; +import { Injectable } from "@nestjs/common"; + +import { CalendarsTasker as BaseCalendarsTasker } from "@calcom/platform-libraries/calendars"; + +@Injectable() +export class CalendarsTasker extends BaseCalendarsTasker { + constructor( + syncTasker: CalendarsSyncTaskerService, + asyncTasker: CalendarsTriggerTaskerService, + logger: Logger + ) { + super({ + logger, + asyncTasker: asyncTasker, + syncTasker: syncTasker, + }); + } +} diff --git a/apps/api/v2/src/lib/services/tasker/calendars-trigger-tasker.service.ts b/apps/api/v2/src/lib/services/tasker/calendars-trigger-tasker.service.ts new file mode 100644 index 0000000000..dc6be1d25a --- /dev/null +++ b/apps/api/v2/src/lib/services/tasker/calendars-trigger-tasker.service.ts @@ -0,0 +1,13 @@ +import { Logger } from "@/lib/logger.bridge"; +import { Injectable } from "@nestjs/common"; + +import { CalendarsTriggerTasker as BaseCalendarsTriggerTasker } from "@calcom/platform-libraries/calendars"; + +@Injectable() +export class CalendarsTriggerTaskerService extends BaseCalendarsTriggerTasker { + constructor(logger: Logger) { + super({ + logger, + }); + } +} diff --git a/packages/features/calendars/di/tasker/CalendarsSyncTasker.module.ts b/packages/features/calendars/di/tasker/CalendarsSyncTasker.module.ts new file mode 100644 index 0000000000..248772bba6 --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsSyncTasker.module.ts @@ -0,0 +1,26 @@ +import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di"; +import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service"; +import { CalendarsSyncTasker } from "@calcom/features/calendars/lib/tasker/CalendarsSyncTasker"; + +import { moduleLoader as calendarsTaskServiceModuleLoader } from "./CalendarsTaskService.module"; +import { CALENDARS_TASKER_DI_TOKENS } from "./tokens"; + +const thisModule = createModule(); +const token = CALENDARS_TASKER_DI_TOKENS.CALENDARS_SYNC_TASKER; +const moduleToken = CALENDARS_TASKER_DI_TOKENS.CALENDARS_SYNC_TASKER_MODULE; + +const loadModule = bindModuleToClassOnToken({ + module: thisModule, + moduleToken, + token, + classs: CalendarsSyncTasker, + depsMap: { + logger: loggerServiceModule, + calendarsTaskService: calendarsTaskServiceModuleLoader, + }, +}); + +export const moduleLoader = { + token, + loadModule, +} satisfies ModuleLoader; diff --git a/packages/features/calendars/di/tasker/CalendarsTaskService.container.ts b/packages/features/calendars/di/tasker/CalendarsTaskService.container.ts new file mode 100644 index 0000000000..730ad99a12 --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsTaskService.container.ts @@ -0,0 +1,11 @@ +import { createContainer } from "@calcom/features/di/di"; +import { CalendarsTaskService } from "@calcom/features/calendars/lib/tasker/CalendarsTaskService"; + +import { moduleLoader as taskServiceModuleLoader } from "./CalendarsTaskService.module"; + +const container = createContainer(); + +export function getCalendarsTaskService(): CalendarsTaskService { + taskServiceModuleLoader.loadModule(container); + return container.get(taskServiceModuleLoader.token); +} diff --git a/packages/features/calendars/di/tasker/CalendarsTaskService.module.ts b/packages/features/calendars/di/tasker/CalendarsTaskService.module.ts new file mode 100644 index 0000000000..d53a512a5a --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsTaskService.module.ts @@ -0,0 +1,26 @@ +import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di"; +import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service"; +import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma"; +import { CalendarsTaskService } from "@calcom/features/calendars/lib/tasker/CalendarsTaskService"; + +import { CALENDARS_TASKER_DI_TOKENS } from "./tokens"; + +const thisModule = createModule(); +const token = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TASK_SERVICE; +const moduleToken = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TASK_SERVICE_MODULE; + +const loadModule = bindModuleToClassOnToken({ + module: thisModule, + moduleToken, + token, + classs: CalendarsTaskService, + depsMap: { + logger: loggerServiceModule, + prisma: prismaModuleLoader, + }, +}); + +export const moduleLoader = { + token, + loadModule, +} satisfies ModuleLoader; diff --git a/packages/features/calendars/di/tasker/CalendarsTasker.container.ts b/packages/features/calendars/di/tasker/CalendarsTasker.container.ts new file mode 100644 index 0000000000..982702c727 --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsTasker.container.ts @@ -0,0 +1,11 @@ +import { createContainer } from "@calcom/features/di/di"; +import { CalendarsTasker } from "@calcom/features/calendars/lib/tasker/CalendarsTasker"; + +import { moduleLoader as taskerModuleLoader } from "./CalendarsTasker.module"; + +const container = createContainer(); + +export function getCalendarsTasker(): CalendarsTasker { + taskerModuleLoader.loadModule(container); + return container.get(taskerModuleLoader.token); +} diff --git a/packages/features/calendars/di/tasker/CalendarsTasker.module.ts b/packages/features/calendars/di/tasker/CalendarsTasker.module.ts new file mode 100644 index 0000000000..0605cf2e3b --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsTasker.module.ts @@ -0,0 +1,28 @@ +import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di"; +import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service"; +import { CalendarsTasker } from "@calcom/features/calendars/lib/tasker/CalendarsTasker"; + +import { moduleLoader as calendarsSyncTaskerModuleLoader } from "./CalendarsSyncTasker.module"; +import { moduleLoader as calendarsTriggerTaskerModuleLoader } from "./CalendarsTriggerTasker.module"; +import { CALENDARS_TASKER_DI_TOKENS } from "./tokens"; + +const thisModule = createModule(); +const token = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TASKER; +const moduleToken = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TASKER_MODULE; + +const loadModule = bindModuleToClassOnToken({ + module: thisModule, + moduleToken, + token, + classs: CalendarsTasker, + depsMap: { + logger: loggerServiceModule, + syncTasker: calendarsSyncTaskerModuleLoader, + asyncTasker: calendarsTriggerTaskerModuleLoader, + }, +}); + +export const moduleLoader = { + token, + loadModule, +} satisfies ModuleLoader; diff --git a/packages/features/calendars/di/tasker/CalendarsTriggerTasker.module.ts b/packages/features/calendars/di/tasker/CalendarsTriggerTasker.module.ts new file mode 100644 index 0000000000..1453498c45 --- /dev/null +++ b/packages/features/calendars/di/tasker/CalendarsTriggerTasker.module.ts @@ -0,0 +1,24 @@ +import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di"; +import { moduleLoader as loggerServiceModule } from "@calcom/features/di/shared/services/logger.service"; +import { CalendarsTriggerTasker } from "@calcom/features/calendars/lib/tasker/CalendarsTriggerTasker"; + +import { CALENDARS_TASKER_DI_TOKENS } from "./tokens"; + +const thisModule = createModule(); +const token = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TRIGGER_TASKER; +const moduleToken = CALENDARS_TASKER_DI_TOKENS.CALENDARS_TRIGGER_TASKER_MODULE; + +const loadModule = bindModuleToClassOnToken({ + module: thisModule, + moduleToken, + token, + classs: CalendarsTriggerTasker, + depsMap: { + logger: loggerServiceModule, + }, +}); + +export const moduleLoader = { + token, + loadModule, +} satisfies ModuleLoader; diff --git a/packages/features/calendars/di/tasker/tokens.ts b/packages/features/calendars/di/tasker/tokens.ts new file mode 100644 index 0000000000..d8c8f10d00 --- /dev/null +++ b/packages/features/calendars/di/tasker/tokens.ts @@ -0,0 +1,10 @@ +export const CALENDARS_TASKER_DI_TOKENS = { + CALENDARS_TASK_SERVICE: Symbol("CalendarsTaskService"), + CALENDARS_TASK_SERVICE_MODULE: Symbol("CalendarsTaskServiceModule"), + CALENDARS_SYNC_TASKER: Symbol("CalendarsSyncTasker"), + CALENDARS_SYNC_TASKER_MODULE: Symbol("CalendarsSyncTaskerModule"), + CALENDARS_TRIGGER_TASKER: Symbol("CalendarsTriggerTasker"), + CALENDARS_TRIGGER_TASKER_MODULE: Symbol("CalendarsTriggerTaskerModule"), + CALENDARS_TASKER: Symbol("CalendarsTasker"), + CALENDARS_TASKER_MODULE: Symbol("CalendarsTaskerModule"), +}; diff --git a/packages/features/calendars/lib/tasker/CalendarsSyncTasker.ts b/packages/features/calendars/lib/tasker/CalendarsSyncTasker.ts new file mode 100644 index 0000000000..531459c40f --- /dev/null +++ b/packages/features/calendars/lib/tasker/CalendarsSyncTasker.ts @@ -0,0 +1,23 @@ +import type { ITaskerDependencies } from "@calcom/lib/tasker/types"; +import { nanoid } from "nanoid"; + +import type { CalendarsTaskService } from "./CalendarsTaskService"; +import type { ICalendarsTasker } from "./types"; + +export interface ICalendarsSyncTaskerDependencies { + calendarsTaskService: CalendarsTaskService; +} + +export class CalendarsSyncTasker implements ICalendarsTasker { + constructor( + public readonly dependencies: ITaskerDependencies & ICalendarsSyncTaskerDependencies + ) {} + + async ensureDefaultCalendars( + payload: Parameters[0] + ): Promise<{ runId: string }> { + const runId = `sync_${nanoid(10)}`; + await this.dependencies.calendarsTaskService.ensureDefaultCalendars(payload); + return { runId }; + } +} diff --git a/packages/features/calendars/lib/tasker/CalendarsTaskService.ts b/packages/features/calendars/lib/tasker/CalendarsTaskService.ts new file mode 100644 index 0000000000..62f7166ea2 --- /dev/null +++ b/packages/features/calendars/lib/tasker/CalendarsTaskService.ts @@ -0,0 +1,58 @@ +import type { ITaskerDependencies } from "@calcom/lib/tasker/types"; +import type { PrismaClient } from "@calcom/prisma"; + +import { UserRepository } from "../../../users/repositories/UserRepository"; +import type { CalendarsTasks } from "./types"; + +export interface ICalendarsTaskServiceDependencies { + prisma: PrismaClient; +} + +export class CalendarsTaskService implements CalendarsTasks { + constructor( + public readonly dependencies: { + logger: ITaskerDependencies["logger"]; + } & ICalendarsTaskServiceDependencies + ) {} + + async ensureDefaultCalendars( + payload: Parameters[0] + ): Promise { + const { userId } = payload; + const { prisma, logger } = this.dependencies; + + try { + const userRepository = new UserRepository(prisma); + const userWithCalendars = await userRepository.findByIdWithSelectedCalendars({ userId }); + + if (!userWithCalendars) { + logger.error(`User not found for ensureDefaultCalendars`, { userId }); + return; + } + + const { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } = await import( + "@calcom/features/calendars/lib/getConnectedDestinationCalendars" + ); + + await getConnectedDestinationCalendarsAndEnsureDefaultsInDb({ + user: { + ...userWithCalendars, + allSelectedCalendars: userWithCalendars.selectedCalendars, + userLevelSelectedCalendars: userWithCalendars.selectedCalendars.filter( + (calendar) => !calendar.eventTypeId + ), + }, + onboarding: true, + eventTypeId: null, + prisma, + }); + + logger.info("Successfully ensured default calendars for user", { userId }); + } catch (err) { + logger.error(`Failed to ensure default calendars for user`, { + userId, + error: err instanceof Error ? err.message : "Unknown error", + }); + } + } +} diff --git a/packages/features/calendars/lib/tasker/CalendarsTasker.ts b/packages/features/calendars/lib/tasker/CalendarsTasker.ts new file mode 100644 index 0000000000..8db7aa6ca9 --- /dev/null +++ b/packages/features/calendars/lib/tasker/CalendarsTasker.ts @@ -0,0 +1,43 @@ +import { Tasker } from "@calcom/lib/tasker/Tasker"; +import type { ILogger } from "@calcom/lib/tasker/types"; +import type { TriggerOptions } from "@trigger.dev/sdk"; +import type { CalendarsSyncTasker } from "./CalendarsSyncTasker"; +import type { CalendarsTriggerTasker } from "./CalendarsTriggerTasker"; +import type { ICalendarsTasker, CalendarsTaskPayload } from "./types"; + +export interface ICalendarsTaskerDependencies { + asyncTasker: CalendarsTriggerTasker; + syncTasker: CalendarsSyncTasker; + logger: ILogger; +} + +export class CalendarsTasker extends Tasker { + constructor(public readonly dependencies: ICalendarsTaskerDependencies) { + super(dependencies); + } + + public async ensureDefaultCalendars(data: { + payload: CalendarsTaskPayload; + options?: TriggerOptions; + }): Promise<{ runId: string }> { + const { payload } = data; + let taskResponse: { + runId: string; + } = { runId: "task-not-found" }; + + try { + taskResponse = await this.dispatch("ensureDefaultCalendars", payload, data.options); + + this.logger.info(`CalendarsTasker ensureDefaultCalendars success:`, taskResponse, { + userId: payload.userId, + }); + } catch { + taskResponse = { runId: "task-failed" }; + this.logger.error(`CalendarsTasker ensureDefaultCalendars failed`, taskResponse, { + userId: payload.userId, + }); + } + + return taskResponse; + } +} diff --git a/packages/features/calendars/lib/tasker/CalendarsTriggerTasker.ts b/packages/features/calendars/lib/tasker/CalendarsTriggerTasker.ts new file mode 100644 index 0000000000..1815dd7ac1 --- /dev/null +++ b/packages/features/calendars/lib/tasker/CalendarsTriggerTasker.ts @@ -0,0 +1,16 @@ +import type { ITaskerDependencies } from "@calcom/lib/tasker/types"; +import type { TriggerOptions } from "@trigger.dev/sdk"; +import type { ICalendarsTasker } from "./types"; + +export class CalendarsTriggerTasker implements ICalendarsTasker { + constructor(public readonly dependencies: ITaskerDependencies) {} + + async ensureDefaultCalendars( + payload: Parameters[0], + options?: TriggerOptions + ): Promise<{ runId: string }> { + const { ensureDefaultCalendars } = await import("./trigger/ensure-default-calendars"); + const handle = await ensureDefaultCalendars.trigger(payload, options); + return { runId: handle.id }; + } +} diff --git a/packages/features/calendars/lib/tasker/trigger/config.ts b/packages/features/calendars/lib/tasker/trigger/config.ts new file mode 100644 index 0000000000..a53989a963 --- /dev/null +++ b/packages/features/calendars/lib/tasker/trigger/config.ts @@ -0,0 +1,19 @@ +import { type schemaTask, queue } from "@trigger.dev/sdk"; + +type CalendarsTask = Pick[0], "machine" | "retry" | "queue">; + +export const calendarsQueue = queue({ + name: "calendars", + concurrencyLimit: 10, +}); + +export const calendarsTaskConfig: CalendarsTask = { + queue: calendarsQueue, + retry: { + maxAttempts: 3, + factor: 2, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + randomize: true, + }, +}; diff --git a/packages/features/calendars/lib/tasker/trigger/ensure-default-calendars.ts b/packages/features/calendars/lib/tasker/trigger/ensure-default-calendars.ts new file mode 100644 index 0000000000..babbd86f87 --- /dev/null +++ b/packages/features/calendars/lib/tasker/trigger/ensure-default-calendars.ts @@ -0,0 +1,24 @@ +import { schemaTask, type TaskWithSchema } from "@trigger.dev/sdk"; +import type { z } from "zod"; + +import { calendarsTaskConfig } from "./config"; +import { calendarsTaskSchema } from "./schema"; + +export const ENSURE_DEFAULT_CALENDARS_JOB_ID = "calendars.ensure-default-calendars"; + +export const ensureDefaultCalendars: TaskWithSchema< + typeof ENSURE_DEFAULT_CALENDARS_JOB_ID, + typeof calendarsTaskSchema +> = schemaTask({ + id: ENSURE_DEFAULT_CALENDARS_JOB_ID, + ...calendarsTaskConfig, + schema: calendarsTaskSchema, + run: async (payload: z.infer) => { + const { getCalendarsTaskService } = await import( + "@calcom/features/calendars/di/tasker/CalendarsTaskService.container" + ); + + const calendarsTaskService = getCalendarsTaskService(); + await calendarsTaskService.ensureDefaultCalendars(payload); + }, +}); diff --git a/packages/features/calendars/lib/tasker/trigger/schema.ts b/packages/features/calendars/lib/tasker/trigger/schema.ts new file mode 100644 index 0000000000..fea289b2f4 --- /dev/null +++ b/packages/features/calendars/lib/tasker/trigger/schema.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; + +export const calendarsTaskSchema = z.object({ + userId: z.number(), +}); diff --git a/packages/features/calendars/lib/tasker/types.ts b/packages/features/calendars/lib/tasker/types.ts new file mode 100644 index 0000000000..efec307a95 --- /dev/null +++ b/packages/features/calendars/lib/tasker/types.ts @@ -0,0 +1,15 @@ +import type { TriggerOptions } from "@trigger.dev/sdk"; +import type { z } from "zod"; +import type { calendarsTaskSchema } from "./trigger/schema"; + +type WithVoidReturns = { + [K in keyof T]: T[K] extends (...args: infer P) => unknown ? (...args: P) => void : T[K]; +}; + +export type CalendarsTaskPayload = z.infer; + +export interface ICalendarsTasker { + ensureDefaultCalendars(payload: CalendarsTaskPayload, options?: TriggerOptions): Promise<{ runId: string }>; +} + +export type CalendarsTasks = WithVoidReturns>; diff --git a/packages/features/package.json b/packages/features/package.json index 1bd41ab0a5..268d7d9e29 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -27,6 +27,7 @@ "city-timezones": "1.2.1", "class-variance-authority": "0.7.1", "date-fns-tz": "3.2.0", + "deasync": "^0.1.31", "framer-motion": "10.12.8", "p-limit": "6.2.0", "react-awesome-query-builder": "5.1.2", @@ -48,6 +49,7 @@ "devDependencies": { "@calcom/testing": "workspace:*", "@testing-library/react-hooks": "8.0.1", + "@types/deasync": "^0", "@types/web-push": "3.6.3", "trigger.dev": "4.3.2" } diff --git a/packages/features/trigger.config.ts b/packages/features/trigger.config.ts index 987ad096ad..7e7e47b2bc 100644 --- a/packages/features/trigger.config.ts +++ b/packages/features/trigger.config.ts @@ -16,7 +16,11 @@ export default defineConfig({ project: process.env.TRIGGER_DEV_PROJECT_REF ?? "", // e.g., "proj_abc123" // Directories containing your tasks - dirs: ["./bookings/lib/tasker/trigger/notifications", "./ee/billing/service/proration/tasker/trigger"], // Customize based on your project structure + dirs: [ + "./bookings/lib/tasker/trigger/notifications", + "./calendars/lib/tasker/trigger", + "./ee/billing/service/proration/tasker/trigger", + ], // Customize based on your project structure // Retry configuration retries: { @@ -35,7 +39,16 @@ export default defineConfig({ // Build configuration (optional) build: { - external: ["@prisma/client", "nodemailer", "jsdom", "playwright-core", "playwright", "chromium-bidi"], + external: [ + "@prisma/client", + "nodemailer", + "jsdom", + "playwright-core", + "playwright", + "chromium-bidi", + "http-cookie-agent", + "deasync", + ], extensions: canSyncEnvVars ? [ syncVercelEnvVars({ diff --git a/packages/features/users/repositories/UserRepository.ts b/packages/features/users/repositories/UserRepository.ts index f5be9cf229..c310e5ac82 100644 --- a/packages/features/users/repositories/UserRepository.ts +++ b/packages/features/users/repositories/UserRepository.ts @@ -1487,4 +1487,16 @@ export class UserRepository { select: userSelect, }); } + + async findByIdWithSelectedCalendars({ userId }: { userId: number }) { + return this.prismaClient.user.findUnique({ + where: { id: userId }, + select: { + id: true, + email: true, + selectedCalendars: true, + destinationCalendar: true, + }, + }); + } } diff --git a/packages/platform/libraries/calendars.ts b/packages/platform/libraries/calendars.ts new file mode 100644 index 0000000000..e4e9d80803 --- /dev/null +++ b/packages/platform/libraries/calendars.ts @@ -0,0 +1,4 @@ +export { CalendarsTaskService } from "@calcom/features/calendars/lib/tasker/CalendarsTaskService"; +export { CalendarsSyncTasker } from "@calcom/features/calendars/lib/tasker/CalendarsSyncTasker"; +export { CalendarsTriggerTasker } from "@calcom/features/calendars/lib/tasker/CalendarsTriggerTasker"; +export { CalendarsTasker } from "@calcom/features/calendars/lib/tasker/CalendarsTasker"; diff --git a/yarn.lock b/yarn.lock index 13a3cd22b9..9eec504e09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2607,11 +2607,13 @@ __metadata: "@tanstack/react-virtual": "npm:3.10.9" "@testing-library/react-hooks": "npm:8.0.1" "@trigger.dev/sdk": "npm:4.3.2" + "@types/deasync": "npm:^0" "@types/web-push": "npm:3.6.3" "@vercel/functions": "npm:1.4.0" city-timezones: "npm:1.2.1" class-variance-authority: "npm:0.7.1" date-fns-tz: "npm:3.2.0" + deasync: "npm:^0.1.31" framer-motion: "npm:10.12.8" p-limit: "npm:6.2.0" react-awesome-query-builder: "npm:5.1.2" @@ -15798,6 +15800,13 @@ __metadata: languageName: node linkType: hard +"@types/deasync@npm:^0": + version: 0.1.5 + resolution: "@types/deasync@npm:0.1.5" + checksum: 10/5a8fec89572abc6fbcce7b4f5b5357ba58b2d6d6860b640eb452eb9e87a8d44f787a40450e456b6e2d7fa12cf52bac2ab0d2f9be431dd51bd40584e316ffaa2c + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -20796,6 +20805,16 @@ __metadata: languageName: node linkType: hard +"deasync@npm:^0.1.31": + version: 0.1.31 + resolution: "deasync@npm:0.1.31" + dependencies: + bindings: "npm:^1.5.0" + node-addon-api: "npm:^1.7.1" + checksum: 10/e3430cea041d7de593e0cc80a88f9f9b9b8c4f5cc635bcd3a237b7a5371d62dbd50daed0f14ed4d148754a345b993aa7a1e4da848bda69f69c60fb58fd5fac07 + languageName: node + linkType: hard + "debounce-fn@npm:^4.0.0": version: 4.0.0 resolution: "debounce-fn@npm:4.0.0" @@ -30425,6 +30444,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^1.7.1": + version: 1.7.2 + resolution: "node-addon-api@npm:1.7.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10/6bf8217a8cd8148f4bbfd319b46d33587e9fb2e63e3c856ded67a76715167f7a6b17e1d9b8bbf3b8508befeb6a4adb10d92b8998ed5c19ca8448343f4cea11d6 + languageName: node + linkType: hard + "node-addon-api@npm:^7.0.0": version: 7.1.1 resolution: "node-addon-api@npm:7.1.1"