movie detail page + importer architecture fix
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{AnnotatedRow, import::RowResult},
|
||||
value_objects::{ExternalMetadataId, ImportSessionId, MovieTitle, ReleaseYear, UserId},
|
||||
};
|
||||
use importer::{AnnotatedRow, ParsedFile, apply_mapping};
|
||||
|
||||
use crate::{commands::ApplyImportMappingCommand, context::AppContext};
|
||||
|
||||
@@ -15,32 +15,27 @@ pub async fn execute(ctx: &AppContext, cmd: ApplyImportMappingCommand) -> Result
|
||||
.await?
|
||||
.ok_or_else(|| DomainError::NotFound("import session".into()))?;
|
||||
|
||||
let parsed: ParsedFile = serde_json::from_str(&session.parsed_data)
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
|
||||
// clone to avoid borrow conflict when mutating session fields below
|
||||
let parsed = session.parsed_file.clone()
|
||||
.ok_or_else(|| DomainError::ValidationError("session has no parsed file".into()))?;
|
||||
|
||||
let mut annotated = apply_mapping(&parsed, &mappings);
|
||||
let mut annotated = ctx.document_parser.apply_mapping(&parsed, &mappings);
|
||||
|
||||
for row in annotated.iter_mut() {
|
||||
if let importer::RowResult::Valid(ref import_row) = row.result {
|
||||
if let RowResult::Valid(ref import_row) = row.result {
|
||||
row.is_duplicate = check_duplicate(ctx, import_row).await?;
|
||||
}
|
||||
}
|
||||
|
||||
session.field_mappings = Some(
|
||||
serde_json::to_string(&mappings)
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?
|
||||
);
|
||||
session.row_results = Some(
|
||||
serde_json::to_string(&annotated)
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?
|
||||
);
|
||||
session.field_mappings = Some(mappings);
|
||||
session.row_results = Some(annotated.clone());
|
||||
|
||||
ctx.import_session_repository.update(&session).await?;
|
||||
|
||||
Ok(annotated)
|
||||
}
|
||||
|
||||
async fn check_duplicate(ctx: &AppContext, row: &importer::ImportRow) -> Result<bool, DomainError> {
|
||||
async fn check_duplicate(ctx: &AppContext, row: &domain::models::ImportRow) -> Result<bool, DomainError> {
|
||||
if let Some(ext_id) = &row.external_metadata_id {
|
||||
if let Ok(eid) = ExternalMetadataId::new(ext_id.clone()) {
|
||||
if ctx.movie_repository.get_movie_by_external_id(&eid).await?.is_some() {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use chrono::Utc;
|
||||
use domain::{errors::DomainError, models::ImportSession, value_objects::{ImportSessionId, UserId}};
|
||||
use importer::{ImportError, ParsedFile};
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::ImportSession,
|
||||
value_objects::{ImportSessionId, UserId},
|
||||
};
|
||||
|
||||
use crate::{commands::{CreateImportSessionCommand, FileFormat}, context::AppContext};
|
||||
use crate::{commands::CreateImportSessionCommand, context::AppContext};
|
||||
|
||||
pub struct CreateSessionResult {
|
||||
pub session_id: ImportSessionId,
|
||||
@@ -14,31 +17,19 @@ pub async fn execute(ctx: &AppContext, cmd: CreateImportSessionCommand) -> Resul
|
||||
let user_id = UserId::from_uuid(cmd.user_id);
|
||||
ctx.import_session_repository.delete_expired_for_user(&user_id).await?;
|
||||
|
||||
let parsed = parse(cmd.bytes, cmd.format).map_err(|e| DomainError::ValidationError(e.to_string()))?;
|
||||
let parsed = ctx.document_parser
|
||||
.parse(&cmd.bytes, cmd.format)
|
||||
.map_err(|e| DomainError::ValidationError(e.to_string()))?;
|
||||
|
||||
let sample_rows = parsed.rows.iter().take(5).cloned().collect();
|
||||
let columns = parsed.columns.clone();
|
||||
|
||||
let parsed_data = serde_json::to_string(&parsed)
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let session = ImportSession::new(ImportSessionId::generate(), user_id, parsed_data, now);
|
||||
let mut session = ImportSession::new(ImportSessionId::generate(), user_id, now);
|
||||
let session_id = session.id.clone();
|
||||
session.parsed_file = Some(parsed);
|
||||
|
||||
ctx.import_session_repository.create(&session).await?;
|
||||
|
||||
Ok(CreateSessionResult { session_id, columns, sample_rows })
|
||||
}
|
||||
|
||||
fn parse(bytes: Vec<u8>, format: FileFormat) -> Result<ParsedFile, ImportError> {
|
||||
match format {
|
||||
FileFormat::Csv => importer::parse_csv(&bytes),
|
||||
FileFormat::Json => importer::parse_json(&bytes),
|
||||
FileFormat::Xlsx => {
|
||||
#[cfg(feature = "xlsx")]
|
||||
{ importer::parse_xlsx(&bytes) }
|
||||
#[cfg(not(feature = "xlsx"))]
|
||||
{ Err(ImportError::Xlsx("XLSX support not compiled in".into())) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use domain::{errors::DomainError, value_objects::{ImportSessionId, UserId}};
|
||||
use importer::{AnnotatedRow, ImportRow, RowResult};
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{ImportRow, import::RowResult},
|
||||
value_objects::{ImportSessionId, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{commands::{ExecuteImportCommand, LogReviewCommand}, context::AppContext, use_cases::log_review};
|
||||
@@ -20,11 +23,7 @@ pub async fn execute(ctx: &AppContext, cmd: ExecuteImportCommand) -> Result<Impo
|
||||
.await?
|
||||
.ok_or_else(|| DomainError::NotFound("import session".into()))?;
|
||||
|
||||
let row_results: Vec<AnnotatedRow> = session.row_results
|
||||
.as_deref()
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
let row_results = session.row_results.unwrap_or_default();
|
||||
let confirmed_set: std::collections::HashSet<usize> = confirmed_indices.into_iter().collect();
|
||||
|
||||
let mut imported = 0;
|
||||
|
||||
34
crates/application/src/use_cases/get_movie_social_page.rs
Normal file
34
crates/application/src/use_cases/get_movie_social_page.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{FeedEntry, Movie, MovieStats, collections::{PageParams, Paginated}},
|
||||
value_objects::MovieId,
|
||||
};
|
||||
|
||||
use crate::{context::AppContext, queries::GetMovieSocialPageQuery};
|
||||
|
||||
pub struct MovieSocialPageResult {
|
||||
pub movie: Movie,
|
||||
pub stats: MovieStats,
|
||||
pub reviews: Paginated<FeedEntry>,
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
ctx: &AppContext,
|
||||
query: GetMovieSocialPageQuery,
|
||||
) -> Result<MovieSocialPageResult, DomainError> {
|
||||
let movie_id = MovieId::from_uuid(query.movie_id);
|
||||
let page = PageParams::new(Some(query.limit), Some(query.offset))?;
|
||||
|
||||
let movie = ctx
|
||||
.movie_repository
|
||||
.get_movie_by_id(&movie_id)
|
||||
.await?
|
||||
.ok_or_else(|| DomainError::NotFound(format!("Movie {}", query.movie_id)))?;
|
||||
|
||||
let (stats, reviews) = tokio::try_join!(
|
||||
ctx.diary_repository.get_movie_stats(&movie_id),
|
||||
ctx.diary_repository.get_movie_social_feed(&movie_id, &page),
|
||||
)?;
|
||||
|
||||
Ok(MovieSocialPageResult { movie, stats, reviews })
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub mod save_import_profile;
|
||||
pub mod export_diary;
|
||||
pub mod get_activity_feed;
|
||||
pub mod get_diary;
|
||||
pub mod get_movie_social_page;
|
||||
pub mod get_review_history;
|
||||
pub mod get_user_profile;
|
||||
pub mod get_users;
|
||||
|
||||
Reference in New Issue
Block a user