diff --git a/crates/radicle-tauri/src/commands/cob/patch.rs b/crates/radicle-tauri/src/commands/cob/patch.rs index 1e89845bf..43f366903 100644 --- a/crates/radicle-tauri/src/commands/cob/patch.rs +++ b/crates/radicle-tauri/src/commands/cob/patch.rs @@ -19,8 +19,6 @@ use radicle_types::traits::patch::PatchesMut; use crate::AppState; -use either::Either; - #[tauri::command] pub async fn list_patches( ctx: tauri::State<'_, AppState>, @@ -32,43 +30,9 @@ pub async fn list_patches( take: Option, ) -> Result>, Error> { let profile = ctx.profile(); - let cursor = skip.unwrap_or(0); let aliases = profile.aliases(); - let counts = sqlite_service.counts(rid)?; - let patches = match status.clone() { - None => Either::Left(sqlite_service.list(rid)?), - Some(s) => Either::Right(sqlite_service.list_by_status(rid, s.into())?), - }; - match take { - None => { - let content = patches - .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases)) - .collect::>(); - - Ok::<_, Error>(cobs::PaginatedQuery { - cursor: 0, - more: false, - content, - }) - } - Some(take) => { - let total = status.map_or_else(|| counts.total(), |status| counts[status.into()]); - let more = cursor + take < total; - - let content = patches - .skip(cursor) - .take(take) - .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases)) - .collect::>(); - - Ok::<_, Error>(cobs::PaginatedQuery { - cursor, - more, - content, - }) - } - } + Ok(sqlite_service.list_paginated(rid, status, skip, take, &aliases)?) } #[tauri::command] diff --git a/crates/radicle-types/src/domain/patch/traits.rs b/crates/radicle-types/src/domain/patch/traits.rs index 75005e394..5a1f03677 100644 --- a/crates/radicle-types/src/domain/patch/traits.rs +++ b/crates/radicle-types/src/domain/patch/traits.rs @@ -1,8 +1,10 @@ use radicle::identity; +use radicle::node::AliasStore; use radicle::patch; use radicle::patch::Patch; use radicle::patch::PatchId; +use crate::cobs; use crate::domain::patch::models; pub trait PatchStorage { @@ -39,4 +41,49 @@ pub trait PatchService { &self, rid: identity::RepoId, ) -> Result; + + /// One page of patch summaries, shared by all drivers so pagination + /// behaves identically everywhere. Rows are consumed lazily: with `take` + /// set, patches beyond the requested page are never deserialized. + /// Without `take` the full list is returned and `skip` is ignored. + fn list_paginated( + &self, + rid: identity::RepoId, + status: Option, + skip: Option, + take: Option, + aliases: &impl AliasStore, + ) -> Result>, models::patch::ListPatchesError> + { + let patches: Box + '_> = match status { + None => Box::new(self.list(rid)?), + Some(status) => Box::new(self.list_by_status(rid, status.into())?), + }; + let summary = + |(id, patch): (PatchId, Patch)| models::patch::Patch::new(id, &patch, aliases); + + match take { + None => Ok(cobs::PaginatedQuery { + cursor: 0, + more: false, + content: patches.map(summary).collect::>(), + }), + Some(take) => { + let cursor = skip.unwrap_or(0); + let mut content = patches + .skip(cursor) + .take(take + 1) + .map(summary) + .collect::>(); + let more = content.len() > take; + content.truncate(take); + + Ok(cobs::PaginatedQuery { + cursor, + more, + content, + }) + } + } + } } diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs index 4824abc18..c2b827c07 100644 --- a/crates/test-http-api/src/api.rs +++ b/crates/test-http-api/src/api.rs @@ -15,9 +15,9 @@ use radicle::node::NodeId; use radicle::{git, identity}; use radicle_types as types; use radicle_types::cobs::CobOptions; +use radicle_types::cobs::FromRadicleAction; use radicle_types::cobs::issue; use radicle_types::cobs::issue::NewIssue; -use radicle_types::cobs::{self, FromRadicleAction}; use radicle_types::config::Version; use radicle_types::domain::issue::service::Service as IssueService; use radicle_types::domain::issue::traits::IssueService as _; @@ -627,7 +627,7 @@ async fn issue_threads_handler( struct PatchesBody { pub rid: identity::RepoId, pub skip: Option, - pub take: Option, + pub take: Option, pub status: Option, } @@ -640,50 +640,12 @@ async fn patches_handler( status, }): Json, ) -> impl IntoResponse { - let profile = ctx.profile; - let cursor = skip.unwrap_or(0); - let aliases = profile.aliases(); + let aliases = ctx.profile.aliases(); + let page = ctx + .patches + .list_paginated(rid, status, skip, take, &aliases)?; - let patches = match status { - None => ctx.patches.list(rid)?.collect::>(), - Some(s) => ctx - .patches - .list_by_status(rid, s.into())? - .collect::>(), - }; - - if let Some(t) = take - && t < 0 - { - // Return all patches - let content = patches - .into_iter() - .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases)) - .collect::>(); - - 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() - .skip(cursor) - .take(take) - .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases)) - .collect::>(); - - Ok::<_, Error>(Json(cobs::PaginatedQuery { - cursor, - more, - content: patches, - })) + Ok::<_, Error>(Json(page)) } #[derive(Serialize, Deserialize)]