mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-04 03:00:32 +08:00
Add issues_by_id, patches_by_id, revisions_by_patch, revisions_by_id
Also provides all the types related to it and a new npm script that allows generating those types quickly.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"check-js": "scripts/check-js",
|
||||
"check-rs": "scripts/check-rs",
|
||||
"format": "npx prettier '**/*.@(ts|js|svelte|json|css|html|yml)' --write",
|
||||
"generate-types": "cargo test --manifest-path ./src-tauri/Cargo.toml && npx prettier ./src-tauri/bindings --write",
|
||||
"tauri": "npx tauri"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
import type { Edit } from "./Edit";
|
||||
import type { Never } from "./Never";
|
||||
|
||||
export type Comment<T = Never> = {
|
||||
id: string;
|
||||
author: Author;
|
||||
edits: Array<Edit>;
|
||||
reactions: string;
|
||||
replyTo?: string;
|
||||
location?: T;
|
||||
resolved: boolean;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
|
||||
export type Edit = {
|
||||
author: Author;
|
||||
timestamp: bigint;
|
||||
body: string;
|
||||
embeds: { name: string; content: string };
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
import type { Comment } from "./Comment";
|
||||
import type { Never } from "./Never";
|
||||
|
||||
export type Issue = {
|
||||
id: string;
|
||||
@@ -7,6 +9,7 @@ export type Issue = {
|
||||
title: string;
|
||||
state: { status: "closed"; reason: "other" | "solved" } | { status: "open" };
|
||||
assignees: Array<Author>;
|
||||
discussion: Array<Comment<Never>>;
|
||||
labels: Array<string>;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* The `Infallible` type does not have a `Serialize`/`Deserialize`
|
||||
* implementation. The `Never` type imitates `Infallible` and
|
||||
* provides the derived implementations.
|
||||
*/
|
||||
export type Never = never;
|
||||
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
import type { CodeLocation } from "./CodeLocation";
|
||||
|
||||
export type Reaction = {
|
||||
emoji: string;
|
||||
authors: Array<Author>;
|
||||
location?: CodeLocation;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
import type { CodeLocation } from "./CodeLocation";
|
||||
import type { Comment } from "./Comment";
|
||||
|
||||
export type Review = {
|
||||
id: string;
|
||||
author: Author;
|
||||
verdict?: "accept" | "reject";
|
||||
summary: string | null;
|
||||
comments: Array<Comment<CodeLocation>>;
|
||||
timestamp: bigint;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "./Author";
|
||||
import type { CodeLocation } from "./CodeLocation";
|
||||
import type { Comment } from "./Comment";
|
||||
import type { Edit } from "./Edit";
|
||||
import type { Reaction } from "./Reaction";
|
||||
import type { Review } from "./Review";
|
||||
|
||||
export type Revision = {
|
||||
author: Author;
|
||||
description: Array<Edit>;
|
||||
base: string;
|
||||
oid: string;
|
||||
reviews: Array<Review>;
|
||||
timestamp: bigint;
|
||||
discussion: Array<Comment<CodeLocation>>;
|
||||
reactions: Array<Reaction>;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
use radicle::crypto::ssh;
|
||||
|
||||
use crate::{error::Error, AppState};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use radicle::cob::ObjectId;
|
||||
use radicle::git::Oid;
|
||||
use radicle::identity::RepoId;
|
||||
use radicle::issue::cache::Issues;
|
||||
use radicle::issue::IssueId;
|
||||
use radicle::patch::cache::Patches;
|
||||
|
||||
use crate::error::Error;
|
||||
@@ -32,6 +37,22 @@ pub fn list_issues(
|
||||
Ok::<_, Error>(issues)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn issues_by_id(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: RepoId,
|
||||
id: IssueId,
|
||||
) -> Result<Option<cobs::Issue>, Error> {
|
||||
let (repo, _) = ctx.repo(rid)?;
|
||||
let issues = ctx.profile.issues(&repo)?;
|
||||
let issue = issues.get(&id)?;
|
||||
|
||||
let aliases = &ctx.profile.aliases();
|
||||
let issue = issue.map(|issue| cobs::Issue::new(id, issue, aliases));
|
||||
|
||||
Ok::<_, Error>(issue)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_patches(
|
||||
ctx: tauri::State<AppState>,
|
||||
@@ -58,6 +79,66 @@ pub fn list_patches(
|
||||
Ok::<_, Error>(patches)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn patches_by_id(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: RepoId,
|
||||
id: String,
|
||||
) -> Result<Option<cobs::Patch>, Error> {
|
||||
let id = ObjectId::from_str(&id)?;
|
||||
let (repo, _) = ctx.repo(rid)?;
|
||||
let patches = ctx.profile.patches(&repo)?;
|
||||
let patch = patches.get(&id)?;
|
||||
|
||||
let aliases = &ctx.profile.aliases();
|
||||
let patches = patch.map(|patch| cobs::Patch::new(id, patch, aliases));
|
||||
|
||||
Ok::<_, Error>(patches)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn revisions_by_patch(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: RepoId,
|
||||
id: String,
|
||||
) -> Result<Option<Vec<cobs::Revision>>, Error> {
|
||||
let id = ObjectId::from_str(&id)?;
|
||||
let (repo, _) = ctx.repo(rid)?;
|
||||
let patches = ctx.profile.patches(&repo)?;
|
||||
|
||||
let revisions = patches.get(&id)?.map(|patch| {
|
||||
let aliases = &ctx.profile.aliases();
|
||||
|
||||
patch
|
||||
.revisions()
|
||||
.map(|(_, r)| cobs::Revision::new(r.clone(), aliases))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
Ok::<_, Error>(revisions)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn revisions_by_id(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: RepoId,
|
||||
id: String,
|
||||
revision_id: String,
|
||||
) -> Result<Option<cobs::Revision>, Error> {
|
||||
let id = ObjectId::from_str(&id)?;
|
||||
let (repo, _) = ctx.repo(rid)?;
|
||||
let patches = ctx.profile.patches(&repo)?;
|
||||
let revision = patches.get(&id)?.and_then(|patch| {
|
||||
let revision_id = Oid::from_str(&revision_id).ok()?;
|
||||
let aliases = &ctx.profile.aliases();
|
||||
|
||||
patch
|
||||
.revision(&revision_id.into())
|
||||
.map(|r| cobs::Revision::new(r.clone(), aliases))
|
||||
});
|
||||
Ok::<_, Error>(revision)
|
||||
}
|
||||
|
||||
mod query {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -141,9 +141,13 @@ pub fn run() {
|
||||
repos::list_repos,
|
||||
repos::repo_by_id,
|
||||
cobs::list_issues,
|
||||
cobs::issues_by_id,
|
||||
cobs::list_patches,
|
||||
thread::create_issue_comment,
|
||||
thread::create_patch_comment,
|
||||
cobs::patches_by_id,
|
||||
cobs::revisions_by_patch,
|
||||
cobs::revisions_by_id,
|
||||
profile::config,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
+249
-5
@@ -1,15 +1,19 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::git;
|
||||
use radicle::crypto;
|
||||
use radicle::identity;
|
||||
use radicle::issue;
|
||||
use radicle::node::{Alias, AliasStore};
|
||||
use radicle::patch;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
use radicle::storage::git;
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
pub(crate) struct Author {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Author {
|
||||
#[ts(as = "String")]
|
||||
did: identity::Did,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -38,6 +42,7 @@ pub struct Issue {
|
||||
#[ts(type = "{ status: 'closed', reason: 'other' | 'solved' } | { status: 'open' } ")]
|
||||
state: issue::State,
|
||||
assignees: Vec<Author>,
|
||||
discussion: Vec<Comment>,
|
||||
#[ts(as = "Vec<String>")]
|
||||
labels: Vec<cob::Label>,
|
||||
#[ts(type = "number")]
|
||||
@@ -55,6 +60,10 @@ impl Issue {
|
||||
.assignees()
|
||||
.map(|did| Author::new(*did, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
discussion: issue
|
||||
.comments()
|
||||
.map(|(id, c)| Comment::<Never>::new(*id, c.clone(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
labels: issue.labels().cloned().collect::<Vec<_>>(),
|
||||
timestamp: issue.timestamp(),
|
||||
}
|
||||
@@ -106,6 +115,230 @@ impl Patch {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Revision {
|
||||
author: Author,
|
||||
description: Vec<Edit>,
|
||||
#[ts(as = "String")]
|
||||
base: git::Oid,
|
||||
#[ts(as = "String")]
|
||||
oid: git::Oid,
|
||||
reviews: Vec<Review>,
|
||||
#[ts(as = "i64")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
discussion: Vec<Comment<CodeLocation>>,
|
||||
reactions: Vec<Reaction>,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
pub fn new(value: cob::patch::Revision, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
author: Author::new(*value.author().id(), aliases),
|
||||
description: value
|
||||
.edits()
|
||||
.map(|e| Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
base: *value.base(),
|
||||
oid: value.head(),
|
||||
reviews: value
|
||||
.reviews()
|
||||
.map(|(id, r)| Review::new(*id, r.clone(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: value.timestamp(),
|
||||
discussion: value
|
||||
.discussion()
|
||||
.comments()
|
||||
.map(|(id, c)| Comment::<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)| {
|
||||
Reaction::new(
|
||||
*emoji,
|
||||
authors,
|
||||
location.as_ref().map(|l| CodeLocation::new(l.clone())),
|
||||
aliases,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Reaction {
|
||||
#[ts(as = "String")]
|
||||
emoji: cob::Reaction,
|
||||
authors: Vec<Author>,
|
||||
#[ts(optional)]
|
||||
location: Option<CodeLocation>,
|
||||
}
|
||||
|
||||
impl Reaction {
|
||||
pub fn new(
|
||||
emoji: cob::Reaction,
|
||||
authors: Vec<&crypto::PublicKey>,
|
||||
location: Option<CodeLocation>,
|
||||
aliases: &impl AliasStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
emoji,
|
||||
authors: authors
|
||||
.into_iter()
|
||||
.map(|a| Author::new(a.into(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
location,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Review {
|
||||
#[ts(as = "String")]
|
||||
id: identity::PublicKey,
|
||||
author: Author,
|
||||
#[ts(type = "'accept' | 'reject'")]
|
||||
#[ts(optional)]
|
||||
verdict: Option<cob::patch::Verdict>,
|
||||
summary: Option<String>,
|
||||
comments: Vec<Comment<CodeLocation>>,
|
||||
#[ts(as = "i64")]
|
||||
timestamp: cob::common::Timestamp,
|
||||
}
|
||||
|
||||
impl Review {
|
||||
pub fn new(
|
||||
id: identity::PublicKey,
|
||||
review: cob::patch::Review,
|
||||
aliases: &impl AliasStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
author: Author::new(review.author().id, aliases),
|
||||
verdict: review.verdict(),
|
||||
summary: review.summary().map(|s| s.to_string()),
|
||||
comments: review
|
||||
.comments()
|
||||
.map(|(id, c)| Comment::<CodeLocation>::new(*id, c.clone(), aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: review.timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(TS, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Edit {
|
||||
pub author: Author,
|
||||
#[ts(as = "i64")]
|
||||
pub timestamp: cob::common::Timestamp,
|
||||
pub body: String,
|
||||
#[ts(type = "{ name: string, content: string }")]
|
||||
pub embeds: Vec<cob::change::store::Embed<cob::common::Uri>>,
|
||||
}
|
||||
|
||||
impl Edit {
|
||||
pub fn new(edit: &cob::thread::Edit, aliases: &impl AliasStore) -> Self {
|
||||
Self {
|
||||
author: Author::new(edit.author.into(), aliases),
|
||||
timestamp: edit.timestamp,
|
||||
body: edit.body.clone(),
|
||||
embeds: edit.embeds.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Infallible` type does not have a `Serialize`/`Deserialize`
|
||||
/// implementation. The `Never` type imitates `Infallible` and
|
||||
/// provides the derived implementations.
|
||||
#[derive(TS, Serialize)]
|
||||
pub enum Never {}
|
||||
|
||||
#[derive(Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Comment<T = Never> {
|
||||
#[ts(as = "String")]
|
||||
id: cob::thread::CommentId,
|
||||
author: Author,
|
||||
edits: Vec<Edit>,
|
||||
#[ts(as = "String")]
|
||||
reactions: BTreeMap<cob::common::Reaction, Vec<cob::op::ActorId>>,
|
||||
#[ts(as = "Option<String>")]
|
||||
#[ts(optional)]
|
||||
reply_to: Option<cob::thread::CommentId>,
|
||||
#[ts(optional)]
|
||||
location: Option<T>,
|
||||
resolved: bool,
|
||||
}
|
||||
|
||||
impl Comment<CodeLocation> {
|
||||
pub fn new(
|
||||
id: cob::thread::CommentId,
|
||||
comment: cob::thread::Comment<cob::common::CodeLocation>,
|
||||
aliases: &impl AliasStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
author: Author::new(comment.author().into(), aliases),
|
||||
edits: comment
|
||||
.edits()
|
||||
.map(|e| Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: comment
|
||||
.reactions()
|
||||
.into_iter()
|
||||
.map(|(r, a)| (*r, a.into_iter().copied().collect::<Vec<_>>()))
|
||||
.collect::<BTreeMap<cob::common::Reaction, Vec<cob::op::ActorId>>>(),
|
||||
reply_to: comment.reply_to(),
|
||||
location: comment.location().map(|l| CodeLocation::new(l.clone())),
|
||||
resolved: comment.is_resolved(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn new(
|
||||
id: cob::thread::CommentId,
|
||||
comment: cob::thread::Comment,
|
||||
aliases: &impl AliasStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
author: Author::new(comment.author().into(), aliases),
|
||||
edits: comment
|
||||
.edits()
|
||||
.map(|e| Edit::new(e, aliases))
|
||||
.collect::<Vec<_>>(),
|
||||
reactions: comment
|
||||
.reactions()
|
||||
.into_iter()
|
||||
.map(|(r, a)| (*r, a.into_iter().copied().collect::<Vec<_>>()))
|
||||
.collect::<BTreeMap<cob::common::Reaction, Vec<cob::op::ActorId>>>(),
|
||||
reply_to: comment.reply_to(),
|
||||
location: None,
|
||||
resolved: comment.is_resolved(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(TS, Serialize, Deserialize)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -156,6 +389,17 @@ impl From<cob::CodeLocation> for CodeLocation {
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeLocation {
|
||||
pub fn new(location: cob::common::CodeLocation) -> Self {
|
||||
Self {
|
||||
commit: location.commit,
|
||||
path: location.path,
|
||||
old: location.old.map(|l| l.into()),
|
||||
new: location.new.map(|l| l.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLocation> for cob::CodeLocation {
|
||||
fn from(val: CodeLocation) -> Self {
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user