Refactor application context and repository structure
- Updated `AppContext` to include separate repositories for movies, reviews, diaries, and stats. - Modified use cases to utilize the new repository structure, ensuring that the correct repositories are called for their respective operations. - Introduced `DiaryRepository` and `StatsRepository` traits to encapsulate diary and statistics-related operations. - Updated all relevant use cases, handlers, and tests to reflect the changes in repository usage. - Ensured that panic repositories are updated to implement the new traits for testing purposes.
This commit is contained in:
@@ -2,7 +2,7 @@ use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
ports::MovieRepository,
|
||||
ports::{MovieRepository, ReviewRepository},
|
||||
value_objects::{ReviewId, UserId},
|
||||
};
|
||||
use domain::ports::EventHandler;
|
||||
@@ -15,17 +15,19 @@ use crate::urls::{actor_url, review_url};
|
||||
|
||||
pub struct ActivityPubEventHandler {
|
||||
ap_service: Arc<ActivityPubService>,
|
||||
movie_repo: Arc<dyn MovieRepository>,
|
||||
movie_repository: Arc<dyn MovieRepository>,
|
||||
review_repository: Arc<dyn ReviewRepository>,
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl ActivityPubEventHandler {
|
||||
pub fn new(
|
||||
ap_service: Arc<ActivityPubService>,
|
||||
movie_repo: Arc<dyn MovieRepository>,
|
||||
movie_repository: Arc<dyn MovieRepository>,
|
||||
review_repository: Arc<dyn ReviewRepository>,
|
||||
base_url: String,
|
||||
) -> Self {
|
||||
Self { ap_service, movie_repo, base_url }
|
||||
Self { ap_service, movie_repository, review_repository, base_url }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +50,7 @@ impl ActivityPubEventHandler {
|
||||
user_id: &UserId,
|
||||
review_id: &ReviewId,
|
||||
) -> anyhow::Result<()> {
|
||||
let review = match self.movie_repo.get_review_by_id(review_id).await? {
|
||||
let review = match self.review_repository.get_review_by_id(review_id).await? {
|
||||
Some(r) => r,
|
||||
None => return Ok(()),
|
||||
};
|
||||
@@ -56,7 +58,7 @@ impl ActivityPubEventHandler {
|
||||
let ap_id = review_url(&self.base_url, review_id);
|
||||
let actor = actor_url(&self.base_url, user_id.value());
|
||||
|
||||
let movie = self.movie_repo.get_movie_by_id(review.movie_id()).await.ok().flatten();
|
||||
let movie = self.movie_repository.get_movie_by_id(review.movie_id()).await.ok().flatten();
|
||||
let movie_title = movie.as_ref()
|
||||
.map(|m| m.title().value().to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
@@ -4,7 +4,7 @@ use activitypub_base::ApObjectHandler;
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
models::{Review, ReviewSource},
|
||||
ports::MovieRepository,
|
||||
ports::{DiaryRepository, MovieRepository},
|
||||
value_objects::{Comment, MovieId, Rating, ReviewId, UserId},
|
||||
};
|
||||
use url::Url;
|
||||
@@ -14,7 +14,8 @@ use crate::remote_review_repository::RemoteReviewRepository;
|
||||
use crate::urls::{actor_url, review_url};
|
||||
|
||||
pub struct ReviewObjectHandler {
|
||||
pub movie_repo: Arc<dyn MovieRepository>,
|
||||
pub movie_repository: Arc<dyn MovieRepository>,
|
||||
pub diary_repository: Arc<dyn DiaryRepository>,
|
||||
pub review_store: Arc<dyn RemoteReviewRepository>,
|
||||
pub base_url: String,
|
||||
}
|
||||
@@ -26,7 +27,7 @@ impl ApObjectHandler for ReviewObjectHandler {
|
||||
user_id: uuid::Uuid,
|
||||
) -> anyhow::Result<Vec<(Url, serde_json::Value)>> {
|
||||
let domain_user_id = UserId::from_uuid(user_id);
|
||||
let history = self.movie_repo.get_user_history(&domain_user_id).await?;
|
||||
let history = self.diary_repository.get_user_history(&domain_user_id).await?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for entry in history {
|
||||
@@ -38,7 +39,7 @@ impl ApObjectHandler for ReviewObjectHandler {
|
||||
let ap_id = review_url(&self.base_url, review.id());
|
||||
let actor_url = actor_url(&self.base_url, user_id);
|
||||
|
||||
let movie = self.movie_repo.get_movie_by_id(review.movie_id()).await.ok().flatten();
|
||||
let movie = self.movie_repository.get_movie_by_id(review.movie_id()).await.ok().flatten();
|
||||
let movie_title = movie.as_ref()
|
||||
.map(|m| m.title().value().to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
@@ -7,7 +7,7 @@ use domain::{
|
||||
Review, ReviewHistory, ReviewSource, SortDirection, UserStats, UserTrends,
|
||||
collections::{PageParams, Paginated},
|
||||
},
|
||||
ports::MovieRepository,
|
||||
ports::{DiaryRepository, MovieRepository, ReviewRepository, StatsRepository},
|
||||
value_objects::{ExternalMetadataId, MovieId, MovieTitle, ReleaseYear, ReviewId, UserId},
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
@@ -378,6 +378,18 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_movie(&self, movie_id: &MovieId) -> Result<(), DomainError> {
|
||||
let id = movie_id.value().to_string();
|
||||
sqlx::query!("DELETE FROM movies WHERE id = ?", id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReviewRepository for SqliteMovieRepository {
|
||||
async fn save_review(&self, review: &Review) -> Result<DomainEvent, DomainError> {
|
||||
let id = review.id().value().to_string();
|
||||
let movie_id = review.movie_id().value().to_string();
|
||||
@@ -416,6 +428,33 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_review_by_id(&self, review_id: &ReviewId) -> Result<Option<Review>, DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query_as!(
|
||||
ReviewRow,
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url
|
||||
FROM reviews WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?
|
||||
.map(ReviewRow::to_domain)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn delete_review(&self, review_id: &ReviewId) -> Result<(), DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query!("DELETE FROM reviews WHERE id = ?", id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DiaryRepository for SqliteMovieRepository {
|
||||
async fn query_diary(&self, filter: &DiaryFilter) -> Result<Paginated<DiaryEntry>, DomainError> {
|
||||
let limit = filter.page.limit as i64;
|
||||
let offset = filter.page.offset as i64;
|
||||
@@ -465,37 +504,29 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_review_by_id(&self, review_id: &ReviewId) -> Result<Option<Review>, DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query_as!(
|
||||
ReviewRow,
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url
|
||||
FROM reviews WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?
|
||||
.map(ReviewRow::to_domain)
|
||||
.transpose()
|
||||
}
|
||||
async fn query_activity_feed(
|
||||
&self,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<FeedEntry>, DomainError> {
|
||||
let limit = page.limit as i64;
|
||||
let offset = page.offset as i64;
|
||||
|
||||
async fn delete_review(&self, review_id: &ReviewId) -> Result<(), DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query!("DELETE FROM reviews WHERE id = ?", id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
let (total, rows) = tokio::try_join!(
|
||||
self.count_feed_entries(),
|
||||
self.fetch_feed_rows(limit, offset)
|
||||
)?;
|
||||
|
||||
async fn delete_movie(&self, movie_id: &MovieId) -> Result<(), DomainError> {
|
||||
let id = movie_id.value().to_string();
|
||||
sqlx::query!("DELETE FROM movies WHERE id = ?", id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
let items = rows
|
||||
.into_iter()
|
||||
.map(FeedRow::to_domain)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Paginated {
|
||||
items,
|
||||
total_count: total as u64,
|
||||
limit: page.limit,
|
||||
offset: page.offset,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_review_history(&self, movie_id: &MovieId) -> Result<ReviewHistory, DomainError> {
|
||||
@@ -529,50 +560,6 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
Ok(ReviewHistory::new(movie, viewings))
|
||||
}
|
||||
|
||||
async fn query_activity_feed(
|
||||
&self,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<FeedEntry>, DomainError> {
|
||||
let limit = page.limit as i64;
|
||||
let offset = page.offset as i64;
|
||||
|
||||
let (total, rows) = tokio::try_join!(
|
||||
self.count_feed_entries(),
|
||||
self.fetch_feed_rows(limit, offset)
|
||||
)?;
|
||||
|
||||
let items = rows
|
||||
.into_iter()
|
||||
.map(FeedRow::to_domain)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Paginated {
|
||||
items,
|
||||
total_count: total as u64,
|
||||
limit: page.limit,
|
||||
offset: page.offset,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_user_stats(&self, user_id: &UserId) -> Result<UserStats, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
|
||||
let (totals, fav_director, most_active) = tokio::try_join!(
|
||||
self.fetch_user_totals(&uid),
|
||||
self.fetch_user_favorite_director(&uid),
|
||||
self.fetch_user_most_active_month(&uid)
|
||||
)?;
|
||||
|
||||
let most_active_month = most_active.map(|ym| format_year_month(&ym));
|
||||
|
||||
Ok(UserStats {
|
||||
total_movies: totals.total,
|
||||
avg_rating: totals.avg_rating,
|
||||
favorite_director: fav_director,
|
||||
most_active_month,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_user_history(&self, user_id: &UserId) -> Result<Vec<DiaryEntry>, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
let rows = sqlx::query_as!(
|
||||
@@ -591,6 +578,28 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
|
||||
rows.into_iter().map(DiaryRow::to_domain).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StatsRepository for SqliteMovieRepository {
|
||||
async fn get_user_stats(&self, user_id: &UserId) -> Result<UserStats, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
|
||||
let (totals, fav_director, most_active) = tokio::try_join!(
|
||||
self.fetch_user_totals(&uid),
|
||||
self.fetch_user_favorite_director(&uid),
|
||||
self.fetch_user_most_active_month(&uid)
|
||||
)?;
|
||||
|
||||
let most_active_month = most_active.map(|ym| format_year_month(&ym));
|
||||
|
||||
Ok(UserStats {
|
||||
total_movies: totals.total,
|
||||
avg_rating: totals.avg_rating,
|
||||
favorite_director: fav_director,
|
||||
most_active_month,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_user_trends(&self, user_id: &UserId) -> Result<UserTrends, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
|
||||
Reference in New Issue
Block a user