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