diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index 84488ec3..a370a150 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -14,8 +14,8 @@ use serde_json::json; use tokio::sync::RwLock; use tower_http::cors::{self, CorsLayer}; -use radicle::cob::issue::Issues; -use radicle::cob::patch::Patches; +use radicle::cob::issue; +use radicle::cob::patch; use radicle::identity::Id; use radicle::storage::{ReadRepository, ReadStorage}; use radicle::Profile; @@ -50,8 +50,8 @@ impl Context { let doc = repo.identity_doc()?.1.verified()?; let payload = doc.project()?; let delegates = doc.delegates; - let issues = Issues::open(&repo)?.counts()?; - let patches = Patches::open(&repo)?.counts()?; + let issues = issue::Issues::open(&repo)?.counts()?; + let patches = patch::Patches::open(&repo)?.counts()?; Ok(project::Info { payload, @@ -115,6 +115,52 @@ pub struct PaginationQuery { pub per_page: Option, } +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CobsQuery { + pub page: Option, + pub per_page: Option, + pub state: Option, +} + +#[derive(Default, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub enum IssueState { + Closed, + #[default] + Open, +} + +impl IssueState { + pub fn matches(&self, issue: &issue::State) -> bool { + match self { + Self::Open => matches!(issue, issue::State::Open), + Self::Closed => matches!(issue, issue::State::Closed { .. }), + } + } +} + +#[derive(Default, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub enum PatchState { + #[default] + Open, + Draft, + Archived, + Merged, +} + +impl PatchState { + pub fn matches(&self, patch: &patch::State) -> bool { + match self { + Self::Open => matches!(patch, patch::State::Open), + Self::Draft => matches!(patch, patch::State::Draft), + Self::Archived => matches!(patch, patch::State::Archived), + Self::Merged => matches!(patch, patch::State::Merged), + } + } +} + mod project { use nonempty::NonEmpty; use serde::Serialize; diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs index 2efe091d..7e04d972 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/projects.rs @@ -21,7 +21,7 @@ use radicle_surf::{Glob, Oid, Repository}; use crate::api::error::Error; use crate::api::project::Info; -use crate::api::{self, Context, PaginationQuery}; +use crate::api::{self, CobsQuery, Context, PaginationQuery}; use crate::axum_extra::{Path, Query}; const CACHE_1_HOUR: &str = "public, max-age=3600, must-revalidate"; @@ -416,15 +416,27 @@ async fn readme_handler( async fn issues_handler( State(ctx): State, Path(project): Path, - Query(qs): Query, + Query(qs): Query>, ) -> impl IntoResponse { - let PaginationQuery { page, per_page } = qs; + let CobsQuery { + page, + per_page, + state, + } = qs; let page = page.unwrap_or(0); let per_page = per_page.unwrap_or(10); + let state = state.unwrap_or_default(); let storage = &ctx.profile.storage; let repo = storage.repository(project)?; let issues = issue::Issues::open(&repo)?; - let mut issues: Vec<_> = issues.all()?.filter_map(|r| r.ok()).collect::>(); + let mut issues: Vec<_> = issues + .all()? + .filter_map(|r| { + let (id, issue, clock) = r.ok()?; + (state.matches(issue.state())).then_some((id, issue, clock)) + }) + .collect::>(); + issues.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp())); let issues = issues .into_iter() @@ -687,15 +699,26 @@ async fn patch_update_handler( async fn patches_handler( State(ctx): State, Path(project): Path, - Query(qs): Query, + Query(qs): Query>, ) -> impl IntoResponse { - let PaginationQuery { page, per_page } = qs; + let CobsQuery { + page, + per_page, + state, + } = qs; let page = page.unwrap_or(0); let per_page = per_page.unwrap_or(10); + let state = state.unwrap_or_default(); let storage = &ctx.profile.storage; let repo = storage.repository(project)?; let patches = patch::Patches::open(&repo)?; - let mut patches = patches.all()?.filter_map(|r| r.ok()).collect::>(); + let mut patches = patches + .all()? + .filter_map(|r| { + let (id, patch, clock) = r.ok()?; + (state.matches(&patch.state())).then_some((id, patch, clock)) + }) + .collect::>(); patches.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp())); let patches = patches .into_iter() diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index 40eca7e3..6d0d31a8 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -63,7 +63,7 @@ impl std::fmt::Display for State { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Closed { .. } => write!(f, "closed"), - Self::Open { .. } => write!(f, "open"), + Self::Open => write!(f, "open"), } } } @@ -71,8 +71,8 @@ impl std::fmt::Display for State { impl State { pub fn lifecycle_message(self) -> String { match self { - State::Open => "Open issue".to_owned(), - State::Closed { .. } => "Close issue".to_owned(), + Self::Open => "Open issue".to_owned(), + Self::Closed { .. } => "Close issue".to_owned(), } } }