feat: wire remaining handlers — tag, quota, register asset, sidecar, processing
14 new endpoints: POST tags, GET quota, POST register, 6 sidecar, 7 processing. DTOs, AppState groups, LogSidecarWriter, full bootstrap wiring.
This commit is contained in:
197
crates/presentation/src/handlers/processing.rs
Normal file
197
crates/presentation/src/handlers/processing.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use crate::{errors::AppError, extractors::JwtClaims, state::AppState};
|
||||
use api_types::{
|
||||
requests::{
|
||||
CompleteJobRequest, ConfigurePipelineRequest, EnqueueJobRequest, FailJobRequest,
|
||||
ManagePluginRequest,
|
||||
},
|
||||
responses::{BatchProgressResponse, JobResponse, PipelineResponse, PluginResponse},
|
||||
};
|
||||
use application::processing::{
|
||||
CompleteJobCommand, ConfigurePipelineCommand, EnqueueJobCommand, FailJobCommand,
|
||||
ManagePluginCommand, PipelineStepConfig, PluginAction, ReportBatchProgressQuery,
|
||||
StartJobCommand,
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use domain::{
|
||||
entities::{JobType, PluginType},
|
||||
errors::DomainError,
|
||||
value_objects::{MetadataValue, StructuredData, SystemId},
|
||||
};
|
||||
|
||||
fn parse_job_type(s: &str) -> JobType {
|
||||
match s {
|
||||
"scan_directory" => JobType::ScanDirectory,
|
||||
"extract_metadata" => JobType::ExtractMetadata,
|
||||
"generate_derivative" => JobType::GenerateDerivative,
|
||||
"sync_sidecar" => JobType::SyncSidecar,
|
||||
"detect_duplicates" => JobType::DetectDuplicates,
|
||||
other => JobType::Custom(other.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_plugin_type(s: &str) -> Result<PluginType, AppError> {
|
||||
match s {
|
||||
"media_processor" => Ok(PluginType::MediaProcessor),
|
||||
"scheduled_task" => Ok(PluginType::ScheduledTask),
|
||||
"sidecar_writer" => Ok(PluginType::SidecarWriter),
|
||||
_ => Err(AppError::from(DomainError::Validation(format!(
|
||||
"Invalid plugin type: {s}"
|
||||
)))),
|
||||
}
|
||||
}
|
||||
|
||||
fn hashmap_to_structured(
|
||||
map: &std::collections::HashMap<String, serde_json::Value>,
|
||||
) -> StructuredData {
|
||||
let mut sd = StructuredData::new();
|
||||
for (k, v) in map {
|
||||
sd.insert(k.clone(), MetadataValue::from(v.clone()));
|
||||
}
|
||||
sd
|
||||
}
|
||||
|
||||
pub async fn enqueue_job(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Json(req): Json<EnqueueJobRequest>,
|
||||
) -> Result<(StatusCode, Json<JobResponse>), AppError> {
|
||||
let payload = req
|
||||
.payload
|
||||
.as_ref()
|
||||
.map(hashmap_to_structured)
|
||||
.unwrap_or_default();
|
||||
|
||||
let cmd = EnqueueJobCommand {
|
||||
job_type: parse_job_type(&req.job_type),
|
||||
priority: req.priority.unwrap_or(0),
|
||||
payload,
|
||||
target_asset_id: req.target_asset_id.map(SystemId::from_uuid),
|
||||
batch_id: req.batch_id.map(SystemId::from_uuid),
|
||||
};
|
||||
let job = state.processing.enqueue_job.execute(cmd).await?;
|
||||
Ok((StatusCode::CREATED, Json(JobResponse::from_domain(&job))))
|
||||
}
|
||||
|
||||
pub async fn start_job(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Path((job_id,)): Path<(uuid::Uuid,)>,
|
||||
) -> Result<Json<JobResponse>, AppError> {
|
||||
let cmd = StartJobCommand {
|
||||
job_id: SystemId::from_uuid(job_id),
|
||||
};
|
||||
let job = state.processing.start_job.execute(cmd).await?;
|
||||
Ok(Json(JobResponse::from_domain(&job)))
|
||||
}
|
||||
|
||||
pub async fn complete_job(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Path((job_id,)): Path<(uuid::Uuid,)>,
|
||||
Json(req): Json<CompleteJobRequest>,
|
||||
) -> Result<Json<JobResponse>, AppError> {
|
||||
let cmd = CompleteJobCommand {
|
||||
job_id: SystemId::from_uuid(job_id),
|
||||
result: hashmap_to_structured(&req.result),
|
||||
};
|
||||
let job = state.processing.complete_job.execute(cmd).await?;
|
||||
Ok(Json(JobResponse::from_domain(&job)))
|
||||
}
|
||||
|
||||
pub async fn fail_job(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Path((job_id,)): Path<(uuid::Uuid,)>,
|
||||
Json(req): Json<FailJobRequest>,
|
||||
) -> Result<Json<JobResponse>, AppError> {
|
||||
let cmd = FailJobCommand {
|
||||
job_id: SystemId::from_uuid(job_id),
|
||||
error: req.error,
|
||||
};
|
||||
let job = state.processing.fail_job.execute(cmd).await?;
|
||||
Ok(Json(JobResponse::from_domain(&job)))
|
||||
}
|
||||
|
||||
pub async fn batch_progress(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Path((batch_id,)): Path<(uuid::Uuid,)>,
|
||||
) -> Result<Json<BatchProgressResponse>, AppError> {
|
||||
let query = ReportBatchProgressQuery {
|
||||
batch_id: SystemId::from_uuid(batch_id),
|
||||
};
|
||||
let progress = state.processing.batch_progress.execute(query).await?;
|
||||
Ok(Json(BatchProgressResponse::from_domain(&progress)))
|
||||
}
|
||||
|
||||
pub async fn manage_plugin(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Json(req): Json<ManagePluginRequest>,
|
||||
) -> Result<(StatusCode, Json<PluginResponse>), AppError> {
|
||||
let action = match req.action.as_str() {
|
||||
"create" => {
|
||||
let name = req.name.ok_or_else(|| {
|
||||
AppError::from(DomainError::Validation("name required for create".into()))
|
||||
})?;
|
||||
let pt = req.plugin_type.as_deref().unwrap_or("media_processor");
|
||||
let plugin_type = parse_plugin_type(pt)?;
|
||||
let config = req
|
||||
.config
|
||||
.as_ref()
|
||||
.map(hashmap_to_structured)
|
||||
.unwrap_or_default();
|
||||
PluginAction::Create {
|
||||
name,
|
||||
plugin_type,
|
||||
config,
|
||||
}
|
||||
}
|
||||
"enable" => PluginAction::Enable,
|
||||
"disable" => PluginAction::Disable,
|
||||
other => {
|
||||
return Err(AppError::from(DomainError::Validation(format!(
|
||||
"Invalid plugin action: {other}. Use create, enable, or disable"
|
||||
))));
|
||||
}
|
||||
};
|
||||
|
||||
let cmd = ManagePluginCommand {
|
||||
plugin_id: req.plugin_id.map(SystemId::from_uuid),
|
||||
action,
|
||||
};
|
||||
let plugin = state.processing.manage_plugin.execute(cmd).await?;
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(PluginResponse::from_domain(&plugin)),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn configure_pipeline(
|
||||
State(state): State<AppState>,
|
||||
_claims: JwtClaims,
|
||||
Json(req): Json<ConfigurePipelineRequest>,
|
||||
) -> Result<(StatusCode, Json<PipelineResponse>), AppError> {
|
||||
let steps = req
|
||||
.steps
|
||||
.iter()
|
||||
.map(|s| PipelineStepConfig {
|
||||
plugin_id: SystemId::from_uuid(s.plugin_id),
|
||||
config: hashmap_to_structured(&s.config),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cmd = ConfigurePipelineCommand {
|
||||
trigger_event: req.trigger_event,
|
||||
steps,
|
||||
};
|
||||
let pipeline = state.processing.configure_pipeline.execute(cmd).await?;
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(PipelineResponse::from_domain(&pipeline)),
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user