feat: M0-03 feature flag whitelist + more config integration
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 37s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 37s
This commit is contained in:
parent
8d5ff27a3c
commit
a1ac07bf88
@ -0,0 +1 @@
|
||||
ALTER TABLE FeatureFlag ADD COLUMN whitelist TEXT NULL;
|
||||
@ -860,6 +860,7 @@ model FeatureFlag {
|
||||
enabled Boolean @default(false)
|
||||
description String? @db.VarChar(500)
|
||||
rolloutPct Int @default(100)
|
||||
whitelist String? @db.Text
|
||||
updatedBy String? @db.VarChar(100)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@ -49,8 +49,13 @@ export class AppConfigController {
|
||||
|
||||
@Post('flags/:name')
|
||||
@AdminRoles('SUPER_ADMIN' as AdminRole)
|
||||
async toggleFlag(@Param('name') name: string, @Body() d: { enabled: boolean }, @Req() req: any) {
|
||||
await this.flags.setEnabled(name, d.enabled, req.adminUser?.email);
|
||||
async toggleFlag(@Param('name') name: string, @Body() d: { enabled?: boolean; whitelist?: string }, @Req() req: any) {
|
||||
if (typeof d.enabled === 'boolean') {
|
||||
await this.flags.setEnabled(name, d.enabled, req.adminUser?.email);
|
||||
}
|
||||
if (d.whitelist !== undefined) {
|
||||
await this.flags.setWhitelist(name, d.whitelist);
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@ export class FeatureFlagService {
|
||||
private readonly redis: RedisService,
|
||||
) {}
|
||||
|
||||
async isEnabled(name: string): Promise<boolean> {
|
||||
/** Check if flag is enabled, with optional user-level whitelist */
|
||||
async isEnabled(name: string, userId?: string): Promise<boolean> {
|
||||
try {
|
||||
const cached = await this.redis.get(FF_PREFIX + name);
|
||||
if (cached !== null) return cached === '1';
|
||||
@ -20,6 +21,12 @@ export class FeatureFlagService {
|
||||
|
||||
const flag = await this.prisma.featureFlag.findUnique({ where: { name } });
|
||||
const enabled = flag?.enabled ?? false;
|
||||
|
||||
// If user whitelist is set, only enabled for whitelisted users
|
||||
if (enabled && flag?.whitelist) {
|
||||
const whitelist = (flag.whitelist as string).split(',').map(s => s.trim());
|
||||
if (!userId || !whitelist.includes(userId)) return false;
|
||||
}
|
||||
try { await this.redis.set(FF_PREFIX + name, enabled ? '1' : '0', FF_TTL); } catch {}
|
||||
return enabled;
|
||||
}
|
||||
@ -38,5 +45,10 @@ export class FeatureFlagService {
|
||||
try { await this.redis.set(FF_PREFIX + name, enabled ? '1' : '0', FF_TTL); } catch {}
|
||||
}
|
||||
|
||||
async setWhitelist(name: string, whitelist: string): Promise<void> {
|
||||
await this.prisma.featureFlag.update({ where: { name }, data: { whitelist } });
|
||||
try { await this.redis.del(FF_PREFIX + name); } catch {}
|
||||
}
|
||||
|
||||
async getAll() { return this.prisma.featureFlag.findMany({ orderBy: { name: 'asc' } }) }
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user