articles_rs/databases/
images_repository.rsuse async_trait::async_trait;
use chrono::{DateTime, Utc};
use sqlx::{
postgres::{PgPool, PgRow},
Error as SqlxError, FromRow, Row,
};
use std::collections::HashMap;
use uuid::Uuid;
use super::postgres_repository::PostgresRepository;
#[derive(Debug)]
pub struct DbArticleImage {
pub id: Option<Uuid>,
pub article_id: Uuid,
pub image_path: String,
pub visible: bool,
pub created_at: DateTime<Utc>,
}
impl<'r> FromRow<'r, PgRow> for DbArticleImage {
fn from_row(row: &'r PgRow) -> Result<Self, SqlxError> {
let row_id = row.try_get("id").ok();
Ok(Self {
id: row_id,
article_id: row.get("article_id"),
image_path: row.get("image_path"),
visible: row.get("visible"),
created_at: row
.try_get::<DateTime<Utc>, _>("created_at")
.unwrap_or_else(|_| {
let naive_date: chrono::NaiveDateTime = row.try_get("created_at").unwrap();
DateTime::<Utc>::from_naive_utc_and_offset(naive_date, Utc)
}),
})
}
}
pub struct ImageRepository {
pool: PgPool,
}
impl ImageRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
#[async_trait]
impl PostgresRepository for ImageRepository {
type Error = SqlxError;
type Item = DbArticleImage;
async fn find_by_id(&self, id: Uuid) -> Result<Option<Self::Item>, Self::Error> {
let row = sqlx::query_as::<_, DbArticleImage>(
r#"
SELECT id, article_id, image_path, visible, created_at
FROM article_images
WHERE id = $1
"#,
)
.bind(id)
.fetch_optional(&self.pool)
.await?;
Ok(row)
}
async fn find_by_name(&self, _name: String) -> Result<Option<Self::Item>, Self::Error> {
Err(SqlxError::RowNotFound)
}
async fn list(
&self,
filters: Option<HashMap<String, String>>,
) -> Result<Vec<Self::Item>, Self::Error> {
let mut query = String::from(
"SELECT id, article_id, image_path, visible, created_at FROM article_images WHERE 1=1",
);
let mut params = vec![];
let mut param_count = 1;
if let Some(filters) = filters {
for (key, value) in filters {
if key == "id" || key == "article_id" {
query.push_str(&format!(" AND {} = ${}::uuid", key, param_count));
} else {
query.push_str(&format!(" AND {} = ${}", key, param_count));
}
params.push(value);
param_count += 1;
}
}
let mut query_builder = sqlx::query(&query);
for param in params {
query_builder = query_builder.bind(param);
}
let rows = query_builder
.fetch_all(&self.pool)
.await?
.into_iter()
.map(|row| DbArticleImage::from_row(&row))
.collect::<Result<Vec<_>, _>>()?;
Ok(rows)
}
async fn create(&self, item: &Self::Item) -> Result<Uuid, Self::Error> {
let row = sqlx::query_scalar::<_, Uuid>(
r#"
INSERT INTO article_images (article_id, image_path, visible)
VALUES ($1, $2, $3)
RETURNING id
"#,
)
.bind(item.article_id)
.bind(&item.image_path)
.bind(item.visible)
.fetch_one(&self.pool)
.await?;
Ok(row)
}
async fn delete(&self, id: Uuid) -> Result<(), Self::Error> {
let result = sqlx::query(
r#"
DELETE FROM article_images
WHERE id = $1
"#,
)
.bind(id)
.execute(&self.pool)
.await?;
if result.rows_affected() == 0 {
return Err(SqlxError::RowNotFound);
}
Ok(())
}
async fn delete_all(
&self,
filters: Option<HashMap<String, String>>,
) -> Result<(), Self::Error> {
let items = self.list(filters).await?;
for item in items {
if let Some(id) = item.id {
self.delete(id).await?;
}
}
Ok(())
}
async fn update(&self, _item: &Self::Item) -> Result<(), Self::Error> {
Err(SqlxError::RowNotFound)
}
}