diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index 8c50843962..8dd2878af3 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -11,7 +11,7 @@ use radicle::patch::cache::Patches as _; use radicle::storage::git::Repository; use serde::{Deserialize, Serialize}; use serde_json::json; -use tower_http::cors::{self, CorsLayer}; +use tower_http::cors::{Any, CorsLayer}; use radicle::cob::{issue, patch, Author}; use radicle::identity::{DocAt, RepoId}; @@ -47,16 +47,15 @@ impl Context { } } - pub fn project_info( + pub fn repo_info( &self, repo: &R, doc: DocAt, - ) -> Result { + ) -> Result { 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, }) } @@ -105,7 +104,7 @@ pub fn router(ctx: Context) -> Router { .layer( CorsLayer::new() .max_age(Duration::from_secs(86400)) - .allow_origin(cors::Any) + .allow_origin(Any) .allow_methods([Method::GET]) .allow_headers([CONTENT_TYPE]), ) @@ -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, pub per_page: Option, } #[derive(Serialize, Deserialize, Clone, Default)] #[serde(rename_all = "camelCase")] -pub enum ProjectQuery { +pub enum RepoQuery { All, #[default] Pinned, @@ -209,13 +208,15 @@ impl PatchStatus { mod search { use std::cmp::Ordering; + use std::collections::BTreeMap; use nonempty::NonEmpty; + use radicle::identity::doc::{Payload, PayloadId}; use serde::{Deserialize, Serialize}; use serde_json::json; use radicle::crypto::Verified; - use radicle::identity::{Project, RepoId}; + use radicle::identity::RepoId; use radicle::node::routing::Store; use radicle::node::AliasStore; use radicle::node::Database; @@ -234,7 +235,7 @@ mod search { pub struct SearchResult { pub rid: RepoId, #[serde(flatten)] - pub payload: Project, + pub payload: BTreeMap, pub delegates: NonEmpty, pub seeds: usize, #[serde(skip)] @@ -251,8 +252,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 +268,7 @@ mod search { Some(SearchResult { rid: info.rid, - payload, + payload: info.doc.payload, delegates, seeds, index, @@ -299,29 +301,31 @@ 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. + /// Repos metadata. #[serde(flatten)] - pub payload: Project, + pub payload: BTreeMap, pub delegates: Vec, 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, } } diff --git a/radicle-httpd/src/api/v1.rs b/radicle-httpd/src/api/v1.rs index 66156e3945..7b4e5f9a11 100644 --- a/radicle-httpd/src/api/v1.rs +++ b/radicle-httpd/src/api/v1.rs @@ -1,6 +1,6 @@ mod delegates; mod node; -mod projects; +mod repos; mod stats; use axum::extract::State; @@ -20,7 +20,7 @@ pub fn router(ctx: Context) -> Router { .merge(root_router) .merge(node::router(ctx.clone())) .merge(delegates::router(ctx.clone())) - .merge(projects::router(ctx.clone())) + .merge(repos::router(ctx.clone())) .merge(stats::router(ctx)); Router::new().nest("/v1", routes) @@ -36,8 +36,8 @@ async fn root_handler(State(ctx): State) -> impl IntoResponse { "path": "/api/v1", "links": [ { - "href": "/projects", - "rel": "projects", + "href": "/repos", + "rel": "repos", "type": "GET" }, { @@ -51,8 +51,8 @@ async fn root_handler(State(ctx): State) -> impl IntoResponse { "type": "GET" }, { - "href": "/delegates/:did/projects", - "rel": "projects", + "href": "/delegates/:did/repos", + "rel": "repos", "type": "GET" }, { diff --git a/radicle-httpd/src/api/v1/delegates.rs b/radicle-httpd/src/api/v1/delegates.rs index 9d9aa95235..48e7fae583 100644 --- a/radicle-httpd/src/api/v1/delegates.rs +++ b/radicle-httpd/src/api/v1/delegates.rs @@ -12,49 +12,36 @@ 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}; 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, Path(delegate): Path, Query(qs): Query, ) -> impl IntoResponse { - let PaginationQuery { - show, - page, - per_page, - } = qs; + let PaginationQuery { page, per_page, .. } = qs; let page = page.unwrap_or(0); let per_page = per_page.unwrap_or(10); 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 - .repositories()? - .into_iter() - .filter(|repo| repo.doc.visibility.is_public()) - .collect::>(), - ProjectQuery::Pinned => storage.repositories_by_id(pinned.repositories.iter())?, - }; - projects.sort_by_key(|p| p.rid); + let mut repos = storage + .repositories()? + .into_iter() + .filter(|repo| repo.doc.visibility.is_public()) + .collect::>(); + 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,9 +53,6 @@ async fn delegates_projects_handler( let Ok((_, head)) = repo.head() else { return None; }; - let Ok(payload) = id.doc.project() else { - return None; - }; let Ok(issues) = ctx.profile.issues(&repo) else { return None; }; @@ -92,14 +76,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, }) }) @@ -128,7 +112,7 @@ mod routes { .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 +126,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 +152,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 +182,7 @@ mod routes { "open": 0, "closed": 0, }, - "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", + "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", "seeding": 1, } ]) @@ -206,7 +194,7 @@ mod routes { )))); let response = get( &app, - "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all", + "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/repos?show=all", ) .await; @@ -220,9 +208,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 +234,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 +264,7 @@ mod routes { "open": 0, "closed": 0, }, - "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", + "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", "seeding": 1, } ]) diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/repos.rs similarity index 86% rename from radicle-httpd/src/api/v1/projects.rs rename to radicle-httpd/src/api/v1/repos.rs index f9992bba34..d748b452ec 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/repos.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, Query(qs): Query, ) -> 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::>(), - ProjectQuery::Pinned => storage + RepoQuery::Pinned => storage .repositories_by_id(pinned.repositories.iter())? .into_iter() .filter(|repo| repo.doc.visibility.is_public()) .collect::>(), }; - 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,9 +96,6 @@ async fn project_root_handler( let Ok((_, head)) = repo.head() else { return None; }; - let Ok(payload) = info.doc.project() else { - return None; - }; let Ok(issues) = ctx.profile.issues(&repo) else { return None; }; @@ -123,15 +117,15 @@ async fn project_root_handler( .collect::>(); 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 +137,7 @@ async fn project_root_handler( } /// Search repositories by name. -/// `GET /projects/search?q=` +/// `GET /repos/search?q=` /// /// 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 +147,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, Query(SearchQueryString { q, per_page, page }): Query, ) -> impl IntoResponse { @@ -178,11 +172,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, Path(rid): Path) -> impl IntoResponse { +/// Get repo metadata. +/// `GET /repos/:rid` +async fn repo_handler(State(ctx): State, Path(rid): Path) -> 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 +191,8 @@ pub struct CommitsQueryString { pub per_page: Option, } -/// Get project commit range. -/// `GET /projects/:project/commits?parent=` +/// Get repo commit range. +/// `GET /repos/:rid/commits?parent=` async fn history_handler( State(ctx): State, Path(rid): Path, @@ -220,7 +214,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 +251,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, - 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 +318,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, - 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 +384,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, - Path(project): Path, + Path(rid): Path, ) -> 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 +413,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, Path((rid, sha)): Path<(RepoId, Oid)>, @@ -428,17 +422,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, - 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 +443,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, - 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, - Path(project): Path, -) -> impl IntoResponse { - let (repo, doc) = ctx.repo(project)?; +/// Get all repo remotes. +/// `GET /repos/:rid/remotes` +async fn remotes_handler(State(ctx): State, Path(rid): Path) -> impl IntoResponse { + let (repo, doc) = ctx.repo(rid)?; let delegates = &doc.delegates; let aliases = &ctx.profile.aliases(); let remotes = repo @@ -510,13 +501,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, - 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 +528,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, - 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 +551,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, - 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 +595,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, - Path(project): Path, + Path(rid): Path, Query(qs): Query>, ) -> impl IntoResponse { - let (repo, _) = ctx.repo(project)?; + let (repo, _) = ctx.repo(rid)?; let CobsQuery { page, per_page, @@ -641,13 +632,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, - 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 +649,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, Path(rid): Path, @@ -694,8 +685,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, Path((rid, patch_id)): Path<(RepoId, Oid)>, @@ -726,21 +717,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 +755,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 +785,7 @@ mod routes { "open": 0, "closed": 0, }, - "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", + "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", "seeding": 1, }, ]) @@ -800,16 +795,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 +828,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 +858,7 @@ mod routes { "open": 0, "closed": 0, }, - "id": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", + "rid": "rad:z4GypKmh1gkEfmkXtarcYnkvtFUfE", "seeding": 1, }, ]) @@ -870,15 +869,17 @@ mod routes { async fn test_projects() { 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,7 +901,7 @@ mod routes { "open": 1, "closed": 0, }, - "id": RID, + "rid": RID, "seeding": 1, }) ); @@ -910,17 +911,19 @@ mod routes { async fn test_search_projects() { 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 +933,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, @@ -950,7 +955,7 @@ mod routes { async fn test_search_projects_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 +963,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, @@ -977,7 +984,7 @@ mod routes { async fn test_projects_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); } @@ -986,7 +993,7 @@ mod routes { async fn test_projects_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!( @@ -1049,7 +1056,7 @@ mod routes { async fn test_projects_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!( @@ -1219,7 +1226,7 @@ mod routes { let app = super::router(seed(tmp.path())); let response = get( &app, - format!("/projects/{RID}/commits/ffffffffffffffffffffffffffffffffffffffff"), + format!("/repos/{RID}/commits/ffffffffffffffffffffffffffffffffffffffff"), ) .await; @@ -1230,7 +1237,7 @@ mod routes { async fn test_projects_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!( @@ -1249,7 +1256,7 @@ mod routes { async fn test_projects_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 +1299,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!( @@ -1335,12 +1342,12 @@ mod routes { 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); } @@ -1348,7 +1355,7 @@ mod routes { async fn test_projects_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!( @@ -1372,7 +1379,7 @@ mod routes { let app = super::router(seed(tmp.path())); let response = get( &app, - format!("/projects/{RID}/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"), + format!("/repos/{RID}/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"), ) .await; @@ -1395,7 +1402,7 @@ mod routes { let app = super::router(seed(tmp.path())); let response = get( &app, - format!("/projects/{RID}/remotes/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"), + format!("/repos/{RID}/remotes/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT"), ) .await; @@ -1406,7 +1413,7 @@ mod routes { async fn test_projects_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!( @@ -1441,7 +1448,7 @@ mod routes { async fn test_projects_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); } @@ -1450,7 +1457,7 @@ mod routes { async fn test_projects_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!( @@ -1483,11 +1490,7 @@ mod routes { async fn test_projects_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!( @@ -1589,7 +1592,7 @@ mod routes { async fn test_projects_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!( @@ -1642,7 +1645,7 @@ mod routes { async fn test_projects_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!( @@ -1693,7 +1696,7 @@ mod routes { async fn test_projects_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!( @@ -1753,7 +1756,7 @@ mod routes { async fn test_projects_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!( @@ -1818,19 +1821,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); } } diff --git a/radicle-httpd/src/git.rs b/radicle-httpd/src/git.rs index c2f0de8f36..1f0960ed93 100644 --- a/radicle-httpd/src/git.rs +++ b/radicle-httpd/src/git.rs @@ -24,13 +24,13 @@ use crate::error::GitError as Error; pub fn router(profile: Arc, aliases: HashMap) -> 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, HashMap)>, - AxumPath((project, request)): AxumPath<(String, String)>, + AxumPath((repository, request)): AxumPath<(String, String)>, method: Method, headers: HeaderMap, ConnectInfo(remote): ConnectInfo, @@ -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(_) => { diff --git a/radicle-httpd/src/lib.rs b/radicle-httpd/src/lib.rs index 8db83d54fe..ac055244bc 100644 --- a/radicle-httpd/src/lib.rs +++ b/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" }