articles_rs/articles/
repo.rsuse async_trait::async_trait;
use sqlx::{postgres::PgPool, Error as SqlxError, FromRow, Row};
use std::collections::HashMap;
use uuid::Uuid;
use crate::postgres_repository::PostgresRepository;
use super::Article;
pub struct ArticleRepository {
pool: PgPool,
}
impl ArticleRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
#[async_trait]
impl PostgresRepository for ArticleRepository {
type Error = SqlxError;
type Item = Article;
async fn create(&self, article: &Self::Item) -> Result<Uuid, Self::Error> {
let row = sqlx::query(
r#"
INSERT INTO articles (title, slug, description, author, status, date, content)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id
"#,
)
.bind(&article.title)
.bind(&article.slug)
.bind(&article.description)
.bind(&article.author)
.bind(&article.status)
.bind(article.date)
.bind(&article.content)
.fetch_one(&self.pool)
.await?;
let id: Uuid = row.try_get("id")?;
Ok(id)
}
async fn update(&self, article: &Self::Item) -> Result<(), Self::Error> {
let affected_rows = sqlx::query(
r#"
UPDATE articles
SET title = $1, slug = $2, author = $3, status = $4, date = $5, content = $6, description = $7
WHERE id = $8
"#,
)
.bind(&article.title)
.bind(&article.slug)
.bind(&article.author)
.bind(&article.status)
.bind(article.date)
.bind(&article.content)
.bind(&article.description)
.bind(article.id)
.execute(&self.pool)
.await?
.rows_affected();
if affected_rows == 0 {
return Err(SqlxError::RowNotFound);
}
Ok(())
}
async fn delete(&self, id: Uuid) -> Result<(), Self::Error> {
let affected_rows = sqlx::query(
r#"
DELETE FROM articles
WHERE id = $1
"#,
)
.bind(id)
.execute(&self.pool)
.await?
.rows_affected();
if affected_rows == 0 {
return Err(SqlxError::RowNotFound);
}
Ok(())
}
async fn find_by_id(&self, id: Uuid) -> Result<Option<Self::Item>, Self::Error> {
let row_result = sqlx::query(
"SELECT id, title, slug, description, author, status, date, content FROM articles WHERE id = $1",
)
.bind(id)
.fetch_optional(&self.pool)
.await?;
match row_result {
Some(row) => {
let article = Article::from_row(&row)?;
Ok(Some(article))
}
None => Ok(None),
}
}
async fn list(
&self,
filters: Option<HashMap<String, String>>,
) -> Result<Vec<Self::Item>, Self::Error> {
let mut query = String::from(
"SELECT id, title, slug, description, author, status, date::timestamptz, content FROM articles",
);
let mut params: Vec<String> = Vec::new();
let mut param_count = 1;
if let Some(filter_map) = filters {
if !filter_map.is_empty() {
query.push_str(" WHERE ");
for (column, value) in filter_map {
if param_count > 1 {
query.push_str(" AND ");
}
query.push_str(&format!("{} = ${}", column, param_count));
params.push(value);
param_count += 1;
}
}
}
query.push_str(" ORDER BY date DESC");
let mut query_builder = sqlx::query(&query);
for param in params {
if let Ok(uuid) = sqlx::types::Uuid::parse_str(¶m) {
query_builder = query_builder.bind(uuid);
} else {
query_builder = query_builder.bind(param);
}
}
let articles = query_builder
.fetch_all(&self.pool)
.await?
.into_iter()
.map(|row| Article::from_row(&row))
.collect::<Result<Vec<_>, _>>()?;
Ok(articles)
}
}