1use crate::error::DbError;
4use crate::handle::DbHandle;
5use sova_core::Request;
6use sea_orm::{EntityTrait, FromQueryResult, PaginatorTrait, Select};
7use serde::Serialize;
8
9const DEFAULT_PER_PAGE: u64 = 15;
10const MAX_PER_PAGE: u64 = 100;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct PageParams {
15 pub page: u64,
16 pub per_page: u64,
17}
18
19impl Default for PageParams {
20 fn default() -> Self {
21 Self {
22 page: 1,
23 per_page: DEFAULT_PER_PAGE,
24 }
25 }
26}
27
28impl PageParams {
29 pub fn new(page: u64, per_page: u64) -> Self {
30 Self {
31 page: page.max(1),
32 per_page: per_page.clamp(1, MAX_PER_PAGE),
33 }
34 }
35
36 pub fn from_request(req: &Request) -> Self {
37 let page = req
38 .query("page")
39 .and_then(|s| s.parse().ok())
40 .unwrap_or(1);
41 let per_page = req
42 .query("per_page")
43 .and_then(|s| s.parse().ok())
44 .unwrap_or(DEFAULT_PER_PAGE);
45 Self::new(page, per_page)
46 }
47
48 pub fn offset(&self) -> u64 {
49 self.page.saturating_sub(1).saturating_mul(self.per_page)
50 }
51}
52
53#[derive(Debug, Clone, Serialize)]
55pub struct Page<T> {
56 pub data: Vec<T>,
57 pub total: u64,
58 pub page: u64,
59 pub per_page: u64,
60 pub last_page: u64,
61}
62
63impl<T> Page<T> {
64 pub fn has_more(&self) -> bool {
65 self.page < self.last_page
66 }
67}
68
69pub trait PageExt {
71 fn page_params(&self) -> PageParams;
72}
73
74impl PageExt for Request {
75 fn page_params(&self) -> PageParams {
76 PageParams::from_request(self)
77 }
78}
79
80pub trait PaginateExt<E>
82where
83 E: EntityTrait,
84{
85 fn paginate_sova(
86 self,
87 db: &DbHandle,
88 params: PageParams,
89 ) -> impl std::future::Future<Output = Result<Page<E::Model>, DbError>> + Send;
90}
91
92impl<E> PaginateExt<E> for Select<E>
93where
94 E: EntityTrait + Send,
95 E::Model: FromQueryResult + Sized + Send + Sync,
96{
97 async fn paginate_sova(
98 self,
99 db: &DbHandle,
100 params: PageParams,
101 ) -> Result<Page<E::Model>, DbError> {
102 let params = PageParams::new(params.page, params.per_page);
103 let paginator = self.paginate(db, params.per_page);
104 let total = paginator.num_items().await.map_err(DbError::from)?;
105 let last_page = if total == 0 {
106 1
107 } else {
108 total.div_ceil(params.per_page).max(1)
109 };
110 let page = params.page.min(last_page);
111 let data = paginator
112 .fetch_page(page.saturating_sub(1))
113 .await
114 .map_err(DbError::from)?;
115 Ok(Page {
116 data,
117 total,
118 page,
119 per_page: params.per_page,
120 last_page,
121 })
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use sova_core::Request;
129
130 #[test]
131 fn page_params_from_query() {
132 let req = Request::builder()
133 .path("/notes")
134 .query_param("page", "3")
135 .query_param("per_page", "10")
136 .build();
137 let p = PageParams::from_request(&req);
138 assert_eq!(p.page, 3);
139 assert_eq!(p.per_page, 10);
140 assert_eq!(p.offset(), 20);
141 }
142
143 #[test]
144 fn page_params_clamp() {
145 let p = PageParams::new(0, 999);
146 assert_eq!(p.page, 1);
147 assert_eq!(p.per_page, 100);
148 }
149}