use std::sync::Arc;
use chrono::Utc;
use uuid::Uuid;
use super::error::{ServiceError, ServiceResult};
use crate::domain::entity::{ConversionJob, ConversionStatus};
use crate::infrastructure::persistence::ConversionJobRepository;
use crate::infrastructure::persistence::StoredFileRepository;
pub struct ConversionService {
conversion_repo: Arc<ConversionJobRepository>,
file_repo: Arc<StoredFileRepository>,
}
impl ConversionService {
pub fn new(
conversion_repo: Arc<ConversionJobRepository>,
file_repo: Arc<StoredFileRepository>,
) -> Self {
Self { conversion_repo, file_repo }
}
pub async fn request_conversion(
&self,
source_file_id: Uuid,
target_format: &str,
options: Option<serde_json::Value>,
) -> ServiceResult<ConversionJob> {
let _file = self.file_repo
.find_by_id(&source_file_id.to_string())
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)?;
let mut builder = ConversionJob::builder()
.source_file_id(source_file_id)
.target_format(target_format.to_string())
.status(ConversionStatus::Pending)
.progress(0);
if let Some(opts) = options {
builder = builder.conversion_options(opts);
}
let job = builder.build()
.map_err(|e| ServiceError::Validation(e))?;
let created = self.conversion_repo
.create(&job)
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?;
Ok(created)
}
pub async fn mark_started(&self, job_id: Uuid) -> ServiceResult<ConversionJob> {
let mut job = self.find_job(job_id).await?;
if job.status != ConversionStatus::Pending {
return Err(ServiceError::Validation(format!("Job {} is not in pending state", job_id)));
}
job.status = ConversionStatus::Processing;
job.started_at = Some(Utc::now());
job.metadata.touch();
let id_str = job.id.to_string();
self.conversion_repo
.update(&id_str, &job)
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)
}
pub async fn update_progress(&self, job_id: Uuid, progress: i32) -> ServiceResult<ConversionJob> {
let mut job = self.find_job(job_id).await?;
if progress < 0 || progress > 100 {
return Err(ServiceError::Validation("Progress must be between 0 and 100".into()));
}
job.progress = progress;
job.metadata.touch();
let id_str = job.id.to_string();
self.conversion_repo
.update(&id_str, &job)
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)
}
pub async fn mark_completed(
&self,
job_id: Uuid,
result_file_id: Uuid,
) -> ServiceResult<ConversionJob> {
let mut job = self.find_job(job_id).await?;
job.status = ConversionStatus::Completed;
job.result_file_id = Some(result_file_id);
job.progress = 100;
job.completed_at = Some(Utc::now());
job.metadata.touch();
let id_str = job.id.to_string();
self.conversion_repo
.update(&id_str, &job)
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)
}
pub async fn mark_failed(
&self,
job_id: Uuid,
error_message: &str,
) -> ServiceResult<ConversionJob> {
let mut job = self.find_job(job_id).await?;
job.status = ConversionStatus::Failed;
job.error_message = Some(error_message.to_string());
job.completed_at = Some(Utc::now());
job.metadata.touch();
let id_str = job.id.to_string();
self.conversion_repo
.update(&id_str, &job)
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)
}
async fn find_job(&self, job_id: Uuid) -> ServiceResult<ConversionJob> {
self.conversion_repo
.find_by_id(&job_id.to_string())
.await
.map_err(|e| ServiceError::Repository(backbone_core::RepositoryError::DatabaseError(e.to_string())))?
.ok_or(ServiceError::NotFound)
}
}