articles_rs/databases/
images_repository.rs

1//! Image repository module for database operations
2//! 
3//! This module provides the database model and repository implementation for article images.
4//! It handles CRUD operations and queries for images paths stored in a PostgreSQL database.
5
6use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use sqlx::{
9    postgres::{PgPool, PgRow},
10    Error as SqlxError, FromRow, Row,
11};
12use std::collections::HashMap;
13use uuid::Uuid;
14
15use super::postgres_repository::PostgresRepository;
16
17/// Database model representing an article's image
18#[derive(Debug)]
19pub struct DbArticleImage {
20    pub id: Option<Uuid>,
21    pub article_id: Uuid,
22    pub image_path: String,
23    pub visible: bool,
24    pub created_at: DateTime<Utc>,
25}
26
27/// Implementation for converting database rows to DbArticleImage
28impl<'r> FromRow<'r, PgRow> for DbArticleImage {
29    fn from_row(row: &'r PgRow) -> Result<Self, SqlxError> {
30        let row_id = row.try_get("id").ok();
31        Ok(Self {
32            id: row_id,
33            article_id: row.get("article_id"),
34            image_path: row.get("image_path"),
35            visible: row.get("visible"),
36            created_at: row
37                .try_get::<DateTime<Utc>, _>("created_at")
38                .unwrap_or_else(|_| {
39                    let naive_date: chrono::NaiveDateTime = row.try_get("created_at").unwrap();
40                    DateTime::<Utc>::from_naive_utc_and_offset(naive_date, Utc)
41                }),
42        })
43    }
44}
45
46/// Repository for handling article image database operations
47pub struct ImageRepository {
48    pool: PgPool,
49}
50
51impl ImageRepository {
52    /// Creates a new ImageRepository instance
53    ///
54    /// # Arguments
55    /// * `pool` - PostgreSQL connection pool
56    pub fn new(pool: PgPool) -> Self {
57        Self { pool }
58    }
59}
60
61/// Implementation of PostgresRepository trait for ImageRepository
62#[async_trait]
63impl PostgresRepository for ImageRepository {
64    type Error = SqlxError;
65    type Item = DbArticleImage;
66
67    async fn find_by_id(&self, id: Uuid) -> Result<Option<Self::Item>, Self::Error> {
68        let row = sqlx::query_as::<_, DbArticleImage>(
69            r#"
70            SELECT id, article_id, image_path, visible, created_at
71            FROM article_images
72            WHERE id = $1
73            "#,
74        )
75        .bind(id)
76        .fetch_optional(&self.pool)
77        .await?;
78
79        Ok(row)
80    }
81
82    async fn find_by_name(&self, _name: String) -> Result<Option<Self::Item>, Self::Error> {
83        Err(SqlxError::RowNotFound)
84    }
85
86    async fn list(
87        &self,
88        filters: Option<HashMap<String, String>>,
89    ) -> Result<Vec<Self::Item>, Self::Error> {
90        let mut query = String::from(
91            "SELECT id, article_id, image_path, visible, created_at FROM article_images WHERE 1=1",
92        );
93        let mut params = vec![];
94        let mut param_count = 1;
95
96        if let Some(filters) = filters {
97            for (key, value) in filters {
98                if key == "id" || key == "article_id" {
99                    // Cast the parameter to UUID for id fields
100                    query.push_str(&format!(" AND {} = ${}::uuid", key, param_count));
101                } else {
102                    query.push_str(&format!(" AND {} = ${}", key, param_count));
103                }
104                params.push(value);
105                param_count += 1;
106            }
107        }
108
109        let mut query_builder = sqlx::query(&query);
110        for param in params {
111            query_builder = query_builder.bind(param);
112        }
113
114        let rows = query_builder
115            .fetch_all(&self.pool)
116            .await?
117            .into_iter()
118            .map(|row| DbArticleImage::from_row(&row))
119            .collect::<Result<Vec<_>, _>>()?;
120
121        Ok(rows)
122    }
123
124    async fn create(&self, item: &Self::Item) -> Result<Uuid, Self::Error> {
125        let row = sqlx::query_scalar::<_, Uuid>(
126            r#"
127            INSERT INTO article_images (article_id, image_path, visible)
128            VALUES ($1, $2, $3)
129            RETURNING id
130            "#,
131        )
132        .bind(item.article_id)
133        .bind(&item.image_path)
134        .bind(item.visible)
135        .fetch_one(&self.pool)
136        .await?;
137
138        Ok(row)
139    }
140
141    async fn delete(&self, id: Uuid) -> Result<(), Self::Error> {
142        let result = sqlx::query(
143            r#"
144            DELETE FROM article_images
145            WHERE id = $1
146            "#,
147        )
148        .bind(id)
149        .execute(&self.pool)
150        .await?;
151
152        if result.rows_affected() == 0 {
153            return Err(SqlxError::RowNotFound);
154        }
155
156        Ok(())
157    }
158
159    async fn delete_all(
160        &self,
161        filters: Option<HashMap<String, String>>,
162    ) -> Result<(), Self::Error> {
163        let items = self.list(filters).await?;
164        for item in items {
165            if let Some(id) = item.id {
166                self.delete(id).await?;
167            }
168        }
169        Ok(())
170    }
171
172    async fn update(&self, _item: &Self::Item) -> Result<(), Self::Error> {
173        Err(SqlxError::RowNotFound)
174    }
175}