Spaces:
Runtime error
Runtime error
| import { NextRequest, NextResponse } from "next/server"; | |
| import { z } from "zod"; | |
| import { isAuthenticated } from "@/shared/utils/apiAuth"; | |
| import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; | |
| import { FEATURE_FLAG_DEFINITIONS } from "@/shared/constants/featureFlagDefinitions"; | |
| import { | |
| getFeatureFlagOverrides, | |
| setFeatureFlagOverride, | |
| removeFeatureFlagOverride, | |
| clearAllFeatureFlagOverrides, | |
| } from "@/lib/db/featureFlags"; | |
| import { resolveAllFeatureFlags } from "@/shared/utils/featureFlags"; | |
| import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; | |
| const ACTIVE_VALUES = new Set(["true", "1", "yes"]); | |
| function isActive(value: string): boolean { | |
| return ACTIVE_VALUES.has(value); | |
| } | |
| /** | |
| * GET /api/settings/feature-flags | |
| * Returns all feature flags with their effective values and a summary. | |
| */ | |
| export async function GET(request: NextRequest) { | |
| if (!(await isAuthenticated(request))) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| } | |
| try { | |
| const resolved = resolveAllFeatureFlags(); | |
| const flags = resolved.map(({ key, effectiveValue, source, definition }) => ({ | |
| key, | |
| label: definition.label, | |
| description: definition.description, | |
| category: definition.category, | |
| type: definition.type, | |
| enumValues: definition.enumValues ?? null, | |
| defaultValue: definition.defaultValue, | |
| effectiveValue, | |
| source, | |
| requiresRestart: definition.requiresRestart, | |
| warningLevel: definition.warningLevel, | |
| })); | |
| const total = flags.length; | |
| const active = flags.filter((f) => isActive(f.effectiveValue)).length; | |
| const inactive = total - active; | |
| const overriddenByDb = flags.filter((f) => f.source === "db").length; | |
| const overriddenByEnv = flags.filter((f) => f.source === "env").length; | |
| return NextResponse.json({ | |
| flags, | |
| summary: { total, active, inactive, overriddenByDb, overriddenByEnv }, | |
| }); | |
| } catch (error) { | |
| return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 }); | |
| } | |
| } | |
| const putFeatureFlagSchema = z.object({ | |
| key: z.string().min(1), | |
| value: z.string().optional(), | |
| }); | |
| /** | |
| * PUT /api/settings/feature-flags | |
| * Set or remove a feature flag override. | |
| * Body: { key: string; value?: string } | |
| * If value is omitted, the override is removed. | |
| */ | |
| export async function PUT(request: NextRequest) { | |
| if (!(await isAuthenticated(request))) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| } | |
| let rawBody: unknown; | |
| try { | |
| rawBody = await request.json(); | |
| } catch { | |
| return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); | |
| } | |
| const validation = validateBody(putFeatureFlagSchema, rawBody); | |
| if (isValidationFailure(validation)) { | |
| return NextResponse.json({ error: validation.error }, { status: 400 }); | |
| } | |
| const { key, value } = validation.data; | |
| // Validate key against known definitions | |
| const definition = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === key); | |
| if (!definition) { | |
| return NextResponse.json({ error: `Unknown feature flag key: ${key}` }, { status: 400 }); | |
| } | |
| // Validate enum values | |
| if (value !== undefined && definition.type === "enum" && definition.enumValues) { | |
| if (!definition.enumValues.includes(value)) { | |
| return NextResponse.json( | |
| { | |
| error: `Invalid value "${value}" for enum flag ${key}. Allowed: ${definition.enumValues.join(", ")}`, | |
| }, | |
| { status: 400 } | |
| ); | |
| } | |
| } | |
| try { | |
| // Capture previous state before modifying | |
| const allFlagsBefore = resolveAllFeatureFlags(); | |
| const prevFlag = allFlagsBefore.find((f) => f.key === key); | |
| const previousValue = prevFlag?.effectiveValue ?? definition.defaultValue; | |
| const previousSource = prevFlag?.source ?? "default"; | |
| if (value === undefined) { | |
| removeFeatureFlagOverride(key); | |
| } else { | |
| setFeatureFlagOverride(key, value); | |
| } | |
| // After write — get new effective value | |
| const allFlagsAfter = resolveAllFeatureFlags(); | |
| const updatedFlag = allFlagsAfter.find((f) => f.key === key); | |
| const newEffectiveValue = updatedFlag?.effectiveValue ?? definition.defaultValue; | |
| const newSource = updatedFlag?.source ?? "default"; | |
| return NextResponse.json({ | |
| key, | |
| effectiveValue: newEffectiveValue, | |
| source: newSource, | |
| previousValue, | |
| previousSource, | |
| requiresRestart: definition.requiresRestart, | |
| }); | |
| } catch (error) { | |
| return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 }); | |
| } | |
| } | |
| /** | |
| * DELETE /api/settings/feature-flags | |
| * Clear all feature flag overrides. | |
| */ | |
| export async function DELETE(request: NextRequest) { | |
| if (!(await isAuthenticated(request))) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| } | |
| try { | |
| const overrides = getFeatureFlagOverrides(); | |
| const count = Object.keys(overrides).length; | |
| clearAllFeatureFlagOverrides(); | |
| return NextResponse.json({ | |
| cleared: count, | |
| message: `Cleared ${count} feature flag override${count !== 1 ? "s" : ""}`, | |
| }); | |
| } catch (error) { | |
| return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 }); | |
| } | |
| } | |