feat(domain): add WrapUpStatsQuery port and in-memory fake

This commit is contained in:
2026-06-02 21:42:15 +02:00
parent e8b2d4f7ee
commit 4df78221a8
2 changed files with 77 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use chrono::{DateTime, NaiveDateTime, Utc};
use uuid::Uuid;
use crate::{
errors::DomainError,
@@ -12,6 +13,7 @@ use crate::{
ReviewHistory, SearchQuery, SearchResults, User, UserStats, UserSummary, UserTrends,
WatchEvent, WatchEventStatus, WatchlistEntry, WatchlistWithMovie, WebhookToken,
collections::{self, PageParams, Paginated},
wrapup::{DateRange, WrapUpScope},
},
value_objects::{
Email, ExternalMetadataId, ImportProfileId, ImportSessionId, MovieId, MovieTitle,
@@ -468,3 +470,33 @@ pub trait WebhookTokenRepository: Send + Sync {
async fn delete(&self, id: &WebhookTokenId, user_id: &UserId) -> Result<(), DomainError>;
async fn touch_last_used(&self, id: &WebhookTokenId) -> Result<(), DomainError>;
}
// ── Wrap-up / Year-in-Review ─────────────────────────────────────────────────
#[derive(Clone, Debug)]
pub struct WrapUpMovieRow {
pub movie_id: Uuid,
pub title: String,
pub release_year: u16,
pub director: Option<String>,
pub poster_path: Option<String>,
pub rating: u8,
pub watched_at: NaiveDateTime,
pub user_id: Uuid,
pub runtime_minutes: Option<u32>,
pub budget_usd: Option<i64>,
pub original_language: Option<String>,
pub genres: Vec<String>,
pub keywords: Vec<String>,
pub cast_names: Vec<(String, u32)>,
pub cast_profile_paths: Vec<Option<String>>,
}
#[async_trait]
pub trait WrapUpStatsQuery: Send + Sync {
async fn get_reviews_with_profiles(
&self,
scope: &WrapUpScope,
range: &DateRange,
) -> Result<Vec<WrapUpMovieRow>, DomainError>;
}

View File

@@ -991,3 +991,47 @@ impl crate::ports::WebhookTokenRepository for PanicWebhookTokenRepository {
panic!("PanicWebhookTokenRepository called")
}
}
// ── InMemoryWrapUpStatsQuery ────────────────────────────────────────────────
pub struct InMemoryWrapUpStatsQuery {
pub rows: Mutex<Vec<crate::ports::WrapUpMovieRow>>,
}
impl InMemoryWrapUpStatsQuery {
pub fn new() -> Arc<Self> {
Arc::new(Self {
rows: Mutex::new(Vec::new()),
})
}
pub fn with_rows(rows: Vec<crate::ports::WrapUpMovieRow>) -> Arc<Self> {
Arc::new(Self {
rows: Mutex::new(rows),
})
}
}
#[async_trait]
impl crate::ports::WrapUpStatsQuery for InMemoryWrapUpStatsQuery {
async fn get_reviews_with_profiles(
&self,
scope: &crate::models::wrapup::WrapUpScope,
range: &crate::models::wrapup::DateRange,
) -> Result<Vec<crate::ports::WrapUpMovieRow>, DomainError> {
let rows = self.rows.lock().unwrap();
let filtered: Vec<_> = rows
.iter()
.filter(|r| {
let date = r.watched_at.date();
date >= range.start && date < range.end
})
.filter(|r| match scope {
crate::models::wrapup::WrapUpScope::User(uid) => r.user_id == *uid,
crate::models::wrapup::WrapUpScope::Global => true,
})
.cloned()
.collect();
Ok(filtered)
}
}