From 5dfe87a57cf1a42002e43da32f2b9a1fb1631d26 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Mon, 18 Nov 2024 16:44:12 +0100 Subject: [PATCH] feat(serve): support contributors.txt --- Cargo.toml | 48 ++++++++++++------------ crates/rari-cli/serve.rs | 53 +++++++++++++++++++++++++-- crates/rari-doc/src/cached_readers.rs | 2 +- crates/rari-doc/src/contributors.rs | 11 ++---- 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4cb9639a..44844651 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.0.20" edition = "2021" license = "MPL-2.0" authors = [ - "Florian Dieminger ", - "The MDN Engineering Team ", + "Florian Dieminger ", + "The MDN Engineering Team ", ] homepage = "https://github.com/mdn/rari" repository = "https://github.com/mdn/rari" @@ -30,27 +30,27 @@ codegen-units = 1 [workspace] resolver = "2" members = [ - "crates/rari-data", - "crates/rari-utils", - "crates/rari-deps", - "crates/rari-types", - "crates/rari-templ-func", - "crates/rari-md", - "crates/rari-doc", - "crates/rari-linter", - "crates/rari-tools", - "crates/css-syntax", - "crates/css-syntax-types", - "crates/css-definition-syntax", - "crates/diff-test", + "crates/rari-data", + "crates/rari-utils", + "crates/rari-deps", + "crates/rari-types", + "crates/rari-templ-func", + "crates/rari-md", + "crates/rari-doc", + "crates/rari-linter", + "crates/rari-tools", + "crates/css-syntax", + "crates/css-syntax-types", + "crates/css-definition-syntax", + "crates/diff-test", ] [workspace.package] edition = "2021" license = "MPL-2.0" authors = [ - "Florian Dieminger ", - "The MDN Engineering Team ", + "Florian Dieminger ", + "The MDN Engineering Team ", ] rust-version = "1.81" @@ -81,10 +81,10 @@ html-escape = "0.2" ignore = "0.4" rayon = "1" reqwest = { version = "0.12", default-features = false, features = [ - "blocking", - "json", - "rustls-tls", - "gzip", + "blocking", + "json", + "rustls-tls", + "gzip", ] } indoc = "2" base64 = "0.22" @@ -108,9 +108,9 @@ anyhow.workspace = true dashmap.workspace = true self_update = { version = "0.41", default-features = false, features = [ - "rustls", - "compression-flate2", - "compression-zip-deflate", + "rustls", + "compression-flate2", + "compression-zip-deflate", ] } clap = { version = "4.5.1", features = ["derive"] } clap-verbosity-flag = "2" diff --git a/crates/rari-cli/serve.rs b/crates/rari-cli/serve.rs index 92bf8984..08fbf623 100644 --- a/crates/rari-cli/serve.rs +++ b/crates/rari-cli/serve.rs @@ -3,11 +3,14 @@ use std::str::FromStr; use std::sync::atomic::AtomicU64; use std::sync::Arc; +use axum::body::Body; use axum::extract::{Path, Request, State}; -use axum::http::StatusCode; +use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::routing::get; use axum::{Json, Router}; +use rari_doc::cached_readers::wiki_histories; +use rari_doc::contributors::contributors_txt; use rari_doc::error::DocError; use rari_doc::issues::{to_display_issues, InMemoryLayer}; use rari_doc::pages::json::BuiltPage; @@ -28,14 +31,23 @@ struct SearchItem { title: String, url: String, } + +async fn handler(state: State>, req: Request) -> Response { + if req.uri().path().ends_with("/contributors.txt") { + get_contributors_handler(req).await.into_response() + } else { + get_json_handler(state, req).await.into_response() + } +} + async fn get_json_handler( State(memory_layer): State>, req: Request, ) -> Result, AppError> { + let url = req.uri().path(); let req_id = REQ_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let span = span!(Level::WARN, "serve", req = req_id); let _enter1 = span.enter(); - let url = req.uri().path(); let mut json = get_json(url)?; if let BuiltPage::Doc(json_doc) = &mut json { let m = memory_layer.get_events(); @@ -66,6 +78,38 @@ fn get_json(url: &str) -> Result { Ok(json) } +async fn get_contributors_handler(req: Request) -> impl IntoResponse { + let url = req.uri().path(); + match get_contributors(url.strip_suffix("/contributors.txt").unwrap_or(url)) { + Ok(contributors_txt_str) => ( + StatusCode::OK, + [(header::CONTENT_TYPE, "text/plain")], + contributors_txt_str, + ) + .into_response(), + Err(e) => { + tracing::error!("error generating contributors.txt for {url}: {e:?}"); + (StatusCode::INTERNAL_SERVER_ERROR).into_response() + } + } +} + +fn get_contributors(url: &str) -> Result { + let page = Page::from_url_with_fallback(url)?; + let json = page.build()?; + let github_file_url = if let BuiltPage::Doc(ref doc) = json { + &doc.doc.source.github_url + } else { + "" + }; + let wiki_histories = wiki_histories(); + let wiki_history = wiki_histories + .get(&page.locale()) + .and_then(|wh| wh.get(page.slug())); + let contributors_txt_str = contributors_txt(wiki_history, github_file_url); + Ok(contributors_txt_str) +} + async fn get_search_index_handler( Path(locale): Path, ) -> Result>, AppError> { @@ -118,10 +162,11 @@ fn get_search_index(locale: Locale) -> Result, DocError> { Ok(out) } +#[derive(Debug)] struct AppError(anyhow::Error); impl IntoResponse for AppError { - fn into_response(self) -> Response { + fn into_response(self) -> Response { (StatusCode::INTERNAL_SERVER_ERROR, error!("🤷: {}", self.0)).into_response() } } @@ -142,7 +187,7 @@ pub fn serve(memory_layer: InMemoryLayer) -> Result<(), anyhow::Error> { .block_on(async { let app = Router::new() .route("/:locale/search-index.json", get(get_search_index_handler)) - .fallback(get_json_handler) + .fallback(handler) .with_state(Arc::new(memory_layer)); let listener = tokio::net::TcpListener::bind("0.0.0.0:8083").await.unwrap(); diff --git a/crates/rari-doc/src/cached_readers.rs b/crates/rari-doc/src/cached_readers.rs index 8d8d98e3..4c3c62de 100644 --- a/crates/rari-doc/src/cached_readers.rs +++ b/crates/rari-doc/src/cached_readers.rs @@ -613,7 +613,7 @@ pub fn contributor_spotlight_files() -> Cow<'static, UrlToPageMap> { /// /// - Panics if the translated content root or individual directory names are invalid. /// - Panics if the `_wikihistory.json` file is missing or contains malformed JSON. -pub(crate) fn wiki_histories() -> Cow<'static, WikiHistories> { +pub fn wiki_histories() -> Cow<'static, WikiHistories> { fn gather() -> Result { let mut map = HashMap::new(); if let Some(ctr) = content_translated_root() { diff --git a/crates/rari-doc/src/contributors.rs b/crates/rari-doc/src/contributors.rs index c0e90897..a4113978 100644 --- a/crates/rari-doc/src/contributors.rs +++ b/crates/rari-doc/src/contributors.rs @@ -4,12 +4,12 @@ use rari_types::locale::Locale; use serde::Deserialize; #[derive(Clone, Debug, Deserialize)] -pub(crate) struct WikiHistoryEntry { +pub struct WikiHistoryEntry { pub contributors: Vec, } -pub(crate) type WikiHistory = HashMap; -pub(crate) type WikiHistories = HashMap; +pub type WikiHistory = HashMap; +pub type WikiHistories = HashMap; /// Generates a contributors text report summarizing commit history and original Wiki contributors. /// @@ -61,10 +61,7 @@ pub(crate) type WikiHistories = HashMap; /// // # Contributors by commit history /// // https://github.com/user/repo/commits/main/file.txt /// ``` -pub(crate) fn contributors_txt( - wiki_history: Option<&WikiHistoryEntry>, - github_file_url: &str, -) -> String { +pub fn contributors_txt(wiki_history: Option<&WikiHistoryEntry>, github_file_url: &str) -> String { let mut out = String::new(); out.extend([ "# Contributors by commit history\n",