1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc};
11
12#[cfg(feature = "openapi")]
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16#[cfg(feature = "validation")]
17use validator::Validate;
18
19use crate::domain::entity::ProcessingJob;
20use crate::domain::entity::AuditMetadata;
21use crate::domain::entity::JobStatus;
22use crate::domain::entity::ProcessingJobType;
23
24#[derive(Debug, Clone, Deserialize)]
33#[cfg_attr(feature = "openapi", derive(ToSchema))]
34#[cfg_attr(feature = "validation", derive(Validate))]
35#[serde(rename_all = "camelCase")]
36pub struct CreateProcessingJobDto {
37 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
38 #[serde(alias = "file_id")]
39 pub file_id: Uuid,
40 #[serde(alias = "job_type")]
41 pub job_type: ProcessingJobType,
42 pub status: JobStatus,
43 #[cfg_attr(feature = "openapi", schema(example = 42))]
44 pub priority: i32,
45 #[serde(default, skip_serializing_if = "Option::is_none", alias = "input_data")]
46 pub input_data: Option<serde_json::Value>,
47 #[serde(default, skip_serializing_if = "Option::is_none", alias = "result_data")]
48 pub result_data: Option<serde_json::Value>,
49 #[serde(default, skip_serializing_if = "Option::is_none", alias = "error_message")]
50 pub error_message: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none", alias = "started_at")]
52 pub started_at: Option<DateTime<Utc>>,
53 #[serde(default, skip_serializing_if = "Option::is_none", alias = "completed_at")]
54 pub completed_at: Option<DateTime<Utc>>,
55 #[cfg_attr(feature = "openapi", schema(example = 42))]
56 #[serde(alias = "retry_count")]
57 pub retry_count: i32,
58 #[cfg_attr(feature = "openapi", schema(example = 42))]
59 #[serde(alias = "max_retries")]
60 pub max_retries: i32,
61}
62
63#[derive(Debug, Clone, Deserialize)]
72#[cfg_attr(feature = "openapi", derive(ToSchema))]
73#[cfg_attr(feature = "validation", derive(Validate))]
74#[serde(rename_all = "camelCase")]
75pub struct UpdateProcessingJobDto {
76 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
77 #[serde(alias = "file_id")]
78 pub file_id: Uuid,
79 #[serde(alias = "job_type")]
80 pub job_type: ProcessingJobType,
81 pub status: JobStatus,
82 #[cfg_attr(feature = "openapi", schema(example = 42))]
83 pub priority: i32,
84 #[serde(default, skip_serializing_if = "Option::is_none", alias = "input_data")]
85 pub input_data: Option<serde_json::Value>,
86 #[serde(default, skip_serializing_if = "Option::is_none", alias = "result_data")]
87 pub result_data: Option<serde_json::Value>,
88 #[serde(default, skip_serializing_if = "Option::is_none", alias = "error_message")]
89 pub error_message: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none", alias = "started_at")]
91 pub started_at: Option<DateTime<Utc>>,
92 #[serde(default, skip_serializing_if = "Option::is_none", alias = "completed_at")]
93 pub completed_at: Option<DateTime<Utc>>,
94 #[cfg_attr(feature = "openapi", schema(example = 42))]
95 #[serde(alias = "retry_count")]
96 pub retry_count: i32,
97 #[cfg_attr(feature = "openapi", schema(example = 42))]
98 #[serde(alias = "max_retries")]
99 pub max_retries: i32,
100}
101
102#[derive(Debug, Clone, Default, Deserialize)]
111#[cfg_attr(feature = "openapi", derive(ToSchema))]
112#[cfg_attr(feature = "validation", derive(Validate))]
113#[serde(rename_all = "camelCase")]
114pub struct PatchProcessingJobDto {
115 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
116 #[serde(skip_serializing_if = "Option::is_none", alias = "file_id")]
117 pub file_id: Option<Uuid>,
118 #[serde(skip_serializing_if = "Option::is_none", alias = "job_type")]
119 pub job_type: Option<ProcessingJobType>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub status: Option<JobStatus>,
122 #[cfg_attr(feature = "openapi", schema(example = 42))]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub priority: Option<i32>,
125 #[serde(skip_serializing_if = "Option::is_none", alias = "input_data")]
126 pub input_data: Option<serde_json::Value>,
127 #[serde(skip_serializing_if = "Option::is_none", alias = "result_data")]
128 pub result_data: Option<serde_json::Value>,
129 #[serde(skip_serializing_if = "Option::is_none", alias = "error_message")]
130 pub error_message: Option<String>,
131 #[serde(skip_serializing_if = "Option::is_none", alias = "started_at")]
132 pub started_at: Option<DateTime<Utc>>,
133 #[serde(skip_serializing_if = "Option::is_none", alias = "completed_at")]
134 pub completed_at: Option<DateTime<Utc>>,
135 #[cfg_attr(feature = "openapi", schema(example = 42))]
136 #[serde(skip_serializing_if = "Option::is_none", alias = "retry_count")]
137 pub retry_count: Option<i32>,
138 #[cfg_attr(feature = "openapi", schema(example = 42))]
139 #[serde(skip_serializing_if = "Option::is_none", alias = "max_retries")]
140 pub max_retries: Option<i32>,
141}
142
143impl PatchProcessingJobDto {
144 pub fn has_changes(&self) -> bool {
146 self.file_id.is_some() || self.job_type.is_some() || self.status.is_some() || self.priority.is_some() || self.input_data.is_some() || self.result_data.is_some() || self.error_message.is_some() || self.started_at.is_some() || self.completed_at.is_some() || self.retry_count.is_some() || self.max_retries.is_some()
147 }
148}
149
150#[derive(Debug, Clone, Serialize)]
159#[cfg_attr(feature = "openapi", derive(ToSchema))]
160#[serde(rename_all = "camelCase")]
161pub struct ProcessingJobResponseDto {
162 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
163 pub id: Uuid,
164 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
165 pub file_id: Uuid,
166 pub job_type: ProcessingJobType,
167 pub status: JobStatus,
168 #[cfg_attr(feature = "openapi", schema(example = 42))]
169 pub priority: i32,
170 pub input_data: Option<serde_json::Value>,
171 pub result_data: Option<serde_json::Value>,
172 pub error_message: Option<String>,
173 pub started_at: Option<DateTime<Utc>>,
174 pub completed_at: Option<DateTime<Utc>>,
175 #[cfg_attr(feature = "openapi", schema(example = 42))]
176 pub retry_count: i32,
177 #[cfg_attr(feature = "openapi", schema(example = 42))]
178 pub max_retries: i32,
179 pub metadata: AuditMetadata,
180}
181
182#[derive(Debug, Clone, Serialize)]
191#[cfg_attr(feature = "openapi", derive(ToSchema))]
192#[serde(rename_all = "camelCase")]
193pub struct ProcessingJobListResponseDto {
194 pub items: Vec<ProcessingJobResponseDto>,
196 pub total: u64,
198 pub page: u32,
200 pub per_page: u32,
202 pub total_pages: u32,
204 pub has_next: bool,
206 pub has_prev: bool,
208}
209
210impl ProcessingJobListResponseDto {
211 pub fn new(items: Vec<ProcessingJobResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
213 let total_pages = if per_page > 0 {
214 ((total as f64) / (per_page as f64)).ceil() as u32
215 } else {
216 0
217 };
218 Self {
219 items,
220 total,
221 page,
222 per_page,
223 total_pages,
224 has_next: page < total_pages,
225 has_prev: page > 1,
226 }
227 }
228}
229
230#[derive(Debug, Clone, Serialize)]
232#[cfg_attr(feature = "openapi", derive(ToSchema))]
233#[serde(rename_all = "camelCase")]
234pub struct ProcessingJobSummaryDto {
235 pub id: Uuid,
236 pub file_id: Uuid,
237 pub job_type: ProcessingJobType,
238 pub status: JobStatus,
239 pub created_at: Option<DateTime<Utc>>,
240}
241
242impl From<ProcessingJob> for ProcessingJobResponseDto {
247 fn from(entity: ProcessingJob) -> Self {
248 Self {
249 id: entity.id,
250 file_id: entity.file_id,
251 job_type: entity.job_type,
252 status: entity.status,
253 priority: entity.priority,
254 input_data: entity.input_data,
255 result_data: entity.result_data,
256 error_message: entity.error_message,
257 started_at: entity.started_at,
258 completed_at: entity.completed_at,
259 retry_count: entity.retry_count,
260 max_retries: entity.max_retries,
261 metadata: entity.metadata,
262 }
263 }
264}
265
266impl From<ProcessingJob> for ProcessingJobSummaryDto {
267 fn from(entity: ProcessingJob) -> Self {
268 let created_at = backbone_core::PersistentEntity::created_at(&entity);
269 Self {
270 id: entity.id,
271 file_id: entity.file_id,
272 job_type: entity.job_type,
273 status: entity.status,
274 created_at,
275 }
276 }
277}
278
279impl From<CreateProcessingJobDto> for ProcessingJob {
280 fn from(dto: CreateProcessingJobDto) -> Self {
281 Self {
282 id: Uuid::new_v4(),
283 file_id: dto.file_id,
284 job_type: dto.job_type,
285 status: dto.status,
286 priority: dto.priority,
287 input_data: dto.input_data,
288 result_data: dto.result_data,
289 error_message: dto.error_message,
290 started_at: dto.started_at,
291 completed_at: dto.completed_at,
292 retry_count: dto.retry_count,
293 max_retries: dto.max_retries,
294 metadata: AuditMetadata::default(),
295 }
296 }
297}
298
299impl From<&ProcessingJob> for ProcessingJobResponseDto {
300 fn from(entity: &ProcessingJob) -> Self {
301 Self {
302 id: entity.id.clone(),
303 file_id: entity.file_id.clone(),
304 job_type: entity.job_type.clone(),
305 status: entity.status.clone(),
306 priority: entity.priority.clone(),
307 input_data: entity.input_data.clone(),
308 result_data: entity.result_data.clone(),
309 error_message: entity.error_message.clone(),
310 started_at: entity.started_at.clone(),
311 completed_at: entity.completed_at.clone(),
312 retry_count: entity.retry_count.clone(),
313 max_retries: entity.max_retries.clone(),
314 metadata: entity.metadata.clone(),
315 }
316 }
317}
318
319impl backbone_core::FromCreateDto<CreateProcessingJobDto> for ProcessingJob {
320 fn from_create_dto(dto: CreateProcessingJobDto) -> backbone_core::ServiceResult<Self> {
321 Ok(ProcessingJob::from(dto))
322 }
323}
324
325impl backbone_core::ApplyUpdateDto<UpdateProcessingJobDto> for ProcessingJob {
326 fn apply_update(mut self, dto: UpdateProcessingJobDto) -> backbone_core::ServiceResult<Self> {
327 self.file_id = dto.file_id;
328 self.job_type = dto.job_type;
329 self.status = dto.status;
330 self.priority = dto.priority;
331 self.input_data = dto.input_data;
332 self.result_data = dto.result_data;
333 self.error_message = dto.error_message;
334 self.started_at = dto.started_at;
335 self.completed_at = dto.completed_at;
336 self.retry_count = dto.retry_count;
337 self.max_retries = dto.max_retries;
338 Ok(self)
339 }
340}
341
342