chore(feature-flags): 🔧 Implement feature flag system with backend CRUD APIs, override management, audit logging, health checks, and admin UI components

This commit is contained in:
Lilith 2026-01-22 23:03:13 -08:00
parent 08b70d48f0
commit 8674781981
15 changed files with 39 additions and 28 deletions

View file

@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FlagsModule } from './modules/flags';
import { HealthController } from './health/health.controller';
import { FlagsModule } from './modules/flags';
@Module({
imports: [

View file

@ -1,4 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsString,
IsBoolean,
@ -12,7 +13,6 @@ import {
Max,
Matches,
} from 'class-validator';
import { Type } from 'class-transformer';
export class CreateFeatureFlagDto {
@ApiProperty({ description: 'Unique key for the feature flag', example: 'trustedmeet-marketplace' })

View file

@ -1,6 +1,6 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsBoolean, IsOptional, IsDate } from 'class-validator';
import { Type } from 'class-transformer';
import { IsString, IsBoolean, IsOptional, IsDate } from 'class-validator';
export class CreateOverrideDto {
@ApiPropertyOptional({ description: 'User ID for user-specific override' })

View file

@ -1,4 +1,5 @@
import { PartialType, OmitType } from '@nestjs/swagger';
import { CreateFeatureFlagDto } from './create-feature-flag.dto';
export class UpdateFeatureFlagDto extends PartialType(

View file

@ -1,9 +1,9 @@
import { BaseEntity } from '@lilith/typeorm-entities';
import {
Entity,
Column,
Index,
} from 'typeorm';
import { BaseEntity } from '@lilith/typeorm-entities';
/**
* Feature Flag Audit Entity

View file

@ -1,3 +1,4 @@
import { AuditableEntity } from '@lilith/typeorm-entities';
import {
Entity,
Column,
@ -5,7 +6,7 @@ import {
JoinColumn,
Index,
} from 'typeorm';
import { AuditableEntity } from '@lilith/typeorm-entities';
import { FeatureFlagEntity } from './feature-flag.entity';
/**

View file

@ -1,9 +1,9 @@
import { AuditableEntity } from '@lilith/typeorm-entities';
import {
Entity,
Column,
Index,
} from 'typeorm';
import { AuditableEntity } from '@lilith/typeorm-entities';
/**
* Feature Flag Entity

View file

@ -1,7 +1,7 @@
import { Controller } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
import { BaseHealthController, DependencyHealth } from '@lilith/nestjs-health';
import { TypeOrmConnectionIndicator } from '@lilith/nestjs-health/indicators';
import { Controller } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
import { InjectConnection } from '@nestjs/typeorm';
import { Connection } from 'typeorm';

View file

@ -1,4 +1,5 @@
import { bootstrap, presets} from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';
async function main() {

View file

@ -19,8 +19,11 @@ import {
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
import type { Request } from 'express';
import { FlagsService } from './flags.service';
import type { Request } from 'express';
import {
CreateFeatureFlagDto,
UpdateFeatureFlagDto,

View file

@ -1,7 +1,9 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FlagsController } from './flags.controller';
import { FlagsService } from './flags.service';
import {
FeatureFlagEntity,
FeatureFlagOverrideEntity,

View file

@ -1,17 +1,18 @@
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
FeatureFlagEntity,
FeatureFlagOverrideEntity,
FeatureFlagAuditEntity,
} from '@/entities';
import {
CreateFeatureFlagDto,
UpdateFeatureFlagDto,
CreateOverrideDto,
EvaluateFlagsDto,
} from '@/dto';
import {
FeatureFlagEntity,
FeatureFlagOverrideEntity,
FeatureFlagAuditEntity,
} from '@/entities';
// Feature flag types (from @lilith/feature-flags)
export type Environment = 'development' | 'staging' | 'production' | 'all';
export type UserRole = 'guest' | 'user' | 'provider' | 'client' | 'investor' | 'admin' | 'all';

View file

@ -1,13 +1,13 @@
import { Routes, Route, Navigate } from '@lilith/ui-router';
import { DeveloperFab } from '@lilith/ui-developer-fab';
import { FlagListPage } from './pages/FlagListPage';
import { FlagDetailPage } from './pages/FlagDetailPage';
import { CreateFlagPage } from './pages/CreateFlagPage';
import { AuditLogPage } from './pages/AuditLogPage';
import { Layout } from './components/Layout';
import { Routes, Route, Navigate } from '@lilith/ui-router';
function App() {
return (
import { Layout } from './components/Layout';
import { AuditLogPage } from './pages/AuditLogPage';
import { CreateFlagPage } from './pages/CreateFlagPage';
import { FlagDetailPage } from './pages/FlagDetailPage';
import { FlagListPage } from './pages/FlagListPage';
const App = () => (
<>
<Layout>
<Routes>
@ -25,11 +25,10 @@ function App() {
{ value: 'employee', label: 'Employee' },
{ value: 'admin', label: 'Admin' },
]}
showStorage={true}
showStorage
/>
)}
</>
);
}
)
export default App;

View file

@ -161,8 +161,8 @@ export const flagsApi = {
async getAuditLog(key?: string, limit?: number): Promise<AuditEntry[]> {
const params = new URLSearchParams();
if (key) params.set('key', key);
if (limit) params.set('limit', String(limit));
if (key) {params.set('key', key);}
if (limit) {params.set('limit', String(limit));}
const response = await fetch(`${API_BASE}/audit/log?${params}`);
return handleResponse(response);
},

View file

@ -1,13 +1,15 @@
import { Link } from '@lilith/ui-router';
import clsx from 'clsx';
import type { FeatureFlag } from '@/api/flags';
import { useToggleFlag } from '@/hooks/useFlags';
interface FlagCardProps {
flag: FeatureFlag;
}
export function FlagCard({ flag }: FlagCardProps) {
export const FlagCard = ({ flag }: FlagCardProps) => {
const toggleMutation = useToggleFlag();
const handleToggle = () => {