From 849231e49a18d11ce6f3f1dc4b170a9c96d0635b Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Thu, 3 Sep 2026 04:59:29 +0000 Subject: [PATCH] Web dashboard: dashboard module scaffold for parallel steps Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM --- docs/plans/briefs/web-dashboard/00-shared.md | 23 +++++++++ src/web/dashboard/articles.rs | 10 ++++ src/web/dashboard/jobs.rs | 10 ++++ src/web/dashboard/mod.rs | 53 ++++++++++++++++++++ src/web/dashboard/profile.rs | 10 ++++ src/web/dashboard/ratings.rs | 10 ++++ src/web/dashboard/runs.rs | 10 ++++ src/web/dashboard/settings.rs | 10 ++++ src/web/dashboard/stats.rs | 10 ++++ src/web/dashboard/users.rs | 10 ++++ src/web/mod.rs | 17 +------ 11 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 src/web/dashboard/articles.rs create mode 100644 src/web/dashboard/jobs.rs create mode 100644 src/web/dashboard/mod.rs create mode 100644 src/web/dashboard/profile.rs create mode 100644 src/web/dashboard/ratings.rs create mode 100644 src/web/dashboard/runs.rs create mode 100644 src/web/dashboard/settings.rs create mode 100644 src/web/dashboard/stats.rs create mode 100644 src/web/dashboard/users.rs diff --git a/docs/plans/briefs/web-dashboard/00-shared.md b/docs/plans/briefs/web-dashboard/00-shared.md index a5df4e3..ade3e5a 100644 --- a/docs/plans/briefs/web-dashboard/00-shared.md +++ b/docs/plans/briefs/web-dashboard/00-shared.md @@ -41,3 +41,26 @@ tests, rustfmt defaults, no `unwrap()` outside tests, tracing spans). - When you finish, write a short handoff at `docs/plans/briefs/web-dashboard/handoff-step.md`: what landed, deviations from the plan and why, anything left for the next step, test counts. + +## Parallel steps (added after step 2) + +From step 3 on, steps may run **in parallel in separate git worktrees**, so +file ownership matters: + +- `src/web/dashboard/mod.rs` already declares one submodule per page group + (`runs`, `articles`, `ratings`, `profile`, `settings`, `jobs`, `stats`, + `users`), each exposing `routes() -> Router` that the dashboard + router merges under the admin layer. Put your routes in **your** submodule's + `routes()`; do not touch `web::router` in `src/web/mod.rs` for dashboard + routes. Only step 3 edits the `overview` handler in `dashboard/mod.rs`. +- You own: your `src/web/dashboard/.rs` files, your templates under + `src/web/templates/dashboard/`, partials you introduce, your tests. +- Shared files you may touch **additively only** (no reformatting, reordering + or renaming of existing code): `src/web/mod.rs` (helpers), `src/db.rs` (new + query helpers appended inside `impl Db`), `src/main.rs`, `src/lib.rs`, + `Cargo.toml`, `src/web/static/app.css` and `app.js` (append a block at the + end under a `/* step N: … */` comment), `src/web/templates/layout.html`. + Keep such edits small so merges stay trivial. +- If your worktree is on its own branch, finish by committing **one** commit + on that branch (message `Web dashboard step N: …`); the orchestrator merges. + If you are on `web-dashboard` itself, do not commit. diff --git a/src/web/dashboard/articles.rs b/src/web/dashboard/articles.rs new file mode 100644 index 0000000..82efb40 --- /dev/null +++ b/src/web/dashboard/articles.rs @@ -0,0 +1,10 @@ +//! Dashboard: articles pages. Filled in by web dashboard plan step 3. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/jobs.rs b/src/web/dashboard/jobs.rs new file mode 100644 index 0000000..22b8ae8 --- /dev/null +++ b/src/web/dashboard/jobs.rs @@ -0,0 +1,10 @@ +//! Dashboard: jobs pages. Filled in by web dashboard plan step 6. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/mod.rs b/src/web/dashboard/mod.rs new file mode 100644 index 0000000..ada2a3f --- /dev/null +++ b/src/web/dashboard/mod.rs @@ -0,0 +1,53 @@ +//! The operator dashboard (`/dashboard/*`, dashboard plan §9–§14). +//! +//! Every route here sits under the admin `permission_required!` layer that +//! `web::router` applies to the merged router; handlers can therefore trust +//! that `AuthSession::user()` is an admin. One submodule per page group; each +//! exposes `routes()` and this module merges them. + +pub mod articles; +pub mod jobs; +pub mod profile; +pub mod ratings; +pub mod runs; +pub mod settings; +pub mod stats; +pub mod users; + +use askama::Template; +use axum::Router; +use axum::response::{IntoResponse, Response}; +use axum::routing::get; + +use crate::server::AppState; +use crate::web::session::{AuthSession, Viewer}; +use crate::web::{Html, Page, WebError}; + +/// Every dashboard route, without the admin layer (applied by the caller). +pub fn router() -> Router { + Router::new() + .route("/dashboard", get(overview)) + .merge(runs::routes()) + .merge(articles::routes()) + .merge(ratings::routes()) + .merge(profile::routes()) + .merge(settings::routes()) + .merge(jobs::routes()) + .merge(stats::routes()) + .merge(users::routes()) +} + +#[derive(Template)] +#[template(path = "dashboard/overview.html")] +struct OverviewTemplate { + page: Page, +} + +/// `GET /dashboard` — the overview (§9.1). Step 3 fills this in. +async fn overview(auth: AuthSession) -> Result { + let viewer = auth.user().await.map(Viewer::from); + Ok(Html(OverviewTemplate { + page: Page::new("Overview", viewer, "dashboard"), + }) + .into_response()) +} diff --git a/src/web/dashboard/profile.rs b/src/web/dashboard/profile.rs new file mode 100644 index 0000000..48c6e79 --- /dev/null +++ b/src/web/dashboard/profile.rs @@ -0,0 +1,10 @@ +//! Dashboard: profile pages. Filled in by web dashboard plan step 4. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/ratings.rs b/src/web/dashboard/ratings.rs new file mode 100644 index 0000000..aaff9a8 --- /dev/null +++ b/src/web/dashboard/ratings.rs @@ -0,0 +1,10 @@ +//! Dashboard: ratings pages. Filled in by web dashboard plan step 4. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/runs.rs b/src/web/dashboard/runs.rs new file mode 100644 index 0000000..18c324d --- /dev/null +++ b/src/web/dashboard/runs.rs @@ -0,0 +1,10 @@ +//! Dashboard: runs pages. Filled in by web dashboard plan step 3. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/settings.rs b/src/web/dashboard/settings.rs new file mode 100644 index 0000000..19a9939 --- /dev/null +++ b/src/web/dashboard/settings.rs @@ -0,0 +1,10 @@ +//! Dashboard: settings pages. Filled in by web dashboard plan step 5. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/stats.rs b/src/web/dashboard/stats.rs new file mode 100644 index 0000000..92907cf --- /dev/null +++ b/src/web/dashboard/stats.rs @@ -0,0 +1,10 @@ +//! Dashboard: stats pages. Filled in by web dashboard plan step 6. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/dashboard/users.rs b/src/web/dashboard/users.rs new file mode 100644 index 0000000..19e5ccb --- /dev/null +++ b/src/web/dashboard/users.rs @@ -0,0 +1,10 @@ +//! Dashboard: users pages. Filled in by web dashboard plan step 7. + +use axum::Router; + +use crate::server::AppState; + +/// Routes contributed by this page group (merged by `dashboard::router`). +pub fn routes() -> Router { + Router::new() +} diff --git a/src/web/mod.rs b/src/web/mod.rs index 3956e3f..358838a 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,3 +1,4 @@ +pub mod dashboard; pub mod issue; pub mod public; pub mod rate; @@ -367,7 +368,7 @@ pub fn router(config: &crate::config::Config) -> axum::Router axum::Router Result { - let viewer = auth.user().await.map(session::Viewer::from); - Ok(Html(OverviewTemplate { - page: Page::new("Overview", viewer, "dashboard"), - }) - .into_response()) -} - async fn map_forbidden(request: Request, next: Next) -> Response { let mut response = next.run(request).await; if response.status() == StatusCode::FORBIDDEN {