refactor: move AppContext to presentation crate, structurally enforce boundary
All checks were successful
CI / Check / Test (push) Successful in 39m33s
All checks were successful
CI / Check / Test (push) Successful in 39m33s
This commit is contained in:
11
Makefile
11
Makefile
@@ -1,9 +1,18 @@
|
|||||||
.DEFAULT_GOAL := check
|
.DEFAULT_GOAL := check
|
||||||
|
|
||||||
# Run the full local check suite — same order as CI would.
|
# Run the full local check suite — same order as CI would.
|
||||||
check: fmt-check clippy test
|
check: fmt-check clippy test check-appcontext
|
||||||
@echo "✅ All checks passed"
|
@echo "✅ All checks passed"
|
||||||
|
|
||||||
|
# Enforce that no application use case imports AppContext (god-object guard).
|
||||||
|
check-appcontext:
|
||||||
|
@if grep -rn "AppContext" crates/application/src --include="*.rs" | grep -q .; then \
|
||||||
|
echo "❌ AppContext found in application crate:"; \
|
||||||
|
grep -rn "AppContext" crates/application/src --include="*.rs"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "✅ No AppContext in application crate"
|
||||||
|
|
||||||
# Apply rustfmt to all files.
|
# Apply rustfmt to all files.
|
||||||
fmt:
|
fmt:
|
||||||
cargo fmt
|
cargo fmt
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use application::movies::{
|
use application::movies::{
|
||||||
commands::EnrichMovieCommand,
|
commands::EnrichMovieCommand, deps::EnrichMovieDeps, enrich_movie, request_enrichment,
|
||||||
deps::EnrichMovieDeps,
|
|
||||||
enrich_movie,
|
|
||||||
request_enrichment,
|
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::{
|
use domain::{
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ use crate::{
|
|||||||
auth::{
|
auth::{
|
||||||
commands::RegisterCommand,
|
commands::RegisterCommand,
|
||||||
deps::{LoginDeps, RefreshDeps, RegisterDeps},
|
deps::{LoginDeps, RefreshDeps, RegisterDeps},
|
||||||
login, logout, refresh, register,
|
login, logout,
|
||||||
queries::LoginQuery,
|
queries::LoginQuery,
|
||||||
|
refresh, register,
|
||||||
},
|
},
|
||||||
test_helpers::TestContextBuilder,
|
test_helpers::TestContextBuilder,
|
||||||
};
|
};
|
||||||
@@ -52,10 +53,7 @@ async fn logout_revokes_refresh_token() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
logout::execute(
|
logout::execute(b.refresh_session_repo.clone(), &login_result.refresh_token)
|
||||||
b.refresh_session_repo.clone(),
|
|
||||||
&login_result.refresh_token,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ use crate::{
|
|||||||
auth::{
|
auth::{
|
||||||
commands::RegisterCommand,
|
commands::RegisterCommand,
|
||||||
deps::{LoginDeps, RefreshDeps, RegisterDeps},
|
deps::{LoginDeps, RefreshDeps, RegisterDeps},
|
||||||
login, refresh, register,
|
login,
|
||||||
queries::LoginQuery,
|
queries::LoginQuery,
|
||||||
|
refresh, register,
|
||||||
},
|
},
|
||||||
test_helpers::TestContextBuilder,
|
test_helpers::TestContextBuilder,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,5 +16,7 @@ pub async fn execute(
|
|||||||
let entries = diary
|
let entries = diary
|
||||||
.get_user_history(&UserId::from_uuid(query.user_id))
|
.get_user_history(&UserId::from_uuid(query.user_id))
|
||||||
.await?;
|
.await?;
|
||||||
diary_exporter.serialize_entries(&entries, query.format).await
|
diary_exporter
|
||||||
|
.serialize_entries(&entries, query.format)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ use domain::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diary::commands::DeleteReviewCommand,
|
diary::commands::DeleteReviewCommand, diary::delete_review, diary::deps::DeleteReviewDeps,
|
||||||
diary::delete_review,
|
|
||||||
diary::deps::DeleteReviewDeps,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn make_movie() -> Movie {
|
fn make_movie() -> Movie {
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ use domain::errors::DomainError;
|
|||||||
use domain::testing::{FakeDiaryRepository, NoopSocialQueryPort};
|
use domain::testing::{FakeDiaryRepository, NoopSocialQueryPort};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::AppConfig,
|
config::AppConfig, diary::deps::GetActivityFeedDeps, diary::get_activity_feed,
|
||||||
diary::deps::GetActivityFeedDeps,
|
diary::queries::GetActivityFeedQuery, test_helpers::TestContextBuilder,
|
||||||
diary::get_activity_feed,
|
|
||||||
diary::queries::GetActivityFeedQuery,
|
|
||||||
test_helpers::TestContextBuilder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn default_deps() -> GetActivityFeedDeps {
|
fn default_deps() -> GetActivityFeedDeps {
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ use domain::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diary::deps::GetMovieSocialPageDeps,
|
diary::deps::GetMovieSocialPageDeps, diary::get_movie_social_page,
|
||||||
diary::get_movie_social_page,
|
|
||||||
diary::queries::GetMovieSocialPageQuery,
|
diary::queries::GetMovieSocialPageQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ pub async fn execute(
|
|||||||
) -> Result<GoalWithProgress, DomainError> {
|
) -> Result<GoalWithProgress, DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
|
|
||||||
let existing = goal
|
let existing = goal.find_by_user_and_year(&user_id, cmd.year).await?;
|
||||||
.find_by_user_and_year(&user_id, cmd.year)
|
|
||||||
.await?;
|
|
||||||
if existing.is_some() {
|
if existing.is_some() {
|
||||||
return Err(DomainError::ValidationError(
|
return Err(DomainError::ValidationError(
|
||||||
"Goal already exists for this year".into(),
|
"Goal already exists for this year".into(),
|
||||||
@@ -34,9 +32,7 @@ pub async fn execute(
|
|||||||
)?;
|
)?;
|
||||||
goal.save(&g).await?;
|
goal.save(&g).await?;
|
||||||
|
|
||||||
let current_count = goal
|
let current_count = goal.count_reviews_in_year(&user_id, cmd.year).await?;
|
||||||
.count_reviews_in_year(&user_id, cmd.year)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
event_publisher
|
event_publisher
|
||||||
.publish(&DomainEvent::GoalCreated {
|
.publish(&DomainEvent::GoalCreated {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
use super::queries::GetGoalQuery;
|
use super::queries::GetGoalQuery;
|
||||||
|
|
||||||
@@ -10,15 +12,11 @@ pub async fn execute(
|
|||||||
) -> Result<Option<GoalWithProgress>, DomainError> {
|
) -> Result<Option<GoalWithProgress>, DomainError> {
|
||||||
let user_id = UserId::from_uuid(query.user_id);
|
let user_id = UserId::from_uuid(query.user_id);
|
||||||
|
|
||||||
let found = goal
|
let found = goal.find_by_user_and_year(&user_id, query.year).await?;
|
||||||
.find_by_user_and_year(&user_id, query.year)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(g) = found else { return Ok(None) };
|
let Some(g) = found else { return Ok(None) };
|
||||||
|
|
||||||
let current_count = goal
|
let current_count = goal.count_reviews_in_year(&user_id, query.year).await?;
|
||||||
.count_reviews_in_year(&user_id, query.year)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Some(GoalWithProgress {
|
Ok(Some(GoalWithProgress {
|
||||||
goal: g,
|
goal: g,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
use super::queries::ListGoalsQuery;
|
use super::queries::ListGoalsQuery;
|
||||||
|
|
||||||
@@ -13,9 +15,7 @@ pub async fn execute(
|
|||||||
|
|
||||||
let mut result = Vec::with_capacity(goals.len());
|
let mut result = Vec::with_capacity(goals.len());
|
||||||
for g in goals {
|
for g in goals {
|
||||||
let current_count = goal
|
let current_count = goal.count_reviews_in_year(&user_id, g.year()).await?;
|
||||||
.count_reviews_in_year(&user_id, g.year())
|
|
||||||
.await?;
|
|
||||||
result.push(GoalWithProgress {
|
result.push(GoalWithProgress {
|
||||||
goal: g,
|
goal: g,
|
||||||
current_count,
|
current_count,
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ pub async fn execute(
|
|||||||
g.update_target(cmd.target_count)?;
|
g.update_target(cmd.target_count)?;
|
||||||
goal.update(&g).await?;
|
goal.update(&g).await?;
|
||||||
|
|
||||||
let current_count = goal
|
let current_count = goal.count_reviews_in_year(&user_id, cmd.year).await?;
|
||||||
.count_reviews_in_year(&user_id, cmd.year)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
event_publisher
|
event_publisher
|
||||||
.publish(&DomainEvent::GoalUpdated {
|
.publish(&DomainEvent::GoalUpdated {
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ pub async fn execute(
|
|||||||
cmd: CreateImportSessionCommand,
|
cmd: CreateImportSessionCommand,
|
||||||
) -> Result<CreateSessionResult, DomainError> {
|
) -> Result<CreateSessionResult, DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
import_session
|
import_session.delete_expired_for_user(&user_id).await?;
|
||||||
.delete_expired_for_user(&user_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let parsed = document_parser
|
let parsed = document_parser
|
||||||
.parse(&cmd.bytes, cmd.format)
|
.parse(&cmd.bytes, cmd.format)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::ImportProfile, ports::ImportProfileRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::ImportProfile, ports::ImportProfileRepository,
|
||||||
|
value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
import_profile: Arc<dyn ImportProfileRepository>,
|
import_profile: Arc<dyn ImportProfileRepository>,
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ async fn returns_empty_when_no_profiles() {
|
|||||||
let profiles = InMemoryImportProfileRepository::new();
|
let profiles = InMemoryImportProfileRepository::new();
|
||||||
|
|
||||||
let user_id = UserId::from_uuid(Uuid::new_v4());
|
let user_id = UserId::from_uuid(Uuid::new_v4());
|
||||||
let result = list_profiles::execute(Arc::clone(&profiles) as _, &user_id).await.unwrap();
|
let result = list_profiles::execute(Arc::clone(&profiles) as _, &user_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::integrations::commands::GenerateWebhookTokenCommand;
|
use crate::integrations::commands::GenerateWebhookTokenCommand;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::integrations::queries::GetWatchQueueQuery;
|
use crate::integrations::queries::GetWatchQueueQuery;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::integrations::queries::GetWebhookTokensQuery;
|
use crate::integrations::queries::GetWebhookTokensQuery;
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ pub async fn execute(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| DomainError::Unauthorized("invalid webhook token".into()))?;
|
.ok_or_else(|| DomainError::Unauthorized("invalid webhook token".into()))?;
|
||||||
|
|
||||||
let _ = deps
|
let _ = deps.webhook_token.touch_last_used(webhook_token.id()).await;
|
||||||
.webhook_token
|
|
||||||
.touch_last_used(webhook_token.id())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let parsed = match parser.parse_playback_event(&cmd.raw_payload)? {
|
let parsed = match parser.parse_playback_event(&cmd.raw_payload)? {
|
||||||
Some(event) => event,
|
Some(event) => event,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
|
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
|
||||||
use domain::testing::{InMemoryWebhookTokenRepository, InMemoryWatchEventRepository, NoopEventPublisher};
|
use domain::testing::{
|
||||||
|
InMemoryWatchEventRepository, InMemoryWebhookTokenRepository, NoopEventPublisher,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::integrations::commands::{GenerateWebhookTokenCommand, IngestWatchEventCommand};
|
use crate::integrations::commands::{GenerateWebhookTokenCommand, IngestWatchEventCommand};
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::{errors::DomainError, ports::{ImportSessionRepository, PeriodicJob}};
|
use domain::{
|
||||||
|
errors::DomainError,
|
||||||
|
ports::{ImportSessionRepository, PeriodicJob},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ImportSessionCleanupJob {
|
pub struct ImportSessionCleanupJob {
|
||||||
import_session: Arc<dyn ImportSessionRepository>,
|
import_session: Arc<dyn ImportSessionRepository>,
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::{errors::DomainError, ports::{PeriodicJob, WatchEventRepository}};
|
use domain::{
|
||||||
|
errors::DomainError,
|
||||||
|
ports::{PeriodicJob, WatchEventRepository},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct WatchEventCleanupJob {
|
pub struct WatchEventCleanupJob {
|
||||||
watch_event: Arc<dyn WatchEventRepository>,
|
watch_event: Arc<dyn WatchEventRepository>,
|
||||||
|
|||||||
@@ -114,10 +114,7 @@ impl PeriodicJob for WrapUpCleanupJob {
|
|||||||
|
|
||||||
async fn run(&self) -> Result<(), DomainError> {
|
async fn run(&self) -> Result<(), DomainError> {
|
||||||
let cutoff = chrono::Utc::now().naive_utc() - chrono::Duration::days(7);
|
let cutoff = chrono::Utc::now().naive_utc() - chrono::Duration::days(7);
|
||||||
let n = self
|
let n = self.wrapup_repo.delete_failed_older_than(cutoff).await?;
|
||||||
.wrapup_repo
|
|
||||||
.delete_failed_older_than(cutoff)
|
|
||||||
.await?;
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
tracing::info!("wrapup cleanup: removed {n} failed records");
|
tracing::info!("wrapup cleanup: removed {n} failed records");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod context;
|
|
||||||
pub mod jobs;
|
pub mod jobs;
|
||||||
pub mod ports;
|
pub mod ports;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ pub async fn execute(deps: &SyncPosterDeps, cmd: SyncPosterCommand) -> Result<()
|
|||||||
})?
|
})?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let poster_url = match deps
|
let poster_url = match deps.metadata.get_poster_url(&external_metadata_id).await {
|
||||||
.metadata
|
|
||||||
.get_poster_url(&external_metadata_id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(url)) => url,
|
Ok(Some(url)) => url,
|
||||||
Ok(None) => return Ok(()),
|
Ok(None) => return Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -43,10 +39,7 @@ pub async fn execute(deps: &SyncPosterDeps, cmd: SyncPosterCommand) -> Result<()
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_bytes = deps
|
let image_bytes = deps.poster_fetcher.fetch_poster_bytes(&poster_url).await?;
|
||||||
.poster_fetcher
|
|
||||||
.fetch_poster_bytes(&poster_url)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let stored_path = deps
|
let stored_path = deps
|
||||||
.object_storage
|
.object_storage
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ use domain::{
|
|||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
models::Movie,
|
models::Movie,
|
||||||
ports::{MetadataClient, MovieRepository},
|
ports::{MetadataClient, MovieRepository},
|
||||||
testing::{InMemoryMovieProfileRepository, InMemoryMovieRepository, NoopEventPublisher, NoopObjectStorage, FakeSearchCommand},
|
testing::{
|
||||||
|
FakeSearchCommand, InMemoryMovieProfileRepository, InMemoryMovieRepository,
|
||||||
|
NoopEventPublisher, NoopObjectStorage,
|
||||||
|
},
|
||||||
value_objects::{ExternalMetadataId, MovieTitle, PosterUrl, ReleaseYear},
|
value_objects::{ExternalMetadataId, MovieTitle, PosterUrl, ReleaseYear},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
models::{SearchQuery, SearchResults},
|
models::{SearchQuery, SearchResults},
|
||||||
ports::SearchPort,
|
ports::SearchPort,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
search_port: Arc<dyn SearchPort>,
|
search_port: Arc<dyn SearchPort>,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::testing::{
|
use domain::testing::{
|
||||||
InMemoryGoalRepository, InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery,
|
InMemoryGoalRepository, InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopSocialQueryPort,
|
||||||
NoopRemoteWatchlistRepository, NoopSocialQueryPort,
|
|
||||||
};
|
};
|
||||||
use domain::{
|
use domain::{
|
||||||
ports::{
|
ports::{
|
||||||
@@ -29,12 +28,7 @@ use domain::{
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::errors::DomainError;
|
use domain::errors::DomainError;
|
||||||
|
|
||||||
use crate::{
|
use crate::{config::AppConfig, diary::commands::LogReviewCommand, ports::ReviewLogger};
|
||||||
config::AppConfig,
|
|
||||||
context::{AppContext, Repositories, Services},
|
|
||||||
diary::commands::LogReviewCommand,
|
|
||||||
ports::ReviewLogger,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct NoopReviewLogger;
|
pub struct NoopReviewLogger;
|
||||||
|
|
||||||
@@ -263,48 +257,4 @@ impl TestContextBuilder {
|
|||||||
self.config = config;
|
self.config = config;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> AppContext {
|
|
||||||
AppContext {
|
|
||||||
repos: Repositories {
|
|
||||||
movie: self.movie_repo,
|
|
||||||
review: self.review_repo,
|
|
||||||
diary: self.diary_repo,
|
|
||||||
stats: self.stats_repo,
|
|
||||||
user: self.user_repo,
|
|
||||||
import_session: self.import_session_repo,
|
|
||||||
import_profile: self.import_profile_repo,
|
|
||||||
movie_profile: self.movie_profile_repo,
|
|
||||||
watchlist: self.watchlist_repo,
|
|
||||||
watch_event: self.watch_event_repo,
|
|
||||||
webhook_token: self.webhook_token_repo,
|
|
||||||
profile_fields: self.profile_fields_repo,
|
|
||||||
person_command: self.person_command,
|
|
||||||
person_query: self.person_query,
|
|
||||||
search_port: self.search_port,
|
|
||||||
search_command: self.search_command,
|
|
||||||
remote_watchlist: Arc::new(NoopRemoteWatchlistRepository),
|
|
||||||
social_query: self.social_query,
|
|
||||||
wrapup_stats: self.wrapup_stats,
|
|
||||||
wrapup_repo: self.wrapup_repo,
|
|
||||||
goal: self.goal_repo,
|
|
||||||
user_settings: self.user_settings_repo,
|
|
||||||
remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository),
|
|
||||||
refresh_session: self.refresh_session_repo,
|
|
||||||
},
|
|
||||||
services: Services {
|
|
||||||
auth: self.auth_service,
|
|
||||||
password_hasher: self.password_hasher,
|
|
||||||
metadata: self.metadata_client,
|
|
||||||
poster_fetcher: self.poster_fetcher,
|
|
||||||
object_storage: self.object_storage,
|
|
||||||
event_publisher: self.event_publisher,
|
|
||||||
diary_exporter: self.diary_exporter,
|
|
||||||
document_parser: self.document_parser,
|
|
||||||
review_logger: self.review_logger,
|
|
||||||
person_enrichment: None,
|
|
||||||
},
|
|
||||||
config: self.config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{errors::DomainError, models::UserSettings, ports::UserSettingsRepository, value_objects::UserId};
|
use domain::{
|
||||||
|
errors::DomainError, models::UserSettings, ports::UserSettingsRepository, value_objects::UserId,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
user_settings: Arc<dyn UserSettingsRepository>,
|
user_settings: Arc<dyn UserSettingsRepository>,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::users::queries::GetUsersQuery;
|
use crate::users::queries::GetUsersQuery;
|
||||||
use domain::{errors::DomainError, models::UserSummary, ports::{RemoteActorInfo, SocialQueryPort, UserRepository}};
|
use domain::{
|
||||||
|
errors::DomainError,
|
||||||
|
models::UserSummary,
|
||||||
|
ports::{RemoteActorInfo, SocialQueryPort, UserRepository},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct UsersListData {
|
pub struct UsersListData {
|
||||||
pub users: Vec<UserSummary>,
|
pub users: Vec<UserSummary>,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ async fn returns_default_settings() {
|
|||||||
let b = TestContextBuilder::new();
|
let b = TestContextBuilder::new();
|
||||||
let user_settings = b.user_settings_repo.clone();
|
let user_settings = b.user_settings_repo.clone();
|
||||||
|
|
||||||
let settings = get_settings::execute(user_settings, Uuid::nil()).await.unwrap();
|
let settings = get_settings::execute(user_settings, Uuid::nil())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(!settings.federate_goals());
|
assert!(!settings.federate_goals());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ async fn returns_empty_when_no_users() {
|
|||||||
let user = b.user_repo.clone();
|
let user = b.user_repo.clone();
|
||||||
let social_query = b.social_query.clone();
|
let social_query = b.social_query.clone();
|
||||||
|
|
||||||
let result = get_users::execute(user, social_query, GetUsersQuery).await.unwrap();
|
let result = get_users::execute(user, social_query, GetUsersQuery)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(result.users.is_empty());
|
assert!(result.users.is_empty());
|
||||||
assert!(result.remote_actors.is_empty());
|
assert!(result.remote_actors.is_empty());
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ use crate::{
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn updates_federate_goals() {
|
async fn updates_federate_goals() {
|
||||||
let settings_repo = InMemoryUserSettingsRepository::new();
|
let settings_repo = InMemoryUserSettingsRepository::new();
|
||||||
let b = TestContextBuilder::new()
|
let b = TestContextBuilder::new().with_user_settings(Arc::clone(&settings_repo) as _);
|
||||||
.with_user_settings(Arc::clone(&settings_repo) as _);
|
|
||||||
let user_settings = b.user_settings_repo.clone();
|
let user_settings = b.user_settings_repo.clone();
|
||||||
|
|
||||||
let uid = Uuid::nil();
|
let uid = Uuid::nil();
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
|||||||
|
|
||||||
use crate::users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps};
|
use crate::users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps};
|
||||||
|
|
||||||
pub async fn execute(deps: &UpdateProfileDeps, cmd: UpdateProfileCommand) -> Result<(), DomainError> {
|
pub async fn execute(
|
||||||
|
deps: &UpdateProfileDeps,
|
||||||
|
cmd: UpdateProfileCommand,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
|
|
||||||
let user = deps
|
let user = deps
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError, events::DomainEvent, models::UserProfile, ports::{EventPublisher, UserProfileFieldsRepository}, value_objects::UserId,
|
errors::DomainError,
|
||||||
|
events::DomainEvent,
|
||||||
|
models::UserProfile,
|
||||||
|
ports::{EventPublisher, UserProfileFieldsRepository},
|
||||||
|
value_objects::UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::users::commands::UpdateProfileFieldsCommand;
|
use crate::users::commands::UpdateProfileFieldsCommand;
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ use crate::{
|
|||||||
watchlist::{commands::AddToWatchlistCommand, deps::WatchlistAddDeps},
|
watchlist::{commands::AddToWatchlistCommand, deps::WatchlistAddDeps},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn execute(deps: &WatchlistAddDeps, cmd: AddToWatchlistCommand) -> Result<(), DomainError> {
|
pub async fn execute(
|
||||||
|
deps: &WatchlistAddDeps,
|
||||||
|
cmd: AddToWatchlistCommand,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
|
|
||||||
let movie = if let Some(id) = cmd.input.movie_id {
|
let movie = if let Some(id) = cmd.input.movie_id {
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ pub async fn execute(
|
|||||||
|
|
||||||
match compute::execute(deps.wrapup_stats.clone(), query).await {
|
match compute::execute(deps.wrapup_stats.clone(), query).await {
|
||||||
Ok(report) => {
|
Ok(report) => {
|
||||||
deps.wrapup_repo
|
deps.wrapup_repo.set_complete(&wrapup_id, &report).await?;
|
||||||
.set_complete(&wrapup_id, &report)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
deps.event_publisher
|
deps.event_publisher
|
||||||
.publish(&DomainEvent::WrapUpCompleted { wrapup_id })
|
.publish(&DomainEvent::WrapUpCompleted { wrapup_id })
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use domain::ports::{
|
|||||||
WrapUpStatsQuery,
|
WrapUpStatsQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::config::AppConfig;
|
use application::config::AppConfig;
|
||||||
use crate::ports::ReviewLogger;
|
use application::ports::ReviewLogger;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Repositories {
|
pub struct Repositories {
|
||||||
@@ -8,10 +8,10 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use application::diary::{
|
use application::diary::{
|
||||||
commands::DeleteReviewCommand,
|
commands::DeleteReviewCommand,
|
||||||
delete_review, export_diary as export_diary_uc, get_activity_feed as get_feed_uc, get_diary,
|
delete_review,
|
||||||
log_review,
|
|
||||||
queries::{ExportQuery, GetActivityFeedQuery},
|
|
||||||
deps::{DeleteReviewDeps, GetActivityFeedDeps},
|
deps::{DeleteReviewDeps, GetActivityFeedDeps},
|
||||||
|
export_diary as export_diary_uc, get_activity_feed as get_feed_uc, get_diary, log_review,
|
||||||
|
queries::{ExportQuery, GetActivityFeedQuery},
|
||||||
};
|
};
|
||||||
use domain::models::ExportFormat;
|
use domain::models::ExportFormat;
|
||||||
|
|
||||||
@@ -81,7 +81,11 @@ pub async fn post_review(
|
|||||||
Json(req): Json<LogReviewRequest>,
|
Json(req): Json<LogReviewRequest>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> Result<impl IntoResponse, ApiError> {
|
||||||
let data = LogReviewData::try_from(req).map_err(ApiError)?;
|
let data = LogReviewData::try_from(req).map_err(ApiError)?;
|
||||||
log_review::execute(&state.app_ctx.services.review_logger, data.into_command(user.0.value())).await?;
|
log_review::execute(
|
||||||
|
&state.app_ctx.services.review_logger,
|
||||||
|
data.into_command(user.0.value()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(StatusCode::CREATED)
|
Ok(StatusCode::CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +248,12 @@ pub async fn post_review_html(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match log_review::execute(&state.app_ctx.services.review_logger, data.into_command(user_id.value())).await {
|
match log_review::execute(
|
||||||
|
&state.app_ctx.services.review_logger,
|
||||||
|
data.into_command(user_id.value()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => Redirect::to("/").into_response(),
|
Ok(_) => Redirect::to("/").into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = encode_error(&e.to_string());
|
let msg = encode_error(&e.to_string());
|
||||||
|
|||||||
@@ -109,10 +109,8 @@ pub async fn get_import_page(
|
|||||||
Extension(csrf): Extension<CsrfToken>,
|
Extension(csrf): Extension<CsrfToken>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let ctx = super::helpers::build_page_context(&state, Some(user_id.clone()), csrf.0).await;
|
let ctx = super::helpers::build_page_context(&state, Some(user_id.clone()), csrf.0).await;
|
||||||
let profiles = list_import_profiles::execute(
|
let profiles =
|
||||||
state.app_ctx.repos.import_profile.clone(),
|
list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id)
|
||||||
&user_id,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -765,7 +763,8 @@ pub async fn api_get_profiles(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AuthenticatedUser(user_id): AuthenticatedUser,
|
AuthenticatedUser(user_id): AuthenticatedUser,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id).await {
|
match list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id).await
|
||||||
|
{
|
||||||
Ok(profiles) => axum::Json(
|
Ok(profiles) => axum::Json(
|
||||||
profiles
|
profiles
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ pub async fn post_revoke_token(
|
|||||||
user_id: user_id.value(),
|
user_id: user_id.value(),
|
||||||
token_id,
|
token_id,
|
||||||
};
|
};
|
||||||
if let Err(e) = revoke_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await {
|
if let Err(e) =
|
||||||
|
revoke_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await
|
||||||
|
{
|
||||||
tracing::error!("revoke token failed: {:?}", e);
|
tracing::error!("revoke token failed: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,11 @@ use uuid::Uuid;
|
|||||||
use application::{
|
use application::{
|
||||||
diary::{
|
diary::{
|
||||||
commands::SyncPosterCommand,
|
commands::SyncPosterCommand,
|
||||||
deps::{GetMovieSocialPageDeps},
|
deps::GetMovieSocialPageDeps,
|
||||||
get_movie_social_page, get_review_history,
|
get_movie_social_page, get_review_history,
|
||||||
queries::{GetMovieSocialPageQuery, GetReviewHistoryQuery},
|
queries::{GetMovieSocialPageQuery, GetReviewHistoryQuery},
|
||||||
},
|
},
|
||||||
movies::{
|
movies::{deps::SyncPosterDeps, get_movies, queries::GetMoviesQuery, sync_poster},
|
||||||
deps::SyncPosterDeps,
|
|
||||||
get_movies,
|
|
||||||
queries::GetMoviesQuery,
|
|
||||||
sync_poster,
|
|
||||||
},
|
|
||||||
watchlist::{is_on as is_on_watchlist, queries::IsOnWatchlistQuery},
|
watchlist::{is_on as is_on_watchlist, queries::IsOnWatchlistQuery},
|
||||||
};
|
};
|
||||||
use domain::services::review_history::Trend;
|
use domain::services::review_history::Trend;
|
||||||
@@ -88,8 +83,11 @@ pub async fn get_review_history(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(movie_id): Path<Uuid>,
|
Path(movie_id): Path<Uuid>,
|
||||||
) -> Result<Json<ReviewHistoryResponse>, ApiError> {
|
) -> Result<Json<ReviewHistoryResponse>, ApiError> {
|
||||||
let (history, trend) =
|
let (history, trend) = get_review_history::execute(
|
||||||
get_review_history::execute(&state.app_ctx.repos.diary, GetReviewHistoryQuery { movie_id }).await?;
|
&state.app_ctx.repos.diary,
|
||||||
|
GetReviewHistoryQuery { movie_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Json(ReviewHistoryResponse {
|
Ok(Json(ReviewHistoryResponse {
|
||||||
movie: crate::mappers::movies::movie_to_dto(history.movie()),
|
movie: crate::mappers::movies::movie_to_dto(history.movie()),
|
||||||
@@ -210,12 +208,7 @@ pub async fn get_movie_profile(
|
|||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
use application::movies::get_movie_profile;
|
use application::movies::get_movie_profile;
|
||||||
let query = get_movie_profile::GetMovieProfileQuery { movie_id };
|
let query = get_movie_profile::GetMovieProfileQuery { movie_id };
|
||||||
match get_movie_profile::execute(
|
match get_movie_profile::execute(state.app_ctx.repos.movie_profile.clone(), query).await {
|
||||||
state.app_ctx.repos.movie_profile.clone(),
|
|
||||||
query,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(result)) => {
|
Ok(Some(result)) => {
|
||||||
let p = result.profile;
|
let p = result.profile;
|
||||||
Json(MovieProfileResponse {
|
Json(MovieProfileResponse {
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ use application::integrations::{
|
|||||||
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
|
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
|
||||||
IngestWatchEventCommand, RevokeWebhookTokenCommand, WatchEventConfirmation,
|
IngestWatchEventCommand, RevokeWebhookTokenCommand, WatchEventConfirmation,
|
||||||
},
|
},
|
||||||
confirm as confirm_watch_events, deps::IngestWatchEventDeps,
|
confirm as confirm_watch_events,
|
||||||
|
deps::IngestWatchEventDeps,
|
||||||
dismiss as dismiss_watch_events, generate_token as generate_webhook_token,
|
dismiss as dismiss_watch_events, generate_token as generate_webhook_token,
|
||||||
get_queue as get_watch_queue, get_tokens as get_webhook_tokens,
|
get_queue as get_watch_queue, get_tokens as get_webhook_tokens, ingest as ingest_watch_event,
|
||||||
ingest as ingest_watch_event, queries::{GetWatchQueueQuery, GetWebhookTokensQuery},
|
queries::{GetWatchQueueQuery, GetWebhookTokensQuery},
|
||||||
revoke_token as revoke_webhook_token,
|
revoke_token as revoke_webhook_token,
|
||||||
};
|
};
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
@@ -164,7 +165,8 @@ pub async fn post_generate_webhook_token(
|
|||||||
label: req.label,
|
label: req.label,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = generate_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await?;
|
let result =
|
||||||
|
generate_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await?;
|
||||||
|
|
||||||
let base_url = &state.app_ctx.config.base_url;
|
let base_url = &state.app_ctx.config.base_url;
|
||||||
let webhook_url = format!("{base_url}/api/v1/webhooks/{provider}");
|
let webhook_url = format!("{base_url}/api/v1/webhooks/{provider}");
|
||||||
@@ -191,7 +193,8 @@ pub async fn get_webhook_tokens(
|
|||||||
let query = GetWebhookTokensQuery {
|
let query = GetWebhookTokensQuery {
|
||||||
user_id: user.0.value(),
|
user_id: user.0.value(),
|
||||||
};
|
};
|
||||||
let tokens = get_webhook_tokens::execute(state.app_ctx.repos.webhook_token.clone(), query).await?;
|
let tokens =
|
||||||
|
get_webhook_tokens::execute(state.app_ctx.repos.webhook_token.clone(), query).await?;
|
||||||
|
|
||||||
let dtos = tokens
|
let dtos = tokens
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -321,6 +324,7 @@ pub async fn post_dismiss_watch_events(
|
|||||||
event_ids: req.event_ids,
|
event_ids: req.event_ids,
|
||||||
};
|
};
|
||||||
|
|
||||||
let dismissed = dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await?;
|
let dismissed =
|
||||||
|
dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await?;
|
||||||
Ok(Json(DismissWatchResponse { dismissed }))
|
Ok(Json(DismissWatchResponse { dismissed }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,10 @@ pub async fn get_status(
|
|||||||
_user: AuthenticatedUser,
|
_user: AuthenticatedUser,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<Json<WrapUpStatusResponse>, ApiError> {
|
) -> Result<Json<WrapUpStatusResponse>, ApiError> {
|
||||||
let record = get_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id))
|
let record = get_wrapup::execute(
|
||||||
|
state.app_ctx.repos.wrapup_repo.clone(),
|
||||||
|
WrapUpId::from_uuid(id),
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?;
|
.ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?;
|
||||||
Ok(Json(record_to_dto(&record)))
|
Ok(Json(record_to_dto(&record)))
|
||||||
@@ -138,7 +141,12 @@ pub async fn get_report(
|
|||||||
_user: AuthenticatedUser,
|
_user: AuthenticatedUser,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match get_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id)).await {
|
match get_wrapup::execute(
|
||||||
|
state.app_ctx.repos.wrapup_repo.clone(),
|
||||||
|
WrapUpId::from_uuid(id),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report {
|
Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report {
|
||||||
Some(ref report) => {
|
Some(ref report) => {
|
||||||
let json = serde_json::to_string(report).unwrap_or_default();
|
let json = serde_json::to_string(report).unwrap_or_default();
|
||||||
@@ -168,7 +176,11 @@ pub async fn delete_wrapup_handler(
|
|||||||
_admin: AdminApiUser,
|
_admin: AdminApiUser,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, ApiError> {
|
) -> Result<StatusCode, ApiError> {
|
||||||
delete_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id)).await?;
|
delete_wrapup::execute(
|
||||||
|
state.app_ctx.repos.wrapup_repo.clone(),
|
||||||
|
WrapUpId::from_uuid(id),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod context;
|
||||||
pub mod csrf;
|
pub mod csrf;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod extractors;
|
pub mod extractors;
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ use anyhow::Context;
|
|||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
use application::{
|
use application::config::AppConfig;
|
||||||
config::AppConfig,
|
|
||||||
context::{AppContext, Repositories, Services},
|
|
||||||
};
|
|
||||||
use export::ExportAdapter;
|
use export::ExportAdapter;
|
||||||
use importer::ImporterDocumentParser;
|
use importer::ImporterDocumentParser;
|
||||||
|
use presentation::context::{AppContext, Repositories, Services};
|
||||||
use presentation::{factory, openapi, routes, state::AppState};
|
use presentation::{factory, openapi, routes, state::AppState};
|
||||||
use rss::RssAdapter;
|
use rss::RssAdapter;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use application::context::AppContext;
|
use crate::context::AppContext;
|
||||||
|
|
||||||
use crate::ports::RssFeedRenderer;
|
use crate::ports::RssFeedRenderer;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use application::{
|
use crate::context::{AppContext, Repositories, Services};
|
||||||
config::AppConfig,
|
use application::config::AppConfig;
|
||||||
context::{AppContext, Repositories, Services},
|
|
||||||
};
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
body::Body,
|
body::Body,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use application::{
|
use application::config::AppConfig;
|
||||||
config::AppConfig,
|
|
||||||
context::{AppContext, Repositories, Services},
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
@@ -25,6 +22,7 @@ use domain::{
|
|||||||
value_objects::{Email, ExternalMetadataId, PasswordHash, PosterUrl, UserId},
|
value_objects::{Email, ExternalMetadataId, PasswordHash, PosterUrl, UserId},
|
||||||
};
|
};
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
|
use presentation::context::{AppContext, Repositories, Services};
|
||||||
use presentation::{routes, state::AppState};
|
use presentation::{routes, state::AppState};
|
||||||
use rss::RssAdapter;
|
use rss::RssAdapter;
|
||||||
use sqlite::SqliteMovieRepository;
|
use sqlite::SqliteMovieRepository;
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use domain::ports::{
|
use domain::ports::{
|
||||||
DiaryRepository, ImageRefCommand, ImageRefQuery, ImportProfileRepository,
|
ImageRefCommand, ImageRefQuery, ImportSessionRepository, LocalApContentQuery,
|
||||||
ImportSessionRepository, LocalApContentQuery, MovieProfileRepository, MovieRepository,
|
MovieProfileRepository, MovieRepository, PersonCommand, PersonQuery, SearchCommand,
|
||||||
PersonCommand, PersonQuery, ReviewRepository, SearchCommand, SearchPort, StatsRepository,
|
UserRepository, WatchEventRepository,
|
||||||
UserProfileFieldsRepository, UserRepository, WatchEventRepository, WatchlistRepository,
|
|
||||||
WebhookTokenRepository,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum DbPool {
|
pub enum DbPool {
|
||||||
@@ -18,28 +16,18 @@ pub enum DbPool {
|
|||||||
|
|
||||||
pub struct WorkerDbOutput {
|
pub struct WorkerDbOutput {
|
||||||
pub movie: Arc<dyn MovieRepository>,
|
pub movie: Arc<dyn MovieRepository>,
|
||||||
pub review: Arc<dyn ReviewRepository>,
|
|
||||||
pub diary: Arc<dyn DiaryRepository>,
|
|
||||||
pub stats: Arc<dyn StatsRepository>,
|
|
||||||
pub user: Arc<dyn UserRepository>,
|
pub user: Arc<dyn UserRepository>,
|
||||||
pub import_session: Arc<dyn ImportSessionRepository>,
|
pub import_session: Arc<dyn ImportSessionRepository>,
|
||||||
pub import_profile: Arc<dyn ImportProfileRepository>,
|
|
||||||
pub movie_profile: Arc<dyn MovieProfileRepository>,
|
pub movie_profile: Arc<dyn MovieProfileRepository>,
|
||||||
pub watchlist: Arc<dyn WatchlistRepository>,
|
|
||||||
pub watch_event: Arc<dyn WatchEventRepository>,
|
pub watch_event: Arc<dyn WatchEventRepository>,
|
||||||
pub webhook_token: Arc<dyn WebhookTokenRepository>,
|
|
||||||
pub person_command: Arc<dyn PersonCommand>,
|
pub person_command: Arc<dyn PersonCommand>,
|
||||||
pub person_query: Arc<dyn PersonQuery>,
|
pub person_query: Arc<dyn PersonQuery>,
|
||||||
pub search_command: Arc<dyn SearchCommand>,
|
pub search_command: Arc<dyn SearchCommand>,
|
||||||
pub search_port: Arc<dyn SearchPort>,
|
|
||||||
pub profile_fields: Arc<dyn UserProfileFieldsRepository>,
|
|
||||||
pub ap_content: Arc<dyn LocalApContentQuery>,
|
pub ap_content: Arc<dyn LocalApContentQuery>,
|
||||||
pub image_ref_command: Arc<dyn ImageRefCommand>,
|
pub image_ref_command: Arc<dyn ImageRefCommand>,
|
||||||
pub image_ref_query: Arc<dyn ImageRefQuery>,
|
pub image_ref_query: Arc<dyn ImageRefQuery>,
|
||||||
pub wrapup_stats: Arc<dyn domain::ports::WrapUpStatsQuery>,
|
pub wrapup_stats: Arc<dyn domain::ports::WrapUpStatsQuery>,
|
||||||
pub wrapup_repo: Arc<dyn domain::ports::WrapUpRepository>,
|
pub wrapup_repo: Arc<dyn domain::ports::WrapUpRepository>,
|
||||||
pub goal: Arc<dyn domain::ports::GoalRepository>,
|
|
||||||
pub user_settings: Arc<dyn domain::ports::UserSettingsRepository>,
|
|
||||||
pub remote_goal: Arc<dyn domain::ports::RemoteGoalRepository>,
|
pub remote_goal: Arc<dyn domain::ports::RemoteGoalRepository>,
|
||||||
pub refresh_session: Arc<dyn domain::ports::RefreshSessionRepository>,
|
pub refresh_session: Arc<dyn domain::ports::RefreshSessionRepository>,
|
||||||
pub db_pool: DbPool,
|
pub db_pool: DbPool,
|
||||||
@@ -54,38 +42,24 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
|
|||||||
.context("PostgreSQL connection failed")?;
|
.context("PostgreSQL connection failed")?;
|
||||||
let (image_ref_command, image_ref_query) = postgres::create_image_ref(w.pool.clone());
|
let (image_ref_command, image_ref_query) = postgres::create_image_ref(w.pool.clone());
|
||||||
let (person_command, person_query) = postgres::create_person_adapter(w.pool.clone());
|
let (person_command, person_query) = postgres::create_person_adapter(w.pool.clone());
|
||||||
let (search_command, search_port) =
|
let (search_command, _search_port) =
|
||||||
postgres_search::create_search_adapter(w.pool.clone());
|
postgres_search::create_search_adapter(w.pool.clone());
|
||||||
let pf = postgres::create_profile_fields_repo(w.pool.clone());
|
|
||||||
let we: Arc<dyn WatchEventRepository> =
|
let we: Arc<dyn WatchEventRepository> =
|
||||||
Arc::new(postgres::PostgresWatchEventRepository::new(w.pool.clone()));
|
Arc::new(postgres::PostgresWatchEventRepository::new(w.pool.clone()));
|
||||||
let wt: Arc<dyn WebhookTokenRepository> = Arc::new(
|
|
||||||
postgres::PostgresWebhookTokenRepository::new(w.pool.clone()),
|
|
||||||
);
|
|
||||||
Ok(WorkerDbOutput {
|
Ok(WorkerDbOutput {
|
||||||
movie: w.movie,
|
movie: w.movie,
|
||||||
review: w.review,
|
|
||||||
diary: w.diary,
|
|
||||||
stats: w.stats,
|
|
||||||
user: w.user,
|
user: w.user,
|
||||||
import_session: w.import_session,
|
import_session: w.import_session,
|
||||||
import_profile: w.import_profile,
|
|
||||||
movie_profile: w.movie_profile,
|
movie_profile: w.movie_profile,
|
||||||
watchlist: w.watchlist,
|
|
||||||
watch_event: we,
|
watch_event: we,
|
||||||
webhook_token: wt,
|
|
||||||
person_command,
|
person_command,
|
||||||
person_query,
|
person_query,
|
||||||
search_command,
|
search_command,
|
||||||
search_port,
|
|
||||||
profile_fields: pf,
|
|
||||||
ap_content: w.ap_content,
|
ap_content: w.ap_content,
|
||||||
image_ref_command,
|
image_ref_command,
|
||||||
image_ref_query,
|
image_ref_query,
|
||||||
wrapup_stats: w.wrapup_stats,
|
wrapup_stats: w.wrapup_stats,
|
||||||
wrapup_repo: w.wrapup_repo,
|
wrapup_repo: w.wrapup_repo,
|
||||||
goal: w.goal,
|
|
||||||
user_settings: w.user_settings,
|
|
||||||
remote_goal: w.remote_goal,
|
remote_goal: w.remote_goal,
|
||||||
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(
|
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(
|
||||||
w.pool.clone(),
|
w.pool.clone(),
|
||||||
@@ -100,37 +74,24 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
|
|||||||
.context("SQLite connection failed")?;
|
.context("SQLite connection failed")?;
|
||||||
let (image_ref_command, image_ref_query) = sqlite::create_image_ref(w.pool.clone());
|
let (image_ref_command, image_ref_query) = sqlite::create_image_ref(w.pool.clone());
|
||||||
let (person_command, person_query) = sqlite::create_person_adapter(w.pool.clone());
|
let (person_command, person_query) = sqlite::create_person_adapter(w.pool.clone());
|
||||||
let (search_command, search_port) =
|
let (search_command, _search_port) =
|
||||||
sqlite_search::create_search_adapter(w.pool.clone());
|
sqlite_search::create_search_adapter(w.pool.clone());
|
||||||
let pf = sqlite::create_profile_fields_repo(w.pool.clone());
|
|
||||||
let we: Arc<dyn WatchEventRepository> =
|
let we: Arc<dyn WatchEventRepository> =
|
||||||
Arc::new(sqlite::SqliteWatchEventRepository::new(w.pool.clone()));
|
Arc::new(sqlite::SqliteWatchEventRepository::new(w.pool.clone()));
|
||||||
let wt: Arc<dyn WebhookTokenRepository> =
|
|
||||||
Arc::new(sqlite::SqliteWebhookTokenRepository::new(w.pool.clone()));
|
|
||||||
Ok(WorkerDbOutput {
|
Ok(WorkerDbOutput {
|
||||||
movie: w.movie,
|
movie: w.movie,
|
||||||
review: w.review,
|
|
||||||
diary: w.diary,
|
|
||||||
stats: w.stats,
|
|
||||||
user: w.user,
|
user: w.user,
|
||||||
import_session: w.import_session,
|
import_session: w.import_session,
|
||||||
import_profile: w.import_profile,
|
|
||||||
movie_profile: w.movie_profile,
|
movie_profile: w.movie_profile,
|
||||||
watchlist: w.watchlist,
|
|
||||||
watch_event: we,
|
watch_event: we,
|
||||||
webhook_token: wt,
|
|
||||||
person_command,
|
person_command,
|
||||||
person_query,
|
person_query,
|
||||||
search_command,
|
search_command,
|
||||||
search_port,
|
|
||||||
profile_fields: pf,
|
|
||||||
ap_content: w.ap_content,
|
ap_content: w.ap_content,
|
||||||
image_ref_command,
|
image_ref_command,
|
||||||
image_ref_query,
|
image_ref_query,
|
||||||
wrapup_stats: w.wrapup_stats,
|
wrapup_stats: w.wrapup_stats,
|
||||||
wrapup_repo: w.wrapup_repo,
|
wrapup_repo: w.wrapup_repo,
|
||||||
goal: w.goal,
|
|
||||||
user_settings: w.user_settings,
|
|
||||||
remote_goal: w.remote_goal,
|
remote_goal: w.remote_goal,
|
||||||
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone()))
|
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone()))
|
||||||
as _,
|
as _,
|
||||||
|
|||||||
@@ -6,20 +6,12 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use application::{
|
use application::{
|
||||||
MovieDiscoveryIndexer, SearchCleanupHandler, SearchReindexHandler,
|
MovieDiscoveryIndexer, SearchCleanupHandler, SearchReindexHandler, config::AppConfig,
|
||||||
config::AppConfig,
|
movies::deps::ReindexSearchDeps, worker::WorkerService,
|
||||||
context::{AppContext, Repositories, Services},
|
|
||||||
movies::deps::ReindexSearchDeps,
|
|
||||||
worker::WorkerService,
|
|
||||||
};
|
};
|
||||||
use export::ExportAdapter;
|
|
||||||
use importer::ImporterDocumentParser;
|
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
use domain::ports::{
|
use domain::ports::{EventHandler, MovieEnrichmentClient, PeriodicJob, PersonEnrichmentClient};
|
||||||
DiaryExporter, DocumentParser, EventHandler, MovieEnrichmentClient, PeriodicJob,
|
|
||||||
PersonEnrichmentClient,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
|
#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
|
||||||
compile_error!(
|
compile_error!(
|
||||||
@@ -35,7 +27,6 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let backend = std::env::var("DATABASE_BACKEND").unwrap_or_else(|_| "sqlite".to_string());
|
let backend = std::env::var("DATABASE_BACKEND").unwrap_or_else(|_| "sqlite".to_string());
|
||||||
let app_config = AppConfig::from_env();
|
let app_config = AppConfig::from_env();
|
||||||
|
|
||||||
let (auth_service, password_hasher) = auth::create()?;
|
|
||||||
let metadata_client = metadata::create()?;
|
let metadata_client = metadata::create()?;
|
||||||
let poster_fetcher = poster_fetcher::create()?;
|
let poster_fetcher = poster_fetcher::create()?;
|
||||||
let object_storage = object_storage::create()?;
|
let object_storage = object_storage::create()?;
|
||||||
@@ -60,7 +51,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
fed_follow_repo,
|
fed_follow_repo,
|
||||||
fed_actor_repo,
|
fed_actor_repo,
|
||||||
fed_blocklist_repo,
|
fed_blocklist_repo,
|
||||||
fed_social_query,
|
_fed_social_query,
|
||||||
fed_review_store,
|
fed_review_store,
|
||||||
fed_remote_watchlist_repo,
|
fed_remote_watchlist_repo,
|
||||||
) = match &db.db_pool {
|
) = match &db.db_pool {
|
||||||
@@ -70,61 +61,22 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
db::DbPool::Postgres(pool) => postgres_federation::wire(pool.clone()),
|
db::DbPool::Postgres(pool) => postgres_federation::wire(pool.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let review_logger = Arc::new(application::diary::review_logger::DefaultReviewLogger::new(
|
let movie = db.movie;
|
||||||
Arc::clone(&db.movie),
|
let user = db.user;
|
||||||
Arc::clone(&db.review),
|
let import_session = db.import_session;
|
||||||
Arc::clone(&db.watchlist),
|
let movie_profile = db.movie_profile;
|
||||||
Arc::clone(&metadata_client),
|
let watch_event = db.watch_event;
|
||||||
Arc::clone(&event_publisher_arc),
|
let person_command = db.person_command;
|
||||||
));
|
let person_query = db.person_query;
|
||||||
|
let search_command = db.search_command;
|
||||||
|
let wrapup_stats = db.wrapup_stats;
|
||||||
|
let wrapup_repo = db.wrapup_repo;
|
||||||
|
let remote_goal = db.remote_goal;
|
||||||
|
let refresh_session = db.refresh_session;
|
||||||
|
|
||||||
let mut ctx = AppContext {
|
let event_publisher = event_publisher_arc;
|
||||||
repos: Repositories {
|
let object_storage = object_storage;
|
||||||
movie: db.movie,
|
let metadata = metadata_client;
|
||||||
review: db.review,
|
|
||||||
diary: db.diary,
|
|
||||||
stats: db.stats,
|
|
||||||
user: db.user,
|
|
||||||
import_session: db.import_session,
|
|
||||||
import_profile: db.import_profile,
|
|
||||||
movie_profile: db.movie_profile,
|
|
||||||
watchlist: db.watchlist,
|
|
||||||
watch_event: db.watch_event,
|
|
||||||
webhook_token: db.webhook_token,
|
|
||||||
profile_fields: db.profile_fields,
|
|
||||||
person_command: db.person_command,
|
|
||||||
person_query: db.person_query,
|
|
||||||
search_port: db.search_port,
|
|
||||||
search_command: db.search_command,
|
|
||||||
#[cfg(feature = "federation")]
|
|
||||||
remote_watchlist: fed_remote_watchlist_repo.clone(),
|
|
||||||
#[cfg(not(feature = "federation"))]
|
|
||||||
remote_watchlist: Arc::new(domain::testing::NoopRemoteWatchlistRepository),
|
|
||||||
#[cfg(feature = "federation")]
|
|
||||||
social_query: fed_social_query,
|
|
||||||
#[cfg(not(feature = "federation"))]
|
|
||||||
social_query: Arc::new(domain::testing::NoopSocialQueryPort),
|
|
||||||
wrapup_stats: db.wrapup_stats,
|
|
||||||
wrapup_repo: db.wrapup_repo,
|
|
||||||
goal: db.goal,
|
|
||||||
user_settings: db.user_settings,
|
|
||||||
remote_goal: db.remote_goal,
|
|
||||||
refresh_session: db.refresh_session,
|
|
||||||
},
|
|
||||||
services: Services {
|
|
||||||
auth: auth_service,
|
|
||||||
password_hasher,
|
|
||||||
metadata: metadata_client,
|
|
||||||
poster_fetcher,
|
|
||||||
object_storage,
|
|
||||||
event_publisher: event_publisher_arc,
|
|
||||||
diary_exporter: Arc::new(ExportAdapter) as Arc<dyn DiaryExporter>,
|
|
||||||
document_parser: Arc::new(ImporterDocumentParser) as Arc<dyn DocumentParser>,
|
|
||||||
review_logger,
|
|
||||||
person_enrichment: None,
|
|
||||||
},
|
|
||||||
config: app_config,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Enrichment ────────────────────────────────────────────────────────────
|
// ── Enrichment ────────────────────────────────────────────────────────────
|
||||||
// Both the event handler and the staleness job are gated on TMDB_API_KEY.
|
// Both the event handler and the staleness job are gated on TMDB_API_KEY.
|
||||||
@@ -142,23 +94,21 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
let handler = Arc::new(tmdb_enrichment::MovieEnrichmentHandler::new(
|
let handler = Arc::new(tmdb_enrichment::MovieEnrichmentHandler::new(
|
||||||
Arc::clone(&client) as Arc<dyn MovieEnrichmentClient>,
|
Arc::clone(&client) as Arc<dyn MovieEnrichmentClient>,
|
||||||
Arc::clone(&ctx.repos.movie),
|
Arc::clone(&movie),
|
||||||
Arc::clone(&ctx.repos.movie_profile),
|
Arc::clone(&movie_profile),
|
||||||
Arc::clone(&ctx.repos.person_command),
|
Arc::clone(&person_command),
|
||||||
Arc::clone(&ctx.repos.search_command),
|
Arc::clone(&search_command),
|
||||||
Arc::clone(&ctx.services.object_storage),
|
Arc::clone(&object_storage),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
let person_enrichment_arc =
|
let person_enrichment_arc = Arc::clone(&client) as Arc<dyn PersonEnrichmentClient>;
|
||||||
Arc::clone(&client) as Arc<dyn PersonEnrichmentClient>;
|
|
||||||
ctx.services.person_enrichment = Some(Arc::clone(&person_enrichment_arc));
|
|
||||||
let person_handler = Arc::new(tmdb_enrichment::PersonEnrichmentHandler::new(
|
let person_handler = Arc::new(tmdb_enrichment::PersonEnrichmentHandler::new(
|
||||||
Arc::clone(&ctx.repos.person_query),
|
Arc::clone(&person_query),
|
||||||
Some(person_enrichment_arc),
|
Some(person_enrichment_arc),
|
||||||
Arc::clone(&ctx.repos.person_command),
|
Arc::clone(&person_command),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
let job = Arc::new(application::jobs::EnrichmentStalenessJob::new(
|
let job = Arc::new(application::jobs::EnrichmentStalenessJob::new(
|
||||||
Arc::clone(&ctx.repos.movie_profile),
|
Arc::clone(&movie_profile),
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
)) as Arc<dyn PeriodicJob>;
|
)) as Arc<dyn PeriodicJob>;
|
||||||
(Some(handler), Some(person_handler), Some(job))
|
(Some(handler), Some(person_handler), Some(job))
|
||||||
}
|
}
|
||||||
@@ -171,27 +121,31 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// ── Image conversion ──────────────────────────────────────────────────────
|
// ── Image conversion ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
let conversion = image_converter::build(
|
let conversion = image_converter::build(
|
||||||
Arc::clone(&ctx.services.object_storage),
|
Arc::clone(&object_storage),
|
||||||
image_ref_command,
|
image_ref_command,
|
||||||
image_ref_query,
|
image_ref_query,
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// ── Periodic jobs ─────────────────────────────────────────────────────────
|
// ── Periodic jobs ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let mut periodic_jobs: Vec<Arc<dyn PeriodicJob>> = vec![
|
let mut periodic_jobs: Vec<Arc<dyn PeriodicJob>> = vec![
|
||||||
Arc::new(application::jobs::ImportSessionCleanupJob::new(ctx.repos.import_session.clone())),
|
Arc::new(application::jobs::ImportSessionCleanupJob::new(
|
||||||
Arc::new(application::jobs::WatchEventCleanupJob::new(ctx.repos.watch_event.clone())),
|
import_session.clone(),
|
||||||
|
)),
|
||||||
|
Arc::new(application::jobs::WatchEventCleanupJob::new(
|
||||||
|
watch_event.clone(),
|
||||||
|
)),
|
||||||
Arc::new(application::jobs::WrapUpAutoGenerateJob::new(
|
Arc::new(application::jobs::WrapUpAutoGenerateJob::new(
|
||||||
Arc::clone(&ctx.repos.user),
|
Arc::clone(&user),
|
||||||
Arc::clone(&ctx.repos.wrapup_repo),
|
Arc::clone(&wrapup_repo),
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
)),
|
|
||||||
Arc::new(application::jobs::WrapUpCleanupJob::new(
|
|
||||||
Arc::clone(&ctx.repos.wrapup_repo),
|
|
||||||
)),
|
)),
|
||||||
|
Arc::new(application::jobs::WrapUpCleanupJob::new(Arc::clone(
|
||||||
|
&wrapup_repo,
|
||||||
|
))),
|
||||||
Arc::new(application::jobs::RefreshSessionCleanupJob::new(
|
Arc::new(application::jobs::RefreshSessionCleanupJob::new(
|
||||||
Arc::clone(&ctx.repos.refresh_session),
|
Arc::clone(&refresh_session),
|
||||||
)),
|
)),
|
||||||
];
|
];
|
||||||
if let Some(job) = enrichment_job {
|
if let Some(job) = enrichment_job {
|
||||||
@@ -217,42 +171,40 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let handlers: Vec<Arc<dyn EventHandler>> = {
|
let handlers: Vec<Arc<dyn EventHandler>> = {
|
||||||
let poster = Arc::new(poster_sync::PosterSyncHandler::new(
|
let poster = Arc::new(poster_sync::PosterSyncHandler::new(
|
||||||
Arc::clone(&ctx.repos.movie),
|
Arc::clone(&movie),
|
||||||
Arc::clone(&ctx.services.metadata),
|
Arc::clone(&metadata),
|
||||||
Arc::clone(&ctx.services.poster_fetcher),
|
Arc::clone(&poster_fetcher),
|
||||||
Arc::clone(&ctx.services.object_storage),
|
Arc::clone(&object_storage),
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
3,
|
3,
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
|
|
||||||
let cleanup = Arc::new(object_storage::ImageCleanupHandler::new(Arc::clone(
|
let cleanup = Arc::new(object_storage::ImageCleanupHandler::new(Arc::clone(
|
||||||
&ctx.services.object_storage,
|
&object_storage,
|
||||||
))) as Arc<dyn EventHandler>;
|
))) as Arc<dyn EventHandler>;
|
||||||
|
|
||||||
#[cfg(not(feature = "federation"))]
|
#[cfg(not(feature = "federation"))]
|
||||||
{
|
{
|
||||||
let search_cleanup = Arc::new(SearchCleanupHandler::new(
|
let search_cleanup = Arc::new(SearchCleanupHandler::new(
|
||||||
Arc::clone(&ctx.repos.search_command),
|
Arc::clone(&search_command),
|
||||||
Arc::clone(&ctx.repos.person_query),
|
Arc::clone(&person_query),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new(
|
let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new(
|
||||||
Arc::clone(&ctx.repos.movie),
|
Arc::clone(&movie),
|
||||||
Arc::clone(&ctx.repos.search_command),
|
Arc::clone(&search_command),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
let wrapup_handler = Arc::new(
|
let wrapup_handler =
|
||||||
application::wrapup::event_handler::WrapUpEventHandler::new(
|
Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new(
|
||||||
Arc::clone(&ctx.repos.wrapup_repo),
|
Arc::clone(&wrapup_repo),
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
Arc::clone(&ctx.repos.wrapup_stats),
|
Arc::clone(&wrapup_stats),
|
||||||
),
|
)) as Arc<dyn EventHandler>;
|
||||||
) as Arc<dyn EventHandler>;
|
let reindex_handler = Arc::new(SearchReindexHandler::new(ReindexSearchDeps {
|
||||||
let reindex_handler =
|
movie: Arc::clone(&movie),
|
||||||
Arc::new(SearchReindexHandler::new(ReindexSearchDeps {
|
movie_profile: Arc::clone(&movie_profile),
|
||||||
movie: Arc::clone(&ctx.repos.movie),
|
search_command: Arc::clone(&search_command),
|
||||||
movie_profile: Arc::clone(&ctx.repos.movie_profile),
|
person_command: Arc::clone(&person_command),
|
||||||
search_command: Arc::clone(&ctx.repos.search_command),
|
person_query: Arc::clone(&person_query),
|
||||||
person_command: Arc::clone(&ctx.repos.person_command),
|
|
||||||
person_query: Arc::clone(&ctx.repos.person_query),
|
|
||||||
})) as Arc<dyn EventHandler>;
|
})) as Arc<dyn EventHandler>;
|
||||||
let mut h = vec![
|
let mut h = vec![
|
||||||
poster,
|
poster,
|
||||||
@@ -283,12 +235,12 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
blocklist_repo: fed_blocklist_repo,
|
blocklist_repo: fed_blocklist_repo,
|
||||||
review_store: fed_review_store,
|
review_store: fed_review_store,
|
||||||
remote_watchlist_repo: fed_remote_watchlist_repo,
|
remote_watchlist_repo: fed_remote_watchlist_repo,
|
||||||
remote_goal_repo: Arc::clone(&ctx.repos.remote_goal),
|
remote_goal_repo: Arc::clone(&remote_goal),
|
||||||
local_ap_content: fed_ap_content,
|
local_ap_content: fed_ap_content,
|
||||||
user_repo: fed_user_repo,
|
user_repo: fed_user_repo,
|
||||||
base_url,
|
base_url,
|
||||||
allow_registration,
|
allow_registration,
|
||||||
event_publisher: Arc::clone(&ctx.services.event_publisher),
|
event_publisher: Arc::clone(&event_publisher),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -298,28 +250,26 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}) as Arc<dyn EventHandler>;
|
}) as Arc<dyn EventHandler>;
|
||||||
|
|
||||||
let search_cleanup = Arc::new(SearchCleanupHandler::new(
|
let search_cleanup = Arc::new(SearchCleanupHandler::new(
|
||||||
Arc::clone(&ctx.repos.search_command),
|
Arc::clone(&search_command),
|
||||||
Arc::clone(&ctx.repos.person_query),
|
Arc::clone(&person_query),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new(
|
let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new(
|
||||||
Arc::clone(&ctx.repos.movie),
|
Arc::clone(&movie),
|
||||||
Arc::clone(&ctx.repos.search_command),
|
Arc::clone(&search_command),
|
||||||
)) as Arc<dyn EventHandler>;
|
)) as Arc<dyn EventHandler>;
|
||||||
tracing::info!("federation event handler registered");
|
tracing::info!("federation event handler registered");
|
||||||
let wrapup_handler = Arc::new(
|
let wrapup_handler =
|
||||||
application::wrapup::event_handler::WrapUpEventHandler::new(
|
Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new(
|
||||||
Arc::clone(&ctx.repos.wrapup_repo),
|
Arc::clone(&wrapup_repo),
|
||||||
Arc::clone(&ctx.services.event_publisher),
|
Arc::clone(&event_publisher),
|
||||||
Arc::clone(&ctx.repos.wrapup_stats),
|
Arc::clone(&wrapup_stats),
|
||||||
),
|
)) as Arc<dyn EventHandler>;
|
||||||
) as Arc<dyn EventHandler>;
|
let reindex_handler = Arc::new(SearchReindexHandler::new(ReindexSearchDeps {
|
||||||
let reindex_handler =
|
movie: Arc::clone(&movie),
|
||||||
Arc::new(SearchReindexHandler::new(ReindexSearchDeps {
|
movie_profile: Arc::clone(&movie_profile),
|
||||||
movie: Arc::clone(&ctx.repos.movie),
|
search_command: Arc::clone(&search_command),
|
||||||
movie_profile: Arc::clone(&ctx.repos.movie_profile),
|
person_command: Arc::clone(&person_command),
|
||||||
search_command: Arc::clone(&ctx.repos.search_command),
|
person_query: Arc::clone(&person_query),
|
||||||
person_command: Arc::clone(&ctx.repos.person_command),
|
|
||||||
person_query: Arc::clone(&ctx.repos.person_query),
|
|
||||||
})) as Arc<dyn EventHandler>;
|
})) as Arc<dyn EventHandler>;
|
||||||
let mut h = vec![
|
let mut h = vec![
|
||||||
poster,
|
poster,
|
||||||
|
|||||||
Reference in New Issue
Block a user