use crate::models::common::DEFAULT_REVISION; use actix_web::{HttpResponse, web}; use serde::Deserialize; use utoipa::IntoParams; use crate::api::response::ApiErrorResponse; use crate::error::AppError; use crate::service::AppService; use crate::session::Session; #[derive(Debug, Deserialize, IntoParams)] pub struct PathParams { pub workspace_name: String, pub repo_name: String, } #[derive(Debug, Deserialize, IntoParams)] pub struct QueryParams { pub format: Option, pub treeish: Option, } #[utoipa::path( get, path = "/api/v1/workspaces/{workspace_name}/repos/{repo_name}/git/archive", tag = "Git", operation_id = "gitArchive", params(PathParams, QueryParams), responses( (status = 200, description = "Archive download", content_type = "application/octet-stream"), (status = 401, description = "Unauthorized", body = ApiErrorResponse), (status = 404, description = "Not found", body = ApiErrorResponse), ), security(("session_cookie" = [])) )] pub async fn git_archive( service: web::Data, session: Session, path: web::Path, query: web::Query, ) -> Result { use futures_util::StreamExt; let fmt = match query.format.as_deref().unwrap_or("tar.gz") { "tar" => 1i32, "tar.gz" => 2i32, "tar.bz2" => 3i32, "tar.xz" => 4i32, "zip" => 5i32, _ => return Err(AppError::BadRequest("unsupported archive format".into())), }; let treeish = query.treeish.as_deref().unwrap_or(DEFAULT_REVISION); let mut stream = service .repo .git_archive( &session, &path.workspace_name, &path.repo_name, fmt, treeish, ) .await?; let (tx, rx) = tokio::sync::mpsc::channel::>(16); tokio::spawn(async move { while let Some(Ok(chunk)) = stream.next().await { if tx.send(Ok(web::Bytes::from(chunk.data))).await.is_err() { break; } } }); Ok(HttpResponse::Ok() .content_type("application/octet-stream") .streaming( tokio_stream::wrappers::ReceiverStream::new(rx) .map(|r| r.map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))), )) }