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)
|
enabled Boolean @default(false)
|
||||||
description String? @db.VarChar(500)
|
description String? @db.VarChar(500)
|
||||||
rolloutPct Int @default(100)
|
rolloutPct Int @default(100)
|
||||||
|
whitelist String? @db.Text
|
||||||
updatedBy String? @db.VarChar(100)
|
updatedBy String? @db.VarChar(100)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@ -49,8 +49,13 @@ export class AppConfigController {
|
|||||||
|
|
||||||
@Post('flags/:name')
|
@Post('flags/:name')
|
||||||
@AdminRoles('SUPER_ADMIN' as AdminRole)
|
@AdminRoles('SUPER_ADMIN' as AdminRole)
|
||||||
async toggleFlag(@Param('name') name: string, @Body() d: { enabled: boolean }, @Req() req: any) {
|
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);
|
await this.flags.setEnabled(name, d.enabled, req.adminUser?.email);
|
||||||
|
}
|
||||||
|
if (d.whitelist !== undefined) {
|
||||||
|
await this.flags.setWhitelist(name, d.whitelist);
|
||||||
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,8 @@ export class FeatureFlagService {
|
|||||||
private readonly redis: RedisService,
|
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 {
|
try {
|
||||||
const cached = await this.redis.get(FF_PREFIX + name);
|
const cached = await this.redis.get(FF_PREFIX + name);
|
||||||
if (cached !== null) return cached === '1';
|
if (cached !== null) return cached === '1';
|
||||||
@ -20,6 +21,12 @@ export class FeatureFlagService {
|
|||||||
|
|
||||||
const flag = await this.prisma.featureFlag.findUnique({ where: { name } });
|
const flag = await this.prisma.featureFlag.findUnique({ where: { name } });
|
||||||
const enabled = flag?.enabled ?? false;
|
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 {}
|
try { await this.redis.set(FF_PREFIX + name, enabled ? '1' : '0', FF_TTL); } catch {}
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
@ -38,5 +45,10 @@ export class FeatureFlagService {
|
|||||||
try { await this.redis.set(FF_PREFIX + name, enabled ? '1' : '0', FF_TTL); } catch {}
|
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' } }) }
|
async getAll() { return this.prisma.featureFlag.findMany({ orderBy: { name: 'asc' } }) }
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user