chore: Implement short-lived redis cache for slots (#22787)
* chore: Implement short-lived redis cache for slots * chore: adapt apiv2 redis service to match with upstash redis * chore: safer redis service and ms ttl * fixup! chore: safer redis service and ms ttl * Wrap with timeout, currently doesn't work yet * Updated @upstash/redis for better signal support * Fix type errors, remove ts value * Inject NoopRedisService for NODE_ENV test * chore: bump platform libs * chore: bump platform libs * Upstash Redis upgrade no longer resulted in expected hard crash on init, so updated factory and our Upstash Redis Adapter to mimick old behaviour * Add SLOTS_CACHE_TTL variable for configurable ttl on slots cache * Update parseInt to use right types * chore: bump platform libs * chore: bump platform libs * chore: bump platform libs * update e2e api v2 action * set SLOTS_CACHE_TTL env var api v2 e2e --------- Co-authored-by: cal.com <morgan@cal.com>
This commit is contained in:
co-authored by
cal.com
parent
a71d949a0c
commit
b71d8baccc
@@ -22,6 +22,7 @@ env:
|
||||
STRIPE_API_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||
SLOTS_CACHE_TTL: ${{secret.CI_SLOTS_CACHE_TTL}}
|
||||
jobs:
|
||||
e2e:
|
||||
timeout-minutes: 20
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.283",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.285",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
@@ -104,10 +104,11 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const rescheduledToInfo = databaseBooking.rescheduled
|
||||
? await this.getRescheduledToInfo(databaseBooking.uid)
|
||||
: undefined;
|
||||
|
||||
const rescheduledToUid = rescheduledToInfo?.uid;
|
||||
const rescheduledByEmail = databaseBooking.rescheduled ? rescheduledToInfo?.rescheduledBy : databaseBooking.rescheduledBy;
|
||||
|
||||
const rescheduledToUid = rescheduledToInfo?.uid;
|
||||
const rescheduledByEmail = databaseBooking.rescheduled
|
||||
? rescheduledToInfo?.rescheduledBy
|
||||
: databaseBooking.rescheduledBy;
|
||||
|
||||
const booking = {
|
||||
id: databaseBooking.id,
|
||||
@@ -157,13 +158,12 @@ export class OutputBookingsService_2024_08_13 {
|
||||
|
||||
async getRescheduledToInfo(bookingUid: string): Promise<{ uid?: string; rescheduledBy?: string | null }> {
|
||||
const rescheduledTo = await this.bookingsRepository.getByFromReschedule(bookingUid);
|
||||
return {
|
||||
uid: rescheduledTo?.uid,
|
||||
rescheduledBy: rescheduledTo?.rescheduledBy
|
||||
return {
|
||||
uid: rescheduledTo?.uid,
|
||||
rescheduledBy: rescheduledTo?.rescheduledBy,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
getUserDefinedMetadata(databaseMetadata: DatabaseMetadata) {
|
||||
if (databaseMetadata === null) return {};
|
||||
|
||||
@@ -279,9 +279,11 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const rescheduledToInfo = databaseBooking.rescheduled
|
||||
? await this.getRescheduledToInfo(databaseBooking.uid)
|
||||
: undefined;
|
||||
|
||||
|
||||
const rescheduledToUid = rescheduledToInfo?.uid;
|
||||
const rescheduledByEmail = databaseBooking.rescheduled ? rescheduledToInfo?.rescheduledBy : databaseBooking.rescheduledBy;
|
||||
const rescheduledByEmail = databaseBooking.rescheduled
|
||||
? rescheduledToInfo?.rescheduledBy
|
||||
: databaseBooking.rescheduledBy;
|
||||
|
||||
const booking = {
|
||||
id: databaseBooking.id,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { AvailableSlotsService } from "@/lib/services/available-slots.service";
|
||||
import { CacheService } from "@/lib/services/cache.service";
|
||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { UserAvailabilityService } from "@/lib/services/user-availability.service";
|
||||
|
||||
@@ -25,6 +26,7 @@ import { UserAvailabilityService } from "@/lib/services/user-availability.servic
|
||||
PrismaEventTypeRepository,
|
||||
PrismaRoutingFormResponseRepository,
|
||||
PrismaTeamRepository,
|
||||
RedisService,
|
||||
PrismaFeaturesRepository,
|
||||
CheckBookingLimitsService,
|
||||
CacheService,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { PrismaTeamRepository } from "@/lib/repositories/prisma-team.repository"
|
||||
import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
||||
import { CacheService } from "@/lib/services/cache.service";
|
||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { AvailableSlotsService as BaseAvailableSlotsService } from "@calcom/platform-libraries/slots";
|
||||
@@ -25,6 +26,7 @@ export class AvailableSlotsService extends BaseAvailableSlotsService {
|
||||
selectedSlotRepository: PrismaSelectedSlotRepository,
|
||||
eventTypeRepository: PrismaEventTypeRepository,
|
||||
userRepository: PrismaUserRepository,
|
||||
redisService: RedisService,
|
||||
featuresRepository: PrismaFeaturesRepository
|
||||
) {
|
||||
super({
|
||||
@@ -36,7 +38,8 @@ export class AvailableSlotsService extends BaseAvailableSlotsService {
|
||||
selectedSlotRepo: selectedSlotRepository,
|
||||
eventTypeRepo: eventTypeRepository,
|
||||
userRepo: userRepository,
|
||||
checkBookingLimitsService: new CheckBookingLimitsService(bookingRepository) as any,
|
||||
redisClient: redisService,
|
||||
checkBookingLimitsService: new CheckBookingLimitsService(bookingRepository),
|
||||
cacheService: new CacheService(featuresRepository),
|
||||
userAvailabilityService: new UserAvailabilityService(oooRepoDependency, bookingRepository, eventTypeRepository)
|
||||
});
|
||||
|
||||
@@ -7,12 +7,32 @@ import { Redis } from "ioredis";
|
||||
export class RedisService implements OnModuleDestroy {
|
||||
public redis: Redis;
|
||||
private readonly logger = new Logger("RedisService");
|
||||
private isReady = false; // Track connection status
|
||||
|
||||
constructor(readonly configService: ConfigService<AppConfig>) {
|
||||
const dbUrl = configService.get<string>("db.redisUrl", { infer: true });
|
||||
if (!dbUrl) throw new Error("Misconfigured Redis, halting.");
|
||||
|
||||
this.redis = new Redis(dbUrl);
|
||||
|
||||
this.redis.on("error", (err) => {
|
||||
this.logger.error(`IoRedis connection error: ${err.message}`);
|
||||
this.isReady = false;
|
||||
});
|
||||
|
||||
this.redis.on("connect", () => {
|
||||
this.logger.log("IoRedis connected!");
|
||||
this.isReady = true;
|
||||
});
|
||||
|
||||
this.redis.on("reconnecting", (delay: string) => {
|
||||
this.logger.warn(`IoRedis reconnecting... next retry in ${delay}ms`);
|
||||
});
|
||||
|
||||
this.redis.on("end", () => {
|
||||
this.logger.warn("IoRedis connection ended.");
|
||||
this.isReady = false;
|
||||
});
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
@@ -22,4 +42,99 @@ export class RedisService implements OnModuleDestroy {
|
||||
this.logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async get<TData>(key: string): Promise<TData | null> {
|
||||
let data = null;
|
||||
if (!this.isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
data = await this.redis.get(key);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis get failed: ${err.message}`);
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(data) as TData;
|
||||
} catch (e) {
|
||||
return data as TData;
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<number> {
|
||||
if (!this.isReady) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return this.redis.del(key);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis del failed: ${err.message}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async set<TData>(key: string, value: TData, opts?: { ttl?: number }): Promise<"OK" | TData | null> {
|
||||
if (!this.isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const stringifiedValue = typeof value === "object" ? JSON.stringify(value) : String(value);
|
||||
if (opts?.ttl) {
|
||||
await this.redis.set(key, stringifiedValue, "PX", opts.ttl);
|
||||
} else {
|
||||
await this.redis.set(key, stringifiedValue);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis set failed: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return "OK";
|
||||
}
|
||||
|
||||
async expire(key: string, seconds: number): Promise<0 | 1> {
|
||||
if (!this.isReady) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return this.redis.expire(key, seconds) as Promise<0 | 1>;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis expire failed: ${err.message}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async lrange<TResult = string>(key: string, start: number, end: number): Promise<TResult[]> {
|
||||
if (!this.isReady) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const results = await this.redis.lrange(key, start, end);
|
||||
return results.map((item) => JSON.parse(item) as TResult);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis lrange failed: ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async lpush<TData>(key: string, ...elements: TData[]): Promise<number> {
|
||||
if (!this.isReady) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const stringifiedElements = elements.map((element) =>
|
||||
typeof element === "object" ? JSON.stringify(element) : String(element)
|
||||
);
|
||||
return this.redis.lpush(key, ...stringifiedElements);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) this.logger.error(`IoRedis lpush failed: ${err.message}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,5 @@ process.env = {
|
||||
CALENDSO_ENCRYPTION_KEY: "22gfxhWUlcKliUeXcu8xNah2+HP/29ZX",
|
||||
INTEGRATION_TEST_MODE: "true",
|
||||
e2e: "true",
|
||||
SLOTS_CACHE_TTL: "1"
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"@team-plain/typescript-sdk": "^5.9.0",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@unkey/ratelimit": "^0.1.1",
|
||||
"@upstash/redis": "^1.21.0",
|
||||
"@upstash/redis": "^1.35.2",
|
||||
"@vercel/edge-config": "^0.1.1",
|
||||
"@vercel/edge-functions-ui": "^0.2.1",
|
||||
"@vercel/og": "^0.6.3",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
export interface IRedisService {
|
||||
get: <TData>(key: string) => Promise<TData | null>;
|
||||
|
||||
set: <TData>(key: string, value: TData) => Promise<"OK" | TData | null>;
|
||||
set: <TData>(key: string, value: TData, opts?: { ttl?: number }) => Promise<"OK" | TData | null>;
|
||||
|
||||
expire: (key: string, seconds: number) => Promise<0 | 1>;
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { IRedisService } from "./IRedisService";
|
||||
|
||||
/**
|
||||
* Noop implementation of IRedisService for testing or fallback scenarios.
|
||||
*/
|
||||
|
||||
export class NoopRedisService implements IRedisService {
|
||||
async get<TData>(_key: string): Promise<TData | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async del(_key: string): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async set<TData>(_key: string, _value: TData, _opts?: { ttl?: number }): Promise<"OK" | TData | null> {
|
||||
return "OK";
|
||||
}
|
||||
|
||||
async expire(_key: string, _seconds: number): Promise<0 | 1> {
|
||||
// Implementation for setting expiration time for key in Redis
|
||||
return 0;
|
||||
}
|
||||
|
||||
async lrange<TResult = string>(_key: string, _start: number, _end: number): Promise<TResult[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async lpush<TData>(_key: string, ..._elements: TData[]): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,13 @@ export class RedisService implements IRedisService {
|
||||
private redis: Redis;
|
||||
|
||||
constructor() {
|
||||
this.redis = Redis.fromEnv();
|
||||
// Ensure we throw an Error to mimick old behavior
|
||||
if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {
|
||||
throw new Error("Attempted to initialize Upstash Redis client without url or token.");
|
||||
}
|
||||
this.redis = Redis.fromEnv({
|
||||
signal: () => AbortSignal.timeout(2000),
|
||||
});
|
||||
}
|
||||
|
||||
async get<TData>(key: string): Promise<TData | null> {
|
||||
@@ -17,9 +23,16 @@ export class RedisService implements IRedisService {
|
||||
return this.redis.del(key);
|
||||
}
|
||||
|
||||
async set<TData>(key: string, value: TData): Promise<"OK" | TData | null> {
|
||||
// Implementation for setting value in Redis
|
||||
return this.redis.set(key, value);
|
||||
async set<TData>(key: string, value: TData, opts?: { ttl?: number }): Promise<"OK" | TData | null> {
|
||||
return this.redis.set(
|
||||
key,
|
||||
value,
|
||||
opts?.ttl
|
||||
? {
|
||||
px: opts.ttl,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
async expire(key: string, seconds: number): Promise<0 | 1> {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createModule } from "@evyweb/ioctopus";
|
||||
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
|
||||
import { NoopRedisService } from "../NoopRedisService";
|
||||
import { RedisService } from "../RedisService";
|
||||
|
||||
const redisModule = createModule();
|
||||
|
||||
redisModule.bind(DI_TOKENS.REDIS_CLIENT).toFactory(() => {
|
||||
if (process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN) {
|
||||
return new RedisService();
|
||||
}
|
||||
return new NoopRedisService();
|
||||
}, "singleton");
|
||||
|
||||
export { redisModule };
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createContainer } from "@evyweb/ioctopus";
|
||||
|
||||
import { redisModule } from "@calcom/features/redis/di/redisModule";
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import { prismaModule } from "@calcom/prisma/prisma.module";
|
||||
import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util";
|
||||
@@ -19,6 +20,7 @@ import { userRepositoryModule } from "../modules/user";
|
||||
import { getUserAvailabilityModule } from "../modules/get-user-availability";
|
||||
|
||||
const container = createContainer();
|
||||
container.load(DI_TOKENS.REDIS_CLIENT, redisModule);
|
||||
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
|
||||
container.load(DI_TOKENS.OOO_REPOSITORY_MODULE, oooRepositoryModule);
|
||||
container.load(DI_TOKENS.SCHEDULE_REPOSITORY_MODULE, scheduleRepositoryModule);
|
||||
|
||||
@@ -15,6 +15,7 @@ availableSlotsModule.bind(DI_TOKENS.AVAILABLE_SLOTS_SERVICE).toClass(AvailableSl
|
||||
bookingRepo: DI_TOKENS.BOOKING_REPOSITORY,
|
||||
eventTypeRepo: DI_TOKENS.EVENT_TYPE_REPOSITORY,
|
||||
routingFormResponseRepo: DI_TOKENS.ROUTING_FORM_RESPONSE_REPOSITORY,
|
||||
redisClient: DI_TOKENS.REDIS_CLIENT,
|
||||
cacheService: DI_TOKENS.CACHE_SERVICE,
|
||||
checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE,
|
||||
userAvailabilityService: DI_TOKENS.GET_USER_AVAILABILITY_SERVICE
|
||||
|
||||
@@ -2,6 +2,7 @@ export const DI_TOKENS = {
|
||||
PRISMA_CLIENT: Symbol("PrismaClient"),
|
||||
READ_ONLY_PRISMA_CLIENT: Symbol("ReadOnlyPrismaClient"),
|
||||
PRISMA_MODULE: Symbol("PrismaModule"),
|
||||
REDIS_CLIENT: Symbol("RedisClient"),
|
||||
OOO_REPOSITORY: Symbol("OOORepository"),
|
||||
OOO_REPOSITORY_MODULE: Symbol("OOORepositoryModule"),
|
||||
SCHEDULE_REPOSITORY: Symbol("ScheduleRepository"),
|
||||
|
||||
@@ -154,10 +154,9 @@ type GetUsersAvailabilityProps = {
|
||||
};
|
||||
|
||||
export interface IUserAvailabilityService {
|
||||
eventTypeRepo: EventTypeRepository;
|
||||
eventTypeRepo: EventTypeRepository;
|
||||
oooRepo: PrismaOOORepository;
|
||||
bookingRepo: BookingRepository;
|
||||
|
||||
}
|
||||
|
||||
export class UserAvailabilityService {
|
||||
@@ -611,4 +610,4 @@ export class UserAvailabilityService {
|
||||
}
|
||||
|
||||
getUsersAvailability = withReporting(this._getUsersAvailability.bind(this), "getUsersAvailability");
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { checkForConflicts } from "@calcom/features/bookings/lib/conflictChecker/checkForConflicts";
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
import type { IRedisService } from "@calcom/features/redis/IRedisService";
|
||||
import { findQualifiedHostsWithDelegationCredentials } from "@calcom/lib/bookings/findQualifiedHostsWithDelegationCredentials";
|
||||
import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils";
|
||||
import { RESERVED_SUBDOMAINS } from "@calcom/lib/constants";
|
||||
@@ -64,6 +65,7 @@ import { handleNotificationWhenNoSlots } from "./handleNotificationWhenNoSlots";
|
||||
import type { GetScheduleOptions } from "./types";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[slots/util]"] });
|
||||
const DEFAULT_SLOTS_CACHE_TTL = 2000;
|
||||
|
||||
type GetAvailabilityUserWithDelegationCredentials = Omit<GetAvailabilityUser, "credentials"> & {
|
||||
credentials: CredentialForCalendarService[];
|
||||
@@ -101,7 +103,47 @@ export interface IAvailableSlotsService {
|
||||
routingFormResponseRepo: RoutingFormResponseRepository;
|
||||
cacheService: CacheService;
|
||||
checkBookingLimitsService: CheckBookingLimitsService;
|
||||
userAvailabilityService: UserAvailabilityService
|
||||
userAvailabilityService: UserAvailabilityService;
|
||||
redisClient: IRedisService;
|
||||
}
|
||||
|
||||
function withSlotsCache(
|
||||
redisClient: IRedisService,
|
||||
func: (args: GetScheduleOptions) => Promise<IGetAvailableSlots>
|
||||
) {
|
||||
return async (args: GetScheduleOptions): Promise<IGetAvailableSlots> => {
|
||||
const cacheKey = `${JSON.stringify(args.input)}`;
|
||||
let success = false;
|
||||
let cachedResult: IGetAvailableSlots | null = null;
|
||||
const startTime = process.hrtime();
|
||||
try {
|
||||
cachedResult = await redisClient.get(cacheKey);
|
||||
success = true;
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error && err.name === "TimeoutError") {
|
||||
const endTime = process.hrtime(startTime);
|
||||
log.error(`Redis request timed out after ${endTime[0]}${endTime[1] / 1e6}ms`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// If the cache request fails, we proceed to call the function directly
|
||||
return await func(args);
|
||||
}
|
||||
if (cachedResult) {
|
||||
log.info("[CACHE HIT] Available slots", { cacheKey });
|
||||
return cachedResult;
|
||||
}
|
||||
const result = await func(args);
|
||||
const ttl = parseInt(process.env.SLOTS_CACHE_TTL ?? "", 10) || DEFAULT_SLOTS_CACHE_TTL;
|
||||
// we do not wait for the cache to complete setting; we fire and forget, and hope it'll finish.
|
||||
// this is to already start responding to the client.
|
||||
redisClient.set(cacheKey, result, { ttl });
|
||||
log.info("[CACHE MISS] Available slots", { cacheKey, ttl });
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export class AvailableSlotsService {
|
||||
@@ -884,7 +926,10 @@ export class AvailableSlotsService {
|
||||
"getRegularOrDynamicEventType"
|
||||
);
|
||||
|
||||
getAvailableSlots = withReporting(this._getAvailableSlots.bind(this), "getAvailableSlots");
|
||||
getAvailableSlots = withReporting(
|
||||
withSlotsCache(this.dependencies.redisClient, this._getAvailableSlots.bind(this)),
|
||||
"getAvailableSlots"
|
||||
);
|
||||
|
||||
async _getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<IGetAvailableSlots> {
|
||||
const {
|
||||
|
||||
+2
-1
@@ -490,6 +490,7 @@
|
||||
"NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED",
|
||||
"CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY",
|
||||
"CAL_VIDEO_RECORDING_TOKEN_SECRET",
|
||||
"ORGANIZER_EMAIL_EXEMPT_DOMAINS"
|
||||
"ORGANIZER_EMAIL_EXEMPT_DOMAINS",
|
||||
"SLOTS_CACHE_TTL"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2500,7 +2500,7 @@ __metadata:
|
||||
"@axiomhq/winston": ^1.2.0
|
||||
"@calcom/platform-constants": "*"
|
||||
"@calcom/platform-enums": "*"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.283"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.285"
|
||||
"@calcom/platform-types": "*"
|
||||
"@calcom/platform-utils": "*"
|
||||
"@calcom/prisma": "*"
|
||||
@@ -3556,13 +3556,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.283":
|
||||
version: 0.0.283
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.283"
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.285":
|
||||
version: 0.0.285
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.285"
|
||||
dependencies:
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
checksum: 7d741bd13ac050f8552855658b2348cba0999529c12ca6741e5f802a7bc9ce4e29c9ac69a17306c42beb392f17acb7b5f8a0554b8d2ea9b5fc6d963950d0fca8
|
||||
checksum: 2faebf08e2565a1eb75e70a1bf68af3a7593b8a2c35f4d05af97fdfba7fda6fba962984934a47c3a7fbf0a1a5b4f1ca4dd89029d710b6684b0f6fafa96526e11
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4076,7 +4076,7 @@ __metadata:
|
||||
"@types/turndown": ^5.0.1
|
||||
"@types/uuid": 8.3.1
|
||||
"@unkey/ratelimit": ^0.1.1
|
||||
"@upstash/redis": ^1.21.0
|
||||
"@upstash/redis": ^1.35.2
|
||||
"@vercel/edge-config": ^0.1.1
|
||||
"@vercel/edge-functions-ui": ^0.2.1
|
||||
"@vercel/og": ^0.6.3
|
||||
@@ -18784,12 +18784,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@upstash/redis@npm:^1.21.0":
|
||||
version: 1.30.1
|
||||
resolution: "@upstash/redis@npm:1.30.1"
|
||||
"@upstash/redis@npm:^1.35.2":
|
||||
version: 1.35.2
|
||||
resolution: "@upstash/redis@npm:1.35.2"
|
||||
dependencies:
|
||||
crypto-js: ^4.2.0
|
||||
checksum: 1d8083650fbc1760fe4c58d263c62f9c658d08035480927316932df2757f4e987799f7ce752579e7dd871155e96996bb4caf8ad1034af3ab18965d7ca76421cf
|
||||
uncrypto: ^0.1.3
|
||||
checksum: 1d54217589418873702f08bdc27d9455ea44df0fce30ace612b5637a1c4c261b762a80fb85d7b3c72d3213e82b8938bb57c413ae44f9f1e713eeab3f31d5539a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -23319,13 +23319,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"crypto-js@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "crypto-js@npm:4.2.0"
|
||||
checksum: f051666dbc077c8324777f44fbd3aaea2986f198fe85092535130d17026c7c2ccf2d23ee5b29b36f7a4a07312db2fae23c9094b644cc35f7858b1b4fcaf27774
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"crypto-random-string@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "crypto-random-string@npm:2.0.0"
|
||||
@@ -45439,6 +45432,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uncrypto@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "uncrypto@npm:0.1.3"
|
||||
checksum: 07160e08806dd6cea16bb96c3fd54cd70fc801e02fc3c6f86980144d15c9ebbd1c55587f7280a207b3af6cd34901c0d0b77ada5a02c2f7081a033a05acf409e2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.19.2":
|
||||
version: 6.19.8
|
||||
resolution: "undici-types@npm:6.19.8"
|
||||
|
||||
Reference in New Issue
Block a user