mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Implement a custom list_patches method to read directly from cache
This commit is contained in:
@@ -2,25 +2,55 @@ use radicle::cob;
|
||||
use radicle::git;
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
|
||||
use radicle::patch::{Action, TYPENAME};
|
||||
|
||||
use radicle_types as types;
|
||||
use radicle_types::cobs;
|
||||
use radicle_types::domain::patch::models;
|
||||
use radicle_types::domain::patch::service::Service;
|
||||
use radicle_types::domain::patch::traits::PatchService;
|
||||
use radicle_types::error::Error;
|
||||
use radicle_types::outbound::sqlite::Sqlite;
|
||||
use radicle_types::traits::cobs::Cobs;
|
||||
use radicle_types::traits::patch::Patches;
|
||||
use radicle_types::traits::patch::PatchesMut;
|
||||
use radicle_types::traits::Profile;
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_patches(
|
||||
ctx: tauri::State<'_, AppState>,
|
||||
sqlite_service: tauri::State<'_, Service<Sqlite>>,
|
||||
rid: identity::RepoId,
|
||||
status: Option<types::cobs::query::PatchStatus>,
|
||||
skip: Option<usize>,
|
||||
take: Option<usize>,
|
||||
) -> Result<types::cobs::PaginatedQuery<Vec<types::cobs::patch::Patch>>, Error> {
|
||||
ctx.list_patches(rid, status, skip, take)
|
||||
) -> Result<types::cobs::PaginatedQuery<Vec<models::patch::Patch>>, Error> {
|
||||
let profile = ctx.profile();
|
||||
let cursor = skip.unwrap_or(0);
|
||||
let take = take.unwrap_or(20);
|
||||
let aliases = profile.aliases();
|
||||
let patches = match status {
|
||||
None => sqlite_service.list(rid)?.collect::<Vec<_>>(),
|
||||
Some(s) => sqlite_service
|
||||
.list_by_status(rid, s.into())?
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
let more = cursor + take < patches.len();
|
||||
|
||||
let patches = patches
|
||||
.into_iter()
|
||||
.map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
|
||||
.skip(cursor)
|
||||
.take(take)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok::<_, Error>(cobs::PaginatedQuery {
|
||||
cursor,
|
||||
more,
|
||||
content: patches,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -28,7 +58,7 @@ pub fn patch_by_id(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Option<types::cobs::patch::Patch>, Error> {
|
||||
) -> Result<Option<models::patch::Patch>, Error> {
|
||||
ctx.get_patch(rid, id)
|
||||
}
|
||||
|
||||
@@ -37,7 +67,7 @@ pub fn revisions_by_patch(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Option<Vec<types::cobs::patch::Revision>>, Error> {
|
||||
) -> Result<Option<Vec<models::patch::Revision>>, Error> {
|
||||
ctx.revisions_by_patch(rid, id)
|
||||
}
|
||||
|
||||
@@ -47,7 +77,7 @@ pub fn revision_by_patch_and_id(
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
revision_id: git::Oid,
|
||||
) -> Result<Option<types::cobs::patch::Revision>, Error> {
|
||||
) -> Result<Option<models::patch::Revision>, Error> {
|
||||
ctx.revision_by_id(rid, id, revision_id)
|
||||
}
|
||||
|
||||
@@ -86,7 +116,7 @@ pub fn edit_draft_review(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
edit: types::cobs::patch::ReviewEdit,
|
||||
edit: models::patch::ReviewEdit,
|
||||
) -> Result<(), Error> {
|
||||
ctx.edit_draft_review(rid, cob_id, edit)
|
||||
}
|
||||
@@ -97,7 +127,7 @@ pub fn get_draft_review(
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
revision_id: patch::RevisionId,
|
||||
) -> Option<types::cobs::patch::Review> {
|
||||
) -> Option<models::patch::Review> {
|
||||
ctx.get_draft_review(rid, cob_id, revision_id)
|
||||
}
|
||||
|
||||
@@ -106,9 +136,9 @@ pub fn edit_patch(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
action: types::cobs::patch::Action,
|
||||
opts: types::cobs::CobOptions,
|
||||
) -> Result<types::cobs::patch::Patch, Error> {
|
||||
action: models::patch::Action,
|
||||
opts: cobs::CobOptions,
|
||||
) -> Result<models::patch::Patch, Error> {
|
||||
ctx.edit_patch(rid, cob_id, action, opts)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ mod commands;
|
||||
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
use radicle::cob::cache::COBS_DB_FILE;
|
||||
use radicle::node::{Handle, NOTIFICATIONS_DB_FILE};
|
||||
use radicle::Node;
|
||||
|
||||
@@ -39,6 +40,10 @@ pub fn run() {
|
||||
)?;
|
||||
let inbox_service = domain::inbox::service::Service::new(inbox_db);
|
||||
|
||||
let patch_db =
|
||||
radicle_types::outbound::sqlite::Sqlite::reader(profile.cobs().join(COBS_DB_FILE))?;
|
||||
let patch_service = domain::patch::service::Service::new(patch_db);
|
||||
|
||||
let events_handler = app.handle().clone();
|
||||
let node_handler = app.handle().clone();
|
||||
|
||||
@@ -46,6 +51,7 @@ pub fn run() {
|
||||
let node_status = node.clone();
|
||||
|
||||
app.manage(inbox_service);
|
||||
app.manage(patch_service);
|
||||
app.manage(AppState { profile });
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
|
||||
@@ -1,500 +1 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use radicle::node::AliasStore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::git;
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
|
||||
use crate::cobs;
|
||||
|
||||
#[derive(Debug, TS, Serialize)]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Patch {
|
||||
id: String,
|
||||
author: cobs::Author,
|
||||
title: String,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
head: git::Oid,
|
||||
state: State,
|
||||
assignees: Vec<cobs::Author>,
|
||||
#[ts(as = "Vec<String>")]
|
||||
labels: Vec<cob::Label>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::Timestamp,
|
||||
revision_count: usize,
|
||||
}
|
||||
|
||||
impl Patch {
|
||||
pub fn new(id: patch::PatchId, patch: &patch::Patch, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
author: cobs::Author::new(patch.author().id(), aliases),
|
||||
title: patch.title().to_string(),
|
||||
state: patch.state().clone().into(),
|
||||
base: *patch.base(),
|
||||
head: *patch.head(),
|
||||
assignees: patch
|
||||
.assignees()
|
||||
.map(|did| cobs::Author::new(&did, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
labels: patch.labels().cloned().collect::<Vec<_>>(),
|
||||
timestamp: patch.timestamp(),
|
||||
revision_count: patch.revisions().count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> u64 {
|
||||
self.timestamp.as_millis()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "status")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum State {
|
||||
Draft,
|
||||
Open {
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
#[ts(as = "Option<Vec<(String, String)>>", optional)]
|
||||
conflicts: Vec<(patch::RevisionId, git::Oid)>,
|
||||
},
|
||||
Archived,
|
||||
Merged {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
commit: git::Oid,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<State> for patch::State {
|
||||
fn from(value: State) -> Self {
|
||||
match value {
|
||||
State::Archived => Self::Archived,
|
||||
State::Draft => Self::Draft,
|
||||
State::Merged { revision, commit } => Self::Merged { revision, commit },
|
||||
State::Open { conflicts } => Self::Open { conflicts },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<patch::State> for State {
|
||||
fn from(value: patch::State) -> Self {
|
||||
match value {
|
||||
patch::State::Archived => Self::Archived,
|
||||
patch::State::Draft => Self::Draft,
|
||||
patch::State::Merged { revision, commit } => Self::Merged { revision, commit },
|
||||
patch::State::Open { conflicts } => Self::Open { conflicts },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct ReviewEdit {
|
||||
#[ts(as = "String")]
|
||||
pub review_id: cob::patch::ReviewId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub summary: Option<String>,
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
pub labels: Vec<cob::Label>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Revision {
|
||||
#[ts(as = "String")]
|
||||
id: patch::RevisionId,
|
||||
author: cobs::Author,
|
||||
description: Vec<Edit>,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
head: git::Oid,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
reviews: Vec<Review>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
discussion: Vec<cobs::thread::Comment<cobs::thread::CodeLocation>>,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
reactions: Vec<cobs::thread::Reaction>,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
pub fn new(value: cob::patch::Revision, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: value.id(),
|
||||
author: cobs::Author::new(value.author().id(), aliases),
|
||||
description: value
|
||||
.edits()
|
||||
.map(|e| Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
base: *value.base(),
|
||||
head: value.head(),
|
||||
reviews: value
|
||||
.reviews()
|
||||
.map(|(_, r)| Review::new(r.clone(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: value.timestamp(),
|
||||
discussion: value
|
||||
.discussion()
|
||||
.comments()
|
||||
.map(|(id, c)| {
|
||||
cobs::thread::Comment::<cobs::thread::CodeLocation>::new(
|
||||
*id,
|
||||
c.clone(),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: value
|
||||
.reactions()
|
||||
.iter()
|
||||
.flat_map(|(location, reactions)| {
|
||||
let reaction_by_author = reactions.iter().fold(
|
||||
BTreeMap::new(),
|
||||
|mut acc: BTreeMap<&cob::Reaction, Vec<_>>, (author, emoji)| {
|
||||
acc.entry(emoji).or_default().push(author);
|
||||
acc
|
||||
},
|
||||
);
|
||||
reaction_by_author
|
||||
.into_iter()
|
||||
.map(|(emoji, authors)| {
|
||||
cobs::thread::Reaction::new(
|
||||
*emoji,
|
||||
authors.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
location
|
||||
.as_ref()
|
||||
.map(|l| cobs::thread::CodeLocation::new(l.clone())),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(TS, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Edit {
|
||||
pub author: cobs::Author,
|
||||
#[ts(type = "number")]
|
||||
pub timestamp: cob::common::Timestamp,
|
||||
pub body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
pub embeds: Vec<cobs::thread::Embed>,
|
||||
}
|
||||
|
||||
impl Edit {
|
||||
pub fn new(edit: &cob::thread::Edit, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
author: cobs::Author::new(&edit.author.into(), aliases),
|
||||
timestamp: edit.timestamp,
|
||||
body: edit.body.clone(),
|
||||
embeds: edit
|
||||
.embeds
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|e| e.into())
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Review {
|
||||
#[ts(as = "String")]
|
||||
id: cob::patch::ReviewId,
|
||||
author: cobs::Author,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
comments: Vec<cobs::thread::Comment<cobs::thread::CodeLocation>>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
}
|
||||
|
||||
impl Review {
|
||||
pub fn new(review: cob::patch::Review, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: review.id(),
|
||||
author: cobs::Author::new(&review.author().id, aliases),
|
||||
verdict: review.verdict().map(|v| v.into()),
|
||||
summary: review.summary().map(|s| s.to_string()),
|
||||
comments: review
|
||||
.comments()
|
||||
.map(|(id, c)| {
|
||||
cobs::thread::Comment::<cobs::thread::CodeLocation>::new(
|
||||
*id,
|
||||
c.clone(),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: review.timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum Verdict {
|
||||
Accept,
|
||||
Reject,
|
||||
}
|
||||
|
||||
impl From<cob::patch::Verdict> for Verdict {
|
||||
fn from(value: cob::patch::Verdict) -> Self {
|
||||
match value {
|
||||
cob::patch::Verdict::Accept => Self::Accept,
|
||||
cob::patch::Verdict::Reject => Self::Reject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Verdict> for cob::patch::Verdict {
|
||||
fn from(value: Verdict) -> Self {
|
||||
match value {
|
||||
Verdict::Accept => Self::Accept,
|
||||
Verdict::Reject => Self::Reject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum Action {
|
||||
#[serde(rename = "edit")]
|
||||
Edit {
|
||||
title: String,
|
||||
#[ts(as = "String")]
|
||||
target: patch::MergeTarget,
|
||||
},
|
||||
#[serde(rename = "label")]
|
||||
Label {
|
||||
#[ts(as = "Vec<String>")]
|
||||
labels: BTreeSet<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "lifecycle")]
|
||||
Lifecycle {
|
||||
#[ts(type = "{ status: 'draft' | 'open' | 'archived' }")]
|
||||
state: patch::Lifecycle,
|
||||
},
|
||||
#[serde(rename = "assign")]
|
||||
Assign {
|
||||
#[ts(as = "Vec<String>")]
|
||||
assignees: BTreeSet<identity::Did>,
|
||||
},
|
||||
#[serde(rename = "merge")]
|
||||
Merge {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
commit: git::Oid,
|
||||
},
|
||||
|
||||
#[serde(rename = "review")]
|
||||
Review {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
labels: Vec<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "review.edit")]
|
||||
ReviewEdit {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
labels: Vec<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "review.redact")]
|
||||
ReviewRedact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
},
|
||||
#[serde(rename = "review.comment")]
|
||||
ReviewComment {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
body: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(as = "Option<String>", optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "review.comment.edit")]
|
||||
ReviewCommentEdit {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "review.comment.redact")]
|
||||
ReviewCommentRedact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
#[serde(rename = "review.comment.react")]
|
||||
ReviewCommentReact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
#[serde(rename = "review.comment.resolve")]
|
||||
ReviewCommentResolve {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
#[serde(rename = "review.comment.unresolve")]
|
||||
ReviewCommentUnresolve {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
|
||||
#[serde(rename = "revision")]
|
||||
Revision {
|
||||
description: String,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
oid: git::Oid,
|
||||
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
|
||||
#[ts(as = "Option<BTreeSet<(String, String)>>", optional)]
|
||||
resolves: BTreeSet<(cob::EntryId, cob::thread::CommentId)>,
|
||||
},
|
||||
#[serde(rename = "revision.edit")]
|
||||
RevisionEdit {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
description: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.react")]
|
||||
RevisionReact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
#[serde(rename = "revision.redact")]
|
||||
RevisionRedact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename = "revision.comment")]
|
||||
RevisionComment {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
body: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(as = "Option<String>", optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.comment.edit")]
|
||||
RevisionCommentEdit {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.comment.redact")]
|
||||
RevisionCommentRedact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
},
|
||||
#[serde(rename = "revision.comment.react")]
|
||||
RevisionCommentReact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use radicle::node::AliasStore;
|
||||
use radicle::{cob, git, identity};
|
||||
|
||||
use crate::cobs;
|
||||
use crate::domain::patch::models;
|
||||
|
||||
#[derive(TS, Serialize, Deserialize)]
|
||||
#[ts(export)]
|
||||
@@ -39,7 +40,7 @@ pub struct Comment<T = cobs::Never> {
|
||||
#[ts(as = "String")]
|
||||
id: cob::thread::CommentId,
|
||||
author: cobs::Author,
|
||||
edits: Vec<cobs::patch::Edit>,
|
||||
edits: Vec<models::patch::Edit>,
|
||||
reactions: Vec<cobs::thread::Reaction>,
|
||||
#[ts(as = "Option<String>", optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
@@ -62,7 +63,7 @@ impl Comment<CodeLocation> {
|
||||
author: cobs::Author::new(&comment.author().into(), aliases),
|
||||
edits: comment
|
||||
.edits()
|
||||
.map(|e| cobs::patch::Edit::new(e, aliases))
|
||||
.map(|e| models::patch::Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: comment
|
||||
.reactions()
|
||||
@@ -100,7 +101,7 @@ impl Comment<cobs::Never> {
|
||||
author: cobs::Author::new(&comment.author().into(), aliases),
|
||||
edits: comment
|
||||
.edits()
|
||||
.map(|e| cobs::patch::Edit::new(e, aliases))
|
||||
.map(|e| models::patch::Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: comment
|
||||
.reactions()
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod inbox;
|
||||
pub mod patch;
|
||||
|
||||
@@ -9,6 +9,7 @@ use ts_rs::TS;
|
||||
|
||||
use crate::cobs::stream::{self, CobStream};
|
||||
use crate::cobs::{self, Author};
|
||||
use crate::domain::patch::models;
|
||||
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -157,8 +158,8 @@ pub struct Patch {
|
||||
#[ts(type = "number")]
|
||||
pub timestamp: localtime::LocalTime,
|
||||
pub title: String,
|
||||
pub status: cobs::patch::State,
|
||||
pub actions: Vec<ActionWithAuthor<cobs::patch::Action>>,
|
||||
pub status: models::patch::State,
|
||||
pub actions: Vec<ActionWithAuthor<models::patch::Action>>,
|
||||
}
|
||||
|
||||
/// Type of notification.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod models;
|
||||
pub mod service;
|
||||
pub mod traits;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod patch;
|
||||
@@ -0,0 +1,510 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use radicle::node::AliasStore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::git;
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
|
||||
use crate::cobs;
|
||||
|
||||
#[derive(Debug, TS, Serialize)]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Patch {
|
||||
id: String,
|
||||
author: cobs::Author,
|
||||
title: String,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
head: git::Oid,
|
||||
state: State,
|
||||
assignees: Vec<cobs::Author>,
|
||||
#[ts(as = "Vec<String>")]
|
||||
labels: Vec<cob::Label>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::Timestamp,
|
||||
revision_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ListPatchesError {
|
||||
#[error(transparent)]
|
||||
Sqlite(#[from] sqlite::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
// to be extended as new error scenarios are introduced
|
||||
}
|
||||
|
||||
impl Patch {
|
||||
pub fn new(id: patch::PatchId, patch: &patch::Patch, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
author: cobs::Author::new(patch.author().id(), aliases),
|
||||
title: patch.title().to_string(),
|
||||
state: patch.state().clone().into(),
|
||||
base: *patch.base(),
|
||||
head: *patch.head(),
|
||||
assignees: patch
|
||||
.assignees()
|
||||
.map(|did| cobs::Author::new(&did, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
labels: patch.labels().cloned().collect::<Vec<_>>(),
|
||||
timestamp: patch.timestamp(),
|
||||
revision_count: patch.revisions().count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> u64 {
|
||||
self.timestamp.as_millis()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "status")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum State {
|
||||
Draft,
|
||||
Open {
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
#[ts(as = "Option<Vec<(String, String)>>", optional)]
|
||||
conflicts: Vec<(patch::RevisionId, git::Oid)>,
|
||||
},
|
||||
Archived,
|
||||
Merged {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
commit: git::Oid,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<State> for patch::State {
|
||||
fn from(value: State) -> Self {
|
||||
match value {
|
||||
State::Archived => Self::Archived,
|
||||
State::Draft => Self::Draft,
|
||||
State::Merged { revision, commit } => Self::Merged { revision, commit },
|
||||
State::Open { conflicts } => Self::Open { conflicts },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<patch::State> for State {
|
||||
fn from(value: patch::State) -> Self {
|
||||
match value {
|
||||
patch::State::Archived => Self::Archived,
|
||||
patch::State::Draft => Self::Draft,
|
||||
patch::State::Merged { revision, commit } => Self::Merged { revision, commit },
|
||||
patch::State::Open { conflicts } => Self::Open { conflicts },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct ReviewEdit {
|
||||
#[ts(as = "String")]
|
||||
pub review_id: cob::patch::ReviewId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub summary: Option<String>,
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
pub labels: Vec<cob::Label>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Revision {
|
||||
#[ts(as = "String")]
|
||||
id: patch::RevisionId,
|
||||
author: cobs::Author,
|
||||
description: Vec<Edit>,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
head: git::Oid,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
reviews: Vec<Review>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
discussion: Vec<cobs::thread::Comment<cobs::thread::CodeLocation>>,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
reactions: Vec<cobs::thread::Reaction>,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
pub fn new(value: cob::patch::Revision, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: value.id(),
|
||||
author: cobs::Author::new(value.author().id(), aliases),
|
||||
description: value
|
||||
.edits()
|
||||
.map(|e| Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
base: *value.base(),
|
||||
head: value.head(),
|
||||
reviews: value
|
||||
.reviews()
|
||||
.map(|(_, r)| Review::new(r.clone(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: value.timestamp(),
|
||||
discussion: value
|
||||
.discussion()
|
||||
.comments()
|
||||
.map(|(id, c)| {
|
||||
cobs::thread::Comment::<cobs::thread::CodeLocation>::new(
|
||||
*id,
|
||||
c.clone(),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: value
|
||||
.reactions()
|
||||
.iter()
|
||||
.flat_map(|(location, reactions)| {
|
||||
let reaction_by_author = reactions.iter().fold(
|
||||
BTreeMap::new(),
|
||||
|mut acc: BTreeMap<&cob::Reaction, Vec<_>>, (author, emoji)| {
|
||||
acc.entry(emoji).or_default().push(author);
|
||||
acc
|
||||
},
|
||||
);
|
||||
reaction_by_author
|
||||
.into_iter()
|
||||
.map(|(emoji, authors)| {
|
||||
cobs::thread::Reaction::new(
|
||||
*emoji,
|
||||
authors.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
location
|
||||
.as_ref()
|
||||
.map(|l| cobs::thread::CodeLocation::new(l.clone())),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(TS, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Edit {
|
||||
pub author: cobs::Author,
|
||||
#[ts(type = "number")]
|
||||
pub timestamp: cob::common::Timestamp,
|
||||
pub body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
pub embeds: Vec<cobs::thread::Embed>,
|
||||
}
|
||||
|
||||
impl Edit {
|
||||
pub fn new(edit: &cob::thread::Edit, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
author: cobs::Author::new(&edit.author.into(), aliases),
|
||||
timestamp: edit.timestamp,
|
||||
body: edit.body.clone(),
|
||||
embeds: edit
|
||||
.embeds
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|e| e.into())
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub struct Review {
|
||||
#[ts(as = "String")]
|
||||
id: cob::patch::ReviewId,
|
||||
author: cobs::Author,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
comments: Vec<cobs::thread::Comment<cobs::thread::CodeLocation>>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
}
|
||||
|
||||
impl Review {
|
||||
pub fn new(review: cob::patch::Review, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
id: review.id(),
|
||||
author: cobs::Author::new(&review.author().id, aliases),
|
||||
verdict: review.verdict().map(|v| v.into()),
|
||||
summary: review.summary().map(|s| s.to_string()),
|
||||
comments: review
|
||||
.comments()
|
||||
.map(|(id, c)| {
|
||||
cobs::thread::Comment::<cobs::thread::CodeLocation>::new(
|
||||
*id,
|
||||
c.clone(),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: review.timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum Verdict {
|
||||
Accept,
|
||||
Reject,
|
||||
}
|
||||
|
||||
impl From<cob::patch::Verdict> for Verdict {
|
||||
fn from(value: cob::patch::Verdict) -> Self {
|
||||
match value {
|
||||
cob::patch::Verdict::Accept => Self::Accept,
|
||||
cob::patch::Verdict::Reject => Self::Reject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Verdict> for cob::patch::Verdict {
|
||||
fn from(value: Verdict) -> Self {
|
||||
match value {
|
||||
Verdict::Accept => Self::Accept,
|
||||
Verdict::Reject => Self::Reject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/patch/")]
|
||||
pub enum Action {
|
||||
#[serde(rename = "edit")]
|
||||
Edit {
|
||||
title: String,
|
||||
#[ts(as = "String")]
|
||||
target: patch::MergeTarget,
|
||||
},
|
||||
#[serde(rename = "label")]
|
||||
Label {
|
||||
#[ts(as = "Vec<String>")]
|
||||
labels: BTreeSet<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "lifecycle")]
|
||||
Lifecycle {
|
||||
#[ts(type = "{ status: 'draft' | 'open' | 'archived' }")]
|
||||
state: patch::Lifecycle,
|
||||
},
|
||||
#[serde(rename = "assign")]
|
||||
Assign {
|
||||
#[ts(as = "Vec<String>")]
|
||||
assignees: BTreeSet<identity::Did>,
|
||||
},
|
||||
#[serde(rename = "merge")]
|
||||
Merge {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
commit: git::Oid,
|
||||
},
|
||||
|
||||
#[serde(rename = "review")]
|
||||
Review {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
labels: Vec<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "review.edit")]
|
||||
ReviewEdit {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
summary: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<Verdict>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<Vec<String>>", optional)]
|
||||
labels: Vec<cob::Label>,
|
||||
},
|
||||
#[serde(rename = "review.redact")]
|
||||
ReviewRedact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
},
|
||||
#[serde(rename = "review.comment")]
|
||||
ReviewComment {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
body: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(as = "Option<String>", optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "review.comment.edit")]
|
||||
ReviewCommentEdit {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "review.comment.redact")]
|
||||
ReviewCommentRedact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
#[serde(rename = "review.comment.react")]
|
||||
ReviewCommentReact {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
#[serde(rename = "review.comment.resolve")]
|
||||
ReviewCommentResolve {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
#[serde(rename = "review.comment.unresolve")]
|
||||
ReviewCommentUnresolve {
|
||||
#[ts(as = "String")]
|
||||
review: patch::ReviewId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::EntryId,
|
||||
},
|
||||
|
||||
#[serde(rename = "revision")]
|
||||
Revision {
|
||||
description: String,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
oid: git::Oid,
|
||||
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
|
||||
#[ts(as = "Option<BTreeSet<(String, String)>>", optional)]
|
||||
resolves: BTreeSet<(cob::EntryId, cob::thread::CommentId)>,
|
||||
},
|
||||
#[serde(rename = "revision.edit")]
|
||||
RevisionEdit {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
description: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.react")]
|
||||
RevisionReact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
#[serde(rename = "revision.redact")]
|
||||
RevisionRedact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename = "revision.comment")]
|
||||
RevisionComment {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
location: Option<cobs::thread::CodeLocation>,
|
||||
body: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(as = "Option<String>", optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.comment.edit")]
|
||||
RevisionCommentEdit {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
body: String,
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
embeds: Vec<cobs::thread::Embed>,
|
||||
},
|
||||
#[serde(rename = "revision.comment.redact")]
|
||||
RevisionCommentRedact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
},
|
||||
#[serde(rename = "revision.comment.react")]
|
||||
RevisionCommentReact {
|
||||
#[ts(as = "String")]
|
||||
revision: patch::RevisionId,
|
||||
#[ts(as = "String")]
|
||||
comment: cob::thread::CommentId,
|
||||
#[ts(as = "String")]
|
||||
reaction: cob::Reaction,
|
||||
active: bool,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
use radicle::patch::Patch;
|
||||
use radicle::patch::PatchId;
|
||||
|
||||
use crate::domain::patch::traits::{PatchService, PatchStorage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Service<I>
|
||||
where
|
||||
I: PatchStorage,
|
||||
{
|
||||
patches: I,
|
||||
}
|
||||
|
||||
impl<I> Service<I>
|
||||
where
|
||||
I: PatchStorage,
|
||||
{
|
||||
pub fn new(patches: I) -> Self {
|
||||
Self { patches }
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> PatchService for Service<I>
|
||||
where
|
||||
I: PatchStorage,
|
||||
{
|
||||
fn list(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, super::models::patch::ListPatchesError>
|
||||
{
|
||||
self.patches.list(rid)
|
||||
}
|
||||
|
||||
fn list_by_status(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
status: patch::Status,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, super::models::patch::ListPatchesError>
|
||||
{
|
||||
self.patches.list_by_status(rid, status)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
use radicle::patch::Patch;
|
||||
use radicle::patch::PatchId;
|
||||
|
||||
use crate::domain::patch::models;
|
||||
|
||||
pub trait PatchStorage {
|
||||
fn list(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
|
||||
|
||||
fn list_by_status(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
status: patch::Status,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
|
||||
}
|
||||
|
||||
pub trait PatchService {
|
||||
fn list(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
|
||||
|
||||
fn list_by_status(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
status: patch::Status,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
|
||||
}
|
||||
@@ -17,6 +17,10 @@ pub enum Error {
|
||||
#[from] crate::domain::inbox::models::notification::ListNotificationsError,
|
||||
),
|
||||
|
||||
/// CobStore error.
|
||||
#[error(transparent)]
|
||||
ListPatchesError(#[from] crate::domain::patch::models::patch::ListPatchesError),
|
||||
|
||||
/// CobStore error.
|
||||
#[error(transparent)]
|
||||
CobStore(#[from] radicle::cob::store::Error),
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use radicle::patch::{Patch, PatchId, Status};
|
||||
use radicle::{git, identity};
|
||||
use sqlite as sql;
|
||||
|
||||
use crate::domain::inbox::models::notification;
|
||||
use crate::domain::inbox::traits::InboxStorage;
|
||||
use crate::domain::patch::models::patch::ListPatchesError;
|
||||
use crate::domain::patch::traits::PatchStorage;
|
||||
use crate::error::Error;
|
||||
|
||||
pub struct Sqlite {
|
||||
@@ -29,6 +33,57 @@ impl Sqlite {
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchStorage for Sqlite {
|
||||
fn list(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, ListPatchesError> {
|
||||
let mut stmt = self.db.prepare(
|
||||
"SELECT id, patch, (
|
||||
SELECT MIN(JSON_EXTRACT(revision.value, '$.timestamp'))
|
||||
FROM JSON_EACH(JSON_EXTRACT(p.patch, '$.revisions')) AS revision
|
||||
) AS last_revision_timestamp
|
||||
FROM patches AS p
|
||||
WHERE repo = ?1
|
||||
ORDER BY last_revision_timestamp DESC;
|
||||
",
|
||||
)?;
|
||||
stmt.bind((1, &rid))?;
|
||||
Ok(stmt.into_iter().filter_map(|row| {
|
||||
let row = row.ok()?;
|
||||
let id = PatchId::from_str(row.read::<&str, _>("id")).ok()?;
|
||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch")).ok()?;
|
||||
Some((id, patch))
|
||||
}))
|
||||
}
|
||||
|
||||
fn list_by_status(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
status: Status,
|
||||
) -> Result<impl Iterator<Item = (PatchId, Patch)>, ListPatchesError> {
|
||||
let mut stmt = self.db.prepare(
|
||||
"SELECT id, patch, (
|
||||
SELECT MIN(JSON_EXTRACT(revision.value, '$.timestamp'))
|
||||
FROM JSON_EACH(JSON_EXTRACT(p.patch, '$.revisions')) AS revision
|
||||
) AS last_revision_timestamp
|
||||
FROM patches AS p
|
||||
WHERE repo = ?1
|
||||
AND patch->>'$.state.status' = ?2
|
||||
ORDER BY last_revision_timestamp DESC;
|
||||
",
|
||||
)?;
|
||||
stmt.bind((1, &rid))?;
|
||||
stmt.bind((2, sql::Value::String(status.to_string())))?;
|
||||
Ok(stmt.into_iter().filter_map(|row| {
|
||||
let row = row.ok()?;
|
||||
let id = PatchId::from_str(row.read::<&str, _>("id")).ok()?;
|
||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch")).ok()?;
|
||||
Some((id, patch))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl InboxStorage for Sqlite {
|
||||
fn counts_by_repo(
|
||||
&self,
|
||||
|
||||
@@ -5,59 +5,22 @@ use radicle::storage::ReadStorage;
|
||||
use radicle::{cob, git, identity, patch, Node};
|
||||
|
||||
use crate::cobs;
|
||||
use crate::domain::patch::models;
|
||||
use crate::error::Error;
|
||||
use crate::traits::Profile;
|
||||
|
||||
pub trait Patches: Profile {
|
||||
fn list_patches(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
status: Option<cobs::query::PatchStatus>,
|
||||
skip: Option<usize>,
|
||||
take: Option<usize>,
|
||||
) -> Result<cobs::PaginatedQuery<Vec<cobs::patch::Patch>>, Error> {
|
||||
let profile = self.profile();
|
||||
let cursor = skip.unwrap_or(0);
|
||||
let take = take.unwrap_or(20);
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
let aliases = &profile.aliases();
|
||||
let cache = profile.patches(&repo)?;
|
||||
let patches = match status {
|
||||
None => cache.list()?.collect::<Vec<_>>(),
|
||||
Some(s) => cache.list_by_status(&s.into())?.collect::<Vec<_>>(),
|
||||
};
|
||||
let more = cursor + take < patches.len();
|
||||
|
||||
let mut patches = patches
|
||||
.into_iter()
|
||||
.filter_map(|p| {
|
||||
p.map(|(id, patch)| cobs::patch::Patch::new(id, &patch, aliases))
|
||||
.ok()
|
||||
})
|
||||
.skip(cursor)
|
||||
.take(take)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
patches.sort_by_key(|b| std::cmp::Reverse(b.timestamp()));
|
||||
|
||||
Ok::<_, Error>(cobs::PaginatedQuery {
|
||||
cursor,
|
||||
more,
|
||||
content: patches,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_patch(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Option<cobs::patch::Patch>, Error> {
|
||||
) -> Result<Option<models::patch::Patch>, Error> {
|
||||
let profile = self.profile();
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
let patches = profile.patches(&repo)?;
|
||||
let patch = patches.get(&id.into())?;
|
||||
let aliases = &profile.aliases();
|
||||
let patches = patch.map(|patch| cobs::patch::Patch::new(id.into(), &patch, aliases));
|
||||
let patches = patch.map(|patch| models::patch::Patch::new(id.into(), &patch, aliases));
|
||||
|
||||
Ok::<_, Error>(patches)
|
||||
}
|
||||
@@ -66,7 +29,7 @@ pub trait Patches: Profile {
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Option<Vec<cobs::patch::Revision>>, Error> {
|
||||
) -> Result<Option<Vec<models::patch::Revision>>, Error> {
|
||||
let profile = self.profile();
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
let patches = profile.patches(&repo)?;
|
||||
@@ -75,7 +38,7 @@ pub trait Patches: Profile {
|
||||
|
||||
patch
|
||||
.revisions()
|
||||
.map(|(_, r)| cobs::patch::Revision::new(r.clone(), aliases))
|
||||
.map(|(_, r)| models::patch::Revision::new(r.clone(), aliases))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@@ -87,7 +50,7 @@ pub trait Patches: Profile {
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
revision_id: git::Oid,
|
||||
) -> Result<Option<cobs::patch::Revision>, Error> {
|
||||
) -> Result<Option<models::patch::Revision>, Error> {
|
||||
let profile = self.profile();
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
let patches = profile.patches(&repo)?;
|
||||
@@ -96,7 +59,7 @@ pub trait Patches: Profile {
|
||||
|
||||
patch
|
||||
.revision(&revision_id.into())
|
||||
.map(|r| cobs::patch::Revision::new(r.clone(), aliases))
|
||||
.map(|r| models::patch::Revision::new(r.clone(), aliases))
|
||||
});
|
||||
|
||||
Ok::<_, Error>(revision)
|
||||
@@ -108,9 +71,9 @@ pub trait PatchesMut: Profile {
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
action: cobs::patch::Action,
|
||||
action: models::patch::Action,
|
||||
opts: cobs::CobOptions,
|
||||
) -> Result<cobs::patch::Patch, Error> {
|
||||
) -> Result<models::patch::Patch, Error> {
|
||||
let profile = self.profile();
|
||||
let mut node = Node::new(profile.socket());
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
@@ -120,7 +83,7 @@ pub trait PatchesMut: Profile {
|
||||
let mut patch = patches.get_mut(&cob_id.into())?;
|
||||
|
||||
match action {
|
||||
cobs::patch::Action::RevisionEdit {
|
||||
models::patch::Action::RevisionEdit {
|
||||
revision,
|
||||
description,
|
||||
embeds,
|
||||
@@ -132,13 +95,13 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionCommentRedact { revision, comment } => {
|
||||
models::patch::Action::RevisionCommentRedact { revision, comment } => {
|
||||
patch.comment_redact(revision, comment, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewCommentRedact { review, comment } => {
|
||||
models::patch::Action::ReviewCommentRedact { review, comment } => {
|
||||
patch.redact_review_comment(review, comment, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewCommentReact {
|
||||
models::patch::Action::ReviewCommentReact {
|
||||
review,
|
||||
comment,
|
||||
reaction,
|
||||
@@ -146,16 +109,16 @@ pub trait PatchesMut: Profile {
|
||||
} => {
|
||||
patch.react_review_comment(review, comment, reaction, active, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewCommentResolve { review, comment } => {
|
||||
models::patch::Action::ReviewCommentResolve { review, comment } => {
|
||||
patch.resolve_review_comment(review, comment, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewCommentUnresolve { review, comment } => {
|
||||
models::patch::Action::ReviewCommentUnresolve { review, comment } => {
|
||||
patch.unresolve_review_comment(review, comment, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::Edit { title, target } => {
|
||||
models::patch::Action::Edit { title, target } => {
|
||||
patch.edit(title, target, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewEdit {
|
||||
models::patch::Action::ReviewEdit {
|
||||
review,
|
||||
summary,
|
||||
verdict,
|
||||
@@ -163,7 +126,7 @@ pub trait PatchesMut: Profile {
|
||||
} => {
|
||||
patch.review_edit(review, verdict.map(|v| v.into()), summary, labels, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::Review {
|
||||
models::patch::Action::Review {
|
||||
revision,
|
||||
summary,
|
||||
verdict,
|
||||
@@ -177,10 +140,10 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewRedact { review } => {
|
||||
models::patch::Action::ReviewRedact { review } => {
|
||||
patch.redact_review(review, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewComment {
|
||||
models::patch::Action::ReviewComment {
|
||||
review,
|
||||
body,
|
||||
location,
|
||||
@@ -196,7 +159,7 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::ReviewCommentEdit {
|
||||
models::patch::Action::ReviewCommentEdit {
|
||||
review,
|
||||
comment,
|
||||
body,
|
||||
@@ -210,16 +173,16 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::Lifecycle { state } => {
|
||||
models::patch::Action::Lifecycle { state } => {
|
||||
patch.lifecycle(state, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::Assign { assignees } => {
|
||||
models::patch::Action::Assign { assignees } => {
|
||||
patch.assign(assignees, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::Label { labels } => {
|
||||
models::patch::Action::Label { labels } => {
|
||||
patch.label(labels, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionReact {
|
||||
models::patch::Action::RevisionReact {
|
||||
revision,
|
||||
reaction,
|
||||
location,
|
||||
@@ -233,7 +196,7 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionComment {
|
||||
models::patch::Action::RevisionComment {
|
||||
revision,
|
||||
location,
|
||||
body,
|
||||
@@ -249,7 +212,7 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionCommentEdit {
|
||||
models::patch::Action::RevisionCommentEdit {
|
||||
revision,
|
||||
comment,
|
||||
body,
|
||||
@@ -263,7 +226,7 @@ pub trait PatchesMut: Profile {
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionCommentReact {
|
||||
models::patch::Action::RevisionCommentReact {
|
||||
revision,
|
||||
comment,
|
||||
reaction,
|
||||
@@ -271,13 +234,13 @@ pub trait PatchesMut: Profile {
|
||||
} => {
|
||||
patch.comment_react(revision, comment, reaction, active, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::RevisionRedact { revision } => {
|
||||
models::patch::Action::RevisionRedact { revision } => {
|
||||
patch.redact(revision, &signer)?;
|
||||
}
|
||||
cobs::patch::Action::Merge { .. } => {
|
||||
models::patch::Action::Merge { .. } => {
|
||||
unimplemented!("We don't support merging of patches through the desktop")
|
||||
}
|
||||
cobs::patch::Action::Revision { .. } => {
|
||||
models::patch::Action::Revision { .. } => {
|
||||
unimplemented!("We don't support creating new revisions through the desktop")
|
||||
}
|
||||
}
|
||||
@@ -288,7 +251,7 @@ pub trait PatchesMut: Profile {
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<_, Error>(cobs::patch::Patch::new(*patch.id(), &patch, &aliases))
|
||||
Ok::<_, Error>(models::patch::Patch::new(*patch.id(), &patch, &aliases))
|
||||
}
|
||||
|
||||
/// Gets the draft review of the local user for a specific patch revision in a repository.
|
||||
@@ -302,7 +265,7 @@ pub trait PatchesMut: Profile {
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
revision_id: patch::RevisionId,
|
||||
) -> Option<cobs::patch::Review> {
|
||||
) -> Option<models::patch::Review> {
|
||||
let profile = self.profile();
|
||||
let aliases = profile.aliases();
|
||||
let repo = profile.storage.repository(rid).ok()?;
|
||||
@@ -315,7 +278,7 @@ pub trait PatchesMut: Profile {
|
||||
let review: Option<patch::Review> =
|
||||
revision.and_then(|rev| rev.review_by(signer.public_key()).cloned());
|
||||
|
||||
review.map(|r| cobs::patch::Review::new(r, &aliases))
|
||||
review.map(|r| models::patch::Review::new(r, &aliases))
|
||||
}
|
||||
|
||||
/// Edits a draft review for a specific patch revision in a repository.
|
||||
@@ -326,7 +289,7 @@ pub trait PatchesMut: Profile {
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
cob_id: git::Oid,
|
||||
edit: cobs::patch::ReviewEdit,
|
||||
edit: models::patch::ReviewEdit,
|
||||
) -> Result<(), Error> {
|
||||
let profile = self.profile();
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
|
||||
@@ -8,17 +8,21 @@ use axum::routing::post;
|
||||
use axum::Router;
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use hyper::Method;
|
||||
use radicle_types::domain::inbox::models::notification::NotificationCount;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_http::cors::{self, CorsLayer};
|
||||
|
||||
use radicle::{git, identity};
|
||||
use radicle_types as types;
|
||||
use radicle_types::cobs;
|
||||
use radicle_types::cobs::issue;
|
||||
use radicle_types::cobs::issue::NewIssue;
|
||||
use radicle_types::cobs::patch;
|
||||
use radicle_types::cobs::CobOptions;
|
||||
use radicle_types::domain::inbox::models::notification::NotificationCount;
|
||||
use radicle_types::domain::patch::models;
|
||||
use radicle_types::domain::patch::service::Service;
|
||||
use radicle_types::domain::patch::traits::PatchService;
|
||||
use radicle_types::error::Error;
|
||||
use radicle_types::outbound::sqlite::Sqlite;
|
||||
use radicle_types::traits::auth::Auth;
|
||||
use radicle_types::traits::cobs::Cobs;
|
||||
use radicle_types::traits::issue::{Issues, IssuesMut};
|
||||
@@ -30,6 +34,7 @@ use radicle_types::traits::Profile;
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
profile: Arc<radicle::Profile>,
|
||||
patches: Arc<Service<Sqlite>>,
|
||||
}
|
||||
|
||||
impl Auth for Context {}
|
||||
@@ -47,8 +52,8 @@ impl Profile for Context {
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(profile: Arc<radicle::Profile>) -> Self {
|
||||
Self { profile }
|
||||
pub fn new(profile: Arc<radicle::Profile>, patches: Arc<Service<Sqlite>>) -> Self {
|
||||
Self { profile, patches }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +75,7 @@ pub fn router(ctx: Context) -> Router {
|
||||
)
|
||||
.route(
|
||||
"/activity_by_patch",
|
||||
post(activity_patch_handler::<patch::Action>),
|
||||
post(activity_patch_handler::<models::patch::Action>),
|
||||
)
|
||||
.route("/get_diff", post(diff_handler))
|
||||
.route("/list_issues", post(issues_handler))
|
||||
@@ -345,9 +350,31 @@ async fn patches_handler(
|
||||
status,
|
||||
}): Json<PatchesBody>,
|
||||
) -> impl IntoResponse {
|
||||
let patches = ctx.list_patches(rid, status, skip, take)?;
|
||||
let profile = ctx.profile;
|
||||
let cursor = skip.unwrap_or(0);
|
||||
let take = take.unwrap_or(20);
|
||||
let aliases = profile.aliases();
|
||||
let patches = match status {
|
||||
None => ctx.patches.list(rid)?.collect::<Vec<_>>(),
|
||||
Some(s) => ctx
|
||||
.patches
|
||||
.list_by_status(rid, s.into())?
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
let more = cursor + take < patches.len();
|
||||
|
||||
Ok::<_, Error>(Json(patches))
|
||||
let patches = patches
|
||||
.into_iter()
|
||||
.map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
|
||||
.skip(cursor)
|
||||
.take(take)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok::<_, Error>(Json(cobs::PaginatedQuery {
|
||||
cursor,
|
||||
more,
|
||||
content: patches,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
||||
@@ -4,8 +4,11 @@ use std::sync::Arc;
|
||||
use axum::Router;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use radicle::cob::cache::COBS_DB_FILE;
|
||||
use radicle::Profile;
|
||||
|
||||
use radicle_types::domain::patch::service::Service as PatchService;
|
||||
|
||||
mod api;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -25,7 +28,12 @@ pub async fn run(options: Options) -> anyhow::Result<()> {
|
||||
|
||||
fn router(profile: Profile) -> anyhow::Result<Router> {
|
||||
let profile = Arc::new(profile);
|
||||
let ctx = api::Context::new(profile);
|
||||
|
||||
let patch_db =
|
||||
radicle_types::outbound::sqlite::Sqlite::reader(profile.cobs().join(COBS_DB_FILE))?;
|
||||
let patch_service = PatchService::new(patch_db);
|
||||
|
||||
let ctx = api::Context::new(profile, Arc::new(patch_service));
|
||||
|
||||
Ok(api::router(ctx))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user