Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
http: Rename LoC with projects ocurrences to repos
Sebastian Martinez committed 1 year ago
commit 5d7eb71eab623e3e793a290473feedc8762883a4
parent 06cc559
6 files changed +274 -254
modified radicle-httpd/src/api.rs
@@ -47,16 +47,15 @@ impl Context {
        }
    }

-
    pub fn project_info<R: ReadRepository + radicle::cob::Store>(
+
    pub fn repo_info<R: ReadRepository + radicle::cob::Store>(
        &self,
        repo: &R,
        doc: DocAt,
-
    ) -> Result<project::Info, error::Error> {
+
    ) -> Result<repo::Info, error::Error> {
        let (_, head) = repo.head()?;
        let DocAt { doc, .. } = doc;
-
        let id = repo.id();
+
        let rid = repo.id();

-
        let payload = doc.project()?;
        let aliases = self.profile.aliases();
        let delegates = doc
            .delegates
@@ -66,17 +65,17 @@ impl Context {
        let issues = self.profile.issues(repo)?.counts()?;
        let patches = self.profile.patches(repo)?.counts()?;
        let db = &self.profile.database()?;
-
        let seeding = db.count(&id).unwrap_or_default();
+
        let seeding = db.count(&rid).unwrap_or_default();

-
        Ok(project::Info {
-
            payload,
+
        Ok(repo::Info {
+
            payload: doc.payload,
            delegates,
            threshold: doc.threshold,
            visibility: doc.visibility,
            head,
            issues,
            patches,
-
            id,
+
            rid,
            seeding,
        })
    }
@@ -130,14 +129,14 @@ async fn root_handler() -> impl IntoResponse {
#[serde(rename_all = "camelCase")]
pub struct PaginationQuery {
    #[serde(default)]
-
    pub show: ProjectQuery,
+
    pub show: RepoQuery,
    pub page: Option<usize>,
    pub per_page: Option<usize>,
}

#[derive(Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
-
pub enum ProjectQuery {
+
pub enum RepoQuery {
    All,
    #[default]
    Pinned,
@@ -209,16 +208,17 @@ impl PatchStatus {

mod search {
    use std::cmp::Ordering;
+
    use std::collections::BTreeMap;

    use nonempty::NonEmpty;
    use serde::{Deserialize, Serialize};
    use serde_json::json;

    use radicle::crypto::Verified;
-
    use radicle::identity::{Project, RepoId};
+
    use radicle::identity::doc::{Payload, PayloadId};
+
    use radicle::identity::RepoId;
    use radicle::node::routing::Store;
-
    use radicle::node::AliasStore;
-
    use radicle::node::Database;
+
    use radicle::node::{AliasStore, Database};
    use radicle::profile::Aliases;
    use radicle::storage::RepositoryInfo;

@@ -234,7 +234,7 @@ mod search {
    pub struct SearchResult {
        pub rid: RepoId,
        #[serde(flatten)]
-
        pub payload: Project,
+
        pub payload: BTreeMap<PayloadId, Payload>,
        pub delegates: NonEmpty<serde_json::Value>,
        pub seeds: usize,
        #[serde(skip)]
@@ -251,8 +251,9 @@ mod search {
            if info.doc.visibility.is_private() {
                return None;
            }
-
            let payload = info.doc.project().ok()?;
-
            let index = payload.name().find(q)?;
+
            let Ok(Some(index)) = info.doc.project().map(|p| p.name().find(q)) else {
+
                return None;
+
            };
            let seeds = db.count(&info.rid).unwrap_or_default();
            let delegates = info.doc.delegates.map(|did| match aliases.alias(&did) {
                Some(alias) => json!({
@@ -266,7 +267,7 @@ mod search {

            Some(SearchResult {
                rid: info.rid,
-
                payload,
+
                payload: info.doc.payload,
                delegates,
                seeds,
                index,
@@ -299,29 +300,30 @@ mod search {
    }
}

-
mod project {
+
mod repo {
+
    use std::collections::BTreeMap;
+

    use serde::Serialize;
    use serde_json::Value;

    use radicle::cob;
    use radicle::git::Oid;
-
    use radicle::identity::project::Project;
+
    use radicle::identity::doc::{Payload, PayloadId};
    use radicle::identity::{RepoId, Visibility};

-
    /// Project info.
+
    /// Repos info.
    #[derive(Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct Info {
-
        /// Project metadata.
        #[serde(flatten)]
-
        pub payload: Project,
+
        pub payload: BTreeMap<PayloadId, Payload>,
        pub delegates: Vec<Value>,
        pub threshold: usize,
        pub visibility: Visibility,
        pub head: Oid,
        pub patches: cob::patch::PatchCounts,
        pub issues: cob::issue::IssueCounts,
-
        pub id: RepoId,
+
        pub rid: RepoId,
        pub seeding: usize,
    }
}
modified radicle-httpd/src/api/v1.rs
@@ -36,8 +36,13 @@ async fn root_handler(State(ctx): State<Context>) -> impl IntoResponse {
        "path": "/api/v1",
        "links": [
            {
-
                "href": "/projects",
-
                "rel": "projects",
+
                "href": "/repos",
+
                "rel": "repos",
+
                "type": "GET"
+
            },
+
            {
+
                "href": "/repos/:rid",
+
                "rel": "repo",
                "type": "GET"
            },
            {
@@ -51,8 +56,8 @@ async fn root_handler(State(ctx): State<Context>) -> impl IntoResponse {
                "type": "GET"
            },
            {
-
                "href": "/delegates/:did/projects",
-
                "rel": "projects",
+
                "href": "/delegates/:did/repos",
+
                "rel": "repos",
                "type": "GET"
            },
            {
modified radicle-httpd/src/api/v1/delegates.rs
@@ -12,24 +12,19 @@ use radicle::patch::cache::Patches as _;
use radicle::storage::{ReadRepository, ReadStorage};

use crate::api::error::Error;
-
use crate::api::json;
-
use crate::api::project::Info;
-
use crate::api::Context;
-
use crate::api::{PaginationQuery, ProjectQuery};
+
use crate::api::repo::Info;
+
use crate::api::{json, Context, PaginationQuery, RepoQuery};
use crate::axum_extra::{Path, Query};

pub fn router(ctx: Context) -> Router {
    Router::new()
-
        .route(
-
            "/delegates/:delegate/projects",
-
            get(delegates_projects_handler),
-
        )
+
        .route("/delegates/:delegate/repos", get(delegates_repos_handler))
        .with_state(ctx)
}

-
/// List all projects which delegate is a part of.
-
/// `GET /delegates/:delegate/projects`
-
async fn delegates_projects_handler(
+
/// List all repos which delegate is a part of.
+
/// `GET /delegates/:delegate/repos`
+
async fn delegates_repos_handler(
    State(ctx): State<Context>,
    Path(delegate): Path<Did>,
    Query(qs): Query<PaginationQuery>,
@@ -44,17 +39,21 @@ async fn delegates_projects_handler(
    let storage = &ctx.profile.storage;
    let db = &ctx.profile.database()?;
    let pinned = &ctx.profile.config.web.pinned;
-
    let mut projects = match show {
-
        ProjectQuery::All => storage
+
    let mut repos = match show {
+
        RepoQuery::All => storage
            .repositories()?
            .into_iter()
            .filter(|repo| repo.doc.visibility.is_public())
            .collect::<Vec<_>>(),
-
        ProjectQuery::Pinned => storage.repositories_by_id(pinned.repositories.iter())?,
+
        RepoQuery::Pinned => storage
+
            .repositories_by_id(pinned.repositories.iter())?
+
            .into_iter()
+
            .filter(|repo| repo.doc.visibility.is_public())
+
            .collect::<Vec<_>>(),
    };
-
    projects.sort_by_key(|p| p.rid);
+
    repos.sort_by_key(|p| p.rid);

-
    let infos = projects
+
    let infos = repos
        .into_iter()
        .filter_map(|id| {
            if !id.doc.delegates.iter().any(|d| *d == delegate) {
@@ -66,7 +65,7 @@ async fn delegates_projects_handler(
            let Ok((_, head)) = repo.head() else {
                return None;
            };
-
            let Ok(payload) = id.doc.project() else {
+
            let Ok(_payload) = id.doc.project() else {
                return None;
            };
            let Ok(issues) = ctx.profile.issues(&repo) else {
@@ -92,14 +91,14 @@ async fn delegates_projects_handler(
            let seeding = db.count(&id.rid).unwrap_or_default();

            Some(Info {
-
                payload,
+
                payload: id.doc.payload,
                delegates,
                threshold: id.doc.threshold,
                visibility: id.doc.visibility,
                head,
                issues,
                patches,
-
                id: id.rid,
+
                rid: id.rid,
                seeding,
            })
        })
@@ -121,14 +120,14 @@ mod routes {
    use crate::test::{self, get, CONTRIBUTOR_ALIAS, DID, HEAD, RID};

    #[tokio::test]
-
    async fn test_delegates_projects() {
+
    async fn test_delegates_repos() {
        let tmp = tempfile::tempdir().unwrap();
        let seed = test::seed(tmp.path());
        let app = super::router(seed.clone())
            .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))));
        let response = get(
            &app,
-
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all",
+
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/repos?show=all",
        )
        .await;

@@ -142,9 +141,11 @@ mod routes {
            response.json().await,
            json!([
              {
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for tests",
+
                  "name": "hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -166,13 +167,15 @@ mod routes {
                  "open": 1,
                  "closed": 0,
                },
-
                "id": RID,
+
                "rid": RID,
                "seeding": 1,
              },
              {
-
                "name": "again-hello-world",
-
                "description": "Rad repository for sorting",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for sorting",
+
                  "name": "again-hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -194,7 +197,7 @@ mod routes {
                  "open": 0,
                  "closed": 0,
                },
-
                "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
+
                "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
                "seeding": 1,
              }
            ])
@@ -206,7 +209,7 @@ mod routes {
        ))));
        let response = get(
            &app,
-
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all",
+
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/repos?show=all",
        )
        .await;

@@ -220,9 +223,11 @@ mod routes {
            response.json().await,
            json!([
              {
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for tests",
+
                  "name": "hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -244,13 +249,15 @@ mod routes {
                  "open": 1,
                  "closed": 0,
                },
-
                "id": RID,
+
                "rid": RID,
                "seeding": 1,
              },
              {
-
                "name": "again-hello-world",
-
                "description": "Rad repository for sorting",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for sorting",
+
                  "name": "again-hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -272,7 +279,7 @@ mod routes {
                  "open": 0,
                  "closed": 0,
                },
-
                "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
+
                "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
                "seeding": 1,
              }
            ])
modified radicle-httpd/src/api/v1/projects.rs
@@ -17,44 +17,41 @@ use radicle::node::routing::Store;
use radicle::node::{AliasStore, NodeId};
use radicle::storage::{ReadRepository, ReadStorage, RemoteRepository};

+
use crate::api;
use crate::api::error::Error;
-
use crate::api::project::Info;
use crate::api::search::{SearchQueryString, SearchResult};
-
use crate::api::{self, CobsQuery, Context, PaginationQuery, ProjectQuery};
+
use crate::api::{CobsQuery, Context, PaginationQuery, RepoQuery};
use crate::axum_extra::{cached_response, immutable_response, Path, Query};

const MAX_BODY_LIMIT: usize = 4_194_304;

pub fn router(ctx: Context) -> Router {
    Router::new()
-
        .route("/projects", get(project_root_handler))
-
        .route("/projects/search", get(project_search_handler))
-
        .route("/projects/:project", get(project_handler))
-
        .route("/projects/:project/commits", get(history_handler))
-
        .route("/projects/:project/commits/:sha", get(commit_handler))
-
        .route("/projects/:project/diff/:base/:oid", get(diff_handler))
-
        .route("/projects/:project/activity", get(activity_handler))
-
        .route("/projects/:project/tree/:sha/", get(tree_handler_root))
-
        .route("/projects/:project/tree/:sha/*path", get(tree_handler))
-
        .route(
-
            "/projects/:project/stats/tree/:sha",
-
            get(stats_tree_handler),
-
        )
-
        .route("/projects/:project/remotes", get(remotes_handler))
-
        .route("/projects/:project/remotes/:peer", get(remote_handler))
-
        .route("/projects/:project/blob/:sha/*path", get(blob_handler))
-
        .route("/projects/:project/readme/:sha", get(readme_handler))
-
        .route("/projects/:project/issues", get(issues_handler))
-
        .route("/projects/:project/issues/:id", get(issue_handler))
-
        .route("/projects/:project/patches", get(patches_handler))
-
        .route("/projects/:project/patches/:id", get(patch_handler))
+
        .route("/repos", get(repo_root_handler))
+
        .route("/repos/search", get(repo_search_handler))
+
        .route("/repos/:rid", get(repo_handler))
+
        .route("/repos/:rid/commits", get(history_handler))
+
        .route("/repos/:rid/commits/:sha", get(commit_handler))
+
        .route("/repos/:rid/diff/:base/:oid", get(diff_handler))
+
        .route("/repos/:rid/activity", get(activity_handler))
+
        .route("/repos/:rid/tree/:sha/", get(tree_handler_root))
+
        .route("/repos/:rid/tree/:sha/*path", get(tree_handler))
+
        .route("/repos/:rid/stats/tree/:sha", get(stats_tree_handler))
+
        .route("/repos/:rid/remotes", get(remotes_handler))
+
        .route("/repos/:rid/remotes/:peer", get(remote_handler))
+
        .route("/repos/:rid/blob/:sha/*path", get(blob_handler))
+
        .route("/repos/:rid/readme/:sha", get(readme_handler))
+
        .route("/repos/:rid/issues", get(issues_handler))
+
        .route("/repos/:rid/issues/:id", get(issue_handler))
+
        .route("/repos/:rid/patches", get(patches_handler))
+
        .route("/repos/:rid/patches/:id", get(patch_handler))
        .with_state(ctx)
        .layer(DefaultBodyLimit::max(MAX_BODY_LIMIT))
}

-
/// List all projects.
-
/// `GET /projects`
-
async fn project_root_handler(
+
/// List all repos.
+
/// `GET /repos`
+
async fn repo_root_handler(
    State(ctx): State<Context>,
    Query(qs): Query<PaginationQuery>,
) -> impl IntoResponse {
@@ -65,7 +62,7 @@ async fn project_root_handler(
    } = qs;
    let page = page.unwrap_or(0);
    let per_page = per_page.unwrap_or_else(|| match show {
-
        ProjectQuery::Pinned => ctx.profile.config.web.pinned.repositories.len(),
+
        RepoQuery::Pinned => ctx.profile.config.web.pinned.repositories.len(),
        _ => 10,
    });
    let storage = &ctx.profile.storage;
@@ -73,21 +70,21 @@ async fn project_root_handler(
    let pinned = &ctx.profile.config.web.pinned;
    let policies = ctx.profile.policies()?;

-
    let mut projects = match show {
-
        ProjectQuery::All => storage
+
    let mut repos = match show {
+
        RepoQuery::All => storage
            .repositories()?
            .into_iter()
            .filter(|repo| repo.doc.visibility.is_public())
            .collect::<Vec<_>>(),
-
        ProjectQuery::Pinned => storage
+
        RepoQuery::Pinned => storage
            .repositories_by_id(pinned.repositories.iter())?
            .into_iter()
            .filter(|repo| repo.doc.visibility.is_public())
            .collect::<Vec<_>>(),
    };
-
    projects.sort_by_key(|p| p.rid);
+
    repos.sort_by_key(|p| p.rid);

-
    let infos = projects
+
    let infos = repos
        .into_iter()
        .filter_map(|info| {
            if !policies.is_seeding(&info.rid).unwrap_or_default() {
@@ -99,7 +96,7 @@ async fn project_root_handler(
            let Ok((_, head)) = repo.head() else {
                return None;
            };
-
            let Ok(payload) = info.doc.project() else {
+
            let Ok(_payload) = info.doc.project() else {
                return None;
            };
            let Ok(issues) = ctx.profile.issues(&repo) else {
@@ -123,15 +120,15 @@ async fn project_root_handler(
                .collect::<Vec<_>>();
            let seeding = db.count(&info.rid).unwrap_or_default();

-
            Some(Info {
-
                payload,
+
            Some(api::repo::Info {
+
                payload: info.doc.payload,
                delegates,
                head,
                threshold: info.doc.threshold,
                visibility: info.doc.visibility,
                issues,
                patches,
-
                id: info.rid,
+
                rid: info.rid,
                seeding,
            })
        })
@@ -143,7 +140,7 @@ async fn project_root_handler(
}

/// Search repositories by name.
-
/// `GET /projects/search?q=<query>`
+
/// `GET /repos/search?q=<query>`
///
/// We obtain the byte index of the first character of the query that matches the repo name.
/// And skip if the query doesn't match the repo name.
@@ -153,7 +150,7 @@ async fn project_root_handler(
/// A repo name with a byte index of 0 should come before non-zero indices.
/// If both indices are non-zero and equal, then compare by seeding count.
/// If none of the above, all non-zero indices are compared by their seeding count primarily.
-
async fn project_search_handler(
+
async fn repo_search_handler(
    State(ctx): State<Context>,
    Query(SearchQueryString { q, per_page, page }): Query<SearchQueryString>,
) -> impl IntoResponse {
@@ -178,11 +175,11 @@ async fn project_search_handler(
    Ok::<_, Error>(cached_response(found_repos, 600).into_response())
}

-
/// Get project metadata.
-
/// `GET /projects/:project`
-
async fn project_handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse {
+
/// Get repo metadata.
+
/// `GET /repos/:rid`
+
async fn repo_handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse {
    let (repo, doc) = ctx.repo(rid)?;
-
    let info = ctx.project_info(&repo, doc)?;
+
    let info = ctx.repo_info(&repo, doc)?;

    Ok::<_, Error>(Json(info))
}
@@ -197,8 +194,8 @@ pub struct CommitsQueryString {
    pub per_page: Option<usize>,
}

-
/// Get project commit range.
-
/// `GET /projects/:project/commits?parent=<sha>`
+
/// Get repo commit range.
+
/// `GET /repos/:rid/commits?parent=<sha>`
async fn history_handler(
    State(ctx): State<Context>,
    Path(rid): Path<RepoId>,
@@ -220,7 +217,7 @@ async fn history_handler(

    let sha = match parent {
        Some(commit) => commit,
-
        None => ctx.project_info(&repo, doc)?.head.to_string(),
+
        None => ctx.repo_info(&repo, doc)?.head.to_string(),
    };
    let repo = Repository::open(repo.path())?;

@@ -257,13 +254,13 @@ async fn history_handler(
    }
}

-
/// Get project commit.
-
/// `GET /projects/:project/commits/:sha`
+
/// Get repo commit.
+
/// `GET /repos/:rid/commits/:sha`
async fn commit_handler(
    State(ctx): State<Context>,
-
    Path((project, sha)): Path<(RepoId, Oid)>,
+
    Path((rid, sha)): Path<(RepoId, Oid)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let repo = Repository::open(repo.path())?;
    let commit = repo.commit(sha)?;

@@ -324,12 +321,12 @@ async fn commit_handler(
}

/// Get diff between two commits
-
/// `GET /projects/:project/diff/:base/:oid`
+
/// `GET /repos/:rid/diff/:base/:oid`
async fn diff_handler(
    State(ctx): State<Context>,
-
    Path((project, base, oid)): Path<(RepoId, Oid, Oid)>,
+
    Path((rid, base, oid)): Path<(RepoId, Oid, Oid)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let repo = Repository::open(repo.path())?;
    let base = repo.commit(base)?;
    let commit = repo.commit(oid)?;
@@ -390,13 +387,13 @@ async fn diff_handler(
    Ok::<_, Error>(immutable_response(response))
}

-
/// Get project activity for the past year.
-
/// `GET /projects/:project/activity`
+
/// Get repo activity for the past year.
+
/// `GET /repos/:rid/activity`
async fn activity_handler(
    State(ctx): State<Context>,
-
    Path(project): Path<RepoId>,
+
    Path(rid): Path<RepoId>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let current_date = chrono::Utc::now().timestamp();
    // SAFETY: The number of weeks is static and not out of bounds.
    #[allow(clippy::unwrap_used)]
@@ -419,8 +416,8 @@ async fn activity_handler(
    Ok::<_, Error>(cached_response(json!({ "activity": timestamps }), 3600))
}

-
/// Get project source tree for '/' path.
-
/// `GET /projects/:project/tree/:sha/`
+
/// Get repo source tree for '/' path.
+
/// `GET /repos/:rid/tree/:sha/`
async fn tree_handler_root(
    State(ctx): State<Context>,
    Path((rid, sha)): Path<(RepoId, Oid)>,
@@ -428,17 +425,17 @@ async fn tree_handler_root(
    tree_handler(State(ctx), Path((rid, sha, String::new()))).await
}

-
/// Get project source tree.
-
/// `GET /projects/:project/tree/:sha/*path`
+
/// Get repo source tree.
+
/// `GET /repos/:rid/tree/:sha/*path`
async fn tree_handler(
    State(ctx): State<Context>,
-
    Path((project, sha, path)): Path<(RepoId, Oid, String)>,
+
    Path((rid, sha, path)): Path<(RepoId, Oid, String)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;

    if let Some(ref cache) = ctx.cache {
        let cache = &mut cache.tree.lock().await;
-
        if let Some(response) = cache.get(&(project, sha, path.clone())) {
+
        if let Some(response) = cache.get(&(rid, sha, path.clone())) {
            return Ok::<_, Error>(immutable_response(response.clone()));
        }
    }
@@ -449,32 +446,29 @@ async fn tree_handler(

    if let Some(cache) = &ctx.cache {
        let cache = &mut cache.tree.lock().await;
-
        cache.put((project, sha, path.clone()), response.clone());
+
        cache.put((rid, sha, path.clone()), response.clone());
    }

    Ok::<_, Error>(immutable_response(response))
}

-
/// Get project source tree stats.
-
/// `GET /projects/:project/stats/tree/:sha`
+
/// Get repo source tree stats.
+
/// `GET /repos/:rid/stats/tree/:sha`
async fn stats_tree_handler(
    State(ctx): State<Context>,
-
    Path((project, sha)): Path<(RepoId, Oid)>,
+
    Path((rid, sha)): Path<(RepoId, Oid)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let repo = Repository::open(repo.path())?;
    let stats = repo.stats_from(&sha)?;

    Ok::<_, Error>(immutable_response(stats))
}

-
/// Get all project remotes.
-
/// `GET /projects/:project/remotes`
-
async fn remotes_handler(
-
    State(ctx): State<Context>,
-
    Path(project): Path<RepoId>,
-
) -> impl IntoResponse {
-
    let (repo, doc) = ctx.repo(project)?;
+
/// Get all repo remotes.
+
/// `GET /repos/:rid/remotes`
+
async fn remotes_handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse {
+
    let (repo, doc) = ctx.repo(rid)?;
    let delegates = &doc.delegates;
    let aliases = &ctx.profile.aliases();
    let remotes = repo
@@ -510,13 +504,13 @@ async fn remotes_handler(
    Ok::<_, Error>(Json(remotes))
}

-
/// Get project remote.
-
/// `GET /projects/:project/remotes/:peer`
+
/// Get repo remote.
+
/// `GET /repos/:rid/remotes/:peer`
async fn remote_handler(
    State(ctx): State<Context>,
-
    Path((project, node_id)): Path<(RepoId, NodeId)>,
+
    Path((rid, node_id)): Path<(RepoId, NodeId)>,
) -> impl IntoResponse {
-
    let (repo, doc) = ctx.repo(project)?;
+
    let (repo, doc) = ctx.repo(rid)?;
    let delegates = &doc.delegates;
    let remote = repo.remote(&node_id)?;
    let refs = remote
@@ -537,13 +531,13 @@ async fn remote_handler(
    Ok::<_, Error>(Json(remote))
}

-
/// Get project source file.
-
/// `GET /projects/:project/blob/:sha/*path`
+
/// Get repo source file.
+
/// `GET /repos/:rid/blob/:sha/*path`
async fn blob_handler(
    State(ctx): State<Context>,
-
    Path((project, sha, path)): Path<(RepoId, Oid, String)>,
+
    Path((rid, sha, path)): Path<(RepoId, Oid, String)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let repo = Repository::open(repo.path())?;
    let blob = repo.blob(sha, &path)?;

@@ -560,13 +554,13 @@ async fn blob_handler(
    Ok::<_, Error>(immutable_response(api::json::blob(&blob, &path)).into_response())
}

-
/// Get project readme.
-
/// `GET /projects/:project/readme/:sha`
+
/// Get repo readme.
+
/// `GET /repos/:rid/readme/:sha`
async fn readme_handler(
    State(ctx): State<Context>,
-
    Path((project, sha)): Path<(RepoId, Oid)>,
+
    Path((rid, sha)): Path<(RepoId, Oid)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let repo = Repository::open(repo.path())?;
    let paths = [
        "README",
@@ -604,14 +598,14 @@ async fn readme_handler(
    Err(Error::NotFound)
}

-
/// Get project issues list.
-
/// `GET /projects/:project/issues`
+
/// Get repo issues list.
+
/// `GET /repos/:rid/issues`
async fn issues_handler(
    State(ctx): State<Context>,
-
    Path(project): Path<RepoId>,
+
    Path(rid): Path<RepoId>,
    Query(qs): Query<CobsQuery<api::IssueStatus>>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let CobsQuery {
        page,
        per_page,
@@ -641,13 +635,13 @@ async fn issues_handler(
    Ok::<_, Error>(Json(issues))
}

-
/// Get project issue.
-
/// `GET /projects/:project/issues/:id`
+
/// Get repo issue.
+
/// `GET /repos/:rid/issues/:id`
async fn issue_handler(
    State(ctx): State<Context>,
-
    Path((project, issue_id)): Path<(RepoId, Oid)>,
+
    Path((rid, issue_id)): Path<(RepoId, Oid)>,
) -> impl IntoResponse {
-
    let (repo, _) = ctx.repo(project)?;
+
    let (repo, _) = ctx.repo(rid)?;
    let issue = ctx
        .profile
        .issues(&repo)?
@@ -658,8 +652,8 @@ async fn issue_handler(
    Ok::<_, Error>(Json(api::json::issue(issue_id.into(), issue, &aliases)))
}

-
/// Get project patches list.
-
/// `GET /projects/:project/patches`
+
/// Get repo patches list.
+
/// `GET /repos/:rid/patches`
async fn patches_handler(
    State(ctx): State<Context>,
    Path(rid): Path<RepoId>,
@@ -694,8 +688,8 @@ async fn patches_handler(
    Ok::<_, Error>(Json(patches))
}

-
/// Get project patch.
-
/// `GET /projects/:project/patches/:id`
+
/// Get repo patch.
+
/// `GET /repos/:rid/patches/:id`
async fn patch_handler(
    State(ctx): State<Context>,
    Path((rid, patch_id)): Path<(RepoId, Oid)>,
@@ -726,21 +720,23 @@ mod routes {
    use crate::test::*;

    #[tokio::test]
-
    async fn test_projects_root() {
+
    async fn test_repos_root() {
        let tmp = tempfile::tempdir().unwrap();
        let seed = seed(tmp.path());
        let app = super::router(seed.clone())
            .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))));
-
        let response = get(&app, "/projects?show=all").await;
+
        let response = get(&app, "/repos?show=all").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response.json().await,
            json!([
              {
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for tests",
+
                  "name": "hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -762,13 +758,15 @@ mod routes {
                  "open": 1,
                  "closed": 0,
                },
-
                "id": RID,
+
                "rid": RID,
                "seeding": 1,
              },
              {
-
                "name": "again-hello-world",
-
                "description": "Rad repository for sorting",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for sorting",
+
                  "name": "again-hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -790,7 +788,7 @@ mod routes {
                  "open": 0,
                  "closed": 0,
                },
-
                "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
+
                "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
                "seeding": 1,
              },
            ])
@@ -800,16 +798,18 @@ mod routes {
            [192, 168, 13, 37],
            8080,
        ))));
-
        let response = get(&app, "/projects?show=all").await;
+
        let response = get(&app, "/repos?show=all").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response.json().await,
            json!([
              {
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for tests",
+
                  "name": "hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -831,13 +831,15 @@ mod routes {
                  "open": 1,
                  "closed": 0,
                },
-
                "id": RID,
+
                "rid": RID,
                "seeding": 1,
              },
              {
-
                "name": "again-hello-world",
-
                "description": "Rad repository for sorting",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "name": "again-hello-world",
+
                  "description": "Rad repository for sorting",
+
                  "defaultBranch": "master",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -859,7 +861,7 @@ mod routes {
                  "open": 0,
                  "closed": 0,
                },
-
                "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
+
                "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
                "seeding": 1,
              },
            ])
@@ -867,18 +869,20 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects() {
+
    async fn test_repos() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}")).await;
+
        let response = get(&app, format!("/repos/{RID}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response.json().await,
            json!({
-
               "name": "hello-world",
-
               "description": "Rad repository for tests",
-
               "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                 "defaultBranch": "master",
+
                 "description": "Rad repository for tests",
+
                 "name": "hello-world",
+
                },
               "delegates": [
                 {
                   "id": DID,
@@ -900,27 +904,29 @@ mod routes {
                 "open": 1,
                 "closed": 0,
               },
-
               "id": RID,
+
               "rid": RID,
               "seeding": 1,
            })
        );
    }

    #[tokio::test]
-
    async fn test_search_projects() {
+
    async fn test_search_repos() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, "/projects/search?q=hello").await;
+
        let response = get(&app, "/repos/search?q=hello").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response.json().await,
            json!([
              {
+
                "xyz.radicle.project": {
+
                  "name": "hello-world",
+
                  "description": "Rad repository for tests",
+
                  "defaultBranch": "master",
+
                },
                "rid": "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp",
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
                "delegates": [
                  {
                    "id": DID,
@@ -930,10 +936,12 @@ mod routes {
                "seeds": 1,
              },
              {
+
                "xyz.radicle.project": {
+
                  "name": "again-hello-world",
+
                  "description": "Rad repository for sorting",
+
                  "defaultBranch": "master",
+
                },
                "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE",
-
                "name": "again-hello-world",
-
                "description": "Rad repository for sorting",
-
                "defaultBranch": "master",
                "delegates": [
                  {
                    "id": DID,
@@ -947,10 +955,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_search_projects_pagination() {
+
    async fn test_search_repos_pagination() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, "/projects/search?q=hello&perPage=1").await;
+
        let response = get(&app, "/repos/search?q=hello&perPage=1").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -958,9 +966,11 @@ mod routes {
            json!([
              {
                "rid": "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp",
-
                "name": "hello-world",
-
                "description": "Rad repository for tests",
-
                "defaultBranch": "master",
+
                "xyz.radicle.project": {
+
                  "defaultBranch": "master",
+
                  "description": "Rad repository for tests",
+
                  "name": "hello-world",
+
                },
                "delegates": [
                  {
                    "id": DID,
@@ -974,19 +984,19 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_not_found() {
+
    async fn test_repos_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, "/projects/rad:z2u2CP3ZJzB7ZqE8jHrau19yjcfCQ").await;
+
        let response = get(&app, "/repos/rad:z2u2CP3ZJzB7ZqE8jHrau19yjcfCQ").await;

        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[tokio::test]
-
    async fn test_projects_commits_root() {
+
    async fn test_repos_commits_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/commits")).await;
+
        let response = get(&app, format!("/repos/{RID}/commits")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1046,10 +1056,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_commits() {
+
    async fn test_repos_commits() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/commits/{HEAD}")).await;
+
        let response = get(&app, format!("/repos/{RID}/commits/{HEAD}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1214,12 +1224,12 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_commits_not_found() {
+
    async fn test_repos_commits_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
        let response = get(
            &app,
-
            format!("/projects/{RID}/commits/ffffffffffffffffffffffffffffffffffffffff"),
+
            format!("/repos/{RID}/commits/ffffffffffffffffffffffffffffffffffffffff"),
        )
        .await;

@@ -1227,10 +1237,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_stats() {
+
    async fn test_repos_stats() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/stats/tree/{HEAD}")).await;
+
        let response = get(&app, format!("/repos/{RID}/stats/tree/{HEAD}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1246,10 +1256,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_tree() {
+
    async fn test_repos_tree() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/tree/{HEAD}/")).await;
+
        let response = get(&app, format!("/repos/{RID}/tree/{HEAD}/")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1292,7 +1302,7 @@ mod routes {
            )
        );

-
        let response = get(&app, format!("/projects/{RID}/tree/{HEAD}/dir1")).await;
+
        let response = get(&app, format!("/repos/{RID}/tree/{HEAD}/dir1")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1330,25 +1340,25 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_tree_not_found() {
+
    async fn test_repos_tree_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
        let response = get(
            &app,
-
            format!("/projects/{RID}/tree/ffffffffffffffffffffffffffffffffffffffff"),
+
            format!("/repos/{RID}/tree/ffffffffffffffffffffffffffffffffffffffff"),
        )
        .await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);

-
        let response = get(&app, format!("/projects/{RID}/tree/{HEAD}/unknown")).await;
+
        let response = get(&app, format!("/repos/{RID}/tree/{HEAD}/unknown")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[tokio::test]
-
    async fn test_projects_remotes_root() {
+
    async fn test_repos_remotes_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/remotes")).await;
+
        let response = get(&app, format!("/repos/{RID}/remotes")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1367,12 +1377,12 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_remotes() {
+
    async fn test_repos_remotes() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
        let response = get(
            &app,
-
            format!("/projects/{RID}/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"),
+
            format!("/repos/{RID}/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"),
        )
        .await;

@@ -1390,12 +1400,12 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_remotes_not_found() {
+
    async fn test_repos_remotes_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
        let response = get(
            &app,
-
            format!("/projects/{RID}/remotes/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"),
+
            format!("/repos/{RID}/remotes/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"),
        )
        .await;

@@ -1403,10 +1413,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_blob() {
+
    async fn test_repos_blob() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/blob/{HEAD}/README")).await;
+
        let response = get(&app, format!("/repos/{RID}/blob/{HEAD}/README")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1438,19 +1448,19 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_blob_not_found() {
+
    async fn test_repos_blob_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/blob/{HEAD}/unknown")).await;
+
        let response = get(&app, format!("/repos/{RID}/blob/{HEAD}/unknown")).await;

        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[tokio::test]
-
    async fn test_projects_readme() {
+
    async fn test_repos_readme() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/readme/{INITIAL_COMMIT}")).await;
+
        let response = get(&app, format!("/repos/{RID}/readme/{INITIAL_COMMIT}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1480,14 +1490,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_diff() {
+
    async fn test_repos_diff() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(
-
            &app,
-
            format!("/projects/{RID}/diff/{INITIAL_COMMIT}/{HEAD}"),
-
        )
-
        .await;
+
        let response = get(&app, format!("/repos/{RID}/diff/{INITIAL_COMMIT}/{HEAD}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1586,10 +1592,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_issues_root() {
+
    async fn test_repos_issues_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/issues")).await;
+
        let response = get(&app, format!("/repos/{RID}/issues")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1639,10 +1645,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_issue() {
+
    async fn test_repos_issue() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/issues/{ISSUE_ID}")).await;
+
        let response = get(&app, format!("/repos/{RID}/issues/{ISSUE_ID}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1690,10 +1696,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_patches_root() {
+
    async fn test_repos_patches_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/patches")).await;
+
        let response = get(&app, format!("/repos/{RID}/patches")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1750,10 +1756,10 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_patch() {
+
    async fn test_repos_patch() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(seed(tmp.path()));
-
        let response = get(&app, format!("/projects/{RID}/patches/{PATCH_ID}")).await;
+
        let response = get(&app, format!("/repos/{RID}/patches/{PATCH_ID}")).await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1807,7 +1813,7 @@ mod routes {
    }

    #[tokio::test]
-
    async fn test_projects_private() {
+
    async fn test_repos_private() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = seed(tmp.path());
        let app = super::router(ctx.to_owned());
@@ -1818,19 +1824,19 @@ mod routes {
            .repository(RID_PRIVATE.parse().unwrap())
            .unwrap();

-
        let response = get(&app, format!("/projects/{RID_PRIVATE}")).await;
+
        let response = get(&app, format!("/repos/{RID_PRIVATE}")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);

-
        let response = get(&app, format!("/projects/{RID_PRIVATE}/patches")).await;
+
        let response = get(&app, format!("/repos/{RID_PRIVATE}/patches")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);

-
        let response = get(&app, format!("/projects/{RID_PRIVATE}/issues")).await;
+
        let response = get(&app, format!("/repos/{RID_PRIVATE}/issues")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);

-
        let response = get(&app, format!("/projects/{RID_PRIVATE}/commits")).await;
+
        let response = get(&app, format!("/repos/{RID_PRIVATE}/commits")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);

-
        let response = get(&app, format!("/projects/{RID_PRIVATE}/remotes")).await;
+
        let response = get(&app, format!("/repos/{RID_PRIVATE}/remotes")).await;
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }
}
modified radicle-httpd/src/git.rs
@@ -24,13 +24,13 @@ use crate::error::GitError as Error;

pub fn router(profile: Arc<Profile>, aliases: HashMap<String, RepoId>) -> Router {
    Router::new()
-
        .route("/:project/*request", any(git_handler))
+
        .route("/:rid/*request", any(git_handler))
        .with_state((profile, aliases))
}

async fn git_handler(
    State((profile, aliases)): State<(Arc<Profile>, HashMap<String, RepoId>)>,
-
    AxumPath((project, request)): AxumPath<(String, String)>,
+
    AxumPath((repository, request)): AxumPath<(String, String)>,
    method: Method,
    headers: HeaderMap,
    ConnectInfo(remote): ConnectInfo<SocketAddr>,
@@ -38,7 +38,7 @@ async fn git_handler(
    body: Bytes,
) -> impl IntoResponse {
    let query = query.0.unwrap_or_default();
-
    let name = project.strip_suffix(".git").unwrap_or(&project);
+
    let name = repository.strip_suffix(".git").unwrap_or(&repository);
    let rid: RepoId = match name.parse() {
        Ok(rid) => rid,
        Err(_) => {
modified radicle-httpd/src/lib.rs
@@ -150,7 +150,7 @@ async fn root_index_handler() -> impl IntoResponse {
                "type": "GET"
            },
            {
-
                "href": "/:project/*request",
+
                "href": "/:rid/*request",
                "rel": "git",
                "type": "GET"
            }