This commit is contained in:
@@ -112,6 +112,13 @@ pub enum DomainEvent {
|
||||
user_id: UserId,
|
||||
year: u16,
|
||||
},
|
||||
UserDeleted {
|
||||
user_id: UserId,
|
||||
},
|
||||
UserAccountMoved {
|
||||
user_id: UserId,
|
||||
new_actor_url: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -36,6 +36,8 @@ pub trait MovieRepository: Send + Sync {
|
||||
page: &PageParams,
|
||||
filter: &MovieFilter,
|
||||
) -> Result<Paginated<MovieSummary>, DomainError>;
|
||||
/// Returns all movies that have an external_metadata_id set. Used for deduplication.
|
||||
async fn list_movies_with_external_id(&self) -> Result<Vec<Movie>, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -68,3 +70,15 @@ pub trait MovieEnrichmentClient: Send + Sync {
|
||||
external_metadata_id: &str,
|
||||
) -> Result<MovieProfile, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait MovieDeduplicator: Send + Sync {
|
||||
/// Atomically re-points all foreign keys (reviews, watchlist entries, movie profiles)
|
||||
/// from `old_id` to `canonical_id`, upserts the canonical movie record, then deletes
|
||||
/// the old duplicate. Returns the number of rows re-pointed across all tables.
|
||||
async fn merge_into_canonical(
|
||||
&self,
|
||||
old_id: &MovieId,
|
||||
canonical: &Movie,
|
||||
) -> Result<u64, DomainError>;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ pub trait LocalApContentQuery: Send + Sync {
|
||||
) -> Result<Vec<WatchlistWithMovie>, DomainError>;
|
||||
async fn get_review_by_id(&self, review_id: &ReviewId) -> Result<Option<Review>, DomainError>;
|
||||
async fn get_movie_by_id(&self, movie_id: &MovieId) -> Result<Option<Movie>, DomainError>;
|
||||
async fn get_movie_by_external_metadata_id(
|
||||
&self,
|
||||
external_id: &str,
|
||||
) -> Result<Option<Movie>, DomainError>;
|
||||
async fn count_local_posts(&self) -> Result<u64, DomainError>;
|
||||
async fn get_local_reviews_for_movie(
|
||||
&self,
|
||||
@@ -90,4 +94,5 @@ pub trait LocalApContentQuery: Send + Sync {
|
||||
user_id: &UserId,
|
||||
year: u16,
|
||||
) -> Result<Option<(Goal, u32)>, DomainError>;
|
||||
async fn list_goals_for_user(&self, user_id: &UserId) -> Result<Vec<Goal>, DomainError>;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,17 @@ impl MovieRepository for InMemoryMovieRepository {
|
||||
offset: 0,
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_movies_with_external_id(&self) -> Result<Vec<Movie>, DomainError> {
|
||||
Ok(self
|
||||
.store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.values()
|
||||
.filter(|m| m.external_metadata_id().is_some())
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
// ── InMemoryReviewRepository ──────────────────────────────────────────────────
|
||||
|
||||
@@ -20,6 +20,19 @@ macro_rules! uuid_id {
|
||||
}
|
||||
|
||||
uuid_id!(MovieId);
|
||||
|
||||
impl MovieId {
|
||||
/// Derives a stable, deterministic UUID from an external metadata ID (e.g. `tmdb:12345`).
|
||||
/// All instances that know a movie by the same external ID will produce the same MovieId,
|
||||
/// enabling remote and local reviews to be linked to the same movie record.
|
||||
pub fn from_external(external_id: &crate::value_objects::ExternalMetadataId) -> Self {
|
||||
Self(Uuid::new_v5(
|
||||
&Uuid::NAMESPACE_URL,
|
||||
external_id.value().as_bytes(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
uuid_id!(ReviewId);
|
||||
uuid_id!(UserId);
|
||||
uuid_id!(ImportSessionId);
|
||||
|
||||
Reference in New Issue
Block a user