httpd: Add /raw route to get file at canonical head

Instead of requiring to specify a commit to obtain a file through the
/raw route, this commit allows to prefix `/head` to get the file at the
current canonical head commit.
This commit is contained in:
Sebastian Martinez 2024-05-31 13:04:15 +02:00 committed by cloudhead
parent 3ae7e305bd
commit dff6df10fb
No known key found for this signature in database
1 changed files with 37 additions and 11 deletions

View File

@ -7,6 +7,7 @@ use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
use hyper::HeaderMap;
use radicle_surf::blob::{Blob, BlobRef};
use tower_http::cors;
use radicle::prelude::RepoId;
@ -94,8 +95,9 @@ static MIMES: &[(&str, &str)] = &[
pub fn router(profile: Arc<Profile>) -> Router {
Router::new()
.route("/:project/:sha/*path", get(file_by_path_handler))
.route("/:project/blobs/:oid", get(file_by_oid_handler))
.route("/:rid/:sha/*path", get(file_by_commit_handler))
.route("/:rid/head/*path", get(file_by_canonical_head_handler))
.route("/:rid/blobs/:oid", get(file_by_oid_handler))
.with_state(profile)
.layer(
cors::CorsLayer::new()
@ -106,7 +108,7 @@ pub fn router(profile: Arc<Profile>) -> Router {
)
}
async fn file_by_path_handler(
async fn file_by_commit_handler(
Path((rid, sha, path)): Path<(RepoId, Oid, String)>,
State(profile): State<Arc<Profile>>,
) -> impl IntoResponse {
@ -118,10 +120,36 @@ async fn file_by_path_handler(
return Err(Error::NotFound);
}
let mut response_headers = HeaderMap::new();
let repo: Repository = repo.backend.into();
let blob = repo.blob(sha, &path)?;
blob_response(blob, path)
}
async fn file_by_canonical_head_handler(
Path((rid, path)): Path<(RepoId, String)>,
State(profile): State<Arc<Profile>>,
) -> impl IntoResponse {
let storage = &profile.storage;
let repo = storage.repository(rid)?;
// Don't allow downloading raw files for private repos.
if repo.identity_doc()?.visibility.is_private() {
return Err(Error::NotFound);
}
let (_, sha) = repo.head()?;
let repo: Repository = repo.backend.into();
let blob = repo.blob(sha, &path)?;
blob_response(blob, path)
}
fn blob_response(
blob: Blob<BlobRef>,
path: String,
) -> Result<(StatusCode, HeaderMap, Vec<u8>), Error> {
let mut response_headers = HeaderMap::new();
if blob.size() > MAX_BLOB_SIZE {
return Ok::<_, Error>((StatusCode::PAYLOAD_TOO_LARGE, response_headers, vec![]));
}
@ -171,8 +199,8 @@ async fn file_by_oid_handler(
mod routes {
use axum::http::StatusCode;
use crate::test::{self, get, HEAD, RID, RID_PRIVATE};
use radicle::storage::{ReadRepository, ReadStorage};
use crate::test::{self, get, RID, RID_PRIVATE};
use radicle::storage::ReadStorage;
#[tokio::test]
async fn test_file_handler() {
@ -180,20 +208,18 @@ mod routes {
let ctx = test::seed(tmp.path());
let app = super::router(ctx.profile().to_owned());
let response = get(&app, format!("/{RID}/{HEAD}/dir1/README")).await;
let response = get(&app, format!("/{RID}/head/dir1/README")).await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().await, "Hello World from dir1!\n");
// Make sure the repo exists in storage.
let repo = ctx
.profile()
ctx.profile()
.storage
.repository(RID_PRIVATE.parse().unwrap())
.unwrap();
let (_, head) = repo.head().unwrap();
let response = get(&app, format!("/{RID_PRIVATE}/{head}/README")).await;
let response = get(&app, format!("/{RID_PRIVATE}/head/README")).await;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
}