Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
httpd: Add /raw route to get file at canonical head
Merged did:key:z6MkkfM3...sVz5 opened 1 year ago

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.

1 file changed +37 -11 3ae7e305 dff6df10
modified radicle-httpd/src/raw.rs
@@ -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);
    }
}