29ec2b9cb6
* refactor: split flag repositories into Prisma and Cached layers - Rename FeatureRepository to PrismaFeatureRepository (raw DB access) - Rename TeamFeatureRepository to PrismaTeamFeatureRepository (raw DB access) - Rename UserFeatureRepository to PrismaUserFeatureRepository (raw DB access) - Create CachedFeatureRepository with @Memoize wrapping PrismaFeatureRepository - Create CachedTeamFeatureRepository with @Memoize/@Unmemoize wrapping PrismaTeamFeatureRepository - Create CachedUserFeatureRepository with @Memoize/@Unmemoize wrapping PrismaUserFeatureRepository - Update DI tokens, modules, and containers for all 6 repositories - Update imports in FeatureOptInService and related modules - Update tests to use new repository structure Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: simplify @Memoize key patterns and delegate batch methods to Prisma - Use direct function references for @Memoize key (e.g., KEY.all instead of () => KEY.all()) - Simplify batch methods in Cached repositories to delegate to Prisma repository - Update tests to reflect the new delegation pattern Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: add orderBy to TeamRepository.findAllByParentId for deterministic results Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * test: update TeamRepository test to expect orderBy in findAllByParentId Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: cleanup features repository and add specialized repository methods (#27195) * refactor: cleanup features repository and add findBySlug, update methods - Remove unused methods from FeaturesRepository (keep getTeamsWithFeatureEnabled) - Add findAll(), findBySlug(), update() to IFeatureRepository interface - Add findAll() with caching to CachedFeatureRepository - Add findBySlug() with caching to CachedFeatureRepository - Add update() with Unmemoize to CachedFeatureRepository - Add checkIfFeatureIsEnabledGlobally() to CachedFeatureRepository - Update toggleFeatureFlag.handler.ts to use repository instead of raw Prisma - Add comprehensive unit tests for all new methods Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: update updatedAt timestamp in feature update method Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: move feature check methods to specialized repositories - Replace getUserFeaturesStatus with two checkIfUserHasFeature calls in bookings page - Move checkIfTeamHasFeature to PrismaTeamFeatureRepository with pass-through in CachedTeamFeatureRepository - Move checkIfUserHasFeature and checkIfUserHasFeatureNonHierarchical to PrismaUserFeatureRepository with pass-throughs in CachedUserFeatureRepository - Add getEnabledFeatures to PrismaTeamFeatureRepository with caching in CachedTeamFeatureRepository - Keep FeaturesRepository methods as pass-throughs for backward compatibility - Update test to expect updatedAt in feature update Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: remove getUserFeaturesStatus and unused methods from FeaturesRepository Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * restore comment * fix: invalidate all-features cache on update and enabledFeatures cache on upsert/delete - CachedFeatureRepository: Add KEY.all() to @Unmemoize keys in update() to prevent stale findAll() results - CachedTeamFeatureRepository: Add KEY.enabledFeatures(teamId) to @Unmemoize keys in upsert() and delete() to prevent stale getEnabledFeatures() results Co-Authored-By: unknown <> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: add CachedUserFeatureRepository tests Add comprehensive tests for CachedUserFeatureRepository covering: - findByUserIdAndFeatureId (cache hit, cache miss, not found) - findByUserIdAndFeatureIds (empty input, multiple features) - upsert (with cache invalidation) - delete (with cache invalidation) - findAutoOptInByUserId (cache hit, cache miss, not found) - setAutoOptIn (with cache invalidation) Co-Authored-By: unknown <> * test: remove tests for methods removed from FeaturesRepository Remove integration tests for methods that were intentionally removed: - getUserFeatureStates - getTeamsFeatureStates - getUserAutoOptIn - getTeamsAutoOptIn - setUserAutoOptIn - setTeamAutoOptIn Co-Authored-By: unknown <> * avoid N+1 query * refactor: add select clauses to PrismaFeatureRepository queries - Add explicit select clauses to findAll, findBySlug, and update methods - Only fetch fields needed for FeatureDto (slug, enabled, description, type, stale, lastUsedAt, createdAt, updatedAt, updatedBy) - Update tests to expect select clauses - Fix UserFeatureRepository test to use findMany mock Co-Authored-By: unknown <> * fix bad conflict resolved * use userId --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { Memoize, Unmemoize } from "@calcom/features/cache";
|
|
import type { UserFeaturesDto } from "@calcom/lib/dto/UserFeaturesDto";
|
|
import { UserFeaturesDtoSchema } from "@calcom/lib/dto/UserFeaturesDto";
|
|
import type { FeatureId } from "../config";
|
|
import type { IUserFeatureRepository } from "./PrismaUserFeatureRepository";
|
|
import { booleanSchema } from "./schemas";
|
|
|
|
const CACHE_PREFIX = "features:user";
|
|
const KEY = {
|
|
byUserIdAndFeatureId: (userId: number, featureId: string): string =>
|
|
`${CACHE_PREFIX}:${userId}:${featureId}`,
|
|
autoOptInByUserId: (userId: number): string => `${CACHE_PREFIX}:autoOptIn:${userId}`,
|
|
};
|
|
|
|
export class CachedUserFeatureRepository implements IUserFeatureRepository {
|
|
constructor(private prismaUserFeatureRepository: IUserFeatureRepository) {}
|
|
|
|
@Memoize({
|
|
key: KEY.byUserIdAndFeatureId,
|
|
schema: UserFeaturesDtoSchema,
|
|
})
|
|
async findByUserIdAndFeatureId(userId: number, featureId: FeatureId): Promise<UserFeaturesDto | null> {
|
|
return this.prismaUserFeatureRepository.findByUserIdAndFeatureId(userId, featureId);
|
|
}
|
|
|
|
async findByUserIdAndFeatureIds(
|
|
userId: number,
|
|
featureIds: FeatureId[]
|
|
): Promise<Partial<Record<FeatureId, UserFeaturesDto>>> {
|
|
return this.prismaUserFeatureRepository.findByUserIdAndFeatureIds(userId, featureIds);
|
|
}
|
|
|
|
@Unmemoize({
|
|
keys: (userId: number, featureId: FeatureId) => [KEY.byUserIdAndFeatureId(userId, featureId)],
|
|
})
|
|
async upsert(
|
|
userId: number,
|
|
featureId: FeatureId,
|
|
enabled: boolean,
|
|
assignedBy: string
|
|
): Promise<UserFeaturesDto> {
|
|
return this.prismaUserFeatureRepository.upsert(userId, featureId, enabled, assignedBy);
|
|
}
|
|
|
|
@Unmemoize({
|
|
keys: (userId: number, featureId: FeatureId) => [KEY.byUserIdAndFeatureId(userId, featureId)],
|
|
})
|
|
async delete(userId: number, featureId: FeatureId): Promise<void> {
|
|
return this.prismaUserFeatureRepository.delete(userId, featureId);
|
|
}
|
|
|
|
@Memoize({
|
|
key: KEY.autoOptInByUserId,
|
|
schema: booleanSchema,
|
|
})
|
|
async findAutoOptInByUserId(userId: number): Promise<boolean> {
|
|
return this.prismaUserFeatureRepository.findAutoOptInByUserId(userId);
|
|
}
|
|
|
|
@Unmemoize({
|
|
keys: (userId: number) => [KEY.autoOptInByUserId(userId)],
|
|
})
|
|
async setAutoOptIn(userId: number, enabled: boolean): Promise<void> {
|
|
return this.prismaUserFeatureRepository.setAutoOptIn(userId, enabled);
|
|
}
|
|
|
|
async checkIfUserHasFeature(userId: number, slug: string): Promise<boolean> {
|
|
return this.prismaUserFeatureRepository.checkIfUserHasFeature(userId, slug);
|
|
}
|
|
|
|
async checkIfUserHasFeatureNonHierarchical(userId: number, slug: string): Promise<boolean> {
|
|
return this.prismaUserFeatureRepository.checkIfUserHasFeatureNonHierarchical(userId, slug);
|
|
}
|
|
}
|