Migrate `/projects`: `remotes`, `remote`, `blob`, `readme` handlers

Signed-off-by: xphoniex <dj.2dixx@gmail.com>
This commit is contained in:
xphoniex 2022-11-22 15:17:56 +00:00
parent 84c3cf4cf1
commit b4902d3408
No known key found for this signature in database
GPG Key ID: 9FACFECE1E3B3994
5 changed files with 92 additions and 11 deletions

View File

@ -19,7 +19,7 @@ mod cyphernet;
pub use self::cyphernet::Ed25519; pub use self::cyphernet::Ed25519;
/// Verified (used as type witness). /// Verified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
pub struct Verified; pub struct Verified;
/// Unverified (used as type witness). /// Unverified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]

View File

@ -67,6 +67,10 @@ pub enum Error {
/// Git2 error. /// Git2 error.
#[error(transparent)] #[error(transparent)]
Git2(#[from] radicle::git::raw::Error), Git2(#[from] radicle::git::raw::Error),
/// Storage refs error.
#[error(transparent)]
StorageRef(#[from] radicle::storage::refs::Error),
} }
impl Error { impl Error {

View File

@ -13,8 +13,10 @@ use tower_http::set_header::SetResponseHeaderLayer;
use radicle::cob::issue::Issues; use radicle::cob::issue::Issues;
use radicle::git::raw::BranchType; use radicle::git::raw::BranchType;
use radicle::identity::{Doc, Id}; use radicle::identity::{Doc, Id};
use radicle::node::NodeId;
use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage}; use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage};
use radicle_surf::git::History; use radicle_surf::git::History;
use radicle_surf::Revision::Sha;
use crate::api::axum_extra::{Path, Query}; use crate::api::axum_extra::{Path, Query};
use crate::api::error::Error; use crate::api::error::Error;
@ -39,6 +41,10 @@ pub fn router(ctx: Context) -> Router {
), ),
) )
.route("/projects/:project/tree/:sha/*path", get(tree_handler)) .route("/projects/:project/tree/:sha/*path", get(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))
.layer(Extension(ctx)) .layer(Extension(ctx))
} }
@ -183,7 +189,7 @@ async fn commit_handler(
let repo = storage.repository(project)?; let repo = storage.repository(project)?;
let commit = radicle_surf::commit(&repo.raw().into(), sha)?; let commit = radicle_surf::commit(&repo.raw().into(), sha)?;
Ok::<_, Error>(Json(json!(commit))) Ok::<_, Error>(Json(commit))
} }
/// Get project activity for the past year. /// Get project activity for the past year.
@ -221,11 +227,7 @@ async fn tree_handler(
let path = path.strip_prefix('/').ok_or(Error::NotFound)?.to_string(); let path = path.strip_prefix('/').ok_or(Error::NotFound)?.to_string();
let storage = &ctx.profile.storage; let storage = &ctx.profile.storage;
let repo = storage.repository(project)?; let repo = storage.repository(project)?;
let tree = radicle_surf::object::tree( let tree = radicle_surf::object::tree(&repo.raw().into(), Some(Sha { sha }), Some(path))?;
&repo.raw().into(),
Some(radicle_surf::Revision::Sha { sha }),
Some(path),
)?;
let response = json!({ let response = json!({
"path": &tree.path, "path": &tree.path,
"entries": &tree.entries, "entries": &tree.entries,
@ -236,6 +238,77 @@ async fn tree_handler(
Ok::<_, Error>(Json(response)) Ok::<_, Error>(Json(response))
} }
/// Get all project remotes.
/// `GET /projects/:project/remotes`
async fn remotes_handler(
Extension(ctx): Extension<Context>,
Path(project): Path<Id>,
) -> impl IntoResponse {
let storage = &ctx.profile.storage;
let repo = storage.repository(project)?;
let remotes = repo
.remotes()?
.filter_map(|r| r.map(|r| r.1).ok())
.collect::<Vec<_>>();
Ok::<_, Error>(Json(remotes))
}
/// Get project remote.
/// `GET /projects/:project/remotes/:peer`
async fn remote_handler(
Extension(ctx): Extension<Context>,
Path((project, node_id)): Path<(Id, NodeId)>,
) -> impl IntoResponse {
let storage = &ctx.profile.storage;
let repo = storage.repository(project)?;
let remote = repo.remote(&node_id)?;
Ok::<_, Error>(Json(remote))
}
/// Get project source file.
/// `GET /projects/:project/blob/:sha/*path`
async fn blob_handler(
Extension(ctx): Extension<Context>,
Path((project, sha, path)): Path<(Id, Oid, String)>,
) -> impl IntoResponse {
let path = path.strip_prefix('/').ok_or(Error::NotFound)?;
let storage = &ctx.profile.storage;
let repo = storage.repository(project)?;
let blob = radicle_surf::blob::blob(&repo.raw().into(), Some(Sha { sha }), path)?;
Ok::<_, Error>(Json(blob))
}
/// Get project readme.
/// `GET /projects/:project/readme/:sha`
async fn readme_handler(
Extension(ctx): Extension<Context>,
Path((project, sha)): Path<(Id, Oid)>,
) -> impl IntoResponse {
let storage = &ctx.profile.storage;
let repo = storage.repository(project)?;
let paths = &[
"README",
"README.md",
"README.markdown",
"README.txt",
"README.rst",
"Readme.md",
];
for path in paths {
if let Ok(blob) = radicle_surf::blob::blob(&repo.raw().into(), Some(Sha { sha }), path) {
return Ok::<_, Error>(Json(blob));
}
}
Err(radicle_surf::object::Error::PathNotFound(
radicle_surf::file_system::Path::try_from("README").unwrap(),
))?
}
#[derive(Serialize)] #[derive(Serialize)]
struct Stats { struct Stats {
branches: usize, branches: usize,

View File

@ -6,6 +6,7 @@ use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::{fmt, io}; use std::{fmt, io};
use serde::Serialize;
use thiserror::Error; use thiserror::Error;
use crypto::{PublicKey, Signer, Unverified, Verified}; use crypto::{PublicKey, Signer, Unverified, Verified};
@ -184,11 +185,12 @@ impl<V> From<Remotes<V>> for HashMap<RemoteId, Refs> {
} }
/// A project remote. /// A project remote.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Remote<V = Verified> { pub struct Remote<V = Verified> {
/// ID of remote. /// ID of remote.
pub id: PublicKey, pub id: PublicKey,
/// Git references published under this remote, and their hashes. /// Git references published under this remote, and their hashes.
#[serde(flatten)]
pub refs: SignedRefs<V>, pub refs: SignedRefs<V>,
/// Whether this remote is a delegate for the project. /// Whether this remote is a delegate for the project.
pub delegate: bool, pub delegate: bool,

View File

@ -8,6 +8,7 @@ use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use crypto::{PublicKey, Signature, Signer, SignerError, Unverified, Verified}; use crypto::{PublicKey, Signature, Signer, SignerError, Unverified, Verified};
use serde::Serialize;
use thiserror::Error; use thiserror::Error;
use crate::git; use crate::git;
@ -62,7 +63,7 @@ impl Error {
} }
/// The published state of a local repository. /// The published state of a local repository.
#[derive(Default, Clone, Debug, PartialEq, Eq)] #[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Refs(BTreeMap<git::RefString, Oid>); pub struct Refs(BTreeMap<git::RefString, Oid>);
impl Refs { impl Refs {
@ -197,11 +198,12 @@ impl DerefMut for Refs {
/// ///
/// The type parameter keeps track of whether the signature was [`Verified`] or /// The type parameter keeps track of whether the signature was [`Verified`] or
/// [`Unverified`]. /// [`Unverified`].
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct SignedRefs<V> { pub struct SignedRefs<V> {
pub refs: Refs, pub refs: Refs,
#[serde(skip)]
pub signature: Signature, pub signature: Signature,
#[serde(skip)]
_verified: PhantomData<V>, _verified: PhantomData<V>,
} }