rok-cli 0.3.2

Developer CLI for rok-based Axum applications
use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;

pub struct AdminScaffold;

impl Scaffold for AdminScaffold {
    fn name(&self) -> &'static str {
        "admin"
    }
    fn description(&self) -> &'static str {
        "Admin dashboard: stats, user management, system settings, audit log viewer"
    }

    fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
        let mut r = ScaffoldResult::default();
        let d = args.dry_run;
        write_file(
            &mut r,
            "src/app/controllers/admin/dashboard_controller.rs",
            DASHBOARD,
            d,
        )?;
        write_file(
            &mut r,
            "src/app/controllers/admin/user_management_controller.rs",
            USER_MGMT,
            d,
        )?;
        write_file(&mut r, "src/app/middleware/admin_guard.rs", ADMIN_GUARD, d)?;
        r.warnings
            .push("Register /admin/* routes behind AdminGuard middleware".into());
        r.warnings
            .push("Add 'role' field to users table (e.g., admin/user)".into());
        Ok(r)
    }
}

const DASHBOARD: &str = r#"use axum::{extract::State, response::IntoResponse};
use rok_auth::axum::{Ctx, Response};

pub async fn stats(ctx: Ctx, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: aggregate key metrics (users, revenue, errors, etc.)
    Response::json(serde_json::json!({
        "users_total": 0,
        "users_today": 0,
        "revenue_mtd_cents": 0,
        "active_subscriptions": 0,
    }))
}
"#;

const USER_MGMT: &str = r#"use axum::{extract::{Path, Query, State}, response::IntoResponse, Json};
use rok_auth::axum::{Ctx, Response};
use serde::Deserialize;

#[derive(Deserialize)]
pub struct UserListQuery { pub page: Option<i64>, pub per_page: Option<i64>, pub q: Option<String> }

pub async fn index(ctx: Ctx, State(pool): State<sqlx::PgPool>, Query(q): Query<UserListQuery>) -> impl IntoResponse {
    // TODO: paginated user list with search
    Response::json(serde_json::json!({ "data": [], "meta": { "total": 0 } }))
}

pub async fn show(ctx: Ctx, Path(id): Path<i64>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    Response::json(serde_json::json!({ "data": { "id": id } }))
}

pub async fn suspend(ctx: Ctx, Path(id): Path<i64>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    // TODO: set suspended_at = now()
    Response::json(serde_json::json!({ "message": "user suspended" }))
}

pub async fn unsuspend(ctx: Ctx, Path(id): Path<i64>, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
    Response::json(serde_json::json!({ "message": "user unsuspended" }))
}
"#;

const ADMIN_GUARD: &str = r#"use axum::{extract::Request, middleware::Next, response::Response};
use rok_problem::Problem;

pub async fn admin_guard(req: Request, next: Next) -> Result<Response, Problem> {
    // TODO: extract auth claims, check role == "admin", return 403 if not
    Ok(next.run(req).await)
}
"#;