diff --git a/Cargo.lock b/Cargo.lock index a3ef389..c798432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -1118,6 +1128,19 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "h2" version = "0.4.10" @@ -1447,6 +1470,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.9", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -1747,6 +1786,7 @@ dependencies = [ "axum", "clap", "futures", + "ignore", "lopdf", "quick-xml", "rayon", diff --git a/Cargo.toml b/Cargo.toml index d44a1e3..a2e3b64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,7 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum", "vendored"] } uuid = { version = "1.17.0", features = ["v4"] } walkdir = "2.5.0" zip = "4.2.0" + +[build-dependencies] +ignore = "0.4.23" +zip = { version = "4.2.0", default-features = false, features = ["deflate"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..82dac72 --- /dev/null +++ b/build.rs @@ -0,0 +1,46 @@ +use std::{ + env, + fs::File, + io::{Read, Write}, + path::Path, +}; + +use ignore::Walk; +use zip::{CompressionMethod, write::SimpleFileOptions}; + +fn main() -> Result<(), Box> { + let out_dir = env::var("OUT_DIR")?; + let src_dir = "."; + + let zip_path = Path::new(&out_dir).join("archive.zip"); + let zip_file = File::create(zip_path)?; + + let walkdir = Walk::new(src_dir); + let it = walkdir.into_iter(); + + let mut zip = zip::ZipWriter::new(zip_file); + + let options = SimpleFileOptions::default() + .compression_method(CompressionMethod::Deflated) + .unix_permissions(0o755); + + for entry in it { + let entry = entry?; + let path = entry.path(); + let name = path.strip_prefix(Path::new(src_dir))?; + + if path.is_file() { + zip.start_file(name.to_str().unwrap(), options)?; + let mut f = File::open(path)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + zip.write_all(&buffer)?; + } else if !name.as_os_str().is_empty() { + zip.add_directory(name.to_str().unwrap(), options)?; + } + } + zip.finish()?; + println!("cargo:rerun-if-changed={}", src_dir); + + Ok(()) +} diff --git a/src/api.rs b/src/api.rs index b85f2ec..0aa7c7e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ use utoipa_swagger_ui::SwaggerUi; use crate::{storage::Postgres, text_encoder::TextEncoder, tokenize::Tokenizer}; +pub mod code; pub mod error; pub mod query; pub mod routes; diff --git a/src/api/code.rs b/src/api/code.rs new file mode 100644 index 0000000..2b01294 --- /dev/null +++ b/src/api/code.rs @@ -0,0 +1,38 @@ +use axum::{ + http::{StatusCode, header}, + response::{IntoResponse, Response}, +}; + +use super::TAG; + +const SOURCE_ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/archive.zip")); + +/// Serve the source code archive as a downloadable zip file. +#[utoipa::path( + get, + path = "/code", + tag = TAG, + responses( + (status = 200, + description = "Source code archive as zip file", + content_type = "application/zip", + headers( + ("content-disposition" = String, description = "Attachment filename"), + ("content-length" = String, description = "File size in bytes") + ) + ), + ) +)] +pub async fn route() -> Result { + let content_length = SOURCE_ARCHIVE.len().to_string(); + let headers = [ + (header::CONTENT_TYPE, "application/zip"), + ( + header::CONTENT_DISPOSITION, + "attachment; filename=\"source.zip\"", + ), + (header::CONTENT_LENGTH, content_length.as_str()), + ]; + + Ok((headers, SOURCE_ARCHIVE).into_response()) +} diff --git a/src/api/routes.rs b/src/api/routes.rs index d1c06a1..9da1bc0 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -6,13 +6,13 @@ use tower_http::trace::TraceLayer; use utoipa_axum::{router::OpenApiRouter, routes}; use super::state::AppState; -use crate::api::query; +use crate::api::{code, query}; /// Create the main API router with all endpoints and middleware. pub fn router(state: AppState) -> OpenApiRouter { let store = Arc::new(state); OpenApiRouter::new() - .routes(routes!(query::route)) + .routes(routes!(query::route, code::route)) .layer(TraceLayer::new_for_http()) .with_state(store) }