951a522
use crate::rpcgit::tree::types::TreeEntryWithLatest;
use axum::extract::{Path, Query, State};
use axum::response::Response;
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<String>,
#[serde(default)]
pub path: Option<String>,
}
#[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<GitService>,
Path(path): Path<HtmlRepoPath>,
Query(q): Query<LastCommitQuery>,
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) }
}
}
}