* refactor: move repositories from lib to features domain folders - Move HolidayRepository to features/holidays/repositories - Move PrismaTrackingRepository to features/bookings/repositories - Move PrismaBookingPaymentRepository to features/bookings/repositories - Move PrismaRoutingFormResponseRepository to features/routing-forms/repositories - Move PrismaAssignmentReasonRepository to features/assignment-reason/repositories - Move VerificationTokenRepository to features/auth/repositories - Move WorkspacePlatformRepository to features/workspace-platform/repositories - Move DTO files to their respective feature domains - Merge lib DestinationCalendarRepository into features version - Merge lib SelectedCalendarRepository into features version - Update all import paths across the codebase This follows the vertical slice architecture pattern by organizing repositories by domain rather than by technical layer. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update VerificationTokenService import path to new location Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update test file imports to use new repository locations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * mv * fix structure * fix * refactor: merge unit tests for SelectedCalendarRepository into single file Co-Authored-By: benny@cal.com <sldisek783@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import dayjs from "@calcom/dayjs";
|
|
import { HolidayRepository } from "@calcom/features/holidays/repositories/HolidayRepository";
|
|
|
|
import { GOOGLE_HOLIDAY_CALENDARS, HOLIDAY_CACHE_DAYS } from "./constants";
|
|
import { getGoogleCalendarClient, type GoogleCalendarClient, type GoogleCalendarHoliday } from "./GoogleCalendarClient";
|
|
|
|
export interface CachedHoliday {
|
|
id: string;
|
|
countryCode: string;
|
|
eventId: string;
|
|
name: string;
|
|
date: Date;
|
|
year: number;
|
|
}
|
|
|
|
export class HolidayServiceCachingProxy {
|
|
private calendarClient: GoogleCalendarClient;
|
|
|
|
constructor(calendarClient?: GoogleCalendarClient) {
|
|
this.calendarClient = calendarClient || getGoogleCalendarClient();
|
|
}
|
|
|
|
private async isCacheStale(countryCode: string, year: number): Promise<boolean> {
|
|
const staleDate = dayjs().subtract(HOLIDAY_CACHE_DAYS, "days").toDate();
|
|
|
|
const cachedEntry = await HolidayRepository.findFirstCacheEntry({ countryCode, year });
|
|
|
|
if (!cachedEntry) {
|
|
return true;
|
|
}
|
|
|
|
return cachedEntry.updatedAt < staleDate;
|
|
}
|
|
|
|
private async refreshCache(countryCode: string, year: number): Promise<void> {
|
|
const calendarConfig = GOOGLE_HOLIDAY_CALENDARS[countryCode];
|
|
if (!calendarConfig) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const holidays = await this.calendarClient.fetchHolidays(countryCode, year);
|
|
|
|
await HolidayRepository.refreshCache({
|
|
countryCode,
|
|
year,
|
|
holidays: holidays.map((h: GoogleCalendarHoliday) => ({
|
|
countryCode: h.countryCode,
|
|
calendarId: calendarConfig.calendarId,
|
|
eventId: h.eventId,
|
|
name: h.name,
|
|
date: h.date,
|
|
year: h.year,
|
|
})),
|
|
});
|
|
} catch (error) {
|
|
console.error(`Failed to refresh holiday cache for ${countryCode}:`, error);
|
|
}
|
|
}
|
|
|
|
async getHolidaysForCountry(countryCode: string, year: number): Promise<CachedHoliday[]> {
|
|
const isStale = await this.isCacheStale(countryCode, year);
|
|
|
|
if (isStale) {
|
|
await this.refreshCache(countryCode, year);
|
|
}
|
|
|
|
const cached = await HolidayRepository.findManyCachedHolidays({ countryCode, year });
|
|
|
|
return cached.map((h): CachedHoliday => ({
|
|
id: h.id,
|
|
countryCode: h.countryCode,
|
|
eventId: h.eventId,
|
|
name: h.name,
|
|
date: h.date,
|
|
year: h.year,
|
|
}));
|
|
}
|
|
|
|
async getHolidaysInRange(countryCode: string, startDate: Date, endDate: Date): Promise<CachedHoliday[]> {
|
|
const startYear = dayjs(startDate).year();
|
|
const endYear = dayjs(endDate).year();
|
|
|
|
for (let year = startYear; year <= endYear; year++) {
|
|
const isStale = await this.isCacheStale(countryCode, year);
|
|
if (isStale) {
|
|
await this.refreshCache(countryCode, year);
|
|
}
|
|
}
|
|
|
|
const cached = await HolidayRepository.findCachedHolidaysInRange({
|
|
countryCode,
|
|
startDate,
|
|
endDate,
|
|
});
|
|
|
|
return cached.map((h): CachedHoliday => ({
|
|
id: h.id,
|
|
countryCode: h.countryCode,
|
|
eventId: h.eventId,
|
|
name: h.name,
|
|
date: h.date,
|
|
year: h.year,
|
|
}));
|
|
}
|
|
}
|
|
|
|
let defaultProxy: HolidayServiceCachingProxy | null = null;
|
|
|
|
export function getHolidayServiceCachingProxy(): HolidayServiceCachingProxy {
|
|
if (!defaultProxy) {
|
|
defaultProxy = new HolidayServiceCachingProxy();
|
|
}
|
|
return defaultProxy;
|
|
}
|
|
|