mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-11-08 04:40:58 +08:00
601 lines
16 KiB
Rust
601 lines
16 KiB
Rust
use std::ops::Deref;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use axum::extract::State;
|
|
use axum::response::{IntoResponse, Json};
|
|
use axum::routing::post;
|
|
use axum::Router;
|
|
use hyper::header::CONTENT_TYPE;
|
|
use hyper::Method;
|
|
use serde::{Deserialize, Serialize};
|
|
use tower_http::cors::{self, CorsLayer};
|
|
|
|
use radicle::{git, identity};
|
|
use radicle_types as types;
|
|
use radicle_types::cobs::issue;
|
|
use radicle_types::cobs::issue::NewIssue;
|
|
use radicle_types::cobs::CobOptions;
|
|
use radicle_types::cobs::{self, FromRadicleAction};
|
|
use radicle_types::config::Version;
|
|
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::issue::{Issues, IssuesMut};
|
|
use radicle_types::traits::job::Jobs;
|
|
use radicle_types::traits::patch::{Patches, PatchesMut};
|
|
use radicle_types::traits::repo::{Repo, Show};
|
|
use radicle_types::traits::thread::Thread;
|
|
use radicle_types::traits::Profile;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Context {
|
|
profile: Arc<radicle::Profile>,
|
|
patches: Arc<Service<Sqlite>>,
|
|
}
|
|
|
|
impl Repo for Context {}
|
|
impl Cobs for Context {}
|
|
impl Thread for Context {}
|
|
impl Issues for Context {}
|
|
impl IssuesMut for Context {}
|
|
impl Jobs for Context {}
|
|
impl Patches for Context {}
|
|
impl PatchesMut for Context {}
|
|
impl Profile for Context {
|
|
fn profile(&self) -> radicle::Profile {
|
|
self.profile.deref().clone()
|
|
}
|
|
}
|
|
|
|
impl Context {
|
|
pub fn new(profile: Arc<radicle::Profile>, patches: Arc<Service<Sqlite>>) -> Self {
|
|
Self { profile, patches }
|
|
}
|
|
}
|
|
|
|
pub fn router(ctx: Context) -> Router {
|
|
Router::new()
|
|
.route("/config", post(config_handler))
|
|
.route("/authenticate", post(auth_handler))
|
|
.route("/repo_count", post(repo_count_handler))
|
|
.route("/list_repos", post(repo_root_handler))
|
|
.route("/list_repos_summary", post(list_repos_summary_handler))
|
|
.route(
|
|
"/seeded_not_replicated",
|
|
post(seeded_not_replicated_handler),
|
|
)
|
|
.route("/repo_by_id", post(repo_handler))
|
|
.route("/version", post(version_handler))
|
|
.route("/diff_stats", post(diff_stats_handler))
|
|
.route(
|
|
"/activity_by_issue",
|
|
post(activity_issue_handler::<radicle::issue::Action, issue::Action>),
|
|
)
|
|
.route(
|
|
"/activity_by_patch",
|
|
post(activity_patch_handler::<radicle::patch::Action, models::patch::Action>),
|
|
)
|
|
.route("/repo_readme", post(readme_handler))
|
|
.route("/repo_tree", post(tree_handler))
|
|
.route("/repo_blob", post(blob_handler))
|
|
.route("/get_diff", post(diff_handler))
|
|
.route("/get_commit_diff", post(commit_diff_handler))
|
|
.route("/list_repo_commits", post(list_repo_commits_handler))
|
|
.route("/repo_commit_count", post(repo_commit_count_handler))
|
|
.route("/repo_commit", post(repo_commit_handler))
|
|
.route("/list_issues", post(issues_handler))
|
|
.route("/create_issue", post(create_issue_handler))
|
|
.route("/create_issue_comment", post(create_issue_comment_handler))
|
|
.route("/edit_issue", post(edit_issue_handler))
|
|
.route("/issue_by_id", post(issue_handler))
|
|
.route("/comment_threads_by_issue_id", post(issue_threads_handler))
|
|
.route("/list_patches", post(patches_handler))
|
|
.route("/patch_by_id", post(patch_handler))
|
|
.route("/revisions_by_patch", post(revision_handler))
|
|
.route("/get_embed", post(get_embeds_handler))
|
|
.route("/save_embed_by_path", post(save_embed_handler))
|
|
.route("/save_embed_by_clipboard", post(save_embed_handler))
|
|
.route("/save_embed_by_bytes", post(save_embed_handler))
|
|
.route("/save_embed_to_disk", post(save_embed_handler))
|
|
.route("/list_jobs", post(jobs_handler))
|
|
.route("/list_notifications", post(list_notifications_handler))
|
|
.route("/notification_count", post(notification_count_handler))
|
|
.route("/clear_notifications", post(clear_notifications_handler))
|
|
.layer(
|
|
CorsLayer::new()
|
|
.allow_origin(cors::Any)
|
|
.allow_methods([Method::POST, Method::GET])
|
|
.allow_headers([CONTENT_TYPE]),
|
|
)
|
|
.with_state(ctx)
|
|
}
|
|
|
|
async fn config_handler(State(ctx): State<Context>) -> impl IntoResponse {
|
|
let config = ctx.config();
|
|
|
|
Ok::<_, Error>(Json(config))
|
|
}
|
|
|
|
async fn auth_handler() -> impl IntoResponse {
|
|
Ok::<_, Error>(Json(()))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Options {
|
|
show: Show,
|
|
}
|
|
|
|
async fn repo_root_handler(
|
|
State(ctx): State<Context>,
|
|
Json(Options { show }): Json<Options>,
|
|
) -> impl IntoResponse {
|
|
let repos = ctx.list_repos(show)?;
|
|
|
|
Ok::<_, Error>(Json(repos))
|
|
}
|
|
|
|
async fn repo_count_handler(State(ctx): State<Context>) -> impl IntoResponse {
|
|
let repos = ctx.repo_count()?;
|
|
Ok::<_, Error>(Json(repos))
|
|
}
|
|
|
|
async fn list_repos_summary_handler(State(ctx): State<Context>) -> impl IntoResponse {
|
|
let repos = ctx.list_repos_summary()?;
|
|
Ok::<_, Error>(Json(repos))
|
|
}
|
|
|
|
async fn seeded_not_replicated_handler(State(ctx): State<Context>) -> impl IntoResponse {
|
|
let rids = ctx.seeded_not_replicated()?;
|
|
Ok::<_, Error>(Json(rids))
|
|
}
|
|
|
|
async fn list_notifications_handler() -> impl IntoResponse {
|
|
Ok::<_, Error>(Json(Vec::<
|
|
radicle_types::domain::inbox::models::notification::NotificationsByRepo,
|
|
>::new()))
|
|
}
|
|
|
|
async fn notification_count_handler() -> impl IntoResponse {
|
|
Ok::<_, Error>(Json(0_usize))
|
|
}
|
|
|
|
async fn clear_notifications_handler() -> impl IntoResponse {
|
|
Ok::<_, Error>(Json(()))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct RepoBody {
|
|
pub rid: identity::RepoId,
|
|
}
|
|
|
|
async fn repo_handler(
|
|
State(ctx): State<Context>,
|
|
Json(RepoBody { rid }): Json<RepoBody>,
|
|
) -> impl IntoResponse {
|
|
let info = ctx.repo_by_id(rid)?;
|
|
|
|
Ok::<_, Error>(Json(info))
|
|
}
|
|
|
|
async fn version_handler() -> impl IntoResponse {
|
|
let version = Version {
|
|
version: String::from("0.6.1"),
|
|
head: String::from("51cf6cfbfe0be992ee709c49e6da589aa0f148c5"),
|
|
};
|
|
|
|
Ok::<_, Error>(Json(version))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct DiffStatsBody {
|
|
pub rid: identity::RepoId,
|
|
pub base: git::Oid,
|
|
pub head: git::Oid,
|
|
}
|
|
|
|
async fn diff_stats_handler(
|
|
State(ctx): State<Context>,
|
|
Json(DiffStatsBody { rid, base, head }): Json<DiffStatsBody>,
|
|
) -> impl IntoResponse {
|
|
let info = ctx.diff_stats(rid, base, head)?;
|
|
|
|
Ok::<_, Error>(Json(info))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct DiffBody {
|
|
pub rid: identity::RepoId,
|
|
pub options: types::cobs::diff::DiffOptions,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct ReadmeBody {
|
|
pub rid: identity::RepoId,
|
|
}
|
|
|
|
async fn readme_handler(
|
|
State(ctx): State<Context>,
|
|
Json(ReadmeBody { rid }): Json<ReadmeBody>,
|
|
) -> impl IntoResponse {
|
|
let readme = ctx.repo_readme(rid, None)?;
|
|
|
|
Ok::<_, Error>(Json(readme))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct TreeBody {
|
|
pub rid: identity::RepoId,
|
|
pub path: PathBuf,
|
|
}
|
|
|
|
async fn tree_handler(
|
|
State(ctx): State<Context>,
|
|
Json(TreeBody { rid, path }): Json<TreeBody>,
|
|
) -> impl IntoResponse {
|
|
let info = ctx.repo_tree(rid, path)?;
|
|
|
|
Ok::<_, Error>(Json(info))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct BlobBody {
|
|
pub rid: identity::RepoId,
|
|
pub path: PathBuf,
|
|
}
|
|
|
|
async fn blob_handler(
|
|
State(ctx): State<Context>,
|
|
Json(BlobBody { rid, path }): Json<BlobBody>,
|
|
) -> impl IntoResponse {
|
|
let info = ctx.repo_blob(rid, path)?;
|
|
|
|
Ok::<_, Error>(Json(info))
|
|
}
|
|
|
|
async fn diff_handler(
|
|
State(ctx): State<Context>,
|
|
Json(DiffBody { rid, options }): Json<DiffBody>,
|
|
) -> impl IntoResponse {
|
|
let info = ctx.get_diff(rid, options)?;
|
|
|
|
Ok::<_, Error>(Json(info))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct CommitDiffBody {
|
|
pub rid: identity::RepoId,
|
|
pub sha: git::Oid,
|
|
pub unified: Option<u32>,
|
|
pub highlight: Option<bool>,
|
|
}
|
|
|
|
async fn commit_diff_handler(
|
|
State(ctx): State<Context>,
|
|
Json(CommitDiffBody {
|
|
rid,
|
|
sha,
|
|
unified,
|
|
highlight,
|
|
}): Json<CommitDiffBody>,
|
|
) -> impl IntoResponse {
|
|
let diff = ctx.get_commit_diff(rid, sha, unified, highlight)?;
|
|
|
|
Ok::<_, Error>(Json(diff))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct ListRepoCommitsBody {
|
|
pub rid: identity::RepoId,
|
|
pub head: Option<git::Oid>,
|
|
pub skip: Option<usize>,
|
|
pub take: Option<usize>,
|
|
}
|
|
|
|
async fn list_repo_commits_handler(
|
|
State(ctx): State<Context>,
|
|
Json(ListRepoCommitsBody {
|
|
rid,
|
|
head,
|
|
skip,
|
|
take,
|
|
}): Json<ListRepoCommitsBody>,
|
|
) -> impl IntoResponse {
|
|
let commits = ctx.list_repo_commits(rid, head, skip, take)?;
|
|
|
|
Ok::<_, Error>(Json(commits))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct RepoCommitCountBody {
|
|
pub rid: identity::RepoId,
|
|
pub head: git::Oid,
|
|
}
|
|
|
|
async fn repo_commit_count_handler(
|
|
State(ctx): State<Context>,
|
|
Json(RepoCommitCountBody { rid, head }): Json<RepoCommitCountBody>,
|
|
) -> impl IntoResponse {
|
|
let count = ctx.repo_commit_count(rid, head)?;
|
|
|
|
Ok::<_, Error>(Json(count))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct RepoCommitBody {
|
|
pub rid: identity::RepoId,
|
|
pub sha: git::Oid,
|
|
}
|
|
|
|
async fn repo_commit_handler(
|
|
State(ctx): State<Context>,
|
|
Json(RepoCommitBody { rid, sha }): Json<RepoCommitBody>,
|
|
) -> impl IntoResponse {
|
|
let commit = ctx.repo_commit(rid, sha)?;
|
|
|
|
Ok::<_, Error>(Json(commit))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct IssuesBody {
|
|
pub rid: identity::RepoId,
|
|
pub status: Option<types::cobs::query::IssueStatus>,
|
|
}
|
|
|
|
async fn issues_handler(
|
|
State(ctx): State<Context>,
|
|
Json(IssuesBody { rid, status }): Json<IssuesBody>,
|
|
) -> impl IntoResponse {
|
|
let issues = ctx.list_issues(rid, status)?;
|
|
|
|
Ok::<_, Error>(Json(issues))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct CreateIssuesBody {
|
|
pub rid: identity::RepoId,
|
|
pub opts: CobOptions,
|
|
pub new: NewIssue,
|
|
}
|
|
|
|
async fn create_issue_handler(
|
|
State(ctx): State<Context>,
|
|
Json(CreateIssuesBody { rid, opts, new }): Json<CreateIssuesBody>,
|
|
) -> impl IntoResponse {
|
|
let issues = ctx.create_issue(rid, new, opts)?;
|
|
|
|
Ok::<_, Error>(Json(issues))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct CreateIssueCommentBody {
|
|
pub rid: identity::RepoId,
|
|
pub new: types::cobs::thread::NewIssueComment,
|
|
pub opts: types::cobs::CobOptions,
|
|
}
|
|
|
|
async fn create_issue_comment_handler(
|
|
State(ctx): State<Context>,
|
|
Json(CreateIssueCommentBody { rid, opts, new }): Json<CreateIssueCommentBody>,
|
|
) -> impl IntoResponse {
|
|
let comment = ctx.create_issue_comment(rid, new, opts)?;
|
|
|
|
Ok::<_, Error>(Json(comment))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct EditIssuesBody {
|
|
pub rid: identity::RepoId,
|
|
pub cob_id: git::Oid,
|
|
pub action: issue::Action,
|
|
pub opts: CobOptions,
|
|
}
|
|
|
|
async fn edit_issue_handler(
|
|
State(ctx): State<Context>,
|
|
Json(EditIssuesBody {
|
|
rid,
|
|
cob_id,
|
|
action,
|
|
opts,
|
|
}): Json<EditIssuesBody>,
|
|
) -> impl IntoResponse {
|
|
let issues = ctx.edit_issue(rid, cob_id, action, opts)?;
|
|
|
|
Ok::<_, Error>(Json(issues))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct ActivityBody {
|
|
pub rid: identity::RepoId,
|
|
pub id: git::Oid,
|
|
}
|
|
|
|
async fn activity_issue_handler<
|
|
A: serde::Serialize + serde::de::DeserializeOwned,
|
|
B: FromRadicleAction<A> + serde::Serialize,
|
|
>(
|
|
State(ctx): State<Context>,
|
|
Json(ActivityBody { rid, id }): Json<ActivityBody>,
|
|
) -> impl IntoResponse {
|
|
let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::issue::TYPENAME, id)?;
|
|
|
|
Ok::<_, Error>(Json(activity))
|
|
}
|
|
|
|
async fn activity_patch_handler<
|
|
A: serde::Serialize + serde::de::DeserializeOwned,
|
|
B: FromRadicleAction<A> + serde::Serialize,
|
|
>(
|
|
State(ctx): State<Context>,
|
|
Json(ActivityBody { rid, id }): Json<ActivityBody>,
|
|
) -> impl IntoResponse {
|
|
let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::patch::TYPENAME, id)?;
|
|
|
|
Ok::<_, Error>(Json(activity))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct EmbedBody {
|
|
pub rid: identity::RepoId,
|
|
pub name: Option<String>,
|
|
pub oid: git::Oid,
|
|
}
|
|
|
|
async fn get_embeds_handler(
|
|
State(ctx): State<Context>,
|
|
Json(EmbedBody { rid, name, oid }): Json<EmbedBody>,
|
|
) -> impl IntoResponse {
|
|
let embed = ctx.get_embed(rid, name, oid)?;
|
|
|
|
Ok::<_, Error>(Json(embed))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct CreateEmbedBody {
|
|
pub rid: identity::RepoId,
|
|
pub path: PathBuf,
|
|
}
|
|
|
|
async fn save_embed_handler(
|
|
State(ctx): State<Context>,
|
|
Json(CreateEmbedBody { rid, path }): Json<CreateEmbedBody>,
|
|
) -> impl IntoResponse {
|
|
let embed = ctx.save_embed_by_path(rid, path)?;
|
|
|
|
Ok::<_, Error>(Json(embed))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct IssueBody {
|
|
pub rid: identity::RepoId,
|
|
pub id: git::Oid,
|
|
}
|
|
|
|
async fn issue_handler(
|
|
State(ctx): State<Context>,
|
|
Json(IssueBody { rid, id }): Json<IssueBody>,
|
|
) -> impl IntoResponse {
|
|
let issue = ctx.issue_by_id(rid, id)?;
|
|
|
|
Ok::<_, Error>(Json(issue))
|
|
}
|
|
|
|
async fn issue_threads_handler(
|
|
State(ctx): State<Context>,
|
|
Json(IssueBody { rid, id }): Json<IssueBody>,
|
|
) -> impl IntoResponse {
|
|
let issue_threads = ctx.comment_threads_by_issue_id(rid, id)?;
|
|
|
|
Ok::<_, Error>(Json(issue_threads))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct PatchesBody {
|
|
pub rid: identity::RepoId,
|
|
pub skip: Option<usize>,
|
|
pub take: Option<isize>,
|
|
pub status: Option<types::cobs::query::PatchStatus>,
|
|
}
|
|
|
|
async fn patches_handler(
|
|
State(ctx): State<Context>,
|
|
Json(PatchesBody {
|
|
rid,
|
|
skip,
|
|
take,
|
|
status,
|
|
}): Json<PatchesBody>,
|
|
) -> impl IntoResponse {
|
|
let profile = ctx.profile;
|
|
let cursor = skip.unwrap_or(0);
|
|
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<_>>(),
|
|
};
|
|
|
|
if let Some(t) = take {
|
|
if t < 0 {
|
|
// Return all patches
|
|
let content = patches
|
|
.into_iter()
|
|
.map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
|
|
.collect::<Vec<_>>();
|
|
|
|
return Ok::<_, Error>(Json(cobs::PaginatedQuery {
|
|
cursor: 0,
|
|
more: false,
|
|
content,
|
|
}));
|
|
}
|
|
}
|
|
|
|
let take = take.unwrap_or(20) as usize;
|
|
|
|
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>(Json(cobs::PaginatedQuery {
|
|
cursor,
|
|
more,
|
|
content: patches,
|
|
}))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct PatchBody {
|
|
pub rid: identity::RepoId,
|
|
pub id: git::Oid,
|
|
}
|
|
|
|
async fn patch_handler(
|
|
State(ctx): State<Context>,
|
|
Json(PatchBody { rid, id }): Json<PatchBody>,
|
|
) -> impl IntoResponse {
|
|
let patch = ctx.get_patch(rid, id)?;
|
|
|
|
Ok::<_, Error>(Json(patch))
|
|
}
|
|
|
|
async fn revision_handler(
|
|
State(ctx): State<Context>,
|
|
Json(PatchBody { rid, id }): Json<PatchBody>,
|
|
) -> impl IntoResponse {
|
|
let revisions = ctx.revisions_by_patch(rid, id)?;
|
|
|
|
Ok::<_, Error>(Json(revisions))
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct JobsBody {
|
|
pub rid: identity::RepoId,
|
|
pub sha: git::Oid,
|
|
}
|
|
|
|
async fn jobs_handler(
|
|
State(ctx): State<Context>,
|
|
Json(JobsBody { rid, sha }): Json<JobsBody>,
|
|
) -> impl IntoResponse {
|
|
let jobs = ctx.list_jobs(rid, sha)?;
|
|
|
|
Ok::<_, Error>(Json(jobs))
|
|
}
|