movie detail page + importer architecture fix
This commit is contained in:
75
crates/domain/src/models/import.rs
Normal file
75
crates/domain/src/models/import.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ParsedFile {
|
||||
pub columns: Vec<String>,
|
||||
pub rows: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DomainField {
|
||||
Title,
|
||||
ReleaseYear,
|
||||
Director,
|
||||
Rating,
|
||||
WatchedAt,
|
||||
Comment,
|
||||
ExternalMetadataId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Transform {
|
||||
RatingScale(f64),
|
||||
DateFormat(String),
|
||||
Identity,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FieldMapping {
|
||||
pub source_column: String,
|
||||
pub domain_field: DomainField,
|
||||
pub transform: Transform,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ImportRow {
|
||||
pub title: Option<String>,
|
||||
pub release_year: Option<String>,
|
||||
pub director: Option<String>,
|
||||
pub rating: Option<String>,
|
||||
pub watched_at: Option<String>,
|
||||
pub comment: Option<String>,
|
||||
pub external_metadata_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RowResult {
|
||||
Valid(ImportRow),
|
||||
Invalid { errors: Vec<String>, raw: Vec<(String, String)> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnnotatedRow {
|
||||
pub result: RowResult,
|
||||
pub is_duplicate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ImportError {
|
||||
#[error("CSV parse error: {0}")]
|
||||
Csv(String),
|
||||
#[error("JSON parse error: {0}")]
|
||||
Json(String),
|
||||
#[error("XLSX parse error: {0}")]
|
||||
Xlsx(String),
|
||||
#[error("Empty file")]
|
||||
Empty,
|
||||
#[error("Missing header row")]
|
||||
NoHeader,
|
||||
}
|
||||
|
||||
pub enum FileFormat {
|
||||
Csv,
|
||||
Json,
|
||||
Xlsx,
|
||||
}
|
||||
@@ -1,17 +1,26 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::value_objects::{ImportProfileId, UserId};
|
||||
use crate::{
|
||||
models::FieldMapping,
|
||||
value_objects::{ImportProfileId, UserId},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportProfile {
|
||||
pub id: ImportProfileId,
|
||||
pub user_id: UserId,
|
||||
pub name: String,
|
||||
pub field_mappings: String,
|
||||
pub field_mappings: Vec<FieldMapping>,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl ImportProfile {
|
||||
pub fn new(id: ImportProfileId, user_id: UserId, name: String, field_mappings: String, created_at: NaiveDateTime) -> Self {
|
||||
pub fn new(
|
||||
id: ImportProfileId,
|
||||
user_id: UserId,
|
||||
name: String,
|
||||
field_mappings: Vec<FieldMapping>,
|
||||
created_at: NaiveDateTime,
|
||||
) -> Self {
|
||||
Self { id, user_id, name, field_mappings, created_at }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::value_objects::{ImportSessionId, UserId};
|
||||
use crate::{
|
||||
models::{AnnotatedRow, FieldMapping, ParsedFile},
|
||||
value_objects::{ImportSessionId, UserId},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportSession {
|
||||
pub id: ImportSessionId,
|
||||
pub user_id: UserId,
|
||||
pub parsed_data: String,
|
||||
pub field_mappings: Option<String>,
|
||||
pub row_results: Option<String>,
|
||||
pub parsed_file: Option<ParsedFile>,
|
||||
pub field_mappings: Option<Vec<FieldMapping>>,
|
||||
pub row_results: Option<Vec<AnnotatedRow>>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub expires_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl ImportSession {
|
||||
pub fn new(id: ImportSessionId, user_id: UserId, parsed_data: String, created_at: NaiveDateTime) -> Self {
|
||||
pub fn new(id: ImportSessionId, user_id: UserId, created_at: NaiveDateTime) -> Self {
|
||||
let expires_at = created_at + chrono::Duration::hours(24);
|
||||
Self { id, user_id, parsed_data, field_mappings: None, row_results: None, created_at, expires_at }
|
||||
Self {
|
||||
id,
|
||||
user_id,
|
||||
parsed_file: None,
|
||||
field_mappings: None,
|
||||
row_results: None,
|
||||
created_at,
|
||||
expires_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,14 @@ use crate::{
|
||||
},
|
||||
};
|
||||
pub mod collections;
|
||||
pub mod import;
|
||||
pub mod import_session;
|
||||
pub mod import_profile;
|
||||
|
||||
pub use import::{
|
||||
AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError,
|
||||
ImportRow, ParsedFile, RowResult, Transform,
|
||||
};
|
||||
pub use import_session::ImportSession;
|
||||
pub use import_profile::ImportProfile;
|
||||
|
||||
@@ -216,6 +221,10 @@ impl Review {
|
||||
let r = self.rating.value();
|
||||
[r >= 1, r >= 2, r >= 3, r >= 4, r >= 5]
|
||||
}
|
||||
|
||||
pub fn is_remote(&self) -> bool {
|
||||
matches!(self.source, ReviewSource::Remote { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -259,6 +268,14 @@ impl ReviewHistory {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MovieStats {
|
||||
pub total_count: u64,
|
||||
pub avg_rating: Option<f64>,
|
||||
pub federated_count: u64,
|
||||
pub rating_histogram: [u64; 5], // index 0 = 1★, index 4 = 5★
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum UserRole {
|
||||
#[default]
|
||||
|
||||
Reference in New Issue
Block a user