gitdataai/libs/git/http/routes.rs
ZhenYi 3de4fff11d
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
feat(service): improve model sync and harden git HTTP/SSH stability
Model sync:
- Filter OpenRouter models by what the user's AI client can actually access,
  before upserting metadata (avoids bloating with inaccessible models).
- Fall back to direct endpoint sync when no OpenRouter metadata matches
  (handles Bailian/MiniMax and other non-OpenRouter providers).

Git stability fixes:
- SSH: add 5s timeout on stdin flush/shutdown in channel_eof and
  cleanup_channel to prevent blocking the event loop on unresponsive git.
- SSH: remove dbg!() calls from production code paths.
- HTTP auth: pass proper Logger to SshAuthService instead of discarding
  all auth events to slog::Discard.

Dependencies:
- reqwest: add native-tls feature for HTTPS on Windows/Linux/macOS.
2026-04-17 00:13:40 +08:00

108 lines
3.5 KiB
Rust

use crate::http::HttpAppState;
use crate::http::auth::authorize_repo_access;
use crate::http::handler::GitHttpHandler;
use crate::http::utils::get_repo_model;
use crate::ssh::RepoReceiveSyncTask;
use actix_web::{Error, HttpRequest, HttpResponse, web};
use std::path::PathBuf;
use std::time::Duration;
use tokio::time::timeout;
pub async fn info_refs(
req: HttpRequest,
path: web::Path<(String, String)>,
state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
let ip = extract_ip(&req);
if !state.rate_limiter.is_ip_read_allowed(&ip).await {
return Err(actix_web::error::ErrorTooManyRequests(
"Rate limit exceeded",
));
}
let service_param = req
.query_string()
.split('&')
.find(|s| s.starts_with("service="))
.and_then(|s| s.strip_prefix("service="))
.ok_or_else(|| actix_web::error::ErrorBadRequest("Missing service parameter"))?;
if service_param != "git-upload-pack" && service_param != "git-receive-pack" {
return Err(actix_web::error::ErrorBadRequest("Invalid service"));
}
let path_inner = path.into_inner();
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
let is_write = service_param == "git-receive-pack";
authorize_repo_access(&req, &state.db, &state.logger, &model, is_write).await?;
let storage_path = PathBuf::from(&model.storage_path);
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
handler.info_refs(service_param).await
}
pub async fn upload_pack(
req: HttpRequest,
path: web::Path<(String, String)>,
payload: web::Payload,
state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
let ip = extract_ip(&req);
if !state.rate_limiter.is_ip_read_allowed(&ip).await {
return Err(actix_web::error::ErrorTooManyRequests(
"Rate limit exceeded",
));
}
let path_inner = path.into_inner();
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
authorize_repo_access(&req, &state.db, &state.logger, &model, false).await?;
let storage_path = PathBuf::from(&model.storage_path);
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
handler.upload_pack(payload).await
}
pub async fn receive_pack(
req: HttpRequest,
path: web::Path<(String, String)>,
payload: web::Payload,
state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
let ip = extract_ip(&req);
if !state.rate_limiter.is_ip_write_allowed(&ip).await {
return Err(actix_web::error::ErrorTooManyRequests(
"Rate limit exceeded",
));
}
let path_inner = path.into_inner();
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
authorize_repo_access(&req, &state.db, &state.logger, &model, true).await?;
let storage_path = PathBuf::from(&model.storage_path);
let handler = GitHttpHandler::new(storage_path, model.clone(), state.db.clone());
let result = handler.receive_pack(payload).await;
let _ = tokio::spawn({
let sync = state.sync.clone();
let repo_uid = model.id;
async move {
let _ = timeout(
Duration::from_secs(5),
sync.send(RepoReceiveSyncTask { repo_uid }),
)
.await;
}
});
result
}
fn extract_ip(req: &HttpRequest) -> String {
req.connection_info()
.realip_remote_addr()
.unwrap_or("unknown")
.to_string()
}