httpd: Add missing COB actions
Handles missing issue and patch actions. Adds embeds to patch comments. Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
parent
7c842bcc1d
commit
78e6833672
|
|
@ -27,7 +27,7 @@ pub fn run(
|
||||||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||||
let (body, reply_to) = prompt(message, reply_to, &revision, repo)?;
|
let (body, reply_to) = prompt(message, reply_to, &revision, repo)?;
|
||||||
let comment_id = patch.comment(revision_id, body, reply_to, &signer)?;
|
let comment_id = patch.comment(revision_id, body, reply_to, None, vec![], &signer)?;
|
||||||
let comment = patch
|
let comment = patch
|
||||||
.revision(&revision_id)
|
.revision(&revision_id)
|
||||||
.ok_or(anyhow!("error retrieving revision `{revision_id}`"))?
|
.ok_or(anyhow!("error retrieving revision `{revision_id}`"))?
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ struct Comment<'a> {
|
||||||
#[serde(with = "radicle::serde_ext::localtime::time")]
|
#[serde(with = "radicle::serde_ext::localtime::time")]
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
resolved: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Comment<'a> {
|
impl<'a> Comment<'a> {
|
||||||
|
|
@ -260,6 +261,7 @@ impl<'a> Comment<'a> {
|
||||||
reactions: comment.reactions().collect::<Vec<_>>(),
|
reactions: comment.reactions().collect::<Vec<_>>(),
|
||||||
timestamp: comment.timestamp(),
|
timestamp: comment.timestamp(),
|
||||||
reply_to: comment.reply_to(),
|
reply_to: comment.reply_to(),
|
||||||
|
resolved: comment.resolved(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -732,11 +732,21 @@ async fn issue_update_handler(
|
||||||
} => {
|
} => {
|
||||||
issue.react(id, reaction, active, &signer)?;
|
issue.react(id, reaction, active, &signer)?;
|
||||||
}
|
}
|
||||||
issue::Action::CommentEdit { .. } => {
|
issue::Action::CommentEdit { id, body, embeds } => {
|
||||||
todo!();
|
let embeds: Vec<Embed> = embeds
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|embed| {
|
||||||
|
let content = TryInto::<DataUri>::try_into(&embed.content).ok()?;
|
||||||
|
Some(Embed {
|
||||||
|
name: embed.name,
|
||||||
|
content: content.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
issue.edit_comment(id, body, embeds, &signer)?;
|
||||||
}
|
}
|
||||||
issue::Action::CommentRedact { .. } => {
|
issue::Action::CommentRedact { id } => {
|
||||||
todo!();
|
issue.redact_comment(id, &signer)?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -824,11 +834,26 @@ async fn patch_update_handler(
|
||||||
patch::Action::Edit { title, target } => {
|
patch::Action::Edit { title, target } => {
|
||||||
patch.edit(title, target, &signer)?;
|
patch.edit(title, target, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::RevisionEdit {
|
patch::Action::Label { labels } => {
|
||||||
|
patch.label(labels, &signer)?;
|
||||||
|
}
|
||||||
|
patch::Action::Lifecycle { state } => {
|
||||||
|
patch.lifecycle(state, &signer)?;
|
||||||
|
}
|
||||||
|
patch::Action::Assign { assignees } => {
|
||||||
|
patch.assign(assignees, &signer)?;
|
||||||
|
}
|
||||||
|
patch::Action::Merge { revision, commit } => {
|
||||||
|
// TODO: We should cleanup the stored copy at least.
|
||||||
|
let _ = patch.merge(revision, commit, &signer)?;
|
||||||
|
}
|
||||||
|
patch::Action::Review {
|
||||||
revision,
|
revision,
|
||||||
description,
|
summary,
|
||||||
|
verdict,
|
||||||
|
labels,
|
||||||
} => {
|
} => {
|
||||||
patch.edit_revision(revision, description, &signer)?;
|
patch.review(revision, verdict, summary, labels, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::ReviewEdit {
|
patch::Action::ReviewEdit {
|
||||||
review,
|
review,
|
||||||
|
|
@ -837,20 +862,45 @@ async fn patch_update_handler(
|
||||||
} => {
|
} => {
|
||||||
patch.edit_review(review, summary, verdict, &signer)?;
|
patch.edit_review(review, summary, verdict, &signer)?;
|
||||||
}
|
}
|
||||||
|
patch::Action::ReviewRedact { review } => {
|
||||||
|
patch.redact_review(review, &signer)?;
|
||||||
|
}
|
||||||
patch::Action::ReviewComment {
|
patch::Action::ReviewComment {
|
||||||
review,
|
review,
|
||||||
body,
|
body,
|
||||||
reply_to,
|
reply_to,
|
||||||
..
|
location,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
patch.review_comment(review, body, None, reply_to, &signer)?;
|
let embeds: Vec<Embed> = embeds
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|embed| {
|
||||||
|
let content = TryInto::<DataUri>::try_into(&embed.content).ok()?;
|
||||||
|
Some(Embed {
|
||||||
|
name: embed.name,
|
||||||
|
content: content.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
patch.review_comment(review, body, location, reply_to, embeds, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::ReviewCommentEdit {
|
patch::Action::ReviewCommentEdit {
|
||||||
review,
|
review,
|
||||||
comment,
|
comment,
|
||||||
body,
|
body,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
patch.edit_review_comment(review, comment, body, &signer)?;
|
let embeds: Vec<Embed> = embeds
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|embed| {
|
||||||
|
let content = TryInto::<DataUri>::try_into(&embed.content).ok()?;
|
||||||
|
Some(Embed {
|
||||||
|
name: embed.name,
|
||||||
|
content: content.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
patch.edit_review_comment(review, comment, body, embeds, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::ReviewCommentReact {
|
patch::Action::ReviewCommentReact {
|
||||||
review,
|
review,
|
||||||
|
|
@ -863,8 +913,11 @@ async fn patch_update_handler(
|
||||||
patch::Action::ReviewCommentRedact { review, comment } => {
|
patch::Action::ReviewCommentRedact { review, comment } => {
|
||||||
patch.redact_review_comment(review, comment, &signer)?;
|
patch.redact_review_comment(review, comment, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::Label { labels } => {
|
patch::Action::ReviewCommentResolve { review, comment } => {
|
||||||
patch.label(labels, &signer)?;
|
patch.resolve_review_comment(review, comment, &signer)?;
|
||||||
|
}
|
||||||
|
patch::Action::ReviewCommentUnresolve { review, comment } => {
|
||||||
|
patch.unresolve_review_comment(review, comment, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::Revision {
|
patch::Action::Revision {
|
||||||
description,
|
description,
|
||||||
|
|
@ -874,35 +927,51 @@ async fn patch_update_handler(
|
||||||
} => {
|
} => {
|
||||||
patch.update(description, base, oid, &signer)?;
|
patch.update(description, base, oid, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::Lifecycle { state } => {
|
patch::Action::RevisionEdit {
|
||||||
patch.lifecycle(state, &signer)?;
|
|
||||||
}
|
|
||||||
patch::Action::Review {
|
|
||||||
revision,
|
revision,
|
||||||
summary,
|
description,
|
||||||
verdict,
|
|
||||||
labels,
|
|
||||||
} => {
|
} => {
|
||||||
patch.review(revision, verdict, summary, labels, &signer)?;
|
patch.edit_revision(revision, description, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::Merge { revision, commit } => {
|
patch::Action::RevisionRedact { revision } => {
|
||||||
// TODO: We should cleanup the stored copy at least.
|
patch.redact(revision, &signer)?;
|
||||||
let _ = patch.merge(revision, commit, &signer)?;
|
|
||||||
}
|
}
|
||||||
patch::Action::RevisionComment {
|
patch::Action::RevisionComment {
|
||||||
revision,
|
revision,
|
||||||
body,
|
body,
|
||||||
reply_to,
|
reply_to,
|
||||||
..
|
location,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
patch.comment(revision, body, reply_to, &signer)?;
|
let embeds: Vec<Embed> = embeds
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|embed| {
|
||||||
|
let content = TryInto::<DataUri>::try_into(&embed.content).ok()?;
|
||||||
|
Some(Embed {
|
||||||
|
name: embed.name,
|
||||||
|
content: content.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
patch.comment(revision, body, reply_to, location, embeds, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::RevisionCommentEdit {
|
patch::Action::RevisionCommentEdit {
|
||||||
revision,
|
revision,
|
||||||
comment,
|
comment,
|
||||||
body,
|
body,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
patch.comment_edit(revision, comment, body, &signer)?;
|
let embeds: Vec<Embed> = embeds
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|embed| {
|
||||||
|
let content = TryInto::<DataUri>::try_into(&embed.content).ok()?;
|
||||||
|
Some(Embed {
|
||||||
|
name: embed.name,
|
||||||
|
content: content.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
patch.comment_edit(revision, comment, body, embeds, &signer)?;
|
||||||
}
|
}
|
||||||
patch::Action::RevisionCommentReact {
|
patch::Action::RevisionCommentReact {
|
||||||
revision,
|
revision,
|
||||||
|
|
@ -1937,7 +2006,8 @@ mod routes {
|
||||||
"embeds": [],
|
"embeds": [],
|
||||||
"reactions": [],
|
"reactions": [],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": null
|
"replyTo": null,
|
||||||
|
"resolved": false,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"labels": []
|
"labels": []
|
||||||
|
|
@ -2017,6 +2087,7 @@ mod routes {
|
||||||
"reactions": [],
|
"reactions": [],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": null,
|
"replyTo": null,
|
||||||
|
"resolved": false,
|
||||||
}],
|
}],
|
||||||
"labels": [
|
"labels": [
|
||||||
"bug",
|
"bug",
|
||||||
|
|
@ -2059,7 +2130,7 @@ mod routes {
|
||||||
|
|
||||||
let body = serde_json::to_vec(&json!({
|
let body = serde_json::to_vec(&json!({
|
||||||
"type": "comment.react",
|
"type": "comment.react",
|
||||||
"id": "6fe9fdec2ec9f6436f2875dbcbedb95dd215b863",
|
"id": ISSUE_DISCUSSION_ID,
|
||||||
"reaction": "🚀",
|
"reaction": "🚀",
|
||||||
"active": true,
|
"active": true,
|
||||||
}))
|
}))
|
||||||
|
|
@ -2072,6 +2143,40 @@ mod routes {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let body = serde_json::to_vec(&json!({
|
||||||
|
"type": "comment.edit",
|
||||||
|
"id": ISSUE_DISCUSSION_ID,
|
||||||
|
"body": "EDIT: Change 'hello world' to 'hello anyone'",
|
||||||
|
"embeds": []
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = patch(
|
||||||
|
&app,
|
||||||
|
format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"),
|
||||||
|
Some(Body::from(body)),
|
||||||
|
Some(SESSION_ID.to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.json().await, json!({ "success": true }));
|
||||||
|
|
||||||
|
let body = serde_json::to_vec(&json!({
|
||||||
|
"type": "comment.redact",
|
||||||
|
"id": "6fe9fdec2ec9f6436f2875dbcbedb95dd215b863",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = patch(
|
||||||
|
&app,
|
||||||
|
format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"),
|
||||||
|
Some(Body::from(body)),
|
||||||
|
Some(SESSION_ID.to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.json().await, json!({ "success": true }));
|
||||||
|
|
||||||
let response = get(
|
let response = get(
|
||||||
&app,
|
&app,
|
||||||
format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"),
|
format!("/projects/{CONTRIBUTOR_RID}/issues/{CONTRIBUTOR_ISSUE_ID}"),
|
||||||
|
|
@ -2096,24 +2201,8 @@ mod routes {
|
||||||
"author": {
|
"author": {
|
||||||
"id": CONTRIBUTOR_DID,
|
"id": CONTRIBUTOR_DID,
|
||||||
},
|
},
|
||||||
"body": "Change 'hello world' to 'hello everyone'",
|
"body": "EDIT: Change 'hello world' to 'hello anyone'",
|
||||||
"embeds": [],
|
"embeds": [],
|
||||||
"reactions": [],
|
|
||||||
"timestamp": TIMESTAMP,
|
|
||||||
"replyTo": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6fe9fdec2ec9f6436f2875dbcbedb95dd215b863",
|
|
||||||
"author": {
|
|
||||||
"id": CONTRIBUTOR_DID,
|
|
||||||
},
|
|
||||||
"body": "This is first-level comment",
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"name": "image.jpg",
|
|
||||||
"content": "git:94381b429d7f7fe87e1bade52d893ab348ae29cc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"reactions": [
|
"reactions": [
|
||||||
[
|
[
|
||||||
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
||||||
|
|
@ -2121,7 +2210,8 @@ mod routes {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": CONTRIBUTOR_ISSUE_ID,
|
"replyTo": null,
|
||||||
|
"resolved": false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"labels": [],
|
"labels": [],
|
||||||
|
|
@ -2185,6 +2275,7 @@ mod routes {
|
||||||
"reactions": [],
|
"reactions": [],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": null,
|
"replyTo": null,
|
||||||
|
"resolved": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": ISSUE_COMMENT_ID,
|
"id": ISSUE_COMMENT_ID,
|
||||||
|
|
@ -2196,6 +2287,7 @@ mod routes {
|
||||||
"reactions": [],
|
"reactions": [],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": ISSUE_DISCUSSION_ID,
|
"replyTo": ISSUE_DISCUSSION_ID,
|
||||||
|
"resolved": false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"labels": [],
|
"labels": [],
|
||||||
|
|
@ -2371,7 +2463,68 @@ mod routes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_projects_patches_tag() {
|
async fn test_projects_patches_assign() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let ctx = contributor(tmp.path());
|
||||||
|
let app = super::router(ctx.to_owned());
|
||||||
|
create_session(ctx).await;
|
||||||
|
let body = serde_json::to_vec(&json!({
|
||||||
|
"type": "assign",
|
||||||
|
"assignees": [CONTRIBUTOR_DID]
|
||||||
|
}))
|
||||||
|
.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}"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
response.json().await,
|
||||||
|
json!({
|
||||||
|
"id": CONTRIBUTOR_PATCH_ID,
|
||||||
|
"author": {
|
||||||
|
"id": CONTRIBUTOR_DID,
|
||||||
|
},
|
||||||
|
"title": "A new `hello world`",
|
||||||
|
"state": { "status": "open" },
|
||||||
|
"target": "delegates",
|
||||||
|
"labels": [],
|
||||||
|
"merges": [],
|
||||||
|
"assignees": [CONTRIBUTOR_DID],
|
||||||
|
"revisions": [
|
||||||
|
{
|
||||||
|
"id": CONTRIBUTOR_PATCH_ID,
|
||||||
|
"author": {
|
||||||
|
"id": CONTRIBUTOR_DID,
|
||||||
|
},
|
||||||
|
"description": "change `hello world` in README to something else",
|
||||||
|
"base": PARENT,
|
||||||
|
"oid": HEAD,
|
||||||
|
"refs": [
|
||||||
|
"refs/heads/master",
|
||||||
|
],
|
||||||
|
"discussions": [],
|
||||||
|
"timestamp": TIMESTAMP,
|
||||||
|
"reviews": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_projects_patches_label() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let ctx = contributor(tmp.path());
|
let ctx = contributor(tmp.path());
|
||||||
let app = super::router(ctx.to_owned());
|
let app = super::router(ctx.to_owned());
|
||||||
|
|
@ -2575,6 +2728,68 @@ mod routes {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_projects_patches_revisions_edit() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let ctx = contributor(tmp.path());
|
||||||
|
let app = super::router(ctx.to_owned());
|
||||||
|
create_session(ctx).await;
|
||||||
|
let body = serde_json::to_vec(&json!({
|
||||||
|
"type": "revision.edit",
|
||||||
|
"revision": CONTRIBUTOR_PATCH_ID,
|
||||||
|
"description": "Let's change the description a bit",
|
||||||
|
}))
|
||||||
|
.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}"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
response.json().await,
|
||||||
|
json!({
|
||||||
|
"id": CONTRIBUTOR_PATCH_ID,
|
||||||
|
"author": {
|
||||||
|
"id": CONTRIBUTOR_DID,
|
||||||
|
},
|
||||||
|
"title": "A new `hello world`",
|
||||||
|
"state": { "status": "open" },
|
||||||
|
"target": "delegates",
|
||||||
|
"labels": [],
|
||||||
|
"merges": [],
|
||||||
|
"assignees": [],
|
||||||
|
"revisions": [
|
||||||
|
{
|
||||||
|
"id": CONTRIBUTOR_PATCH_ID,
|
||||||
|
"author": {
|
||||||
|
"id": CONTRIBUTOR_DID,
|
||||||
|
},
|
||||||
|
"description": "Let's change the description a bit",
|
||||||
|
"base": PARENT,
|
||||||
|
"oid": HEAD,
|
||||||
|
"refs": [
|
||||||
|
"refs/heads/master",
|
||||||
|
],
|
||||||
|
"discussions": [],
|
||||||
|
"timestamp": TIMESTAMP,
|
||||||
|
"reviews": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_projects_patches_discussions() {
|
async fn test_projects_patches_discussions() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -2584,7 +2799,13 @@ mod routes {
|
||||||
let thread_body = serde_json::to_vec(&json!({
|
let thread_body = serde_json::to_vec(&json!({
|
||||||
"type": "revision.comment",
|
"type": "revision.comment",
|
||||||
"revision": CONTRIBUTOR_PATCH_ID,
|
"revision": CONTRIBUTOR_PATCH_ID,
|
||||||
"body": "This is a root level comment"
|
"body": "This is a root level comment",
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
"name": "image.jpg",
|
||||||
|
"content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC"
|
||||||
|
}
|
||||||
|
],
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let response = patch(
|
let response = patch(
|
||||||
|
|
@ -2616,7 +2837,13 @@ mod routes {
|
||||||
"type": "revision.comment.edit",
|
"type": "revision.comment.edit",
|
||||||
"revision": CONTRIBUTOR_PATCH_ID,
|
"revision": CONTRIBUTOR_PATCH_ID,
|
||||||
"comment": CONTRIBUTOR_COMMENT_1,
|
"comment": CONTRIBUTOR_COMMENT_1,
|
||||||
"body": "EDIT: This is a root level comment"
|
"body": "EDIT: This is a root level comment",
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
"name": "image.jpg",
|
||||||
|
"content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC"
|
||||||
|
}
|
||||||
|
],
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let response = patch(
|
let response = patch(
|
||||||
|
|
@ -2633,6 +2860,7 @@ mod routes {
|
||||||
"revision": CONTRIBUTOR_PATCH_ID,
|
"revision": CONTRIBUTOR_PATCH_ID,
|
||||||
"body": "This is a root level comment",
|
"body": "This is a root level comment",
|
||||||
"replyTo": CONTRIBUTOR_COMMENT_1,
|
"replyTo": CONTRIBUTOR_COMMENT_1,
|
||||||
|
"embeds": [],
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let response = patch(
|
let response = patch(
|
||||||
|
|
@ -2683,10 +2911,16 @@ mod routes {
|
||||||
"id": CONTRIBUTOR_DID,
|
"id": CONTRIBUTOR_DID,
|
||||||
},
|
},
|
||||||
"body": "EDIT: This is a root level comment",
|
"body": "EDIT: This is a root level comment",
|
||||||
"embeds": [],
|
"embeds": [
|
||||||
|
{
|
||||||
|
"name": "image.jpg",
|
||||||
|
"content": "git:94381b429d7f7fe87e1bade52d893ab348ae29cc",
|
||||||
|
}
|
||||||
|
],
|
||||||
"reactions": [["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8","🚀"]],
|
"reactions": [["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8","🚀"]],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": null,
|
"replyTo": null,
|
||||||
|
"resolved": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": CONTRIBUTOR_COMMENT_2,
|
"id": CONTRIBUTOR_COMMENT_2,
|
||||||
|
|
@ -2698,6 +2932,7 @@ mod routes {
|
||||||
"reactions": [],
|
"reactions": [],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
"replyTo": CONTRIBUTOR_COMMENT_1,
|
"replyTo": CONTRIBUTOR_COMMENT_1,
|
||||||
|
"resolved": false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"timestamp": TIMESTAMP,
|
"timestamp": TIMESTAMP,
|
||||||
|
|
@ -2734,7 +2969,18 @@ mod routes {
|
||||||
let review_comment_body = serde_json::to_vec(&json!({
|
let review_comment_body = serde_json::to_vec(&json!({
|
||||||
"type": "review.comment",
|
"type": "review.comment",
|
||||||
"review": CONTRIBUTOR_PATCH_REVIEW,
|
"review": CONTRIBUTOR_PATCH_REVIEW,
|
||||||
"body": "This is a comment on a review"
|
"body": "This is a comment on a review",
|
||||||
|
"embeds": [],
|
||||||
|
"location": {
|
||||||
|
"path": "README.md",
|
||||||
|
"new": {
|
||||||
|
"type": "lines",
|
||||||
|
"range": {
|
||||||
|
"start": 2,
|
||||||
|
"end": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
patch(
|
patch(
|
||||||
|
|
@ -2749,7 +2995,8 @@ mod routes {
|
||||||
"type": "review.comment.edit",
|
"type": "review.comment.edit",
|
||||||
"review": CONTRIBUTOR_PATCH_REVIEW,
|
"review": CONTRIBUTOR_PATCH_REVIEW,
|
||||||
"comment": CONTRIBUTOR_COMMENT_3,
|
"comment": CONTRIBUTOR_COMMENT_3,
|
||||||
"body": "EDIT: This is a comment on a review"
|
"embeds": [],
|
||||||
|
"body": "EDIT: This is a comment on a review",
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
patch(
|
patch(
|
||||||
|
|
@ -2776,6 +3023,20 @@ mod routes {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let review_resolve_body = serde_json::to_vec(&json!({
|
||||||
|
"type": "review.comment.resolve",
|
||||||
|
"review": CONTRIBUTOR_PATCH_REVIEW,
|
||||||
|
"comment": CONTRIBUTOR_COMMENT_3,
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
patch(
|
||||||
|
&app,
|
||||||
|
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||||
|
Some(Body::from(review_resolve_body)),
|
||||||
|
Some(SESSION_ID.to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let response = get(
|
let response = get(
|
||||||
&app,
|
&app,
|
||||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||||
|
|
@ -2817,15 +3078,27 @@ mod routes {
|
||||||
"verdict": "accept",
|
"verdict": "accept",
|
||||||
"summary": "A small review",
|
"summary": "A small review",
|
||||||
"comments": [[
|
"comments": [[
|
||||||
"dd9743bb964ba22399548c86a3c1765020d58f48",
|
CONTRIBUTOR_COMMENT_3,
|
||||||
{
|
{
|
||||||
"author": "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
"author": CONTRIBUTOR_NID,
|
||||||
|
"location": {
|
||||||
|
"path": "README.md",
|
||||||
|
"old": null,
|
||||||
|
"new": {
|
||||||
|
"type": "lines",
|
||||||
|
"range": {
|
||||||
|
"start": 2,
|
||||||
|
"end": 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"reactions": [
|
"reactions": [
|
||||||
[
|
[
|
||||||
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
||||||
"🚀",
|
"🚀",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
"resolved": true,
|
||||||
"body": "EDIT: This is a comment on a review",
|
"body": "EDIT: This is a comment on a review",
|
||||||
},
|
},
|
||||||
]],
|
]],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
#![recursion_limit = "256"]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ pub const CONTRIBUTOR_NID: &str = "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJm
|
||||||
pub const CONTRIBUTOR_ISSUE_ID: &str = "7466975f0bef37b459887824a9655f3e78262522";
|
pub const CONTRIBUTOR_ISSUE_ID: &str = "7466975f0bef37b459887824a9655f3e78262522";
|
||||||
pub const CONTRIBUTOR_PATCH_ID: &str = "e651ae5869a2c1ac8ad4f6deae4cc835656ffa25";
|
pub const CONTRIBUTOR_PATCH_ID: &str = "e651ae5869a2c1ac8ad4f6deae4cc835656ffa25";
|
||||||
pub const CONTRIBUTOR_PATCH_REVIEW: &str = "ee3eeba95f4ec418b3d0714e18e0d1ff605dc0e6";
|
pub const CONTRIBUTOR_PATCH_REVIEW: &str = "ee3eeba95f4ec418b3d0714e18e0d1ff605dc0e6";
|
||||||
pub const CONTRIBUTOR_COMMENT_1: &str = "d0bb75b2c72ab8b5486d39f6cf5f41f104b63cb1";
|
pub const CONTRIBUTOR_COMMENT_1: &str = "d8ff07edbc8d2229e54e70f2f5bc31614287f0dc";
|
||||||
pub const CONTRIBUTOR_COMMENT_2: &str = "227947fa41420e363752357b2edd54d3ccadf7b3";
|
pub const CONTRIBUTOR_COMMENT_2: &str = "f3fca0add53f85bc51a85198efed3273fe13b88e";
|
||||||
pub const CONTRIBUTOR_COMMENT_3: &str = "dd9743bb964ba22399548c86a3c1765020d58f48";
|
pub const CONTRIBUTOR_COMMENT_3: &str = "06990ff59faa12463f693dae7a98eb33d75afd2e";
|
||||||
|
|
||||||
/// Create a new profile.
|
/// Create a new profile.
|
||||||
pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
||||||
|
|
|
||||||
|
|
@ -563,6 +563,19 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Edit a comment.
|
||||||
|
pub fn edit_comment<G: Signer, S: ToString>(
|
||||||
|
&mut self,
|
||||||
|
id: CommentId,
|
||||||
|
body: S,
|
||||||
|
embeds: impl IntoIterator<Item = Embed>,
|
||||||
|
signer: &G,
|
||||||
|
) -> Result<EntryId, Error> {
|
||||||
|
self.transaction("Edit comment", signer, |tx| {
|
||||||
|
tx.edit_comment(id, body, embeds.into_iter().collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Redact a comment.
|
/// Redact a comment.
|
||||||
pub fn redact_comment<G: Signer>(
|
pub fn redact_comment<G: Signer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use crate::cob::store::{Cob, CobAction};
|
||||||
use crate::cob::thread;
|
use crate::cob::thread;
|
||||||
use crate::cob::thread::Thread;
|
use crate::cob::thread::Thread;
|
||||||
use crate::cob::thread::{Comment, CommentId, Reactions};
|
use crate::cob::thread::{Comment, CommentId, Reactions};
|
||||||
use crate::cob::{op, store, ActorId, EntryId, ObjectId, TypeName};
|
use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName, Uri};
|
||||||
use crate::crypto::{PublicKey, Signer};
|
use crate::crypto::{PublicKey, Signer};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::identity;
|
use crate::identity;
|
||||||
|
|
@ -176,12 +176,16 @@ pub enum Action {
|
||||||
/// Should be [`Some`] otherwise.
|
/// Should be [`Some`] otherwise.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
/// Embeded content.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
embeds: Vec<Embed<Uri>>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "review.comment.edit")]
|
#[serde(rename = "review.comment.edit")]
|
||||||
ReviewCommentEdit {
|
ReviewCommentEdit {
|
||||||
review: ReviewId,
|
review: ReviewId,
|
||||||
comment: EntryId,
|
comment: EntryId,
|
||||||
body: String,
|
body: String,
|
||||||
|
embeds: Vec<Embed<Uri>>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "review.comment.redact")]
|
#[serde(rename = "review.comment.redact")]
|
||||||
ReviewCommentRedact { review: ReviewId, comment: EntryId },
|
ReviewCommentRedact { review: ReviewId, comment: EntryId },
|
||||||
|
|
@ -240,6 +244,9 @@ pub enum Action {
|
||||||
/// Should be the root [`CommentId`] if it's a top-level comment.
|
/// Should be the root [`CommentId`] if it's a top-level comment.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
/// Embeded content.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
embeds: Vec<Embed<Uri>>,
|
||||||
},
|
},
|
||||||
/// Edit a revision comment.
|
/// Edit a revision comment.
|
||||||
#[serde(rename = "revision.comment.edit")]
|
#[serde(rename = "revision.comment.edit")]
|
||||||
|
|
@ -247,6 +254,7 @@ pub enum Action {
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
comment: CommentId,
|
comment: CommentId,
|
||||||
body: String,
|
body: String,
|
||||||
|
embeds: Vec<Embed<Uri>>,
|
||||||
},
|
},
|
||||||
/// Redact a revision comment.
|
/// Redact a revision comment.
|
||||||
#[serde(rename = "revision.comment.redact")]
|
#[serde(rename = "revision.comment.redact")]
|
||||||
|
|
@ -856,6 +864,7 @@ impl Patch {
|
||||||
review,
|
review,
|
||||||
comment,
|
comment,
|
||||||
body,
|
body,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
if let Some(review) = lookup::review_mut(self, &review)? {
|
if let Some(review) = lookup::review_mut(self, &review)? {
|
||||||
thread::edit(
|
thread::edit(
|
||||||
|
|
@ -864,7 +873,7 @@ impl Patch {
|
||||||
comment,
|
comment,
|
||||||
timestamp,
|
timestamp,
|
||||||
body,
|
body,
|
||||||
vec![],
|
embeds,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -883,6 +892,7 @@ impl Patch {
|
||||||
body,
|
body,
|
||||||
location,
|
location,
|
||||||
reply_to,
|
reply_to,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
if let Some(review) = lookup::review_mut(self, &review)? {
|
if let Some(review) = lookup::review_mut(self, &review)? {
|
||||||
thread::comment(
|
thread::comment(
|
||||||
|
|
@ -893,7 +903,7 @@ impl Patch {
|
||||||
body,
|
body,
|
||||||
reply_to,
|
reply_to,
|
||||||
location,
|
location,
|
||||||
vec![],
|
embeds,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1008,6 +1018,7 @@ impl Patch {
|
||||||
revision,
|
revision,
|
||||||
comment,
|
comment,
|
||||||
body,
|
body,
|
||||||
|
embeds,
|
||||||
} => {
|
} => {
|
||||||
if let Some(revision) = lookup::revision_mut(self, &revision)? {
|
if let Some(revision) = lookup::revision_mut(self, &revision)? {
|
||||||
thread::edit(
|
thread::edit(
|
||||||
|
|
@ -1016,7 +1027,7 @@ impl Patch {
|
||||||
comment,
|
comment,
|
||||||
timestamp,
|
timestamp,
|
||||||
body,
|
body,
|
||||||
vec![],
|
embeds,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1554,6 +1565,7 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
reply_to: None,
|
reply_to: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
embeds: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1563,12 +1575,18 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
location: Option<CodeLocation>,
|
||||||
|
embeds: Vec<Embed>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
|
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
||||||
|
|
||||||
|
self.embed(embeds)?;
|
||||||
self.push(Action::RevisionComment {
|
self.push(Action::RevisionComment {
|
||||||
revision,
|
revision,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
reply_to,
|
reply_to,
|
||||||
location: None,
|
location,
|
||||||
|
embeds: hashed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1578,11 +1596,16 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
comment: CommentId,
|
comment: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
|
embeds: Vec<Embed>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
|
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
||||||
|
|
||||||
|
self.embed(embeds)?;
|
||||||
self.push(Action::RevisionCommentEdit {
|
self.push(Action::RevisionCommentEdit {
|
||||||
revision,
|
revision,
|
||||||
comment,
|
comment,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
|
embeds: hashed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1618,26 +1641,54 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
body: S,
|
body: S,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
embeds: Vec<Embed>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
|
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
||||||
|
self.embed(embeds)?;
|
||||||
|
|
||||||
self.push(Action::ReviewComment {
|
self.push(Action::ReviewComment {
|
||||||
review,
|
review,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
location,
|
location,
|
||||||
reply_to,
|
reply_to,
|
||||||
|
embeds: hashed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a review comment.
|
||||||
|
pub fn review_comment_resolve(
|
||||||
|
&mut self,
|
||||||
|
review: ReviewId,
|
||||||
|
comment: CommentId,
|
||||||
|
) -> Result<(), store::Error> {
|
||||||
|
self.push(Action::ReviewCommentResolve { review, comment })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unresolve a review comment.
|
||||||
|
pub fn review_comment_unresolve(
|
||||||
|
&mut self,
|
||||||
|
review: ReviewId,
|
||||||
|
comment: CommentId,
|
||||||
|
) -> Result<(), store::Error> {
|
||||||
|
self.push(Action::ReviewCommentUnresolve { review, comment })
|
||||||
|
}
|
||||||
|
|
||||||
/// Edit review comment.
|
/// Edit review comment.
|
||||||
pub fn edit_review_comment<S: ToString>(
|
pub fn edit_review_comment<S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
review: ReviewId,
|
review: ReviewId,
|
||||||
comment: EntryId,
|
comment: EntryId,
|
||||||
body: S,
|
body: S,
|
||||||
|
embeds: Vec<Embed>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
|
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
||||||
|
|
||||||
|
self.embed(embeds)?;
|
||||||
self.push(Action::ReviewCommentEdit {
|
self.push(Action::ReviewCommentEdit {
|
||||||
review,
|
review,
|
||||||
comment,
|
comment,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
|
embeds: hashed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1712,6 +1763,11 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
self.push(Action::Lifecycle { state })
|
self.push(Action::Lifecycle { state })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assign a patch.
|
||||||
|
pub fn assign(&mut self, assignees: BTreeSet<Did>) -> Result<(), store::Error> {
|
||||||
|
self.push(Action::Assign { assignees })
|
||||||
|
}
|
||||||
|
|
||||||
/// Label a patch.
|
/// Label a patch.
|
||||||
pub fn label(&mut self, labels: impl IntoIterator<Item = Label>) -> Result<(), store::Error> {
|
pub fn label(&mut self, labels: impl IntoIterator<Item = Label>) -> Result<(), store::Error> {
|
||||||
self.push(Action::Label {
|
self.push(Action::Label {
|
||||||
|
|
@ -1828,9 +1884,19 @@ where
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
location: Option<CodeLocation>,
|
||||||
|
embeds: impl IntoIterator<Item = Embed>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Comment", signer, |tx| tx.comment(revision, body, reply_to))
|
self.transaction("Comment", signer, |tx| {
|
||||||
|
tx.comment(
|
||||||
|
revision,
|
||||||
|
body,
|
||||||
|
reply_to,
|
||||||
|
location,
|
||||||
|
embeds.into_iter().collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit a comment on a patch revision.
|
/// Edit a comment on a patch revision.
|
||||||
|
|
@ -1839,10 +1905,11 @@ where
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
comment: CommentId,
|
comment: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
|
embeds: impl IntoIterator<Item = Embed>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit comment", signer, |tx| {
|
self.transaction("Edit comment", signer, |tx| {
|
||||||
tx.comment_edit(revision, comment, body)
|
tx.comment_edit(revision, comment, body, embeds.into_iter().collect())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1879,10 +1946,17 @@ where
|
||||||
body: S,
|
body: S,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
|
embeds: impl IntoIterator<Item = Embed>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Review comment", signer, |tx| {
|
self.transaction("Review comment", signer, |tx| {
|
||||||
tx.review_comment(review, body, location, reply_to)
|
tx.review_comment(
|
||||||
|
review,
|
||||||
|
body,
|
||||||
|
location,
|
||||||
|
reply_to,
|
||||||
|
embeds.into_iter().collect(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1892,10 +1966,11 @@ where
|
||||||
review: ReviewId,
|
review: ReviewId,
|
||||||
comment: EntryId,
|
comment: EntryId,
|
||||||
body: S,
|
body: S,
|
||||||
|
embeds: impl IntoIterator<Item = Embed>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit review comment", signer, |tx| {
|
self.transaction("Edit review comment", signer, |tx| {
|
||||||
tx.edit_review_comment(review, comment, body)
|
tx.edit_review_comment(review, comment, body, embeds.into_iter().collect())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1949,6 +2024,30 @@ where
|
||||||
self.transaction("Redact review", signer, |tx| tx.redact_review(review))
|
self.transaction("Redact review", signer, |tx| tx.redact_review(review))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a patch review comment.
|
||||||
|
pub fn resolve_review_comment<G: Signer>(
|
||||||
|
&mut self,
|
||||||
|
review: ReviewId,
|
||||||
|
comment: CommentId,
|
||||||
|
signer: &G,
|
||||||
|
) -> Result<EntryId, Error> {
|
||||||
|
self.transaction("Resolve review comment", signer, |tx| {
|
||||||
|
tx.review_comment_resolve(review, comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unresolve a patch review comment.
|
||||||
|
pub fn unresolve_review_comment<G: Signer>(
|
||||||
|
&mut self,
|
||||||
|
review: ReviewId,
|
||||||
|
comment: CommentId,
|
||||||
|
signer: &G,
|
||||||
|
) -> Result<EntryId, Error> {
|
||||||
|
self.transaction("Unresolve review comment", signer, |tx| {
|
||||||
|
tx.review_comment_unresolve(review, comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Merge a patch revision.
|
/// Merge a patch revision.
|
||||||
pub fn merge<G: Signer>(
|
pub fn merge<G: Signer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -1985,6 +2084,15 @@ where
|
||||||
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
|
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assign a patch.
|
||||||
|
pub fn assign<G: Signer>(
|
||||||
|
&mut self,
|
||||||
|
assignees: BTreeSet<Did>,
|
||||||
|
signer: &G,
|
||||||
|
) -> Result<EntryId, Error> {
|
||||||
|
self.transaction("Assign", signer, |tx| tx.assign(assignees))
|
||||||
|
}
|
||||||
|
|
||||||
/// Archive a patch.
|
/// Archive a patch.
|
||||||
pub fn archive<G: Signer>(&mut self, signer: &G) -> Result<EntryId, Error> {
|
pub fn archive<G: Signer>(&mut self, signer: &G) -> Result<EntryId, Error> {
|
||||||
self.lifecycle(Lifecycle::Archived, signer)
|
self.lifecycle(Lifecycle::Archived, signer)
|
||||||
|
|
@ -2223,6 +2331,7 @@ where
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
|
@ -2310,7 +2419,7 @@ mod test {
|
||||||
let (revision_id, _) = patch.revisions().last().unwrap();
|
let (revision_id, _) = patch.revisions().last().unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
patch
|
patch
|
||||||
.comment(revision_id, "patch comment", None, &alice.signer)
|
.comment(revision_id, "patch comment", None, None, [], &alice.signer)
|
||||||
.is_ok(),
|
.is_ok(),
|
||||||
"can comment on patch"
|
"can comment on patch"
|
||||||
);
|
);
|
||||||
|
|
@ -2666,6 +2775,7 @@ mod test {
|
||||||
"I like these lines of code",
|
"I like these lines of code",
|
||||||
Some(location.clone()),
|
Some(location.clone()),
|
||||||
None,
|
None,
|
||||||
|
[],
|
||||||
&alice.signer,
|
&alice.signer,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ impl<T: Serialize> Serialize for Comment<T> {
|
||||||
where
|
where
|
||||||
S: serde::ser::Serializer,
|
S: serde::ser::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_struct("Comment", 6)?;
|
let mut state = serializer.serialize_struct("Comment", 8)?;
|
||||||
state.serialize_field("author", &self.author())?;
|
state.serialize_field("author", &self.author())?;
|
||||||
if let Some(loc) = &self.location {
|
if let Some(loc) = &self.location {
|
||||||
state.serialize_field("location", loc)?;
|
state.serialize_field("location", loc)?;
|
||||||
|
|
@ -92,7 +92,11 @@ impl<T: Serialize> Serialize for Comment<T> {
|
||||||
state.serialize_field("replyTo", &to)?;
|
state.serialize_field("replyTo", &to)?;
|
||||||
}
|
}
|
||||||
state.serialize_field("reactions", &self.reactions)?;
|
state.serialize_field("reactions", &self.reactions)?;
|
||||||
|
state.serialize_field("resolved", &self.resolved)?;
|
||||||
state.serialize_field("body", self.body())?;
|
state.serialize_field("body", self.body())?;
|
||||||
|
if let Some(location) = self.location() {
|
||||||
|
state.serialize_field("location", &location)?;
|
||||||
|
}
|
||||||
|
|
||||||
let embeds = self.embeds();
|
let embeds = self.embeds();
|
||||||
if !embeds.is_empty() {
|
if !embeds.is_empty() {
|
||||||
|
|
@ -179,6 +183,11 @@ impl<L> Comment<L> {
|
||||||
self.location.as_ref()
|
self.location.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get comment resolution status.
|
||||||
|
pub fn resolved(&self) -> bool {
|
||||||
|
self.resolved
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the embedded media.
|
/// Return the embedded media.
|
||||||
pub fn embeds(&self) -> &[Embed<Uri>] {
|
pub fn embeds(&self) -> &[Embed<Uri>] {
|
||||||
// SAFETY: There is always at least one edit. This is guaranteed by the [`Comment`]
|
// SAFETY: There is always at least one edit. This is guaranteed by the [`Comment`]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue