add code download route

This commit is contained in:
Sebastian Hugentobler 2025-07-01 14:38:09 +02:00
parent 480e16d070
commit dddc4b4081
Signed by: shu
SSH key fingerprint: SHA256:ppcx6MlixdNZd5EUM1nkHOKoyQYoJwzuQKXM6J/t66M
6 changed files with 131 additions and 2 deletions

40
Cargo.lock generated
View file

@ -299,6 +299,16 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.17.0"
@ -1118,6 +1128,19 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 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]] [[package]]
name = "h2" name = "h2"
version = "0.4.10" version = "0.4.10"
@ -1447,6 +1470,22 @@ dependencies = [
"icu_properties", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.9.0" version = "2.9.0"
@ -1747,6 +1786,7 @@ dependencies = [
"axum", "axum",
"clap", "clap",
"futures", "futures",
"ignore",
"lopdf", "lopdf",
"quick-xml", "quick-xml",
"rayon", "rayon",

View file

@ -29,3 +29,7 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum", "vendored"] }
uuid = { version = "1.17.0", features = ["v4"] } uuid = { version = "1.17.0", features = ["v4"] }
walkdir = "2.5.0" walkdir = "2.5.0"
zip = "4.2.0" zip = "4.2.0"
[build-dependencies]
ignore = "0.4.23"
zip = { version = "4.2.0", default-features = false, features = ["deflate"] }

46
build.rs Normal file
View file

@ -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<dyn std::error::Error>> {
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(())
}

View file

@ -15,6 +15,7 @@ use utoipa_swagger_ui::SwaggerUi;
use crate::{storage::Postgres, text_encoder::TextEncoder, tokenize::Tokenizer}; use crate::{storage::Postgres, text_encoder::TextEncoder, tokenize::Tokenizer};
pub mod code;
pub mod error; pub mod error;
pub mod query; pub mod query;
pub mod routes; pub mod routes;

38
src/api/code.rs Normal file
View file

@ -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<Response, StatusCode> {
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())
}

View file

@ -6,13 +6,13 @@ use tower_http::trace::TraceLayer;
use utoipa_axum::{router::OpenApiRouter, routes}; use utoipa_axum::{router::OpenApiRouter, routes};
use super::state::AppState; use super::state::AppState;
use crate::api::query; use crate::api::{code, query};
/// Create the main API router with all endpoints and middleware. /// Create the main API router with all endpoints and middleware.
pub fn router(state: AppState) -> OpenApiRouter { pub fn router(state: AppState) -> OpenApiRouter {
let store = Arc::new(state); let store = Arc::new(state);
OpenApiRouter::new() OpenApiRouter::new()
.routes(routes!(query::route)) .routes(routes!(query::route, code::route))
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
.with_state(store) .with_state(store)
} }