httpd: Add `alias` field to certain routes

* `/patches`, `/issues`, `/remotes`

Signed-off-by: xphoniex <dj.2dixx@gmail.com>
This commit is contained in:
xphoniex 2023-05-10 16:32:21 +00:00 committed by Alexis Sellier
parent d8711a8d43
commit ae5982cd6b
No known key found for this signature in database
5 changed files with 79 additions and 18 deletions

View File

@ -69,6 +69,10 @@ pub enum Error {
/// Identity doc error.
#[error(transparent)]
IdentityDoc(#[from] radicle::identity::doc::DocError),
/// Tracking store error.
#[error(transparent)]
TrackingStore(#[from] radicle::node::tracking::store::Error),
}
impl IntoResponse for Error {

View File

@ -12,6 +12,7 @@ use radicle::cob::thread;
use radicle::cob::thread::{CommentId, Thread};
use radicle::cob::{ActorId, Author, Reaction, Timestamp};
use radicle::git::RefString;
use radicle::node::tracking::store as TrackingStore;
use radicle::storage::{git, refs, ReadRepository};
use radicle_surf::blob::Blob;
use radicle_surf::tree::Tree;
@ -92,26 +93,31 @@ pub(crate) fn tree(tree: &Tree, path: &str, stats: &Stats) -> Value {
}
/// Returns JSON for an `issue`.
pub(crate) fn issue(id: IssueId, issue: Issue) -> Value {
pub(crate) fn issue(id: IssueId, issue: Issue, aliases: &TrackingStore::Config) -> Value {
json!({
"id": id.to_string(),
"author": issue.author(),
"author": author(&issue.author(), aliases.alias(issue.author().id())),
"title": issue.title(),
"state": issue.state(),
"assignees": issue.assigned().collect::<Vec<_>>(),
"discussion": issue
.comments()
.map(|(id, comment)| Comment::new(id, comment, issue.thread()))
.map(|(id, comment)| Comment::new(id, comment, issue.thread(), aliases))
.collect::<Vec<_>>(),
"tags": issue.tags().collect::<Vec<_>>(),
})
}
/// Returns JSON for a `patch`.
pub(crate) fn patch(id: PatchId, patch: Patch, repo: &git::Repository) -> Value {
pub(crate) fn patch(
id: PatchId,
patch: Patch,
repo: &git::Repository,
aliases: &TrackingStore::Config,
) -> Value {
json!({
"id": id.to_string(),
"author": patch.author(),
"author": author(patch.author(), aliases.alias(patch.author().id())),
"title": patch.title(),
"description": patch.description(),
"state": patch.state(),
@ -126,7 +132,7 @@ pub(crate) fn patch(id: PatchId, patch: Patch, repo: &git::Repository) -> Value
"refs": get_refs(repo, patch.author().id(), &rev.head()).unwrap_or(vec![]),
"merges": rev.merges().collect::<Vec<_>>(),
"discussions": rev.discussion().comments()
.map(|(id, comment)| Comment::new(id, comment, rev.discussion()))
.map(|(id, comment)| Comment::new(id, comment, rev.discussion(), aliases))
.collect::<Vec<_>>(),
"timestamp": rev.timestamp(),
"reviews": rev.reviews().collect::<Vec<_>>(),
@ -135,6 +141,17 @@ pub(crate) fn patch(id: PatchId, patch: Patch, repo: &git::Repository) -> Value
})
}
/// Returns JSON for an `author` and fills in `alias` when present.
fn author(author: &Author, alias: Option<String>) -> Value {
match alias {
Some(alias) => json!({
"id": author.id,
"alias": alias,
}),
None => json!(author),
}
}
/// Returns the name part of a path string.
fn name_in_path(path: &str) -> &str {
match path.rsplit('/').next() {
@ -168,7 +185,7 @@ fn get_refs(
#[serde(rename_all = "camelCase")]
struct Comment<'a> {
id: CommentId,
author: Author,
author: Value,
body: &'a str,
reactions: Vec<(&'a ActorId, &'a Reaction)>,
timestamp: Timestamp,
@ -176,10 +193,16 @@ struct Comment<'a> {
}
impl<'a> Comment<'a> {
fn new(id: &'a CommentId, comment: &'a thread::Comment, thread: &'a Thread) -> Self {
fn new(
id: &'a CommentId,
comment: &'a thread::Comment,
thread: &'a Thread,
aliases: &TrackingStore::Config,
) -> Self {
let comment_author = Author::new(comment.author());
Self {
id: *id,
author: Author::new(comment.author()),
author: author(&comment_author, aliases.alias(comment_author.id())),
body: comment.body(),
reactions: thread.reactions(id).collect::<Vec<_>>(),
timestamp: comment.timestamp(),

View File

@ -319,6 +319,7 @@ async fn remotes_handler(State(ctx): State<Context>, Path(project): Path<Id>) ->
let storage = &ctx.profile.storage;
let repo = storage.repository(project)?;
let delegates = repo.delegates()?;
let tracking_store = &ctx.profile.tracking()?;
let remotes = repo
.remotes()?
.filter_map(|r| r.map(|r| r.1).ok())
@ -333,11 +334,19 @@ async fn remotes_handler(State(ctx): State<Context>, Path(project): Path<Id>) ->
})
.collect::<BTreeMap<String, &Oid>>();
json!({
"id": remote.id,
"heads": refs,
"delegate": delegates.contains(&remote.id.into()),
})
match tracking_store.alias(&remote.id) {
Some(alias) => json!({
"id": remote.id,
"alias": alias,
"heads": refs,
"delegate": delegates.contains(&remote.id.into()),
}),
None => json!({
"id": remote.id,
"heads": refs,
"delegate": delegates.contains(&remote.id.into()),
}),
}
})
.collect::<Vec<_>>();
@ -440,9 +449,10 @@ async fn issues_handler(
.collect::<Vec<_>>();
issues.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp()));
let tracking_store = &ctx.profile.tracking()?;
let issues = issues
.into_iter()
.map(|(id, issue, _)| api::json::issue(id, issue))
.map(|(id, issue, _)| api::json::issue(id, issue, tracking_store))
.skip(page * per_page)
.take(per_page)
.collect::<Vec<_>>();
@ -559,8 +569,13 @@ async fn issue_handler(
let issue = issue::Issues::open(&repo)?
.get(&issue_id.into())?
.ok_or(Error::NotFound)?;
let tracking_store = &ctx.profile.tracking()?;
Ok::<_, Error>(Json(api::json::issue(issue_id.into(), issue)))
Ok::<_, Error>(Json(api::json::issue(
issue_id.into(),
issue,
tracking_store,
)))
}
#[derive(Deserialize, Serialize)]
@ -722,9 +737,10 @@ async fn patches_handler(
})
.collect::<Vec<_>>();
patches.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp()));
let tracking_store = &ctx.profile.tracking()?;
let patches = patches
.into_iter()
.map(|(id, patch, _)| api::json::patch(id, patch, &repo))
.map(|(id, patch, _)| api::json::patch(id, patch, &repo, tracking_store))
.skip(page * per_page)
.take(per_page)
.collect::<Vec<_>>();
@ -743,8 +759,14 @@ async fn patch_handler(
let patch = patch::Patches::open(&repo)?
.get(&patch_id.into())?
.ok_or(Error::NotFound)?;
let tracking_store = &ctx.profile.tracking()?;
Ok::<_, Error>(Json(api::json::patch(patch_id.into(), patch, &repo)))
Ok::<_, Error>(Json(api::json::patch(
patch_id.into(),
patch,
&repo,
tracking_store,
)))
}
#[cfg(test)]

View File

@ -16,6 +16,7 @@ use radicle::crypto::ssh::keystore::MemorySigner;
use radicle::crypto::ssh::Keystore;
use radicle::crypto::{KeyPair, Seed, Signer};
use radicle::git::{raw as git2, RefString};
use radicle::node::tracking::store as TrackingStore;
use radicle::profile::Home;
use radicle::storage::ReadStorage;
use radicle::Storage;
@ -83,6 +84,9 @@ pub fn contributor(dir: &Path) -> Context {
}
fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G) -> Context {
let tracking_db = dir.join("radicle").join("node").join("tracking.db");
TrackingStore::Config::open(tracking_db).unwrap();
let workdir = dir.join("hello-world");
env::set_var("RAD_COMMIT_TIME", TIMESTAMP.to_string());

View File

@ -254,6 +254,14 @@ impl Config {
}
Ok(Box::new(entries.into_iter()))
}
/// Retrieve `alias` of given node.
/// Calls `Self::node_policy` under the hood.
pub fn alias(&self, nid: &NodeId) -> Option<String> {
self.node_policy(nid)
.map(|node| node.and_then(|n| n.alias))
.unwrap_or(None)
}
}
#[cfg(test)]