This commit is contained in:
@@ -45,7 +45,10 @@ pub struct ImportRow {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RowResult {
|
||||
Valid(ImportRow),
|
||||
Invalid { errors: Vec<String>, raw: Vec<(String, String)> },
|
||||
Invalid {
|
||||
errors: Vec<String>,
|
||||
raw: Vec<(String, String)>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::{
|
||||
models::FieldMapping,
|
||||
value_objects::{ImportProfileId, UserId},
|
||||
};
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportProfile {
|
||||
@@ -21,6 +21,12 @@ impl ImportProfile {
|
||||
field_mappings: Vec<FieldMapping>,
|
||||
created_at: NaiveDateTime,
|
||||
) -> Self {
|
||||
Self { id, user_id, name, field_mappings, created_at }
|
||||
Self {
|
||||
id,
|
||||
user_id,
|
||||
name,
|
||||
field_mappings,
|
||||
created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::{
|
||||
models::{AnnotatedRow, FieldMapping, ParsedFile},
|
||||
value_objects::{ImportSessionId, UserId},
|
||||
};
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportSession {
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::{
|
||||
};
|
||||
pub mod collections;
|
||||
pub mod import;
|
||||
pub mod import_session;
|
||||
pub mod import_profile;
|
||||
pub mod import_session;
|
||||
pub mod person;
|
||||
pub mod search;
|
||||
pub mod watchlist;
|
||||
@@ -20,15 +20,15 @@ pub mod remote_watchlist;
|
||||
pub use remote_watchlist::RemoteWatchlistEntry;
|
||||
|
||||
pub use import::{
|
||||
AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError,
|
||||
ImportRow, ParsedFile, RowResult, Transform,
|
||||
AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError, ImportRow, ParsedFile,
|
||||
RowResult, Transform,
|
||||
};
|
||||
pub use import_session::ImportSession;
|
||||
pub use import_profile::ImportProfile;
|
||||
pub use import_session::ImportSession;
|
||||
pub use person::{CastCredit, CrewCredit, ExternalPersonId, Person, PersonCredits, PersonId};
|
||||
pub use search::{
|
||||
EntityType, IndexableDocument, MovieSearchHit, PersonSearchHit,
|
||||
SearchFilters, SearchQuery, SearchResults,
|
||||
EntityType, IndexableDocument, MovieSearchHit, PersonSearchHit, SearchFilters, SearchQuery,
|
||||
SearchResults,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -158,7 +158,9 @@ impl Movie {
|
||||
pub enum ReviewSource {
|
||||
#[default]
|
||||
Local,
|
||||
Remote { actor_url: String },
|
||||
Remote {
|
||||
actor_url: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -377,7 +379,13 @@ impl User {
|
||||
self.password_hash = new_hash;
|
||||
}
|
||||
|
||||
pub fn update_profile(&mut self, bio: Option<String>, avatar_path: Option<String>, banner_path: Option<String>, also_known_as: Option<String>) {
|
||||
pub fn update_profile(
|
||||
&mut self,
|
||||
bio: Option<String>,
|
||||
avatar_path: Option<String>,
|
||||
banner_path: Option<String>,
|
||||
also_known_as: Option<String>,
|
||||
) {
|
||||
self.bio = bio;
|
||||
self.avatar_path = avatar_path;
|
||||
self.banner_path = banner_path;
|
||||
|
||||
@@ -56,7 +56,13 @@ impl Person {
|
||||
known_for_department: Option<String>,
|
||||
profile_path: Option<String>,
|
||||
) -> Self {
|
||||
Self { id, external_id, name, known_for_department, profile_path }
|
||||
Self {
|
||||
id,
|
||||
external_id,
|
||||
name,
|
||||
known_for_department,
|
||||
profile_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &PersonId {
|
||||
|
||||
@@ -19,7 +19,12 @@ fn make_user() -> User {
|
||||
#[test]
|
||||
fn update_profile_sets_fields() {
|
||||
let mut user = make_user();
|
||||
user.update_profile(Some("My bio".to_string()), Some("avatars/abc".to_string()), None, None);
|
||||
user.update_profile(
|
||||
Some("My bio".to_string()),
|
||||
Some("avatars/abc".to_string()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(user.bio(), Some("My bio"));
|
||||
assert_eq!(user.avatar_path(), Some("avatars/abc"));
|
||||
}
|
||||
@@ -27,7 +32,12 @@ fn update_profile_sets_fields() {
|
||||
#[test]
|
||||
fn update_profile_clears_with_none() {
|
||||
let mut user = make_user();
|
||||
user.update_profile(Some("bio".to_string()), Some("path".to_string()), None, None);
|
||||
user.update_profile(
|
||||
Some("bio".to_string()),
|
||||
Some("path".to_string()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
user.update_profile(None, None, None, None);
|
||||
assert_eq!(user.bio(), None);
|
||||
assert_eq!(user.avatar_path(), None);
|
||||
|
||||
@@ -5,11 +5,12 @@ use crate::{
|
||||
errors::DomainError,
|
||||
events::{DomainEvent, EventEnvelope},
|
||||
models::{
|
||||
AnnotatedRow, DiaryEntry, DiaryFilter, ExportFormat, FeedEntry, FieldMapping,
|
||||
FileFormat, ImportError, ImportProfile, ImportSession, Movie, MovieFilter, MovieProfile,
|
||||
MovieStats, MovieSummary, ParsedFile, Review, ReviewHistory, User, UserStats, UserSummary,
|
||||
UserTrends, WatchlistEntry, WatchlistWithMovie, RemoteWatchlistEntry, EntityType, ExternalPersonId,
|
||||
IndexableDocument, Person, PersonCredits, PersonId, SearchQuery, SearchResults,
|
||||
AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId,
|
||||
FeedEntry, FieldMapping, FileFormat, ImportError, ImportProfile, ImportSession,
|
||||
IndexableDocument, Movie, MovieFilter, MovieProfile, MovieStats, MovieSummary, ParsedFile,
|
||||
Person, PersonCredits, PersonId, RemoteWatchlistEntry, Review, ReviewHistory, SearchQuery,
|
||||
SearchResults, User, UserStats, UserSummary, UserTrends, WatchlistEntry,
|
||||
WatchlistWithMovie,
|
||||
collections::{self, PageParams, Paginated},
|
||||
},
|
||||
value_objects::{
|
||||
@@ -66,9 +67,7 @@ pub trait SocialQueryPort: Send + Sync {
|
||||
) -> Result<Vec<String>, DomainError>;
|
||||
|
||||
/// Returns all distinct remote actors followed by any local user on this instance.
|
||||
async fn list_all_followed_remote_actors(
|
||||
&self,
|
||||
) -> Result<Vec<RemoteActorInfo>, DomainError>;
|
||||
async fn list_all_followed_remote_actors(&self) -> Result<Vec<RemoteActorInfo>, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -195,8 +194,15 @@ pub trait UserRepository: Send + Sync {
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserProfileFieldsRepository: Send + Sync {
|
||||
async fn get_fields(&self, user_id: &UserId) -> Result<Vec<crate::models::ProfileField>, DomainError>;
|
||||
async fn set_fields(&self, user_id: &UserId, fields: Vec<crate::models::ProfileField>) -> Result<(), DomainError>;
|
||||
async fn get_fields(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Vec<crate::models::ProfileField>, DomainError>;
|
||||
async fn set_fields(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
fields: Vec<crate::models::ProfileField>,
|
||||
) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -260,7 +266,11 @@ pub trait MovieEnrichmentClient: Send + Sync {
|
||||
#[async_trait]
|
||||
pub trait ImportSessionRepository: Send + Sync {
|
||||
async fn create(&self, session: &ImportSession) -> Result<(), DomainError>;
|
||||
async fn get(&self, id: &ImportSessionId, user_id: &UserId) -> Result<Option<ImportSession>, DomainError>;
|
||||
async fn get(
|
||||
&self,
|
||||
id: &ImportSessionId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<ImportSession>, DomainError>;
|
||||
async fn update(&self, session: &ImportSession) -> Result<(), DomainError>;
|
||||
async fn delete(&self, id: &ImportSessionId) -> Result<(), DomainError>;
|
||||
async fn delete_expired(&self) -> Result<u64, DomainError>;
|
||||
@@ -271,7 +281,11 @@ pub trait ImportSessionRepository: Send + Sync {
|
||||
pub trait ImportProfileRepository: Send + Sync {
|
||||
async fn save(&self, profile: &ImportProfile) -> Result<(), DomainError>;
|
||||
async fn list_for_user(&self, user_id: &UserId) -> Result<Vec<ImportProfile>, DomainError>;
|
||||
async fn get(&self, id: &ImportProfileId, user_id: &UserId) -> Result<Option<ImportProfile>, DomainError>;
|
||||
async fn get(
|
||||
&self,
|
||||
id: &ImportProfileId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<ImportProfile>, DomainError>;
|
||||
async fn delete(&self, id: &ImportProfileId) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
@@ -296,7 +310,10 @@ pub trait PersonCommand: Send + Sync {
|
||||
#[async_trait]
|
||||
pub trait PersonQuery: Send + Sync {
|
||||
async fn get_by_id(&self, id: &PersonId) -> Result<Option<Person>, DomainError>;
|
||||
async fn get_by_external_id(&self, id: &ExternalPersonId) -> Result<Option<Person>, DomainError>;
|
||||
async fn get_by_external_id(
|
||||
&self,
|
||||
id: &ExternalPersonId,
|
||||
) -> Result<Option<Person>, DomainError>;
|
||||
/// Returns the person's full cast and crew credit history across all indexed movies.
|
||||
async fn get_credits(&self, id: &PersonId) -> Result<PersonCredits, DomainError>;
|
||||
/// Returns persons who have no remaining entries in movie_cast or movie_crew.
|
||||
@@ -340,19 +357,21 @@ pub trait WatchlistRepository: Send + Sync {
|
||||
page: &collections::PageParams,
|
||||
) -> Result<collections::Paginated<WatchlistWithMovie>, DomainError>;
|
||||
|
||||
async fn contains(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
movie_id: &MovieId,
|
||||
) -> Result<bool, DomainError>;
|
||||
async fn contains(&self, user_id: &UserId, movie_id: &MovieId) -> Result<bool, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait RemoteWatchlistRepository: Send + Sync {
|
||||
async fn save(&self, entry: RemoteWatchlistEntry) -> Result<(), DomainError>;
|
||||
async fn remove_by_ap_id(&self, ap_id: &str, actor_url: &str) -> Result<(), DomainError>;
|
||||
async fn get_by_actor_url(&self, actor_url: &str) -> Result<Vec<RemoteWatchlistEntry>, DomainError>;
|
||||
async fn get_by_actor_url(
|
||||
&self,
|
||||
actor_url: &str,
|
||||
) -> Result<Vec<RemoteWatchlistEntry>, DomainError>;
|
||||
async fn remove_all_by_actor(&self, actor_url: &str) -> Result<(), DomainError>;
|
||||
/// Find entries for a remote actor whose URL hashes (v5 UUID) to the given UUID.
|
||||
async fn get_by_derived_uuid(&self, uuid: uuid::Uuid) -> Result<Vec<RemoteWatchlistEntry>, DomainError>;
|
||||
async fn get_by_derived_uuid(
|
||||
&self,
|
||||
uuid: uuid::Uuid,
|
||||
) -> Result<Vec<RemoteWatchlistEntry>, DomainError>;
|
||||
}
|
||||
|
||||
@@ -80,9 +80,10 @@ impl MovieTitle {
|
||||
"Movie title cannot be empty".into(),
|
||||
))
|
||||
} else if trimmed.len() > Self::MAX_LENGTH {
|
||||
Err(DomainError::ValidationError(
|
||||
format!("Movie title exceeds {} characters", Self::MAX_LENGTH),
|
||||
))
|
||||
Err(DomainError::ValidationError(format!(
|
||||
"Movie title exceeds {} characters",
|
||||
Self::MAX_LENGTH
|
||||
)))
|
||||
} else {
|
||||
Ok(Self(trimmed.to_string()))
|
||||
}
|
||||
@@ -102,9 +103,10 @@ impl Comment {
|
||||
pub fn new(comment: String) -> Result<Self, DomainError> {
|
||||
let trimmed = comment.trim();
|
||||
if trimmed.len() > Self::MAX_LENGTH {
|
||||
Err(DomainError::ValidationError(
|
||||
format!("Comment exceeds {} characters", Self::MAX_LENGTH),
|
||||
))
|
||||
Err(DomainError::ValidationError(format!(
|
||||
"Comment exceeds {} characters",
|
||||
Self::MAX_LENGTH
|
||||
)))
|
||||
} else {
|
||||
Ok(Self(trimmed.to_string()))
|
||||
}
|
||||
@@ -186,13 +188,11 @@ impl Username {
|
||||
pub fn new(raw: String) -> Result<Self, DomainError> {
|
||||
let s = raw.trim().to_lowercase();
|
||||
if s.len() < Self::MIN_LENGTH || s.len() > Self::MAX_LENGTH {
|
||||
return Err(DomainError::ValidationError(
|
||||
format!(
|
||||
"Username must be {}–{} characters",
|
||||
Self::MIN_LENGTH,
|
||||
Self::MAX_LENGTH
|
||||
),
|
||||
));
|
||||
return Err(DomainError::ValidationError(format!(
|
||||
"Username must be {}–{} characters",
|
||||
Self::MIN_LENGTH,
|
||||
Self::MAX_LENGTH
|
||||
)));
|
||||
}
|
||||
if !s
|
||||
.chars()
|
||||
|
||||
Reference in New Issue
Block a user