feat: Jellyfin/Plex auto-import via watch queue
Some checks failed
CI / Check / Test (push) Failing after 6m5s

Webhook ingestion from media servers — movies land in a pending
watch queue, user rates and confirms to create diary entries.

- domain: WatchEvent, WebhookToken models, MediaServerParser port
- adapters: jellyfin + plex parser crates, SQLite + Postgres repos
- application: ingest/confirm/dismiss/cleanup use cases, token mgmt
- presentation: webhook endpoints (bearer + query param auth),
  watch queue + integrations settings HTML pages, OpenAPI docs
- worker: WatchEventCleanupJob (daily, 30d retention)

Movie resolution deferred to confirm — single canonical path
through log_review for enrichment, poster fetch, federation.
This commit is contained in:
2026-06-02 17:34:16 +02:00
parent 6bd728fd50
commit aadad3cfb0
65 changed files with 2946 additions and 38 deletions

View File

@@ -10,16 +10,16 @@ use domain::{
ImportProfileRepository, ImportSessionRepository, MetadataClient, MovieProfileRepository,
MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
ReviewRepository, SearchCommand, SearchPort, StatsRepository, UserProfileFieldsRepository,
UserRepository, WatchlistRepository,
UserRepository, WatchEventRepository, WatchlistRepository, WebhookTokenRepository,
},
testing::{
FakeAuthService, FakeDiaryRepository, FakeMetadataClient, FakePasswordHasher,
InMemoryMovieRepository, InMemoryReviewRepository, InMemoryUserRepository,
InMemoryWatchlistRepository, NoopEventPublisher, NoopImageStorage, PanicDiaryExporter,
PanicDiaryRepository, PanicDocumentParser, PanicImportProfileRepository,
PanicImportSessionRepository, PanicMovieProfileRepository, PanicPersonCommand,
PanicPersonQuery, PanicPosterFetcher, PanicProfileFieldsRepo, PanicSearchCommand,
PanicSearchPort, PanicStatsRepository,
FakeAuthService, FakeMetadataClient, FakePasswordHasher, InMemoryMovieRepository,
InMemoryReviewRepository, InMemoryUserRepository, InMemoryWatchlistRepository,
NoopEventPublisher, NoopImageStorage, PanicDiaryExporter, PanicDiaryRepository,
PanicDocumentParser, PanicImportProfileRepository, PanicImportSessionRepository,
PanicMovieProfileRepository, PanicPersonCommand, PanicPersonQuery, PanicPosterFetcher,
PanicProfileFieldsRepo, PanicSearchCommand, PanicSearchPort, PanicStatsRepository,
PanicWatchEventRepository, PanicWebhookTokenRepository,
},
};
@@ -43,6 +43,8 @@ pub struct TestContextBuilder {
pub import_profile_repo: Arc<dyn ImportProfileRepository>,
pub movie_profile_repo: Arc<dyn MovieProfileRepository>,
pub watchlist_repo: Arc<dyn WatchlistRepository>,
pub watch_event_repo: Arc<dyn WatchEventRepository>,
pub webhook_token_repo: Arc<dyn WebhookTokenRepository>,
pub profile_fields_repo: Arc<dyn UserProfileFieldsRepository>,
pub person_command: Arc<dyn PersonCommand>,
pub person_query: Arc<dyn PersonQuery>,
@@ -71,6 +73,8 @@ impl TestContextBuilder {
import_profile_repo: Arc::new(PanicImportProfileRepository),
movie_profile_repo: Arc::new(PanicMovieProfileRepository),
watchlist_repo: InMemoryWatchlistRepository::new(),
watch_event_repo: Arc::new(PanicWatchEventRepository),
webhook_token_repo: Arc::new(PanicWebhookTokenRepository),
profile_fields_repo: Arc::new(PanicProfileFieldsRepo),
person_command: Arc::new(PanicPersonCommand),
person_query: Arc::new(PanicPersonQuery),
@@ -138,6 +142,8 @@ impl TestContextBuilder {
import_profile_repository: self.import_profile_repo,
movie_profile_repository: self.movie_profile_repo,
watchlist_repository: self.watchlist_repo,
watch_event_repository: self.watch_event_repo,
webhook_token_repository: self.webhook_token_repo,
profile_fields_repository: self.profile_fields_repo,
person_command: self.person_command,
person_query: self.person_query,