use super::traits::{Service, ServiceError, ServiceResult, ValidationContext};
use crate::core::models::{common::CollectionId, Collection};
use crate::repository::{
collection::{CollectionFilterParams, CollectionRepository},
traits::PaginationParams,
};
use async_trait::async_trait;
use std::sync::Arc;
#[async_trait]
pub trait CollectionService: Service {
async fn get_collection(&self, id: CollectionId) -> ServiceResult<Collection>;
async fn list_collections(
&self,
pagination: Option<PaginationParams>,
filters: Option<CollectionFilterParams>,
) -> ServiceResult<Vec<Collection>>;
async fn create_collection(&self, collection: Collection) -> ServiceResult<Collection>;
async fn update_collection(
&self,
id: CollectionId,
collection: Collection,
) -> ServiceResult<Collection>;
async fn delete_collection(&self, id: CollectionId) -> ServiceResult<()>;
async fn archive_collection(&self, id: CollectionId) -> ServiceResult<()>;
async fn unarchive_collection(&self, id: CollectionId) -> ServiceResult<()>;
async fn move_collection(
&self,
id: CollectionId,
new_parent_id: Option<CollectionId>,
) -> ServiceResult<Collection>;
async fn get_root_collections(&self) -> ServiceResult<Vec<Collection>>;
async fn get_collections_by_parent(
&self,
parent_id: CollectionId,
) -> ServiceResult<Vec<Collection>>;
async fn validate_collection(&self, collection: &Collection) -> ServiceResult<()>;
}
pub struct HttpCollectionService {
repository: Arc<dyn CollectionRepository>,
}
impl HttpCollectionService {
pub fn new(repository: Arc<dyn CollectionRepository>) -> Self {
Self { repository }
}
fn validate_collection_rules(&self, collection: &Collection) -> ServiceResult<()> {
let mut context = ValidationContext::new();
if collection.name.trim().is_empty() {
context.add_error("Collection name cannot be empty");
}
if collection.name.len() > 255 {
context.add_error("Collection name cannot exceed 255 characters");
}
if let Some(desc) = &collection.description {
if desc.len() > 5000 {
context.add_error("Collection description cannot exceed 5000 characters");
}
}
if let Some(color) = &collection.color {
if !color.starts_with('#') || color.len() != 7 {
context.add_error("Collection color must be in hex format (#RRGGBB)");
}
}
if let Some(slug) = &collection.slug {
if slug.contains(' ') {
context.add_error("Collection slug cannot contain spaces");
}
if slug.len() > 100 {
context.add_error("Collection slug cannot exceed 100 characters");
}
}
context.to_result()
}
async fn check_circular_reference(
&self,
id: CollectionId,
parent_id: Option<CollectionId>,
) -> ServiceResult<()> {
if let Some(parent) = parent_id {
if parent == id {
return Err(ServiceError::BusinessRule(
"Cannot set collection as its own parent".to_string(),
));
}
}
Ok(())
}
}
#[async_trait]
impl Service for HttpCollectionService {
fn name(&self) -> &str {
"CollectionService"
}
}
#[async_trait]
impl CollectionService for HttpCollectionService {
async fn get_collection(&self, id: CollectionId) -> ServiceResult<Collection> {
self.repository.get(&id).await.map_err(ServiceError::from)
}
async fn list_collections(
&self,
pagination: Option<PaginationParams>,
filters: Option<CollectionFilterParams>,
) -> ServiceResult<Vec<Collection>> {
self.repository
.list_with_filters(pagination, filters)
.await
.map_err(ServiceError::from)
}
async fn create_collection(&self, collection: Collection) -> ServiceResult<Collection> {
self.validate_collection_rules(&collection)?;
if let Some(parent_id) = collection.parent_id {
self.repository
.get(&CollectionId(parent_id))
.await
.map_err(|_| {
ServiceError::NotFound(format!("Parent collection {} not found", parent_id))
})?;
}
self.repository
.create(&collection)
.await
.map_err(ServiceError::from)
}
async fn update_collection(
&self,
id: CollectionId,
mut collection: Collection,
) -> ServiceResult<Collection> {
collection.id = Some(id);
self.validate_collection_rules(&collection)?;
self.repository.get(&id).await.map_err(ServiceError::from)?;
if let Some(parent_id) = collection.parent_id {
self.check_circular_reference(id, Some(CollectionId(parent_id)))
.await?;
}
self.repository
.update(&id, &collection)
.await
.map_err(ServiceError::from)
}
async fn delete_collection(&self, id: CollectionId) -> ServiceResult<()> {
let collection = self.repository.get(&id).await.map_err(ServiceError::from)?;
if collection.is_personal() {
return Err(ServiceError::BusinessRule(
"Cannot delete personal collections".to_string(),
));
}
self.repository
.delete(&id)
.await
.map_err(ServiceError::from)
}
async fn archive_collection(&self, id: CollectionId) -> ServiceResult<()> {
self.repository
.archive(&id)
.await
.map_err(ServiceError::from)
}
async fn unarchive_collection(&self, id: CollectionId) -> ServiceResult<()> {
self.repository
.unarchive(&id)
.await
.map_err(ServiceError::from)
}
async fn move_collection(
&self,
id: CollectionId,
new_parent_id: Option<CollectionId>,
) -> ServiceResult<Collection> {
self.check_circular_reference(id, new_parent_id).await?;
if let Some(parent) = new_parent_id {
self.repository.get(&parent).await.map_err(|_| {
ServiceError::NotFound(format!("Parent collection {} not found", parent.0))
})?;
}
self.repository
.move_collection(&id, new_parent_id)
.await
.map_err(ServiceError::from)
}
async fn get_root_collections(&self) -> ServiceResult<Vec<Collection>> {
self.repository
.get_root_collections()
.await
.map_err(ServiceError::from)
}
async fn get_collections_by_parent(
&self,
parent_id: CollectionId,
) -> ServiceResult<Vec<Collection>> {
self.repository
.get_by_parent(Some(parent_id))
.await
.map_err(ServiceError::from)
}
async fn validate_collection(&self, collection: &Collection) -> ServiceResult<()> {
self.validate_collection_rules(collection)
}
}