import { db } from "~/utils/db.server"; import { FeatureFlagsFilterType } from "../dtos/FeatureFlagsFilterTypes"; import { AnalyticsEvent, FeatureFlag, FeatureFlagFilter, Prisma } from "@toolctrl/db/prisma-browser"; import { clearCacheKeyPrefix } from "~/utils/cache.server"; export type FeatureFlagWithDetails = FeatureFlag & { filters: FeatureFlagFilter[]; }; export type FeatureFlagWithEvents = FeatureFlagWithDetails & { events: AnalyticsEvent[]; }; export type FeatureFlagScopeLookup = { enabled?: boolean | undefined; forcedFlags?: string[]; tenantId?: string | null; userId?: string | null; scope?: string | null; }; export async function getFeatureFlag(where: { name?: string; id?: string; description?: string; enabled?: boolean | undefined }) { return db.featureFlag.findFirst({ where, include: { filters: true, }, }); } export async function getAllFeatureFlags(): Promise { return db.featureFlag.findMany({ include: { filters: true, }, orderBy: { createdAt: "desc", }, }); } export async function getFeatureFlags(where: FeatureFlagScopeLookup): Promise { const forcedFlags = where.forcedFlags ?? []; const scopedOverrides: Prisma.FeatureFlagWhereInput[] = []; scopedOverrides.push({ scope: "global", tenantId: null, userId: null }); if (where.tenantId) scopedOverrides.push({ scope: "tenant", tenantId: where.tenantId, userId: null }); if (where.userId) scopedOverrides.push({ scope: "user", tenantId: null, userId: where.userId }); if (where.tenantId && where.userId) scopedOverrides.push({ scope: "user", tenantId: where.tenantId, userId: where.userId }); return db.featureFlag.findMany({ where: { AND: [ { OR: [{ enabled: where.enabled }, { name: { in: forcedFlags } }, ...scopedOverrides], }, { OR: scopedOverrides }, ], }, include: { filters: true, }, orderBy: { createdAt: "desc", }, }); } export async function getFeatureFlagsWithEvents(where: { enabled: boolean | undefined }): Promise { return db.featureFlag.findMany({ where: { enabled: where.enabled, }, include: { filters: true, events: true, }, orderBy: { createdAt: "desc", }, }); } export async function createFeatureFlag(data: { name: string; description: string; enabled: boolean; filters: { type: FeatureFlagsFilterType; value: string | null; action?: string | null; }[]; }) { return await db.featureFlag .create({ data: { name: data.name, description: data.description, enabled: data.enabled, filters: { create: data.filters.map((f) => ({ type: f.type.toString(), value: f.value ?? null, action: f.action ?? null, })), }, }, }) .then((item) => { clearCacheKeyPrefix(`featureFlags:enabled`); return item; }); } export async function updateFeatureFlag( id: string, { name, description, filters, enabled, }: { name?: string; description?: string; filters?: { type: FeatureFlagsFilterType; value: string | null; action?: string | null }[]; enabled?: boolean; } ) { return await db.featureFlag .update({ where: { id }, data: { name, description, enabled, filters: !filters ? undefined : { deleteMany: {}, create: filters.map((f) => ({ type: f.type.toString(), value: f.value ?? null, action: f.action ?? null, })), }, }, }) .then((flag) => { clearCacheKeyPrefix(`featureFlags:enabled`); return flag; }); } export async function deleteFeatureFlag(id: string) { return await db.featureFlag .delete({ where: { id }, }) .then((item) => { clearCacheKeyPrefix(`featureFlags:enabled`); return item; }); }