diff --git a/radicle-httpd/src/api/json.rs b/radicle-httpd/src/api/json.rs index d28d8450..91482622 100644 --- a/radicle-httpd/src/api/json.rs +++ b/radicle-httpd/src/api/json.rs @@ -8,8 +8,9 @@ use serde_json::{json, Value}; use radicle::cob::issue::{Issue, IssueId}; use radicle::cob::patch::{Patch, PatchId}; -use radicle::cob::thread::{self, CommentId}; -use radicle::cob::{ActorId, Author, Timestamp}; +use radicle::cob::thread; +use radicle::cob::thread::{CommentId, Thread}; +use radicle::cob::{ActorId, Author, Reaction, Timestamp}; use radicle::git::RefString; use radicle::storage::{git, refs, ReadRepository}; use radicle_surf::blob::Blob; @@ -98,7 +99,10 @@ pub(crate) fn issue(id: IssueId, issue: Issue) -> Value { "title": issue.title(), "state": issue.state(), "assignees": issue.assigned().collect::>(), - "discussion": issue.comments().collect::(), + "discussion": issue + .comments() + .map(|(id, comment)| Comment::new(id, comment, issue.thread())) + .collect::>(), "tags": issue.tags().collect::>(), }) } @@ -121,7 +125,10 @@ pub(crate) fn patch(id: PatchId, patch: Patch, repo: &git::Repository) -> Value "oid": rev.oid, "refs": get_refs(repo, patch.author().id(), &rev.oid).unwrap_or(vec![]), "merges": rev.merges().collect::>(), - "discussions": rev.discussion.comments().collect::(), + "discussions": rev.discussion + .comments() + .map(|(id, comment)| Comment::new(id, comment, &rev.discussion)) + .collect::>(), "timestamp": rev.timestamp, "reviews": rev.reviews().collect::>(), }) @@ -160,33 +167,24 @@ fn get_refs( #[derive(Serialize)] #[serde(rename_all = "camelCase")] -struct Comment { +struct Comment<'a> { id: CommentId, author: Author, - body: String, - reactions: [String; 0], + body: &'a str, + reactions: Vec<(&'a ActorId, &'a Reaction)>, timestamp: Timestamp, reply_to: Option, } -#[derive(Serialize)] -struct Comments(Vec); - -impl<'a> FromIterator<(&'a CommentId, &'a thread::Comment)> for Comments { - fn from_iter>(iter: I) -> Self { - let mut comments = Vec::new(); - - for (id, comment) in iter { - comments.push(Comment { - id: id.to_owned(), - author: comment.author().into(), - body: comment.body().to_owned(), - reactions: [], - timestamp: comment.timestamp(), - reply_to: comment.reply_to(), - }); +impl<'a> Comment<'a> { + fn new(id: &'a CommentId, comment: &'a thread::Comment, thread: &'a Thread) -> Self { + Self { + id: *id, + author: Author::new(comment.author()), + body: comment.body(), + reactions: thread.reactions(id).collect::>(), + timestamp: comment.timestamp(), + reply_to: comment.reply_to(), } - - Comments(comments) } } diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs index 798bbf72..5c9f0d90 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/projects.rs @@ -1523,6 +1523,24 @@ mod routes { assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.json().await, json!({ "success": true })); + let body = serde_json::to_vec(&json!({ + "type": "thread", + "action": { + "type": "react", + "to": "9685b141c2e939c3d60f8ca34f8c7bf01a609af1", + "reaction": "🚀", + "active": true, + } + })) + .unwrap(); + patch( + &app, + format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"), + Some(Body::from(body)), + Some(SESSION_ID.to_string()), + ) + .await; + let response = get( &app, format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"), @@ -1536,11 +1554,11 @@ mod routes { "author": { "id": CONTRIBUTOR_DID, }, - "assignees": [], "title": "Issue #1", "state": { "status": "open", }, + "assignees": [], "discussion": [ { "id": ISSUE_DISCUSSION_ID, @@ -1558,7 +1576,12 @@ mod routes { "id": CONTRIBUTOR_DID, }, "body": "This is first-level comment", - "reactions": [], + "reactions": [ + [ + "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8", + "🚀", + ], + ], "timestamp": TIMESTAMP, "replyTo": null, }, diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index 84afa93b..67c4cb6f 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -80,10 +80,15 @@ impl State { /// Issue state. Accumulates [`Action`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Issue { + /// Actors assigned to this issue. assignees: LWWSet, + /// Title of the issue. title: LWWReg, clock::Lamport>, + /// Current state of the issue. state: LWWReg, clock::Lamport>, + /// Associated tags. tags: LWWSet, + /// Discussion around this issue. thread: Thread, } @@ -195,6 +200,10 @@ impl Issue { self.thread.comments().next().map(|(_, c)| c.body()) } + pub fn thread(&self) -> &Thread { + &self.thread + } + pub fn comments(&self) -> impl Iterator { self.thread.comments() }