chore: redis cache for calendars in api v2 (#23622)

* chore: implement redis cache for calendars api v2

* fixup! chore: implement redis cache for calendars api v2

* fixup! fixup! chore: implement redis cache for calendars api v2

* fix leverage genetic type of redis.get

* refactor: address supalarry's PR feedback

- Create CalendarsCacheService to separate cache responsibilities
- Rename cache methods to be more descriptive (getConnectedAndDestinationCalendarsCache, setConnectedAndDestinationCalendarsCache)
- Remove unused redisService injection from CalendarsController
- Move all cache-related functionality to dedicated service

Addresses comments 9, 10, and 11 from supalarry on PR #23622

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* refactor: address supalarry's latest PR feedback

- Remove redundant deleteConnectedAndDestinationCalendarsCache from CalendarsService
- Update controller to directly inject and use CalendarsCacheService
- Extract TTL constant and remove manual JSON.stringify in cache service

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: update IcsFeedService to use CalendarsCacheService directly

- Inject CalendarsCacheService into IcsFeedService constructor
- Replace calendarsService.deleteCalendarCache with calendarsCacheService.deleteConnectedAndDestinationCalendarsCache
- Completes the refactoring to eliminate all old cache method references

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: update destination-calendars and selected-calendars services to use CalendarsCacheService directly

- Inject CalendarsCacheService in both service constructors
- Replace calendarsService.deleteCalendarCache calls with calendarsCacheService.deleteConnectedAndDestinationCalendarsCache
- Add CalendarsCacheService to module providers for proper dependency injection
- Completes migration of all cache method references to use dedicated cache service

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* chore: use CalendarsCacheService

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Morgan
2025-09-08 21:21:55 +00:00
committed by GitHub
co-authored by morgan@cal.com <morgan@cal.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent f5bb734441
commit 300cd31efb
17 changed files with 205 additions and 21 deletions
@@ -1,6 +1,7 @@
import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller";
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
@@ -41,6 +42,7 @@ import { Module } from "@nestjs/common";
OAuthClientUsersService,
PlatformBookingsService,
CalendarsService,
CalendarsCacheService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
@@ -10,6 +10,7 @@ import { OutputBookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
@@ -71,6 +72,7 @@ import { Module } from "@nestjs/common";
ApiKeysRepository,
PlatformBookingsService,
CalendarsService,
CalendarsCacheService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
@@ -3,6 +3,7 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsController } from "@/ee/calendars/controllers/calendars.controller";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
import { IcsFeedService } from "@/ee/calendars/services/ics-feed.service";
@@ -10,16 +11,18 @@ import { OutlookService } from "@/ee/calendars/services/outlook.service";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, UsersModule, TokensModule],
imports: [PrismaModule, UsersModule, TokensModule, RedisModule],
providers: [
CredentialsRepository,
CalendarsService,
CalendarsCacheService,
OutlookService,
GoogleCalendarService,
AppleCalendarService,
@@ -9,6 +9,7 @@ import {
DeletedCalendarCredentialsOutputDto,
} from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
import { IcsFeedService } from "@/ee/calendars/services/ics-feed.service";
@@ -79,6 +80,7 @@ const calendarStateSchema = z.object({
export class CalendarsController {
constructor(
private readonly calendarsService: CalendarsService,
private readonly calendarsCacheService: CalendarsCacheService,
private readonly outlookService: OutlookService,
private readonly googleCalendarService: GoogleCalendarService,
private readonly appleCalendarService: AppleCalendarService,
@@ -307,6 +309,8 @@ export class CalendarsController {
credentialId
);
this.calendarsCacheService.deleteConnectedAndDestinationCalendarsCache(user.id);
return {
status: SUCCESS_STATUS,
data: plainToClass(
@@ -0,0 +1,36 @@
import { RedisService } from "@/modules/redis/redis.service";
import { Injectable } from "@nestjs/common";
import type { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/platform-libraries";
export const REDIS_CALENDARS_CACHE_KEY = (userId: number) => `apiv2:user:${userId}:calendars`;
export const CALENDARS_CACHE_TTL_MS = 10_000;
type ConnectedDestinationCalendars = Awaited<
ReturnType<typeof getConnectedDestinationCalendarsAndEnsureDefaultsInDb>
>;
@Injectable()
export class CalendarsCacheService {
constructor(private readonly redisService: RedisService) {}
async deleteConnectedAndDestinationCalendarsCache(userId: number) {
await this.redisService.del(REDIS_CALENDARS_CACHE_KEY(userId));
}
async getConnectedAndDestinationCalendarsCache(userId: number) {
const cachedResult = await this.redisService.get<ConnectedDestinationCalendars>(
REDIS_CALENDARS_CACHE_KEY(userId)
);
return cachedResult;
}
async setConnectedAndDestinationCalendarsCache(
userId: number,
calendars: ConnectedDestinationCalendars
): Promise<void> {
await this.redisService.set<ConnectedDestinationCalendars>(REDIS_CALENDARS_CACHE_KEY(userId), calendars, {
ttl: CALENDARS_CACHE_TTL_MS,
});
}
}
@@ -1,4 +1,5 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { AppsRepository } from "@/modules/apps/apps.repository";
import {
CredentialsRepository,
@@ -37,7 +38,8 @@ export class CalendarsService {
private readonly appsRepository: AppsRepository,
private readonly calendarsRepository: CalendarsRepository,
private readonly dbWrite: PrismaWriteService,
private readonly selectedCalendarsRepository: SelectedCalendarsRepository
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
private readonly calendarsCacheService: CalendarsCacheService
) {}
private buildNonDelegationCredentials<TCredential>(credentials: TCredential[]) {
@@ -52,11 +54,17 @@ export class CalendarsService {
}
async getCalendars(userId: number) {
const cachedResult = await this.calendarsCacheService.getConnectedAndDestinationCalendarsCache(userId);
if (cachedResult) {
return cachedResult;
}
const userWithCalendars = await this.usersRepository.findByIdWithCalendars(userId);
if (!userWithCalendars) {
throw new NotFoundException("User not found");
}
return getConnectedDestinationCalendarsAndEnsureDefaultsInDb({
const result = await getConnectedDestinationCalendarsAndEnsureDefaultsInDb({
user: {
...userWithCalendars,
allSelectedCalendars: userWithCalendars.selectedCalendars,
@@ -68,6 +76,10 @@ export class CalendarsService {
eventTypeId: null,
prisma: this.dbWrite.prisma as unknown as PrismaClient,
});
console.log("saving cache", JSON.stringify(result));
await this.calendarsCacheService.setConnectedAndDestinationCalendarsCache(userId, result);
return result;
}
async getBusyTimes(
@@ -187,6 +199,8 @@ export class CalendarsService {
userId,
calendarType
);
await this.calendarsCacheService.deleteConnectedAndDestinationCalendarsCache(userId);
}
async checkCalendarCredentialValidity(userId: number, credentialId: number, type: string) {
@@ -1,7 +1,9 @@
import { ICSFeedCalendarApp } from "@/ee/calendars/calendars.interface";
import { CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { RedisService } from "@/modules/redis/redis.service";
import { BadRequestException, UnauthorizedException, Logger } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
@@ -13,7 +15,9 @@ import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
export class IcsFeedService implements ICSFeedCalendarApp {
constructor(
private readonly calendarsService: CalendarsService,
private readonly credentialRepository: CredentialsRepository
private readonly calendarsCacheService: CalendarsCacheService,
private readonly credentialRepository: CredentialsRepository,
private readonly redisService: RedisService
) {}
private logger = new Logger("IcsFeedService");
@@ -58,6 +62,9 @@ export class IcsFeedService implements ICSFeedCalendarApp {
data.key,
userId
);
await this.calendarsCacheService.deleteConnectedAndDestinationCalendarsCache(userId);
return {
status: SUCCESS_STATUS,
data: {
@@ -1,4 +1,5 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { EventTypesController_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/controllers/event-types.controller";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
@@ -11,6 +12,7 @@ import { AppsRepository } from "@/modules/apps/apps.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersService } from "@/modules/users/services/users.service";
@@ -18,7 +20,7 @@ import { UsersRepository } from "@/modules/users/users.repository";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, MembershipsModule, TokensModule, SelectedCalendarsModule],
imports: [PrismaModule, MembershipsModule, TokensModule, SelectedCalendarsModule, RedisModule],
providers: [
EventTypesRepository_2024_06_14,
EventTypesService_2024_06_14,
@@ -29,6 +31,7 @@ import { Module } from "@nestjs/common";
SchedulesRepository_2024_06_11,
EventTypeResponseTransformPipe,
CalendarsService,
CalendarsCacheService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
+4 -1
View File
@@ -1,4 +1,5 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsCacheService } from "@/ee/calendars/services/calendars-cache.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GcalController } from "@/ee/gcal/gcal.controller";
import { AppsRepository } from "@/modules/apps/apps.repository";
@@ -6,6 +7,7 @@ import { GCalService } from "@/modules/apps/services/gcal.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersRepository } from "@/modules/users/users.repository";
@@ -13,7 +15,7 @@ import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Module({
imports: [PrismaModule, TokensModule, OAuthClientModule],
imports: [PrismaModule, TokensModule, OAuthClientModule, RedisModule],
providers: [
AppsRepository,
ConfigService,
@@ -21,6 +23,7 @@ import { ConfigService } from "@nestjs/config";
SelectedCalendarsRepository,
GCalService,
CalendarsService,
CalendarsCacheService,
UsersRepository,
CalendarsRepository,
],