1use super::traits::{
6 FilterParams, PaginationParams, Repository, RepositoryError, RepositoryResult,
7};
8use crate::core::models::common::CardId;
9use crate::core::models::Card;
10use crate::transport::http_provider_safe::{HttpProviderExt, HttpProviderSafe};
11use async_trait::async_trait;
12use std::sync::Arc;
13
14#[derive(Debug, Clone, Default)]
16pub struct CardFilterParams {
17 pub f: Option<String>,
19 pub model_type: Option<String>,
21 pub archived: Option<bool>,
23 pub collection_id: Option<i32>,
25}
26
27impl CardFilterParams {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_collection(mut self, collection_id: i32) -> Self {
35 self.collection_id = Some(collection_id);
36 self
37 }
38
39 }
45
46#[async_trait]
48pub trait CardRepository: Repository<Entity = Card, Id = CardId> + Send + Sync {
49 fn as_any(&self) -> &dyn std::any::Any;
51 async fn list_with_filters(
53 &self,
54 pagination: Option<PaginationParams>,
55 filters: Option<CardFilterParams>,
56 ) -> RepositoryResult<Vec<Card>>;
57
58 async fn get_by_collection(&self, collection_id: i32) -> RepositoryResult<Vec<Card>>;
60
61 async fn search(&self, query: &str) -> RepositoryResult<Vec<Card>>;
63
64 async fn archive(&self, id: &CardId) -> RepositoryResult<()>;
66
67 async fn unarchive(&self, id: &CardId) -> RepositoryResult<()>;
69
70 async fn copy(&self, id: &CardId, new_name: &str) -> RepositoryResult<Card>;
72
73 async fn execute_query(
75 &self,
76 id: &CardId,
77 parameters: Option<serde_json::Value>,
78 ) -> RepositoryResult<crate::core::models::QueryResult>;
79
80 async fn export_query(
82 &self,
83 id: &CardId,
84 format: crate::core::models::common::ExportFormat,
85 parameters: Option<serde_json::Value>,
86 ) -> RepositoryResult<Vec<u8>>;
87
88 async fn execute_pivot_query(
90 &self,
91 id: &CardId,
92 parameters: Option<serde_json::Value>,
93 ) -> RepositoryResult<crate::core::models::QueryResult>;
94}
95
96pub struct HttpCardRepository {
98 http_provider: Arc<dyn HttpProviderSafe>,
99}
100
101impl HttpCardRepository {
102 pub fn new(http_provider: Arc<dyn HttpProviderSafe>) -> Self {
104 Self { http_provider }
105 }
106
107 fn build_query_params(
109 &self,
110 pagination: Option<PaginationParams>,
111 filters: Option<FilterParams>,
112 ) -> String {
113 let mut params = Vec::new();
114
115 if let Some(p) = pagination {
116 if let Some(page) = p.page {
117 params.push(format!("page={}", page));
118 }
119 if let Some(limit) = p.limit {
120 params.push(format!("limit={}", limit));
121 }
122 if let Some(offset) = p.offset {
123 params.push(format!("offset={}", offset));
124 }
125 }
126
127 if let Some(f) = filters {
128 if let Some(query) = f.query {
129 params.push(format!("q={}", query.replace(' ', "+")));
130 }
131 if let Some(archived) = f.archived {
132 params.push(format!("archived={}", archived));
133 }
134 }
135
136 if params.is_empty() {
137 String::new()
138 } else {
139 format!("?{}", params.join("&"))
140 }
141 }
142}
143
144#[async_trait]
145impl Repository for HttpCardRepository {
146 type Entity = Card;
147 type Id = CardId;
148
149 async fn get(&self, id: &CardId) -> RepositoryResult<Card> {
150 let path = format!("/api/card/{}", id.0);
151 self.http_provider.get(&path).await.map_err(|e| e.into())
152 }
153
154 async fn list(
155 &self,
156 pagination: Option<PaginationParams>,
157 filters: Option<FilterParams>,
158 ) -> RepositoryResult<Vec<Card>> {
159 let query = self.build_query_params(pagination, filters);
160 let path = format!("/api/card{}", query);
161 self.http_provider.get(&path).await.map_err(|e| e.into())
162 }
163
164 async fn create(&self, entity: &Card) -> RepositoryResult<Card> {
165 self.http_provider
166 .post("/api/card", entity)
167 .await
168 .map_err(|e| e.into())
169 }
170
171 async fn update(&self, id: &CardId, entity: &Card) -> RepositoryResult<Card> {
172 let path = format!("/api/card/{}", id.0);
173 self.http_provider
174 .put(&path, entity)
175 .await
176 .map_err(|e| e.into())
177 }
178
179 async fn delete(&self, id: &CardId) -> RepositoryResult<()> {
180 let path = format!("/api/card/{}", id.0);
181 self.http_provider.delete(&path).await.map_err(|e| e.into())
182 }
183}
184
185#[async_trait]
186impl CardRepository for HttpCardRepository {
187 fn as_any(&self) -> &dyn std::any::Any {
188 self
189 }
190 async fn list_with_filters(
191 &self,
192 pagination: Option<PaginationParams>,
193 filters: Option<CardFilterParams>,
194 ) -> RepositoryResult<Vec<Card>> {
195 let mut params = Vec::new();
196
197 if let Some(p) = pagination {
198 if let Some(page) = p.page {
199 params.push(format!("page={}", page));
200 }
201 if let Some(limit) = p.limit {
202 params.push(format!("limit={}", limit));
203 }
204 if let Some(offset) = p.offset {
205 params.push(format!("offset={}", offset));
206 }
207 }
208
209 if let Some(f) = filters {
210 if let Some(f_param) = f.f {
211 params.push(format!("f={}", f_param));
212 }
213 if let Some(model_type) = f.model_type {
214 params.push(format!("model_type={}", model_type));
215 }
216 if let Some(archived) = f.archived {
217 params.push(format!("archived={}", archived));
218 }
219 if let Some(collection_id) = f.collection_id {
220 params.push(format!("collection_id={}", collection_id));
221 }
222 }
223
224 let path = if params.is_empty() {
225 "/api/card".to_string()
226 } else {
227 format!("/api/card?{}", params.join("&"))
228 };
229
230 self.http_provider.get(&path).await.map_err(|e| e.into())
231 }
232
233 async fn get_by_collection(&self, collection_id: i32) -> RepositoryResult<Vec<Card>> {
234 let path = format!("/api/collection/{}/items", collection_id);
235 let _items: serde_json::Value = self
237 .http_provider
238 .get(&path)
239 .await
240 .map_err(RepositoryError::from)?;
241
242 Ok(Vec::new())
245 }
246
247 async fn search(&self, query: &str) -> RepositoryResult<Vec<Card>> {
248 let filters = FilterParams::new().with_query(query);
249 self.list(None, Some(filters)).await
250 }
251
252 async fn archive(&self, id: &CardId) -> RepositoryResult<()> {
253 let path = format!("/api/card/{}", id.0);
254 let body = serde_json::json!({ "archived": true });
255 self.http_provider
256 .put(&path, &body)
257 .await
258 .map(|_: serde_json::Value| ())
259 .map_err(|e| e.into())
260 }
261
262 async fn unarchive(&self, id: &CardId) -> RepositoryResult<()> {
263 let path = format!("/api/card/{}", id.0);
264 let body = serde_json::json!({ "archived": false });
265 self.http_provider
266 .put(&path, &body)
267 .await
268 .map(|_: serde_json::Value| ())
269 .map_err(|e| e.into())
270 }
271
272 async fn copy(&self, id: &CardId, new_name: &str) -> RepositoryResult<Card> {
273 let path = format!("/api/card/{}/copy", id.0);
274 let body = serde_json::json!({ "name": new_name });
275 self.http_provider
276 .post(&path, &body)
277 .await
278 .map_err(|e| e.into())
279 }
280
281 async fn execute_query(
282 &self,
283 id: &CardId,
284 parameters: Option<serde_json::Value>,
285 ) -> RepositoryResult<crate::core::models::QueryResult> {
286 let path = format!("/api/card/{}/query", id.0);
287 let request = if let Some(params) = parameters {
288 serde_json::json!({ "parameters": params })
289 } else {
290 serde_json::json!({})
291 };
292 self.http_provider
293 .post(&path, &request)
294 .await
295 .map_err(|e| e.into())
296 }
297
298 async fn export_query(
299 &self,
300 id: &CardId,
301 format: crate::core::models::common::ExportFormat,
302 parameters: Option<serde_json::Value>,
303 ) -> RepositoryResult<Vec<u8>> {
304 let path = format!("/api/card/{}/query/{}", id.0, format.as_str());
305 let request = if let Some(params) = parameters {
306 serde_json::json!({ "parameters": params })
307 } else {
308 serde_json::json!({})
309 };
310 self.http_provider
312 .post_binary(&path, request)
313 .await
314 .map_err(RepositoryError::from)
315 }
316
317 async fn execute_pivot_query(
318 &self,
319 id: &CardId,
320 parameters: Option<serde_json::Value>,
321 ) -> RepositoryResult<crate::core::models::QueryResult> {
322 let path = format!("/api/card/pivot/{}/query", id.0);
323 let request = if let Some(params) = parameters {
324 serde_json::json!({ "parameters": params })
325 } else {
326 serde_json::json!({})
327 };
328 self.http_provider
329 .post(&path, &request)
330 .await
331 .map_err(|e| e.into())
332 }
333}
334
335pub struct MockCardRepository {
337 cards: Arc<tokio::sync::RwLock<Vec<Card>>>,
338 should_fail: bool,
339}
340
341impl MockCardRepository {
342 pub fn new() -> Self {
344 Self {
345 cards: Arc::new(tokio::sync::RwLock::new(Vec::new())),
346 should_fail: false,
347 }
348 }
349
350 pub fn set_should_fail(&mut self, should_fail: bool) {
352 self.should_fail = should_fail;
353 }
354
355 pub async fn add_card(&self, card: Card) {
357 let mut cards = self.cards.write().await;
358 cards.push(card);
359 }
360}
361
362impl Default for MockCardRepository {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368#[async_trait]
369impl Repository for MockCardRepository {
370 type Entity = Card;
371 type Id = CardId;
372
373 async fn get(&self, id: &CardId) -> RepositoryResult<Card> {
374 if self.should_fail {
375 return Err(RepositoryError::Other("Mock failure".to_string()));
376 }
377
378 let cards = self.cards.read().await;
379 cards
380 .iter()
381 .find(|c| c.id == Some(*id))
382 .cloned()
383 .ok_or_else(|| RepositoryError::NotFound(format!("Card {} not found", id.0)))
384 }
385
386 async fn list(
387 &self,
388 _pagination: Option<PaginationParams>,
389 _filters: Option<FilterParams>,
390 ) -> RepositoryResult<Vec<Card>> {
391 if self.should_fail {
392 return Err(RepositoryError::Other("Mock failure".to_string()));
393 }
394
395 let cards = self.cards.read().await;
396 Ok(cards.clone())
397 }
398
399 async fn create(&self, entity: &Card) -> RepositoryResult<Card> {
400 if self.should_fail {
401 return Err(RepositoryError::Other("Mock failure".to_string()));
402 }
403
404 let mut cards = self.cards.write().await;
405 let mut new_card = entity.clone();
406 if new_card.id.is_none() {
408 new_card.id = Some(CardId((cards.len() + 1) as i32));
409 }
410 cards.push(new_card.clone());
411 Ok(new_card)
412 }
413
414 async fn update(&self, id: &CardId, entity: &Card) -> RepositoryResult<Card> {
415 if self.should_fail {
416 return Err(RepositoryError::Other("Mock failure".to_string()));
417 }
418
419 let mut cards = self.cards.write().await;
420 if let Some(card) = cards.iter_mut().find(|c| c.id == Some(*id)) {
421 *card = entity.clone();
422 card.id = Some(*id); Ok(card.clone())
424 } else {
425 Err(RepositoryError::NotFound(format!(
426 "Card {} not found",
427 id.0
428 )))
429 }
430 }
431
432 async fn delete(&self, id: &CardId) -> RepositoryResult<()> {
433 if self.should_fail {
434 return Err(RepositoryError::Other("Mock failure".to_string()));
435 }
436
437 let mut cards = self.cards.write().await;
438 let initial_len = cards.len();
439 cards.retain(|c| c.id != Some(*id));
440
441 if cards.len() < initial_len {
442 Ok(())
443 } else {
444 Err(RepositoryError::NotFound(format!(
445 "Card {} not found",
446 id.0
447 )))
448 }
449 }
450}
451
452#[async_trait]
453impl CardRepository for MockCardRepository {
454 fn as_any(&self) -> &dyn std::any::Any {
455 self
456 }
457 async fn list_with_filters(
458 &self,
459 pagination: Option<PaginationParams>,
460 _filters: Option<CardFilterParams>,
461 ) -> RepositoryResult<Vec<Card>> {
462 self.list(pagination, None).await
464 }
465
466 async fn get_by_collection(&self, collection_id: i32) -> RepositoryResult<Vec<Card>> {
467 if self.should_fail {
468 return Err(RepositoryError::Other("Mock failure".to_string()));
469 }
470
471 let cards = self.cards.read().await;
472 Ok(cards
473 .iter()
474 .filter(|c| c.collection_id == Some(collection_id))
475 .cloned()
476 .collect())
477 }
478
479 async fn search(&self, query: &str) -> RepositoryResult<Vec<Card>> {
480 if self.should_fail {
481 return Err(RepositoryError::Other("Mock failure".to_string()));
482 }
483
484 let cards = self.cards.read().await;
485 Ok(cards
486 .iter()
487 .filter(|c| {
488 c.name.to_lowercase().contains(&query.to_lowercase())
489 || c.description
490 .as_ref()
491 .map(|d| d.to_lowercase().contains(&query.to_lowercase()))
492 .unwrap_or(false)
493 })
494 .cloned()
495 .collect())
496 }
497
498 async fn archive(&self, id: &CardId) -> RepositoryResult<()> {
499 if self.should_fail {
500 return Err(RepositoryError::Other("Mock failure".to_string()));
501 }
502
503 let mut cards = self.cards.write().await;
504 if let Some(card) = cards.iter_mut().find(|c| c.id == Some(*id)) {
505 card.archived = true;
506 Ok(())
507 } else {
508 Err(RepositoryError::NotFound(format!(
509 "Card {} not found",
510 id.0
511 )))
512 }
513 }
514
515 async fn unarchive(&self, id: &CardId) -> RepositoryResult<()> {
516 if self.should_fail {
517 return Err(RepositoryError::Other("Mock failure".to_string()));
518 }
519
520 let mut cards = self.cards.write().await;
521 if let Some(card) = cards.iter_mut().find(|c| c.id == Some(*id)) {
522 card.archived = false;
523 Ok(())
524 } else {
525 Err(RepositoryError::NotFound(format!(
526 "Card {} not found",
527 id.0
528 )))
529 }
530 }
531
532 async fn copy(&self, id: &CardId, new_name: &str) -> RepositoryResult<Card> {
533 if self.should_fail {
534 return Err(RepositoryError::Other("Mock failure".to_string()));
535 }
536
537 let mut cards = self.cards.write().await;
538 if let Some(original) = cards.iter().find(|c| c.id == Some(*id)) {
539 let mut new_card = original.clone();
540 new_card.id = Some(CardId((cards.len() + 1) as i32));
541 new_card.name = new_name.to_string();
542 cards.push(new_card.clone());
543 Ok(new_card)
544 } else {
545 Err(RepositoryError::NotFound(format!(
546 "Card {} not found",
547 id.0
548 )))
549 }
550 }
551
552 async fn execute_query(
553 &self,
554 id: &CardId,
555 _parameters: Option<serde_json::Value>,
556 ) -> RepositoryResult<crate::core::models::QueryResult> {
557 if self.should_fail {
558 return Err(RepositoryError::Other("Mock failure".to_string()));
559 }
560
561 let cards = self.cards.read().await;
563 if !cards.iter().any(|c| c.id == Some(*id)) {
564 return Err(RepositoryError::NotFound(format!(
565 "Card {} not found",
566 id.0
567 )));
568 }
569
570 use crate::core::models::common::MetabaseId;
572 use crate::core::models::query::{Column, QueryData, QueryStatus};
573
574 Ok(crate::core::models::QueryResult {
575 data: QueryData {
576 rows: vec![vec![serde_json::json!(1), serde_json::json!("test")]],
577 cols: vec![
578 Column {
579 name: "id".to_string(),
580 display_name: "ID".to_string(),
581 base_type: "type/Integer".to_string(),
582 effective_type: None,
583 semantic_type: None,
584 field_ref: None,
585 },
586 Column {
587 name: "name".to_string(),
588 display_name: "Name".to_string(),
589 base_type: "type/Text".to_string(),
590 effective_type: None,
591 semantic_type: None,
592 field_ref: None,
593 },
594 ],
595 native_form: None,
596 insights: vec![],
597 },
598 database_id: MetabaseId(1),
599 started_at: chrono::Utc::now(),
600 finished_at: Some(chrono::Utc::now()),
601 status: QueryStatus::Completed,
602 row_count: Some(1),
603 running_time: Some(100),
604 json_query: serde_json::json!({}),
605 })
606 }
607
608 async fn export_query(
609 &self,
610 id: &CardId,
611 _format: crate::core::models::common::ExportFormat,
612 _parameters: Option<serde_json::Value>,
613 ) -> RepositoryResult<Vec<u8>> {
614 if self.should_fail {
615 return Err(RepositoryError::Other("Mock failure".to_string()));
616 }
617
618 let cards = self.cards.read().await;
620 if !cards.iter().any(|c| c.id == Some(*id)) {
621 return Err(RepositoryError::NotFound(format!(
622 "Card {} not found",
623 id.0
624 )));
625 }
626
627 Ok(b"id,name\n1,Test\n2,Data".to_vec())
629 }
630
631 async fn execute_pivot_query(
632 &self,
633 id: &CardId,
634 _parameters: Option<serde_json::Value>,
635 ) -> RepositoryResult<crate::core::models::QueryResult> {
636 if self.should_fail {
637 return Err(RepositoryError::Other("Mock failure".to_string()));
638 }
639
640 let cards = self.cards.read().await;
642 if !cards.iter().any(|c| c.id == Some(*id)) {
643 return Err(RepositoryError::NotFound(format!(
644 "Card {} not found",
645 id.0
646 )));
647 }
648
649 use crate::core::models::common::MetabaseId;
651 use crate::core::models::query::{Column, QueryData, QueryStatus};
652
653 Ok(crate::core::models::QueryResult {
654 data: QueryData {
655 rows: vec![vec![serde_json::json!("category1"), serde_json::json!(100)]],
656 cols: vec![
657 Column {
658 name: "category".to_string(),
659 display_name: "Category".to_string(),
660 base_type: "type/Text".to_string(),
661 effective_type: None,
662 semantic_type: None,
663 field_ref: None,
664 },
665 Column {
666 name: "total".to_string(),
667 display_name: "Total".to_string(),
668 base_type: "type/Integer".to_string(),
669 effective_type: None,
670 semantic_type: None,
671 field_ref: None,
672 },
673 ],
674 native_form: None,
675 insights: vec![],
676 },
677 database_id: MetabaseId(1),
678 started_at: chrono::Utc::now(),
679 finished_at: Some(chrono::Utc::now()),
680 status: QueryStatus::Completed,
681 row_count: Some(1),
682 running_time: Some(150),
683 json_query: serde_json::json!({}),
684 })
685 }
686}