feat(domain): add WrapUpRecord, WrapUpRepository port
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use chrono::NaiveDate;
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::value_objects::WrapUpId;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DateRange {
|
||||
pub start: NaiveDate,
|
||||
@@ -117,3 +119,24 @@ pub struct WrapUpReport {
|
||||
pub poster_paths: Vec<String>,
|
||||
pub top_cast_profile_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum WrapUpStatus {
|
||||
Pending,
|
||||
Generating,
|
||||
Ready,
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WrapUpRecord {
|
||||
pub id: WrapUpId,
|
||||
pub user_id: Option<Uuid>,
|
||||
pub start_date: NaiveDate,
|
||||
pub end_date: NaiveDate,
|
||||
pub status: WrapUpStatus,
|
||||
pub report_json: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub completed_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@@ -13,12 +13,12 @@ use crate::{
|
||||
ReviewHistory, SearchQuery, SearchResults, User, UserStats, UserSummary, UserTrends,
|
||||
WatchEvent, WatchEventStatus, WatchlistEntry, WatchlistWithMovie, WebhookToken,
|
||||
collections::{self, PageParams, Paginated},
|
||||
wrapup::{DateRange, WrapUpScope},
|
||||
wrapup::{DateRange, WrapUpRecord, WrapUpScope, WrapUpStatus},
|
||||
},
|
||||
value_objects::{
|
||||
Email, ExternalMetadataId, ImportProfileId, ImportSessionId, MovieId, MovieTitle,
|
||||
PasswordHash, PosterUrl, ReleaseYear, ReviewId, UserId, Username, WatchEventId,
|
||||
WebhookTokenId,
|
||||
WebhookTokenId, WrapUpId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -471,6 +471,27 @@ pub trait WebhookTokenRepository: Send + Sync {
|
||||
async fn touch_last_used(&self, id: &WebhookTokenId) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WrapUpRepository: Send + Sync {
|
||||
async fn create(&self, record: &WrapUpRecord) -> Result<(), DomainError>;
|
||||
async fn update_status(
|
||||
&self,
|
||||
id: &WrapUpId,
|
||||
status: &WrapUpStatus,
|
||||
error: Option<&str>,
|
||||
) -> Result<(), DomainError>;
|
||||
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError>;
|
||||
async fn get_by_id(&self, id: &WrapUpId) -> Result<Option<WrapUpRecord>, DomainError>;
|
||||
async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<WrapUpRecord>, DomainError>;
|
||||
async fn list_global(&self) -> Result<Vec<WrapUpRecord>, DomainError>;
|
||||
async fn find_existing(
|
||||
&self,
|
||||
user_id: Option<Uuid>,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
) -> Result<Option<WrapUpRecord>, DomainError>;
|
||||
}
|
||||
|
||||
// ── Wrap-up / Year-in-Review ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -24,11 +24,11 @@ use crate::{
|
||||
ImportSessionRepository, MetadataClient, MetadataSearchCriteria, MovieProfileRepository,
|
||||
MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
|
||||
ReviewRepository, SearchCommand, SearchPort, StatsRepository, UserProfileFieldsRepository,
|
||||
UserRepository, WatchlistRepository,
|
||||
UserRepository, WatchlistRepository, WrapUpRepository,
|
||||
},
|
||||
value_objects::{
|
||||
Email, ExternalMetadataId, ImportProfileId, ImportSessionId, MovieId, MovieTitle,
|
||||
PasswordHash, PosterUrl, ReleaseYear, ReviewId, UserId, Username,
|
||||
PasswordHash, PosterUrl, ReleaseYear, ReviewId, UserId, Username, WrapUpId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1050,3 +1050,143 @@ impl crate::ports::WrapUpStatsQuery for InMemoryWrapUpStatsQuery {
|
||||
Ok(filtered)
|
||||
}
|
||||
}
|
||||
|
||||
// ── InMemoryWrapUpRepository ────────────────────────────────────────────────
|
||||
|
||||
pub struct InMemoryWrapUpRepository {
|
||||
pub store: Mutex<Vec<crate::models::wrapup::WrapUpRecord>>,
|
||||
}
|
||||
|
||||
impl InMemoryWrapUpRepository {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
store: Mutex::new(Vec::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WrapUpRepository for InMemoryWrapUpRepository {
|
||||
async fn create(
|
||||
&self,
|
||||
record: &crate::models::wrapup::WrapUpRecord,
|
||||
) -> Result<(), DomainError> {
|
||||
self.store.lock().unwrap().push(record.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_status(
|
||||
&self,
|
||||
id: &WrapUpId,
|
||||
status: &crate::models::wrapup::WrapUpStatus,
|
||||
error: Option<&str>,
|
||||
) -> Result<(), DomainError> {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
if let Some(rec) = store.iter_mut().find(|r| r.id == *id) {
|
||||
rec.status = status.clone();
|
||||
rec.error_message = error.map(|s| s.to_string());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DomainError::NotFound("wrapup record".into()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_complete(&self, id: &WrapUpId, report_json: &str) -> Result<(), DomainError> {
|
||||
let mut store = self.store.lock().unwrap();
|
||||
if let Some(rec) = store.iter_mut().find(|r| r.id == *id) {
|
||||
rec.status = crate::models::wrapup::WrapUpStatus::Ready;
|
||||
rec.report_json = Some(report_json.to_string());
|
||||
rec.completed_at = Some(chrono::Utc::now().naive_utc());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DomainError::NotFound("wrapup record".into()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_by_id(
|
||||
&self,
|
||||
id: &WrapUpId,
|
||||
) -> Result<Option<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
let store = self.store.lock().unwrap();
|
||||
Ok(store.iter().find(|r| r.id == *id).cloned())
|
||||
}
|
||||
|
||||
async fn list_for_user(
|
||||
&self,
|
||||
user_id: Uuid,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
let store = self.store.lock().unwrap();
|
||||
Ok(store
|
||||
.iter()
|
||||
.filter(|r| r.user_id == Some(user_id))
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn list_global(
|
||||
&self,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
let store = self.store.lock().unwrap();
|
||||
Ok(store.iter().filter(|r| r.user_id.is_none()).cloned().collect())
|
||||
}
|
||||
|
||||
async fn find_existing(
|
||||
&self,
|
||||
user_id: Option<Uuid>,
|
||||
start: chrono::NaiveDate,
|
||||
end: chrono::NaiveDate,
|
||||
) -> Result<Option<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
let store = self.store.lock().unwrap();
|
||||
Ok(store
|
||||
.iter()
|
||||
.find(|r| r.user_id == user_id && r.start_date == start && r.end_date == end)
|
||||
.cloned())
|
||||
}
|
||||
}
|
||||
|
||||
// ── PanicWrapUpRepository ──────────────────────────────────────────────────
|
||||
|
||||
pub struct PanicWrapUpRepository;
|
||||
|
||||
#[async_trait]
|
||||
impl WrapUpRepository for PanicWrapUpRepository {
|
||||
async fn create(&self, _: &crate::models::wrapup::WrapUpRecord) -> Result<(), DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn update_status(
|
||||
&self,
|
||||
_: &WrapUpId,
|
||||
_: &crate::models::wrapup::WrapUpStatus,
|
||||
_: Option<&str>,
|
||||
) -> Result<(), DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn set_complete(&self, _: &WrapUpId, _: &str) -> Result<(), DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn get_by_id(
|
||||
&self,
|
||||
_: &WrapUpId,
|
||||
) -> Result<Option<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn list_for_user(
|
||||
&self,
|
||||
_: Uuid,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn list_global(
|
||||
&self,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn find_existing(
|
||||
&self,
|
||||
_: Option<Uuid>,
|
||||
_: chrono::NaiveDate,
|
||||
_: chrono::NaiveDate,
|
||||
) -> Result<Option<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ uuid_id!(ImportProfileId);
|
||||
uuid_id!(WatchlistEntryId);
|
||||
uuid_id!(WatchEventId);
|
||||
uuid_id!(WebhookTokenId);
|
||||
uuid_id!(WrapUpId);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ExternalMetadataId(String);
|
||||
|
||||
Reference in New Issue
Block a user