use std::sync::Arc;
use rustio_admin::admin::Admin;
use rustio_admin::middleware;
use rustio_admin::templates::Templates;
use rustio_admin::{
auth, background, migrations, register_admin_routes, Db, Error, Response, Result, Router,
Server,
};
// rustio: modules — `mod <name>;` declarations from `rustio-admin startapp` go directly under this marker.
mod comment;
mod post;
// rustio: imports — `use <name>::<Name>;` lines from `rustio-admin startapp` go directly under this marker.
use comment::Comment;
use post::Post;
// The first-boot homepage at `/`. Baked at compile time so the
// binary stays single-file. Replace by editing this file and
// re-running `cargo run`, or remove the `.get("/", …)` route below
// to free the path for your own handler.
const HOMEPAGE_HTML: &str = include_str!("../templates/home.html");
#[tokio::main]
async fn main() -> Result<()> {
// .env is optional; production deploys typically use real env vars.
let _ = dotenvy::dotenv();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let db_url = std::env::var("DATABASE_URL").map_err(|_| {
Error::Internal(
"DATABASE_URL is not set. Copy .env.example to .env and edit it before running."
.into(),
)
})?;
let db = Db::connect(&db_url).await?;
auth::init_tables(&db).await?;
migrations::apply(&db, "migrations").await?;
background::spawn_housekeeping(db.clone());
// `.app_name(...)` flows your project name into the admin tab
// title, login header, and dashboard h1. The `// rustio: models`
// marker below is the insertion point for `.model::<YourModel>()`
// calls printed by `rustio-admin startapp` — chain them between the
// marker and the closing `;`.
let admin = Admin::new()
.app_name("{{name_title}}")
.model::<Post>()
.model::<Comment>()
// rustio: models
;
admin.seed_permissions(&db).await?;
// Templates::new(None) → embedded defaults only.
// Templates::new(Some("templates".into())) → project overrides win.
let template_dir = std::env::var("RUSTIO_TEMPLATE_DIR").ok().map(Into::into);
let templates = Templates::new(template_dir)?;
// Middleware order is locked by DESIGN_AUDIT.md §11:
//
// logger → correlation_id → security_headers → csrf_protect
//
// `correlation_id` MUST sit between logger and csrf_protect so
// every audit row written under one HTTP request shares one
// UUID v7 in `rustio_admin_actions.correlation_id`. The
// built-in `/admin/history` page surfaces this column as a
// pivot; downstream audit-log views (your project's, if you
// add one) can join framework + project rows on the same
// column without per-source post-processing.
let router = Router::new()
.middleware(middleware::logger)
.middleware(middleware::correlation_id)
.middleware(middleware::security_headers)
.middleware(middleware::csrf_protect)
.get("/", |_req| async { Ok(Response::html(HOMEPAGE_HTML)) });
let router = register_admin_routes(router, admin, db, Arc::clone(&templates));
let addr = "127.0.0.1:8000".parse().expect("valid listen address");
log::info!("listening on http://{addr}/admin");
Server::new(router, addr).run().await
}