articles_rs/articles/
service.rsuse chrono::{DateTime, Utc};
use sqlx::postgres::PgPool;
use std::collections::HashMap;
use std::error;
use std::io::ErrorKind;
use uuid::Uuid;
use crate::{
article_repository::{ArticleRepository, DbArticle},
config::DbConfig,
images_repository::{DbArticleImage, ImageRepository},
postgres_repository::PostgresRepository,
};
use super::Article;
pub struct ArticleService {
article_repo: ArticleRepository,
image_repo: ImageRepository,
}
impl std::fmt::Debug for ArticleService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArticleService")
.field("article_repo", &"ArticleRepository")
.field("image_repo", &"ImageRepository")
.finish()
}
}
impl ArticleService {
pub async fn new(config: DbConfig) -> Self {
let url = &config.get_connection_url();
let pool = PgPool::connect(url)
.await
.expect("unable to connect to the database");
let article_repo = ArticleRepository::new(pool.clone());
let image_repo = ImageRepository::new(pool);
ArticleService {
article_repo,
image_repo,
}
}
async fn get_article_with_images(
&self,
article: DbArticle,
) -> Result<Article, Box<dyn error::Error>> {
let mut filter = HashMap::new();
filter.insert(
"article_id".to_string(),
article.id.expect("could not get id").to_string(),
);
let images = self.image_repo.list(Some(filter)).await?;
let image_paths: Vec<String> = images.into_iter().map(|img| img.image_path).collect();
let mut found = Article::from(article);
found.add_images(image_paths);
Ok(found)
}
pub async fn get_article_by_slug(&self, slug: &str) -> Result<Article, Box<dyn error::Error>> {
match self.article_repo.find_by_name(slug.to_string()).await {
Ok(Some(article)) => self.get_article_with_images(article).await,
Ok(None) => Err(Box::new(std::io::Error::new(
ErrorKind::NotFound,
"Article not found",
))),
Err(e) => Err(Box::new(e)),
}
}
pub async fn get_article_by_id(&self, id: Uuid) -> Result<Article, Box<dyn error::Error>> {
match self.article_repo.find_by_id(id).await {
Ok(Some(article)) => self.get_article_with_images(article).await,
Ok(None) => Err(Box::new(std::io::Error::new(
ErrorKind::NotFound,
"Article not found",
))),
Err(e) => Err(Box::new(e)),
}
}
pub async fn delete_article(&self, id: Uuid) -> Result<(), Box<dyn error::Error>> {
let mut filter = HashMap::new();
filter.insert("article_id".to_string(), id.to_string());
self.image_repo.delete_all(Some(filter)).await?;
match self.article_repo.delete(id).await {
Ok(_) => Ok(()),
Err(e) => Err(Box::new(e)),
}
}
pub async fn list_articles(
&self,
filter: Option<HashMap<String, String>>,
) -> Result<Vec<Article>, Box<dyn error::Error>> {
match self.article_repo.list(filter).await {
Ok(v) => {
let articles = v.into_iter().map(Article::from).collect();
Ok(articles)
}
Err(e) => Err(Box::new(e)),
}
}
pub async fn create_article(
&self,
title: &str,
slug: &str,
description: &str,
author: &str,
) -> Result<Uuid, Box<dyn error::Error>> {
let authors = if author.contains('[') && author.contains(']') {
author
.trim_matches(|c| c == '[' || c == ']')
.split(',')
.map(|s| {
s.trim_matches('\'')
.trim()
.trim_matches('(')
.trim_matches(')')
})
.collect::<Vec<&str>>()
} else {
author
.split(',')
.map(|s| s.trim().trim_matches('(').trim_matches(')'))
.collect::<Vec<&str>>()
};
let article = Article::new(title, slug, description, authors);
match self.article_repo.create(&DbArticle::from(article)).await {
Ok(id) => Ok(id),
Err(e) => Err(Box::new(e)),
}
}
pub async fn update_article(
&self,
id: Uuid,
title: &str,
slug: &str,
description: &str,
author: Vec<String>,
status: Option<&str>,
created: Option<DateTime<Utc>>,
contents: &str,
images: Vec<String>,
source: &str,
publish: Option<DateTime<Utc>>,
) -> Result<(), Box<dyn error::Error>> {
let mut article = self
.article_repo
.find_by_id(id)
.await?
.ok_or_else(|| std::io::Error::new(ErrorKind::NotFound, "Article not found"))?;
article.title = title.to_string();
article.slug = slug.to_string();
article.description = description.to_string();
article.author = author.join(",");
article.content = contents.to_string();
article.source = source.to_string();
if let Some(s) = status {
article.status = s.to_string();
}
if let Some(d) = created {
article.created = d;
}
if let Some(p) = publish {
article.published = Some(p);
}
self.article_repo.update(&article).await?;
let mut filter = HashMap::new();
filter.insert("article_id".to_string(), id.to_string());
let current_images = self.image_repo.list(Some(filter)).await?;
let current_paths: Vec<String> = current_images
.iter()
.map(|img| img.image_path.clone())
.collect();
for image_path in &images {
if !image_path.is_empty() && !current_paths.contains(image_path) {
let image = DbArticleImage {
id: None,
article_id: id,
image_path: image_path.clone(),
visible: true,
created_at: Utc::now(),
};
self.image_repo.create(&image).await?;
}
}
for current_image in current_images {
if !images.contains(¤t_image.image_path) {
self.image_repo.delete(current_image.id.unwrap()).await?;
}
}
Ok(())
}
}