use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;
pub struct FlagsScaffold;
impl Scaffold for FlagsScaffold {
fn name(&self) -> &'static str {
"flags"
}
fn description(&self) -> &'static str {
"Feature flags: toggle controller, evaluation, A/B testing, rollout, audit log"
}
fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
let mut r = ScaffoldResult::default();
let d = args.dry_run;
write_file(
&mut r,
"src/app/controllers/flag_controller.rs",
CONTROLLER,
d,
)?;
write_file(
&mut r,
"migrations/create_feature_flags_tables.sql",
MIGRATION,
d,
)?;
r.warnings
.push("Register /admin/flags routes behind admin guard".into());
Ok(r)
}
}
const CONTROLLER: &str = r#"use axum::{extract::{Path, State}, response::IntoResponse, Json};
use rok_auth::axum::{Ctx, Response};
pub async fn index(State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
// TODO: list all feature flags
Response::json(serde_json::json!({ "data": [] }))
}
pub async fn evaluate(Path(name): Path<String>, ctx: Ctx) -> impl IntoResponse {
// TODO: evaluate flag for current user (cohort, percentage rollout, A/B variant)
Response::json(serde_json::json!({ "flag": name, "enabled": false, "variant": null }))
}
pub async fn toggle(Path(name): Path<String>, Json(body): Json<serde_json::Value>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
// TODO: update flag enabled state, log to flag_audit_log
Response::json(serde_json::json!({ "message": "Flag updated" }))
}
"#;
const MIGRATION: &str = r#"CREATE TABLE feature_flags (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
enabled BOOLEAN NOT NULL DEFAULT false,
rollout INTEGER NOT NULL DEFAULT 100,
variant_a TEXT,
variant_b TEXT,
cohort JSONB,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE flag_audit_log (
id BIGSERIAL PRIMARY KEY,
flag_id BIGINT NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
actor_id BIGINT,
action TEXT NOT NULL,
old_value JSONB,
new_value JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
"#;