use crate::error::GitError; use crate::http::HttpAppState; use crate::http::handler::is_valid_oid; use crate::http::lfs::{BatchRequest, CreateLockRequest, LfsHandler}; use crate::http::utils::get_repo_model; use actix_web::{Error, HttpRequest, HttpResponse, web}; use std::path::PathBuf; fn base_url(req: &HttpRequest) -> String { let conn_info = req.connection_info(); format!("{}://{}", conn_info.scheme(), conn_info.host()) } fn bearer_token(req: &HttpRequest) -> Result { let auth_header = req .headers() .get("authorization") .ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing authorization header"))? .to_str() .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid authorization header"))?; if let Some(token) = auth_header.strip_prefix("Bearer ") { Ok(token.to_string()) } else { Err(actix_web::error::ErrorUnauthorized( "Invalid authorization format", )) } } fn user_uid(req: &HttpRequest, repo: &models::repos::repo::Model) -> Result { if let Some(hv) = req.headers().get("x-user-uid") { if let Ok(s) = hv.to_str() { if let Ok(uid) = s.parse::() { return Ok(uid); } } } Ok(repo.created_by) } fn client_ip(req: &HttpRequest) -> String { req.connection_info() .realip_remote_addr() .unwrap_or("unknown") .to_string() } pub async fn lfs_batch( req: HttpRequest, path: web::Path<(String, String)>, body: web::Json, state: web::Data, ) -> Result { let (namespace, repo_name) = path.into_inner(); let ip = client_ip(&req); if !state.rate_limiter.is_ip_read_allowed(&ip).await { return Err(actix_web::error::ErrorTooManyRequests( "Rate limit exceeded", )); } let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); let response = handler .batch(body.into_inner(), &base_url(&req)) .await .map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?; Ok(HttpResponse::Ok() .content_type("application/vnd.git-lfs+json") .json(response)) } pub async fn lfs_upload( req: HttpRequest, path: web::Path<(String, String, String)>, payload: web::Payload, state: web::Data, ) -> Result { let (namespace, repo_name, oid) = path.into_inner(); if !is_valid_oid(&oid) { return Err(actix_web::error::ErrorBadRequest("Invalid OID format")); } let ip = client_ip(&req); if !state.rate_limiter.is_ip_write_allowed(&ip).await { return Err(actix_web::error::ErrorTooManyRequests( "Rate limit exceeded", )); } let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let token = bearer_token(&req)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.upload_object(&oid, payload, &token).await { Ok(response) => Ok(response), Err(GitError::InvalidOid(_)) => Err(actix_web::error::ErrorBadRequest("Invalid OID")), Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } } pub async fn lfs_download( req: HttpRequest, path: web::Path<(String, String, String)>, state: web::Data, ) -> Result { let (namespace, repo_name, oid) = path.into_inner(); if !is_valid_oid(&oid) { return Err(actix_web::error::ErrorBadRequest("Invalid OID format")); } let ip = client_ip(&req); if !state.rate_limiter.is_ip_read_allowed(&ip).await { return Err(actix_web::error::ErrorTooManyRequests( "Rate limit exceeded", )); } let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let token = bearer_token(&req)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.download_object(&oid, &token).await { Ok(response) => Ok(response), Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Object not found")), Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } } pub async fn lfs_lock_create( req: HttpRequest, path: web::Path<(String, String)>, body: web::Json, state: web::Data, ) -> Result { let (namespace, repo_name) = path.into_inner(); let ip = client_ip(&req); if !state.rate_limiter.is_ip_write_allowed(&ip).await { return Err(actix_web::error::ErrorTooManyRequests( "Rate limit exceeded", )); } let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let uid = user_uid(&req, &repo)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.lock_object(&body.oid, uid).await { Ok(lock) => Ok(HttpResponse::Created().json(lock)), Err(GitError::Locked(msg)) => Ok(HttpResponse::Conflict().body(msg)), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } } pub async fn lfs_lock_list( _req: HttpRequest, path: web::Path<(String, String)>, query: web::Query>, state: web::Data, ) -> Result { let (namespace, repo_name) = path.into_inner(); let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let maybe_oid = query.get("oid").map(|s| s.as_str()); let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.list_locks(maybe_oid).await { Ok(list) => Ok(HttpResponse::Ok().json(list)), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } } pub async fn lfs_lock_get( _req: HttpRequest, path: web::Path<(String, String, String)>, state: web::Data, ) -> Result { let (namespace, repo_name, lock_path) = path.into_inner(); let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.get_lock(&lock_path).await { Ok(lock) => Ok(HttpResponse::Ok().json(lock)), Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } } pub async fn lfs_lock_delete( req: HttpRequest, path: web::Path<(String, String, String)>, state: web::Data, ) -> Result { let (namespace, repo_name, lock_id) = path.into_inner(); let ip = client_ip(&req); if !state.rate_limiter.is_ip_write_allowed(&ip).await { return Err(actix_web::error::ErrorTooManyRequests( "Rate limit exceeded", )); } let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let uid = user_uid(&req, &repo)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.unlock_object(&lock_id, uid).await { Ok(()) => Ok(HttpResponse::NoContent().finish()), Err(GitError::PermissionDenied(_)) => Err(actix_web::error::ErrorForbidden("Not allowed")), Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")), Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())), } }