Web dashboard step 1: migration, sessions, users CLI, public site

Migration 0004 (users, sessions, config_changes, profile_versions, jobs,
rating_events.user_id, runs.report_json, issues.issue_json), the issue
snapshot writer and loader, the web module skeleton with layout and static
assets, axum-login/tower-sessions over a sqlx session store, password-auth
users with a CLI, the login throttle, the origin check, security headers,
and the public issue pages, archive, Atom feed and robots.txt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
2026-09-03 04:38:22 +00:00
co-authored by Claude Fable 5.1
parent a70de4af06
commit 69cd736455
35 changed files with 3801 additions and 69 deletions
+10
View File
@@ -70,6 +70,7 @@ pub struct GenerateOptions {
/// What one run produced, for the caller to print (§3.13).
#[derive(Debug)]
pub struct GenerateOutcome {
pub run_id: i64,
pub report: RunReport,
/// `None` only when the run failed before assembly.
pub issue: Option<Issue>,
@@ -271,12 +272,14 @@ pub async fn generate(config: &Config, db: &Db, opts: &GenerateOptions) -> Resul
None,
None,
Some(&report.to_json()),
None,
)
.await
{
tracing::warn!(error = %e, "could not attach the run report to the issue");
}
Ok(GenerateOutcome {
run_id,
report,
issue: stages.issue,
artifacts: stages.artifacts,
@@ -1045,6 +1048,12 @@ async fn record_issue(db: &Db, issue: &Issue, published: &Published) -> Result<(
let x4_path = path_for(Edition::X4);
let xtc_path = published.xtc.as_ref().map(|p| p.display().to_string());
let mut stored_issue = issue.clone();
for pick in &mut stored_issue.lineup.picks {
pick.article.content_html.clear();
}
let issue_json = serde_json::to_string(&stored_issue).context("serializing issue snapshot")?;
db.upsert_issue(
issue.meta.date,
issue.meta.issue_number,
@@ -1054,6 +1063,7 @@ async fn record_issue(db: &Db, issue: &Issue, published: &Published) -> Result<(
xtc_path.as_deref(),
Some(&issue.editorial.front_page_html),
None,
Some(&issue_json),
)
.await?;
db.replace_issue_articles(issue.meta.date, &issue.lineup.picks)