use std::sync::Arc;
use crate::platform::adapters::data::DataAdapter;
use crate::platform::error::PlatformError;
use crate::platform::model::{
ProjectOperationKind, ProjectOperationRecord, ProjectOperationStatus, now_ts, slug_segment,
};
pub struct ProjectOperationService {
data: Arc<dyn DataAdapter>,
}
impl ProjectOperationService {
pub fn new(data: Arc<dyn DataAdapter>) -> Self {
Self { data }
}
pub fn create(
&self,
owner: &str,
project: &str,
kind: ProjectOperationKind,
source_office_id: Option<String>,
target_office_id: Option<String>,
) -> Result<ProjectOperationRecord, PlatformError> {
let owner = slug_segment(owner);
let project = slug_segment(project);
let project_row = self
.data
.get_project(&owner, &project)?
.ok_or_else(|| PlatformError::new("PROJECT_NOT_FOUND", "project not found"))?;
let now = now_ts();
let operation = ProjectOperationRecord {
operation_id: format!("op-{}-{now}", kind.key()),
project_id: project_row.project_id,
owner,
project,
kind,
status: ProjectOperationStatus::Pending,
current_step: "pending".to_string(),
source_office_id,
target_office_id,
artifact_rel_path: None,
artifact_sha256: None,
artifact_bytes: None,
error_message: None,
retry_count: 0,
created_at: now,
updated_at: now,
completed_at: None,
};
self.data.put_project_operation(&operation)?;
Ok(operation)
}
pub fn mark_running(
&self,
record: &ProjectOperationRecord,
step: impl Into<String>,
) -> Result<ProjectOperationRecord, PlatformError> {
let mut next = record.clone();
next.status = ProjectOperationStatus::Running;
next.current_step = step.into();
next.error_message = None;
next.updated_at = now_ts();
self.data.put_project_operation(&next)?;
Ok(next)
}
pub fn mark_failed(
&self,
record: &ProjectOperationRecord,
step: impl Into<String>,
error_message: impl Into<String>,
) -> Result<ProjectOperationRecord, PlatformError> {
let mut next = record.clone();
next.status = ProjectOperationStatus::Failed;
next.current_step = step.into();
next.error_message = Some(error_message.into());
next.updated_at = now_ts();
next.completed_at = None;
self.data.put_project_operation(&next)?;
Ok(next)
}
pub fn mark_completed(
&self,
record: &ProjectOperationRecord,
step: impl Into<String>,
artifact_rel_path: Option<String>,
artifact_sha256: Option<String>,
artifact_bytes: Option<u64>,
) -> Result<ProjectOperationRecord, PlatformError> {
let now = now_ts();
let mut next = record.clone();
next.status = ProjectOperationStatus::Completed;
next.current_step = step.into();
next.artifact_rel_path = artifact_rel_path;
next.artifact_sha256 = artifact_sha256;
next.artifact_bytes = artifact_bytes;
next.error_message = None;
next.updated_at = now;
next.completed_at = Some(now);
self.data.put_project_operation(&next)?;
Ok(next)
}
pub fn get(
&self,
owner: &str,
project: &str,
operation_id: &str,
) -> Result<Option<ProjectOperationRecord>, PlatformError> {
self.data
.get_project_operation(owner, project, operation_id)
}
pub fn list(
&self,
owner: &str,
project: &str,
limit: usize,
) -> Result<Vec<ProjectOperationRecord>, PlatformError> {
self.data.list_project_operations(owner, project, limit)
}
}