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>
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { Memoize, Unmemoize } from "@calcom/features/cache";
|
|
import type { FeatureId } from "@calcom/features/flags/config";
|
|
import type { FeatureDto } from "@calcom/lib/dto/FeatureDto";
|
|
import { FeatureDtoArraySchema, FeatureDtoSchema } from "@calcom/lib/dto/FeatureDto";
|
|
|
|
import type { IFeatureRepository } from "./PrismaFeatureRepository";
|
|
|
|
const CACHE_PREFIX = "features:global";
|
|
const KEY = {
|
|
all: (): string => `${CACHE_PREFIX}:all`,
|
|
bySlug: (slug: string): string => `${CACHE_PREFIX}:slug:${slug}`,
|
|
};
|
|
|
|
export class CachedFeatureRepository implements IFeatureRepository {
|
|
constructor(private prismaFeatureRepository: IFeatureRepository) {}
|
|
|
|
@Memoize({
|
|
key: KEY.all,
|
|
schema: FeatureDtoArraySchema,
|
|
})
|
|
async findAll(): Promise<FeatureDto[]> {
|
|
return this.prismaFeatureRepository.findAll();
|
|
}
|
|
|
|
@Memoize({
|
|
key: KEY.bySlug,
|
|
schema: FeatureDtoSchema.nullable(),
|
|
})
|
|
async findBySlug(slug: string): Promise<FeatureDto | null> {
|
|
return this.prismaFeatureRepository.findBySlug(slug);
|
|
}
|
|
|
|
@Unmemoize({
|
|
keys: (input: { featureId: FeatureId }) => [KEY.bySlug(input.featureId), KEY.all()],
|
|
})
|
|
async update(input: { featureId: FeatureId; enabled: boolean; updatedBy?: number }): Promise<FeatureDto> {
|
|
return this.prismaFeatureRepository.update(input);
|
|
}
|
|
|
|
/**
|
|
* Checks if a feature is enabled globally in the application.
|
|
* Uses the cached findBySlug method to avoid hitting the database on every request.
|
|
* @param slug - The feature flag identifier to check
|
|
* @returns Promise<boolean> - True if the feature is enabled globally, false otherwise
|
|
*/
|
|
async checkIfFeatureIsEnabledGlobally(slug: string): Promise<boolean> {
|
|
const feature = await this.findBySlug(slug);
|
|
return Boolean(feature && feature.enabled);
|
|
}
|
|
}
|