Move CI auth middleware to composition root
web::router() returns bare routes. main.rs decides whether to layer auth on top (production) or skip it (seed/dev). server::run() accepts the pre-composed CI router and is agnostic about auth.
change pytsnozzxxvlokwmylowvzntovwvrwmn
commit 1bf396a84734c713a5bf9341085e2224c9b43cc1
author Alpha Chen <alpha@kejadlen.dev>
date
parent ovkkxnkv
diff --git a/src/bin/quire/commands/dev.rs b/src/bin/quire/commands/dev.rs
index 5453eba..430138d 100644
--- a/src/bin/quire/commands/dev.rs
+++ b/src/bin/quire/commands/dev.rs
@@ -8,12 +8,23 @@ use quire::Quire;
 /// Base timestamp for seed data: 2026-05-06T12:00:00Z.
 const BASE_MS: i64 = 1746532800000;
 
-/// Seed the quire database with realistic CI run data.
+/// Seed a tempdir with realistic CI run data and return a `Quire` pointing at it.
 ///
-/// Wipes any existing data and inserts a fixed corpus of runs covering
-/// every interesting state (complete, failed, active, pending, superseded)
-/// with matching on-disk log artifacts. Idempotent — same input, same output.
-pub fn seed(quire: &Quire) -> Result<()> {
+/// Creates a fresh tempdir under `std::env::temp_dir()`, inserts a fixed corpus
+/// of runs covering every interesting state (complete, failed, active, pending,
+/// superseded) with matching on-disk log artifacts. Idempotent — same input,
+/// same output.
+pub fn seed() -> Result<Quire> {
+    let dir = tempfile::tempdir()
+        .into_diagnostic()
+        .context("failed to create tempdir")?;
+
+    // Leak the TempDir so it outlives the function. The server will
+    // clean up on shutdown, or the OS will when the process exits.
+    let base_dir = dir.keep();
+    tracing::info!(path = %base_dir.display(), "seeded tempdir");
+
+    let quire = Quire::new(base_dir);
     let db_path = quire.db_path();
 
     // Open and migrate.
@@ -24,22 +35,17 @@ pub fn seed(quire: &Quire) -> Result<()> {
         .into_diagnostic()
         .context("failed to run migrations")?;
 
-    // Wipe existing seed data (if any).
-    db.execute_batch("DELETE FROM sh_events; DELETE FROM jobs; DELETE FROM runs;")
-        .into_diagnostic()
-        .context("failed to wipe existing data")?;
-
     insert_runs(&db)?;
     insert_jobs(&db)?;
     insert_sh_events(&db)?;
-    write_log_artifacts(quire)?;
+    write_log_artifacts(&quire)?;
 
     let run_count: i64 = db
         .query_row("SELECT count(*) FROM runs", [], |row| row.get(0))
         .into_diagnostic()?;
 
     tracing::info!(%run_count, "seeded database");
-    Ok(())
+    Ok(quire)
 }
 
 fn insert_runs(db: &rusqlite::Connection) -> Result<()> {
diff --git a/src/bin/quire/commands/serve.rs b/src/bin/quire/commands/serve.rs
index eab82eb..a187fa3 100644
--- a/src/bin/quire/commands/serve.rs
+++ b/src/bin/quire/commands/serve.rs
@@ -2,6 +2,6 @@ use miette::Result;
 
 use quire::Quire;
 
-pub async fn run(quire: &Quire) -> Result<()> {
-    crate::server::run(quire).await
+pub async fn run(quire: &Quire, ci_routes: axum::Router) -> Result<()> {
+    crate::server::run(quire, ci_routes).await
 }
diff --git a/src/bin/quire/main.rs b/src/bin/quire/main.rs
index e120d35..3aae139 100644
--- a/src/bin/quire/main.rs
+++ b/src/bin/quire/main.rs
@@ -208,10 +208,16 @@ async fn main() -> Result<()> {
 
     match command {
         Commands::Serve { seed } => {
-            if seed {
-                commands::dev::seed(&quire)?;
-            }
-            commands::serve::run(&quire).await?
+            let quire = if seed { commands::dev::seed()? } else { quire };
+            let ci_routes = quire::quire::web::router(quire.clone());
+            let ci_routes = if seed {
+                ci_routes
+            } else {
+                ci_routes.layer(axum::middleware::from_fn(
+                    quire::quire::web::auth::require_auth,
+                ))
+            };
+            commands::serve::run(&quire, ci_routes).await?
         }
         Commands::Exec { command } => commands::exec::run(&quire, command).await?,
         Commands::Hook { hook_name } => commands::hook::run(&quire, hook_name).await?,
diff --git a/src/bin/quire/server.rs b/src/bin/quire/server.rs
index b86ec15..ba18bf9 100644
--- a/src/bin/quire/server.rs
+++ b/src/bin/quire/server.rs
@@ -17,7 +17,7 @@ async fn index() -> String {
     format!("quire {}\n", crate::VERSION)
 }
 
-pub async fn run(quire: &Quire) -> Result<()> {
+pub async fn run(quire: &Quire, ci_routes: axum::Router) -> Result<()> {
     let addr: SocketAddr = ([0, 0, 0, 0], 3000).into();
 
     // Set up event socket.
@@ -55,7 +55,7 @@ pub async fn run(quire: &Quire) -> Result<()> {
     let app = Router::new()
         .route("/health", get(health))
         .route("/", get(index))
-        .merge(quire::quire::web::router(quire.clone()));
+        .merge(ci_routes);
 
     tracing::info!(%addr, "starting HTTP server");
 
diff --git a/src/quire/web/mod.rs b/src/quire/web/mod.rs
index 4bf7838..4c142a2 100644
--- a/src/quire/web/mod.rs
+++ b/src/quire/web/mod.rs
@@ -12,18 +12,18 @@ pub mod format;
 pub mod handlers;
 pub mod templates;
 
-use axum::middleware;
-
 use crate::Quire;
 
+/// Bare CI routes without any auth middleware.
+///
+/// The caller decides whether to layer auth on top (e.g.
+/// `.layer(middleware::from_fn(auth::require_auth))`).
 pub fn router(quire: Quire) -> axum::Router {
-    let ci_routes = axum::Router::new()
+    axum::Router::new()
         .route("/{repo}/ci", axum::routing::get(handlers::run_list))
         .route(
             "/{repo}/ci/{run_id}",
             axum::routing::get(handlers::run_detail),
         )
-        .layer(middleware::from_fn(auth::require_auth));
-
-    ci_routes.with_state(quire)
+        .with_state(quire)
 }