1use crate::Result;
4use async_trait::async_trait;
5use serde::{de::DeserializeOwned, Serialize};
6
7#[async_trait]
9pub trait Repository<T>: Send + Sync
10where
11 T: Serialize + DeserializeOwned + Send + Sync,
12{
13 async fn find_by_id(&self, id: &str) -> Result<Option<T>>;
15
16 async fn find_all(&self) -> Result<Vec<T>>;
18
19 async fn create(&self, entity: &T) -> Result<T>;
21
22 async fn update(&self, id: &str, entity: &T) -> Result<T>;
24
25 async fn delete(&self, id: &str) -> Result<()>;
27}
28
29#[async_trait]
37pub trait SoftDelete: Send + Sync {
38 async fn soft_delete(&self, id: &str) -> Result<()>;
40
41 async fn restore(&self, id: &str) -> Result<()>;
43
44 async fn is_deleted(&self, id: &str) -> Result<bool>;
46
47 async fn find_all_with_deleted(&self) -> Result<Vec<serde_json::Value>>;
49
50 async fn purge_deleted(&self, older_than_days: u32) -> Result<u64>;
52}
53
54#[async_trait]
60pub trait ProductScoped<T>: Send + Sync
61where
62 T: Serialize + DeserializeOwned + Send + Sync,
63{
64 async fn find_by_id_scoped(&self, product_id: &str, id: &str) -> Result<Option<T>>;
66
67 async fn find_all_scoped(&self, product_id: &str) -> Result<Vec<T>>;
69
70 async fn create_scoped(&self, product_id: &str, entity: &T) -> Result<T>;
72
73 async fn update_scoped(&self, product_id: &str, id: &str, entity: &T) -> Result<T>;
75
76 async fn delete_scoped(&self, product_id: &str, id: &str) -> Result<()>;
78
79 async fn count_scoped(&self, product_id: &str) -> Result<i64>;
81}
82
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85pub struct PaginationParams {
86 pub offset: i64,
88 pub limit: i64,
90 pub sort_by: Option<String>,
92 pub sort_desc: bool,
94}
95
96impl Default for PaginationParams {
97 fn default() -> Self {
98 Self {
99 offset: 0,
100 limit: 20,
101 sort_by: None,
102 sort_desc: false,
103 }
104 }
105}
106
107impl PaginationParams {
108 pub fn new(offset: i64, limit: i64) -> Self {
110 Self {
111 offset,
112 limit: limit.min(100), sort_by: None,
114 sort_desc: false,
115 }
116 }
117
118 pub fn with_sort(mut self, field: impl Into<String>, desc: bool) -> Self {
120 self.sort_by = Some(field.into());
121 self.sort_desc = desc;
122 self
123 }
124
125 pub fn sql_offset(&self) -> i64 {
127 self.offset
128 }
129
130 pub fn sql_limit(&self) -> i64 {
132 self.limit
133 }
134}
135
136#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
138pub struct PaginatedResponse<T> {
139 pub items: Vec<T>,
140 pub total: i64,
141 pub offset: i64,
142 pub limit: i64,
143 pub has_more: bool,
144}
145
146impl<T> PaginatedResponse<T> {
147 pub fn new(items: Vec<T>, total: i64, params: &PaginationParams) -> Self {
148 let has_more = (params.offset + params.limit) < total;
149 Self {
150 items,
151 total,
152 offset: params.offset,
153 limit: params.limit,
154 has_more,
155 }
156 }
157}
158
159#[async_trait]
161pub trait PaginatedRepository<T>: Send + Sync
162where
163 T: Serialize + DeserializeOwned + Send + Sync,
164{
165 async fn find_paginated(&self, params: &PaginationParams) -> Result<PaginatedResponse<T>>;
167
168 async fn find_paginated_scoped(
170 &self,
171 product_id: &str,
172 params: &PaginationParams,
173 ) -> Result<PaginatedResponse<T>>;
174}
175
176pub struct BaseRepository<T> {
178 _phantom: std::marker::PhantomData<T>,
179}
180
181impl<T> BaseRepository<T> {
182 pub fn new() -> Self {
183 Self {
184 _phantom: std::marker::PhantomData,
185 }
186 }
187}
188
189impl<T> Default for BaseRepository<T> {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[derive(serde::Serialize, serde::Deserialize)]
200 struct TestEntity {
201 id: String,
202 name: String,
203 }
204
205 #[test]
206 fn test_base_repository() {
207 let _repo = BaseRepository::<TestEntity>::new();
208 }
209
210 #[test]
211 fn test_pagination_params() {
212 let params = PaginationParams::new(0, 50);
213 assert_eq!(params.offset, 0);
214 assert_eq!(params.limit, 50);
215 assert_eq!(params.sql_offset(), 0);
216 assert_eq!(params.sql_limit(), 50);
217
218 let params = PaginationParams::new(0, 200);
220 assert_eq!(params.limit, 100); }
222
223 #[test]
224 fn test_pagination_params_with_sort() {
225 let params = PaginationParams::new(20, 10)
226 .with_sort("created_at", true);
227
228 assert_eq!(params.offset, 20);
229 assert_eq!(params.limit, 10);
230 assert_eq!(params.sort_by, Some("created_at".to_string()));
231 assert!(params.sort_desc);
232 }
233
234 #[test]
235 fn test_pagination_params_defaults() {
236 let params = PaginationParams::default();
237 assert_eq!(params.offset, 0);
238 assert_eq!(params.limit, 20);
239 assert_eq!(params.sort_by, None);
240 assert!(!params.sort_desc);
241 }
242
243 #[test]
244 fn test_paginated_response() {
245 let items = vec![
246 TestEntity { id: "1".to_string(), name: "One".to_string() },
247 TestEntity { id: "2".to_string(), name: "Two".to_string() },
248 ];
249 let params = PaginationParams::new(0, 2);
250 let response = PaginatedResponse::new(items, 10, ¶ms);
251
252 assert_eq!(response.items.len(), 2);
253 assert_eq!(response.total, 10);
254 assert_eq!(response.offset, 0);
255 assert_eq!(response.limit, 2);
256 assert!(response.has_more); let params = PaginationParams::new(8, 2);
260 let response: PaginatedResponse<TestEntity> = PaginatedResponse::new(vec![], 10, ¶ms);
261 assert!(!response.has_more); }
263}