use axum::extract::{Path, Query, State}; use axum::response::Response; use gitbaby::tree::types::TreeEntryWithLatest; use maud::Markup; use maud::html; use crate::handler::html::error::html_err; use crate::handler::html::{ HtmlRepoPath, archived_class, first_line, resolve, response_html, short_sha, }; use crate::service::GitService; use crate::share::access::BasicAuth; use crate::share::validate::validate_revision; #[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)] pub struct LastCommitQuery { #[serde(default)] pub revision: Option, #[serde(default)] pub path: Option, } #[utoipa::path( get, path = "/repos/{space_name}/{repo_name}/html/last-commit", tag = "html", description = "渲染目录最近一次提交摘要行。", operation_id = "render_last_commit", params(HtmlRepoPath, LastCommitQuery), responses( (status = 200, description = "last commit html fragment", content_type = "text/html"), (status = 404, description = "repo not found") ) )] pub async fn render_last_commit( State(service): State, Path(path): Path, Query(q): Query, auth: BasicAuth, ) -> Response { let ctx = match resolve(service.clone(), path.clone(), auth).await { Ok(c) => c, Err(resp) => return resp, }; let revision = q .revision .as_deref() .unwrap_or(ctx.dto.default_branch.as_str()) .to_string(); if validate_revision(&revision).is_err() { return html_err(404, "ref invalid"); } let clean_path = q .path .as_deref() .and_then(crate::handler::html::validate_path) .unwrap_or_default(); let rows = match ctx .service .tree_with_commits_html(&ctx.ctx, &revision, &clean_path) .await { Ok(r) => r, Err(e) => { tracing::warn!(error = %e, "last_commit internal"); return html_err(500, "internal"); } }; let latest = rows.into_iter().next(); let base = "git-last-commit rounded-md border border-zinc-200 p-3"; let cls = archived_class(&ctx.dto, base); let markup = html! { div class=(cls) data-component="git-last-commit" data-space=(ctx.space_name) data-repo=(ctx.repo_name) data-revision=(revision) data-archived=(if ctx.dto.is_archived { "true" } else { "false" }) { @if let Some(row) = latest { (render_row(&revision, &path, &row)) } @else { p class="text-xs text-zinc-500" { "no commits yet" } } } }; response_html(markup, 30) } fn render_row(revision: &str, path: &HtmlRepoPath, row: &TreeEntryWithLatest) -> Markup { let sha = row.latest.id.to_string(); let short = short_sha(&sha); let subject = first_line(&row.latest.message); let author = &row.latest.author.name; let iso = row .latest .author .time .format(&time::format_description::well_known::Rfc3339) .unwrap_or_default(); let space = &path.space_name; let repo = &path.repo_name; let commit_url = format!("/{space}/{repo}/commit/{sha}"); html! { a class="flex items-center gap-3 text-zinc-700 hover:text-zinc-900" href=(commit_url) title=(format!("{sha}\n{subject}\n{author}\n{iso}")) { code class="rounded bg-zinc-100 px-1.5 py-0.5 font-mono text-xs" { (short) } span class="truncate text-sm" { (subject) } span class="ml-auto text-xs text-zinc-500" { (author) } span class="sr-only" { (revision) } } } }