refactor: store typed WrapUpReport in domain, serialize in adapters
Some checks failed
CI / Check / Test (push) Failing after 45s

This commit is contained in:
2026-06-03 01:24:02 +02:00
parent e4b8ba550e
commit d94ccbe057
9 changed files with 33 additions and 32 deletions

View File

@@ -4,7 +4,7 @@ use async_trait::async_trait;
use chrono::NaiveDate; use chrono::NaiveDate;
use domain::{ use domain::{
errors::DomainError, errors::DomainError,
models::wrapup::{DateRange, WrapUpRecord, WrapUpScope, WrapUpStatus}, models::wrapup::{DateRange, WrapUpRecord, WrapUpReport, WrapUpScope, WrapUpStatus},
ports::{WrapUpMovieRow, WrapUpRepository, WrapUpStatsQuery}, ports::{WrapUpMovieRow, WrapUpRepository, WrapUpStatsQuery},
value_objects::WrapUpId, value_objects::WrapUpId,
}; };
@@ -68,7 +68,7 @@ impl WrapUpRepository for PostgresWrapUpRepository {
.bind(record.start_date) .bind(record.start_date)
.bind(record.end_date) .bind(record.end_date)
.bind(status) .bind(status)
.bind(&record.report_json) .bind(record.report.as_ref().and_then(|r| serde_json::to_string(r).ok()))
.bind(&record.error_message) .bind(&record.error_message)
.bind(record.created_at) .bind(record.created_at)
.bind(record.completed_at) .bind(record.completed_at)
@@ -99,15 +99,17 @@ impl WrapUpRepository for PostgresWrapUpRepository {
Ok(()) Ok(())
} }
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError> { async fn set_complete(&self, id: &WrapUpId, report: &WrapUpReport) -> Result<(), DomainError> {
let id_str = id.value().to_string(); let id_str = id.value().to_string();
let json = serde_json::to_string(report)
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
sqlx::query( sqlx::query(
"UPDATE wrap_up_records \ "UPDATE wrap_up_records \
SET status = 'ready', report_json = $1, completed_at = NOW() \ SET status = 'ready', report_json = $1, completed_at = NOW() \
WHERE id = $2", WHERE id = $2",
) )
.bind(report_json) .bind(&json)
.bind(&id_str) .bind(&id_str)
.execute(&self.pool) .execute(&self.pool)
.await .await
@@ -227,13 +229,15 @@ fn row_to_record(row: &sqlx::postgres::PgRow) -> Result<WrapUpRecord, DomainErro
let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?; let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
let report = report_json.and_then(|j| serde_json::from_str(&j).ok());
Ok(WrapUpRecord { Ok(WrapUpRecord {
id: WrapUpId::from_uuid(parse_uuid(&id_str)?), id: WrapUpId::from_uuid(parse_uuid(&id_str)?),
user_id, user_id,
start_date, start_date,
end_date, end_date,
status: parse_status(&status_str)?, status: parse_status(&status_str)?,
report_json, report,
error_message, error_message,
created_at: parse_datetime(&created_at_str)?, created_at: parse_datetime(&created_at_str)?,
completed_at: completed_at_str completed_at: completed_at_str

View File

@@ -4,7 +4,7 @@ use async_trait::async_trait;
use chrono::NaiveDate; use chrono::NaiveDate;
use domain::{ use domain::{
errors::DomainError, errors::DomainError,
models::wrapup::{DateRange, WrapUpRecord, WrapUpScope, WrapUpStatus}, models::wrapup::{DateRange, WrapUpRecord, WrapUpReport, WrapUpScope, WrapUpStatus},
ports::{WrapUpMovieRow, WrapUpRepository, WrapUpStatsQuery}, ports::{WrapUpMovieRow, WrapUpRepository, WrapUpStatsQuery},
value_objects::WrapUpId, value_objects::WrapUpId,
}; };
@@ -79,7 +79,7 @@ impl WrapUpRepository for SqliteWrapUpRepository {
.bind(&start) .bind(&start)
.bind(&end) .bind(&end)
.bind(status) .bind(status)
.bind(&record.report_json) .bind(record.report.as_ref().and_then(|r| serde_json::to_string(r).ok()))
.bind(&record.error_message) .bind(&record.error_message)
.bind(&created) .bind(&created)
.bind(&completed) .bind(&completed)
@@ -110,15 +110,17 @@ impl WrapUpRepository for SqliteWrapUpRepository {
Ok(()) Ok(())
} }
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError> { async fn set_complete(&self, id: &WrapUpId, report: &WrapUpReport) -> Result<(), DomainError> {
let id_str = id.value().to_string(); let id_str = id.value().to_string();
let json = serde_json::to_string(report)
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
sqlx::query( sqlx::query(
"UPDATE wrap_up_records \ "UPDATE wrap_up_records \
SET status = 'ready', report_json = ?, completed_at = strftime('%Y-%m-%d %H:%M:%S', 'now') \ SET status = 'ready', report_json = ?, completed_at = strftime('%Y-%m-%d %H:%M:%S', 'now') \
WHERE id = ?", WHERE id = ?",
) )
.bind(report_json) .bind(&json)
.bind(&id_str) .bind(&id_str)
.execute(&self.pool) .execute(&self.pool)
.await .await
@@ -238,13 +240,15 @@ fn row_to_record(row: &sqlx::sqlite::SqliteRow) -> Result<WrapUpRecord, DomainEr
let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?; let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
let report = report_json.and_then(|j| serde_json::from_str(&j).ok());
Ok(WrapUpRecord { Ok(WrapUpRecord {
id: WrapUpId::from_uuid(parse_uuid(&id_str)?), id: WrapUpId::from_uuid(parse_uuid(&id_str)?),
user_id, user_id,
start_date: parse_date(&start_date_str)?, start_date: parse_date(&start_date_str)?,
end_date: parse_date(&end_date_str)?, end_date: parse_date(&end_date_str)?,
status: parse_status(&status_str)?, status: parse_status(&status_str)?,
report_json, report,
error_message, error_message,
created_at: parse_datetime(&created_at_str)?, created_at: parse_datetime(&created_at_str)?,
completed_at: completed_at_str completed_at: completed_at_str

View File

@@ -39,7 +39,7 @@ pub async fn execute(ctx: &AppContext, cmd: RequestWrapUpCommand) -> Result<Wrap
start_date: date_range.start(), start_date: date_range.start(),
end_date: date_range.end(), end_date: date_range.end(),
status: WrapUpStatus::Pending, status: WrapUpStatus::Pending,
report_json: None, report: None,
error_message: None, error_message: None,
created_at: Utc::now().naive_utc(), created_at: Utc::now().naive_utc(),
completed_at: None, completed_at: None,

View File

@@ -40,11 +40,9 @@ pub async fn execute(
match compute::execute(ctx, query).await { match compute::execute(ctx, query).await {
Ok(report) => { Ok(report) => {
let json = serde_json::to_string(&report)
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
ctx.repos ctx.repos
.wrapup_repo .wrapup_repo
.set_complete(&wrapup_id, &json) .set_complete(&wrapup_id, &report)
.await?; .await?;
if let Some(ref renderer) = ctx.services.video_renderer { if let Some(ref renderer) = ctx.services.video_renderer {

View File

@@ -160,7 +160,7 @@ pub struct WrapUpRecord {
pub start_date: NaiveDate, pub start_date: NaiveDate,
pub end_date: NaiveDate, pub end_date: NaiveDate,
pub status: WrapUpStatus, pub status: WrapUpStatus,
pub report_json: Option<String>, pub report: Option<WrapUpReport>,
pub error_message: Option<String>, pub error_message: Option<String>,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub completed_at: Option<NaiveDateTime>, pub completed_at: Option<NaiveDateTime>,

View File

@@ -485,7 +485,7 @@ pub trait WrapUpRepository: Send + Sync {
status: &WrapUpStatus, status: &WrapUpStatus,
error: Option<&str>, error: Option<&str>,
) -> Result<(), DomainError>; ) -> Result<(), DomainError>;
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError>; async fn set_complete(&self, id: &WrapUpId, report: &WrapUpReport) -> Result<(), DomainError>;
async fn get_by_id(&self, id: &WrapUpId) -> Result<Option<WrapUpRecord>, DomainError>; async fn get_by_id(&self, id: &WrapUpId) -> Result<Option<WrapUpRecord>, DomainError>;
async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<WrapUpRecord>, DomainError>; async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<WrapUpRecord>, DomainError>;
async fn list_global(&self) -> Result<Vec<WrapUpRecord>, DomainError>; async fn list_global(&self) -> Result<Vec<WrapUpRecord>, DomainError>;

View File

@@ -1099,11 +1099,11 @@ impl WrapUpRepository for InMemoryWrapUpRepository {
} }
} }
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError> { async fn set_complete(&self, id: &WrapUpId, report: &crate::models::wrapup::WrapUpReport) -> Result<(), DomainError> {
let mut store = self.store.lock().unwrap(); let mut store = self.store.lock().unwrap();
if let Some(rec) = store.iter_mut().find(|r| r.id == *id) { if let Some(rec) = store.iter_mut().find(|r| r.id == *id) {
rec.status = crate::models::wrapup::WrapUpStatus::Ready; rec.status = crate::models::wrapup::WrapUpStatus::Ready;
rec.report_json = Some(report_json.to_string()); rec.report = Some(report.clone());
rec.completed_at = Some(chrono::Utc::now().naive_utc()); rec.completed_at = Some(chrono::Utc::now().naive_utc());
Ok(()) Ok(())
} else { } else {
@@ -1189,7 +1189,7 @@ impl WrapUpRepository for PanicWrapUpRepository {
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
panic!("PanicWrapUpRepository called") panic!("PanicWrapUpRepository called")
} }
async fn set_complete(&self, _: &WrapUpId, _: &str) -> Result<(), DomainError> { async fn set_complete(&self, _: &WrapUpId, _: &crate::models::wrapup::WrapUpReport) -> Result<(), DomainError> {
panic!("PanicWrapUpRepository called") panic!("PanicWrapUpRepository called")
} }
async fn get_by_id( async fn get_by_id(

View File

@@ -134,8 +134,9 @@ pub async fn get_report(
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match get_wrapup::execute(&state.app_ctx, WrapUpId::from_uuid(id)).await { match get_wrapup::execute(&state.app_ctx, WrapUpId::from_uuid(id)).await {
Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report_json { Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report {
Some(json) => { Some(ref report) => {
let json = serde_json::to_string(report).unwrap_or_default();
(StatusCode::OK, [("content-type", "application/json")], json).into_response() (StatusCode::OK, [("content-type", "application/json")], json).into_response()
} }
None => StatusCode::NOT_FOUND.into_response(), None => StatusCode::NOT_FOUND.into_response(),
@@ -295,11 +296,8 @@ pub async fn get_user_wrapup_html(
_ => return StatusCode::NOT_FOUND.into_response(), _ => return StatusCode::NOT_FOUND.into_response(),
}; };
let report: WrapUpReport = match &record.report_json { let report = match record.report {
Some(json) => match serde_json::from_str(json) { Some(r) => r,
Ok(r) => r,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
},
None => return StatusCode::NOT_FOUND.into_response(), None => return StatusCode::NOT_FOUND.into_response(),
}; };
@@ -334,11 +332,8 @@ pub async fn get_global_wrapup_html(
_ => return StatusCode::NOT_FOUND.into_response(), _ => return StatusCode::NOT_FOUND.into_response(),
}; };
let report: WrapUpReport = match &record.report_json { let report = match record.report {
Some(json) => match serde_json::from_str(json) { Some(r) => r,
Ok(r) => r,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
},
None => return StatusCode::NOT_FOUND.into_response(), None => return StatusCode::NOT_FOUND.into_response(),
}; };

View File

@@ -602,7 +602,7 @@ impl domain::ports::WrapUpRepository for Panic {
async fn set_complete( async fn set_complete(
&self, &self,
_: &domain::value_objects::WrapUpId, _: &domain::value_objects::WrapUpId,
_: &str, _: &domain::models::wrapup::WrapUpReport,
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
panic!() panic!()
} }