httpd: Add cob state to listing endpoints

This commit adds the cob state of either patches or issues to the
corresponding endpoint.

So we can either query a cob listing with the required state or expect
the default state.
And once filtered by cob state we paginate.

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez 2023-04-20 18:56:56 +02:00 committed by Alexis Sellier
parent 6f17a4db67
commit 8667553740
No known key found for this signature in database
3 changed files with 83 additions and 14 deletions

View File

@ -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<usize>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CobsQuery<T> {
pub page: Option<usize>,
pub per_page: Option<usize>,
pub state: Option<T>,
}
#[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;

View File

@ -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<Context>,
Path(project): Path<Id>,
Query(qs): Query<PaginationQuery>,
Query(qs): Query<CobsQuery<api::IssueState>>,
) -> 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::<Vec<_>>();
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::<Vec<_>>();
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<Context>,
Path(project): Path<Id>,
Query(qs): Query<PaginationQuery>,
Query(qs): Query<CobsQuery<api::PatchState>>,
) -> 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::<Vec<_>>();
let mut patches = patches
.all()?
.filter_map(|r| {
let (id, patch, clock) = r.ok()?;
(state.matches(&patch.state())).then_some((id, patch, clock))
})
.collect::<Vec<_>>();
patches.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp()));
let patches = patches
.into_iter()

View File

@ -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(),
}
}
}