1use std::{sync::Arc, pin::Pin, future::Future};
7use axum::{
8 extract::{State, Path, Query},
9 response::{Json, Response, IntoResponse},
10 http::StatusCode,
11};
12use serde::{Serialize, Deserialize};
13use serde_json::Value;
14
15use elif_core::Container;
16use crate::{HttpResult, HttpError, ApiResponse};
17
18#[derive(Debug, Serialize, Deserialize)]
20pub struct QueryParams {
21 pub page: Option<u32>,
22 pub per_page: Option<u32>,
23 pub sort: Option<String>,
24 pub order: Option<String>,
25 pub filter: Option<String>,
26}
27
28impl Default for QueryParams {
29 fn default() -> Self {
30 Self {
31 page: Some(1),
32 per_page: Some(20),
33 sort: Some("id".to_string()),
34 order: Some("asc".to_string()),
35 filter: None,
36 }
37 }
38}
39
40#[derive(Debug, Serialize, Deserialize)]
42pub struct PaginationMeta {
43 pub page: u32,
44 pub per_page: u32,
45 pub total: Option<u64>,
46 pub total_pages: Option<u32>,
47 pub has_more: bool,
48}
49
50#[derive(Clone)]
52pub struct BaseController;
53
54impl BaseController {
55 pub fn new() -> Self {
56 Self
57 }
58
59 pub fn normalize_pagination(&self, params: &QueryParams) -> (u32, u32, u64) {
61 let page = params.page.unwrap_or(1).max(1);
62 let per_page = params.per_page.unwrap_or(20).min(100).max(1);
63 let offset = (page - 1) * per_page;
64 (page, per_page, offset as u64)
65 }
66
67 pub fn success_response<T: Serialize>(&self, data: T) -> HttpResult<Response> {
69 let api_response = ApiResponse::success(data);
70 Ok((StatusCode::OK, Json(api_response)).into_response())
71 }
72
73 pub fn created_response<T: Serialize>(&self, data: T) -> HttpResult<Response> {
75 let api_response = ApiResponse::success(data);
76 Ok((StatusCode::CREATED, Json(api_response)).into_response())
77 }
78
79 pub fn paginated_response<T: Serialize>(&self, data: Vec<T>, meta: PaginationMeta) -> HttpResult<Response> {
81 let response_data = serde_json::json!({
82 "data": data,
83 "meta": meta
84 });
85 let api_response = ApiResponse::success(response_data);
86 Ok((StatusCode::OK, Json(api_response)).into_response())
87 }
88
89 pub fn deleted_response<T: Serialize>(&self, resource_name: &str, deleted_id: Option<T>) -> HttpResult<Response> {
91 let mut response_data = serde_json::json!({
92 "message": format!("{} deleted successfully", resource_name)
93 });
94
95 if let Some(id) = deleted_id {
96 response_data["deleted_id"] = serde_json::to_value(id)?;
97 }
98
99 let api_response = ApiResponse::success(response_data);
100 Ok((StatusCode::OK, Json(api_response)).into_response())
101 }
102}
103
104pub trait Controller: Send + Sync {
107 fn index(
109 &self,
110 container: State<Arc<Container>>,
111 params: Query<QueryParams>,
112 ) -> Pin<Box<dyn Future<Output = HttpResult<Response>> + Send>>;
113
114 fn show(
116 &self,
117 container: State<Arc<Container>>,
118 id: Path<String>,
119 ) -> Pin<Box<dyn Future<Output = HttpResult<Response>> + Send>>;
120
121 fn create(
123 &self,
124 container: State<Arc<Container>>,
125 data: Json<Value>,
126 ) -> Pin<Box<dyn Future<Output = HttpResult<Response>> + Send>>;
127
128 fn update(
130 &self,
131 container: State<Arc<Container>>,
132 id: Path<String>,
133 data: Json<Value>,
134 ) -> Pin<Box<dyn Future<Output = HttpResult<Response>> + Send>>;
135
136 fn destroy(
138 &self,
139 container: State<Arc<Container>>,
140 id: Path<String>,
141 ) -> Pin<Box<dyn Future<Output = HttpResult<Response>> + Send>>;
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use serde_json::json;
148
149 #[tokio::test]
150 async fn test_base_controller_creation() {
151 let _controller = BaseController::new();
152 }
153
154 #[tokio::test]
155 async fn test_pagination_normalization() {
156 let controller = BaseController::new();
157 let params = QueryParams {
158 page: Some(5),
159 per_page: Some(10),
160 ..Default::default()
161 };
162
163 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
164 assert_eq!(page, 5);
165 assert_eq!(per_page, 10);
166 assert_eq!(offset, 40);
167 }
168
169 #[tokio::test]
170 async fn test_pagination_limits() {
171 let controller = BaseController::new();
172 let params = QueryParams {
173 page: Some(0),
174 per_page: Some(200),
175 ..Default::default()
176 };
177
178 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
179 assert_eq!(page, 1);
180 assert_eq!(per_page, 100);
181 assert_eq!(offset, 0);
182 }
183
184 #[tokio::test]
185 async fn test_success_response_creation() {
186 let controller = BaseController::new();
187 let data = json!({"message": "test"});
188 let response = controller.success_response(data);
189 assert!(response.is_ok());
190 }
191
192 #[tokio::test]
193 async fn test_pagination_meta_creation() {
194 let meta = PaginationMeta {
195 page: 1,
196 per_page: 20,
197 total: Some(100),
198 total_pages: Some(5),
199 has_more: true,
200 };
201
202 assert_eq!(meta.page, 1);
203 assert_eq!(meta.per_page, 20);
204 assert_eq!(meta.total, Some(100));
205 }
206}