Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
httpd: Add reactions to revisions, improve json for comment reactions
Sebastian Martinez committed 2 years ago
commit 5333f8217aa535b89b834d342fdb54a841fe27eb
parent 3560e0066d5c40ee9e36b5b1aa350815a0826830
5 files changed +164 -20
modified radicle-httpd/src/api/json.rs
@@ -1,10 +1,11 @@
//! Utilities for building JSON responses of our API.

+
use std::collections::BTreeMap;
use std::path::Path;
use std::str;

use base64::prelude::{Engine, BASE64_STANDARD};
-
use radicle::cob::CodeLocation;
+
use radicle::cob::{CodeLocation, Reaction};
use serde_json::{json, Value};

use radicle::cob::issue::{Issue, IssueId};
@@ -130,6 +131,15 @@ pub(crate) fn patch(
                "author": author(rev.author(), aliases.alias(rev.author().id())),
                "description": rev.description(),
                "edits": rev.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
+
                "reactions": rev.reactions().iter().flat_map(|(location, reaction)| {
+
                    let reactions = reaction.iter().fold(BTreeMap::new(), |mut acc: BTreeMap<&Reaction, Vec<_>>, (author, emoji)| {
+
                        acc.entry(emoji).or_default().push(author);
+
                        acc
+
                    });
+
                    reactions.iter().map(|(emoji, authors)|
+
                        json!({"location": location, "emoji": emoji, "authors": authors })
+
                    ).collect::<Vec<_>>()
+
                }).collect::<Vec<_>>(),
                "base": rev.base(),
                "oid": rev.head(),
                "refs": get_refs(repo, patch.author().id(), &rev.head()).unwrap_or_default(),
@@ -191,7 +201,9 @@ fn issue_comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -
        "body": comment.body(),
        "edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
        "embeds": comment.embeds().to_vec(),
-
        "reactions": comment.reactions().collect::<Vec<_>>(),
+
        "reactions": comment.reactions().iter().map(|(emoji, authors)|
+
            json!({ "emoji": emoji, "authors": authors })
+
        ).collect::<Vec<_>>(),
        "timestamp": comment.timestamp().as_secs(),
        "replyTo": comment.reply_to(),
        "resolved": comment.resolved(),
@@ -210,7 +222,9 @@ fn patch_comment(
        "body": comment.body(),
        "edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
        "embeds": comment.embeds().to_vec(),
-
        "reactions": comment.reactions().collect::<Vec<_>>(),
+
        "reactions": comment.reactions().iter().map(|(emoji, authors)|
+
            json!({ "emoji": emoji, "authors": authors })
+
        ).collect::<Vec<_>>(),
        "timestamp": comment.timestamp().as_secs(),
        "replyTo": comment.reply_to(),
        "location": comment.location(),
@@ -230,7 +244,9 @@ fn review_comment(
        "body": comment.body(),
        "edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
        "embeds": comment.embeds().to_vec(),
-
        "reactions": comment.reactions().collect::<Vec<_>>(),
+
        "reactions": comment.reactions().iter().map(|(emoji, authors)|
+
            json!({ "emoji": emoji, "authors": authors })
+
        ).collect::<Vec<_>>(),
        "timestamp": comment.timestamp().as_secs(),
        "replyTo": comment.reply_to(),
        "location": comment.location(),
modified radicle-httpd/src/api/v1/projects.rs
@@ -869,6 +869,12 @@ async fn patch_update_handler(
            patch.edit_revision(revision, description, embeds, &signer)?
        }
        patch::Action::RevisionRedact { revision } => patch.redact(revision, &signer)?,
+
        patch::Action::RevisionReact {
+
            revision,
+
            reaction,
+
            active,
+
            location,
+
        } => patch.react(revision, reaction, location, active, &signer)?,
        patch::Action::RevisionComment {
            revision,
            body,
@@ -903,9 +909,6 @@ async fn patch_update_handler(
        patch::Action::RevisionCommentRedact { revision, comment } => {
            patch.comment_redact(revision, comment, &signer)?
        }
-
        _ => {
-
            todo!();
-
        }
    };

    announce_refs(node, repo.id())?;
@@ -2281,10 +2284,10 @@ mod routes {
                    }
                  ],
                  "reactions": [
-
                    [
-
                    "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
-
                    "🚀",
-
                    ],
+
                    {
+
                      "emoji": "🚀",
+
                      "authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
+
                    },
                  ],
                  "timestamp": TIMESTAMP,
                  "replyTo": null,
@@ -2433,6 +2436,7 @@ mod routes {
                "revisions": [
                  {
                    "id": CONTRIBUTOR_PATCH_ID,
+
                    "reactions": [],
                    "author": {
                      "id": CONTRIBUTOR_DID,
                    },
@@ -2485,6 +2489,7 @@ mod routes {
                "revisions": [
                  {
                    "id": CONTRIBUTOR_PATCH_ID,
+
                    "reactions": [],
                    "author": {
                      "id": CONTRIBUTOR_DID,
                    },
@@ -2576,6 +2581,7 @@ mod routes {
                "revisions": [
                  {
                    "id": CREATED_PATCH_ID,
+
                    "reactions": [],
                    "author": {
                      "id": CONTRIBUTOR_DID,
                    },
@@ -2662,6 +2668,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -2736,6 +2743,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -2809,6 +2817,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -2834,6 +2843,7 @@ mod routes {
                        "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -2893,6 +2903,7 @@ mod routes {
              "revisions": [
                {
                  "id": CONTRIBUTOR_PATCH_ID,
+
                  "reactions": [],
                  "author": {
                    "id": CONTRIBUTOR_DID,
                  },
@@ -2943,6 +2954,52 @@ mod routes {

        assert_eq!(response.status(), StatusCode::OK);

+
        let body = serde_json::to_vec(&json!({
+
          "type": "revision.react",
+
          "revision": CONTRIBUTOR_PATCH_ID,
+
          "reaction": "🚀",
+
          "location": {
+
            "commit": INITIAL_COMMIT,
+
            "path": "./README.md",
+
            "new": {
+
              "type": "lines",
+
              "range": {
+
                "start": 0,
+
                "end": 1
+
              }
+
            }
+
          },
+
          "active": true,
+
        }))
+
        .unwrap();
+
        let response = patch(
+
            &app,
+
            format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
+
            Some(Body::from(body)),
+
            Some(SESSION_ID.to_string()),
+
        )
+
        .await;
+

+
        assert_eq!(response.status(), StatusCode::OK);
+

+
        let body = serde_json::to_vec(&json!({
+
          "type": "revision.react",
+
          "revision": CONTRIBUTOR_PATCH_ID,
+
          "reaction": "🙏",
+
          "location": null,
+
          "active": true,
+
        }))
+
        .unwrap();
+
        let response = patch(
+
            &app,
+
            format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
+
            Some(Body::from(body)),
+
            Some(SESSION_ID.to_string()),
+
        )
+
        .await;
+

+
        assert_eq!(response.status(), StatusCode::OK);
+

        let response = get(
            &app,
            format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
@@ -2987,6 +3044,29 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [
+
                    {
+
                      "location": null,
+
                      "emoji": "🙏",
+
                      "authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"],
+
                    },
+
                    {
+
                      "location": {
+
                        "commit": INITIAL_COMMIT,
+
                        "path": "./README.md",
+
                        "old": null,
+
                        "new": {
+
                          "type": "lines",
+
                          "range": {
+
                            "start": 0,
+
                            "end": 1
+
                          }
+
                        }
+
                      },
+
                      "emoji": "🚀",
+
                      "authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
+
                    },
+
                  ],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -3123,6 +3203,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -3169,7 +3250,12 @@ mod routes {
                          "content": "git:94381b429d7f7fe87e1bade52d893ab348ae29cc",
                        }
                      ],
-
                      "reactions": [["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8","🚀"]],
+
                      "reactions": [
+
                        {
+
                          "emoji": "🚀",
+
                          "authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
+
                        },
+
                      ],
                      "timestamp": TIMESTAMP,
                      "replyTo": null,
                      "location": null,
@@ -3350,6 +3436,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
@@ -3406,10 +3493,10 @@ mod routes {
                            },
                          ],
                          "reactions": [
-
                            [
-
                              "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
-
                              "🚀",
-
                            ],
+
                            {
+
                              "emoji": "🚀",
+
                              "authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"],
+
                            },
                          ],
                          "timestamp": 1671125284,
                          "replyTo": null,
@@ -3506,6 +3593,7 @@ mod routes {
                      "embeds": [],
                    },
                  ],
+
                  "reactions": [],
                  "base": PARENT,
                  "oid": HEAD,
                  "refs": [
modified radicle/src/cob/issue.rs
@@ -1167,9 +1167,10 @@ mod test {

        let id = issue.id;
        let issue = issues.get(&id).unwrap().unwrap();
-
        let (_, r) = issue.comment(&comment).unwrap().reactions().next().unwrap();
+
        let reactions = issue.comment(&comment).unwrap().reactions();
+
        let authors = reactions.get(&reaction).unwrap();

-
        assert_eq!(r, &reaction);
+
        assert_eq!(authors.first().unwrap(), &node.signer.public_key());

        // TODO: Test multiple reactions from same author and different authors
    }
modified radicle/src/cob/patch.rs
@@ -1332,6 +1332,10 @@ impl Revision {
        &self.description.last().embeds
    }

+
    pub fn reactions(&self) -> &BTreeMap<Option<CodeLocation>, BTreeSet<(PublicKey, Reaction)>> {
+
        &self.reactions
+
    }
+

    /// Author of the revision.
    pub fn author(&self) -> &Author {
        &self.author
@@ -1593,6 +1597,22 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
        })
    }

+
    /// React on a patch revision.
+
    pub fn react(
+
        &mut self,
+
        revision: RevisionId,
+
        reaction: Reaction,
+
        location: Option<CodeLocation>,
+
        active: bool,
+
    ) -> Result<(), store::Error> {
+
        self.push(Action::RevisionReact {
+
            revision,
+
            reaction,
+
            location,
+
            active,
+
        })
+
    }
+

    /// Comment on a patch revision.
    pub fn comment<S: ToString>(
        &mut self,
@@ -1924,6 +1944,20 @@ where
        })
    }

+
    /// React on a patch revision.
+
    pub fn react<G: Signer>(
+
        &mut self,
+
        revision: RevisionId,
+
        reaction: Reaction,
+
        location: Option<CodeLocation>,
+
        active: bool,
+
        signer: &G,
+
    ) -> Result<EntryId, Error> {
+
        self.transaction("React", signer, |tx| {
+
            tx.react(revision, reaction, location, active)
+
        })
+
    }
+

    /// Edit a comment on a patch revision.
    pub fn comment_edit<G: Signer, S: ToString>(
        &mut self,
modified radicle/src/cob/thread.rs
@@ -196,8 +196,13 @@ impl<L> Comment<L> {
    }

    /// Comment reactions.
-
    pub fn reactions(&self) -> impl Iterator<Item = (&ActorId, &Reaction)> {
-
        self.reactions.iter().map(|(a, r)| (a, r))
+
    pub fn reactions(&self) -> BTreeMap<&Reaction, Vec<&ActorId>> {
+
        self.reactions
+
            .iter()
+
            .fold(BTreeMap::new(), |mut acc, (author, reaction)| {
+
                acc.entry(reaction).or_default().push(author);
+
                acc
+
            })
    }

    /// Get comment location, if any.