refactor(integrations): IngestWatchEventDeps, scoped Arc deps, WatchEventCleanupJob
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use domain::errors::DomainError;
|
use domain::{errors::DomainError, ports::WatchEventRepository};
|
||||||
|
|
||||||
use crate::context::AppContext;
|
pub async fn execute(watch_event: Arc<dyn WatchEventRepository>) -> Result<u64, DomainError> {
|
||||||
|
|
||||||
pub async fn execute(ctx: &AppContext) -> Result<u64, DomainError> {
|
|
||||||
let cutoff = chrono::Utc::now().naive_utc() - Duration::days(30);
|
let cutoff = chrono::Utc::now().naive_utc() - Duration::days(30);
|
||||||
ctx.repos
|
watch_event.delete_non_pending_older_than(cutoff).await
|
||||||
.watch_event
|
|
||||||
.delete_non_pending_older_than(cutoff)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,24 +1,29 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
models::WatchEventStatus,
|
models::WatchEventStatus,
|
||||||
|
ports::WatchEventRepository,
|
||||||
value_objects::{UserId, WatchEventId},
|
value_objects::{UserId, WatchEventId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::AppContext,
|
|
||||||
diary::commands::{LogReviewCommand, MovieInput},
|
diary::commands::{LogReviewCommand, MovieInput},
|
||||||
integrations::commands::ConfirmWatchEventsCommand,
|
integrations::commands::ConfirmWatchEventsCommand,
|
||||||
|
ports::ReviewLogger,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn execute(ctx: &AppContext, cmd: ConfirmWatchEventsCommand) -> Result<u32, DomainError> {
|
pub async fn execute(
|
||||||
|
watch_event: Arc<dyn WatchEventRepository>,
|
||||||
|
review_logger: Arc<dyn ReviewLogger>,
|
||||||
|
cmd: ConfirmWatchEventsCommand,
|
||||||
|
) -> Result<u32, DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
let mut confirmed = 0u32;
|
let mut confirmed = 0u32;
|
||||||
|
|
||||||
for c in cmd.confirmations {
|
for c in cmd.confirmations {
|
||||||
let event_id = WatchEventId::from_uuid(c.watch_event_id);
|
let event_id = WatchEventId::from_uuid(c.watch_event_id);
|
||||||
let event = ctx
|
let event = watch_event
|
||||||
.repos
|
|
||||||
.watch_event
|
|
||||||
.get_by_id(&event_id)
|
.get_by_id(&event_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| DomainError::NotFound(format!("WatchEvent {}", c.watch_event_id)))?;
|
.ok_or_else(|| DomainError::NotFound(format!("WatchEvent {}", c.watch_event_id)))?;
|
||||||
@@ -53,10 +58,9 @@ pub async fn execute(ctx: &AppContext, cmd: ConfirmWatchEventsCommand) -> Result
|
|||||||
watched_at: *event.watched_at(),
|
watched_at: *event.watched_at(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.services.review_logger.log_review(review_cmd).await?;
|
review_logger.log_review(review_cmd).await?;
|
||||||
|
|
||||||
ctx.repos
|
watch_event
|
||||||
.watch_event
|
|
||||||
.update_status(&event_id, WatchEventStatus::Confirmed)
|
.update_status(&event_id, WatchEventStatus::Confirmed)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
9
crates/application/src/integrations/deps.rs
Normal file
9
crates/application/src/integrations/deps.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
|
||||||
|
|
||||||
|
pub struct IngestWatchEventDeps {
|
||||||
|
pub webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||||
|
pub watch_event: Arc<dyn WatchEventRepository>,
|
||||||
|
pub event_publisher: Arc<dyn EventPublisher>,
|
||||||
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
models::WatchEventStatus,
|
models::WatchEventStatus,
|
||||||
|
ports::WatchEventRepository,
|
||||||
value_objects::{UserId, WatchEventId},
|
value_objects::{UserId, WatchEventId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::commands::DismissWatchEventsCommand};
|
use crate::integrations::commands::DismissWatchEventsCommand;
|
||||||
|
|
||||||
pub async fn execute(ctx: &AppContext, cmd: DismissWatchEventsCommand) -> Result<u32, DomainError> {
|
pub async fn execute(
|
||||||
|
watch_event: Arc<dyn WatchEventRepository>,
|
||||||
|
cmd: DismissWatchEventsCommand,
|
||||||
|
) -> Result<u32, DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
if cmd.event_ids.is_empty() {
|
if cmd.event_ids.is_empty() {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
@@ -18,7 +24,7 @@ pub async fn execute(ctx: &AppContext, cmd: DismissWatchEventsCommand) -> Result
|
|||||||
.map(|id| WatchEventId::from_uuid(*id))
|
.map(|id| WatchEventId::from_uuid(*id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let events = ctx.repos.watch_event.get_by_ids(&ids).await?;
|
let events = watch_event.get_by_ids(&ids).await?;
|
||||||
|
|
||||||
if events.len() != ids.len() {
|
if events.len() != ids.len() {
|
||||||
return Err(DomainError::NotFound(
|
return Err(DomainError::NotFound(
|
||||||
@@ -31,9 +37,7 @@ pub async fn execute(ctx: &AppContext, cmd: DismissWatchEventsCommand) -> Result
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = ctx
|
let count = watch_event
|
||||||
.repos
|
|
||||||
.watch_event
|
|
||||||
.update_status_batch(&ids, WatchEventStatus::Dismissed)
|
.update_status_batch(&ids, WatchEventStatus::Dismissed)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use domain::{errors::DomainError, models::WebhookToken, value_objects::UserId};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::commands::GenerateWebhookTokenCommand};
|
use crate::integrations::commands::GenerateWebhookTokenCommand;
|
||||||
|
|
||||||
pub struct GeneratedWebhookToken {
|
pub struct GeneratedWebhookToken {
|
||||||
pub token_plaintext: String,
|
pub token_plaintext: String,
|
||||||
@@ -9,7 +11,7 @@ pub struct GeneratedWebhookToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
ctx: &AppContext,
|
webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||||
cmd: GenerateWebhookTokenCommand,
|
cmd: GenerateWebhookTokenCommand,
|
||||||
) -> Result<GeneratedWebhookToken, DomainError> {
|
) -> Result<GeneratedWebhookToken, DomainError> {
|
||||||
let plaintext = generate_random_token();
|
let plaintext = generate_random_token();
|
||||||
@@ -18,7 +20,7 @@ pub async fn execute(
|
|||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
let token = WebhookToken::new(user_id, hash, cmd.provider, cmd.label);
|
let token = WebhookToken::new(user_id, hash, cmd.provider, cmd.label);
|
||||||
|
|
||||||
ctx.repos.webhook_token.save(&token).await?;
|
webhook_token.save(&token).await?;
|
||||||
|
|
||||||
Ok(GeneratedWebhookToken {
|
Ok(GeneratedWebhookToken {
|
||||||
token_plaintext: plaintext,
|
token_plaintext: plaintext,
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use domain::{errors::DomainError, models::WatchEvent, value_objects::UserId};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::queries::GetWatchQueueQuery};
|
use domain::{errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId};
|
||||||
|
|
||||||
|
use crate::integrations::queries::GetWatchQueueQuery;
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
ctx: &AppContext,
|
watch_event: Arc<dyn WatchEventRepository>,
|
||||||
query: GetWatchQueueQuery,
|
query: GetWatchQueueQuery,
|
||||||
) -> Result<Vec<WatchEvent>, DomainError> {
|
) -> Result<Vec<WatchEvent>, DomainError> {
|
||||||
let user_id = UserId::from_uuid(query.user_id);
|
let user_id = UserId::from_uuid(query.user_id);
|
||||||
ctx.repos.watch_event.list_pending(&user_id).await
|
watch_event.list_pending(&user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use domain::{errors::DomainError, models::WebhookToken, value_objects::UserId};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::queries::GetWebhookTokensQuery};
|
use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId};
|
||||||
|
|
||||||
|
use crate::integrations::queries::GetWebhookTokensQuery;
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
ctx: &AppContext,
|
webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||||
query: GetWebhookTokensQuery,
|
query: GetWebhookTokensQuery,
|
||||||
) -> Result<Vec<WebhookToken>, DomainError> {
|
) -> Result<Vec<WebhookToken>, DomainError> {
|
||||||
let user_id = UserId::from_uuid(query.user_id);
|
let user_id = UserId::from_uuid(query.user_id);
|
||||||
ctx.repos.webhook_token.list_by_user(&user_id).await
|
webhook_token.list_by_user(&user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -3,23 +3,21 @@ use domain::{
|
|||||||
errors::DomainError, events::DomainEvent, models::WatchEvent, ports::MediaServerParser,
|
errors::DomainError, events::DomainEvent, models::WatchEvent, ports::MediaServerParser,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::commands::IngestWatchEventCommand};
|
use crate::integrations::{commands::IngestWatchEventCommand, deps::IngestWatchEventDeps};
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
ctx: &AppContext,
|
deps: &IngestWatchEventDeps,
|
||||||
cmd: IngestWatchEventCommand,
|
cmd: IngestWatchEventCommand,
|
||||||
parser: &dyn MediaServerParser,
|
parser: &dyn MediaServerParser,
|
||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
let token_hash = super::generate_token::hash_token(&cmd.token);
|
let token_hash = super::generate_token::hash_token(&cmd.token);
|
||||||
let webhook_token = ctx
|
let webhook_token = deps
|
||||||
.repos
|
|
||||||
.webhook_token
|
.webhook_token
|
||||||
.find_by_token_hash(&token_hash)
|
.find_by_token_hash(&token_hash)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| DomainError::Unauthorized("invalid webhook token".into()))?;
|
.ok_or_else(|| DomainError::Unauthorized("invalid webhook token".into()))?;
|
||||||
|
|
||||||
let _ = ctx
|
let _ = deps
|
||||||
.repos
|
|
||||||
.webhook_token
|
.webhook_token
|
||||||
.touch_last_used(webhook_token.id())
|
.touch_last_used(webhook_token.id())
|
||||||
.await;
|
.await;
|
||||||
@@ -34,8 +32,7 @@ pub async fn execute(
|
|||||||
|
|
||||||
if let Some(ref ext_id) = external_metadata_id {
|
if let Some(ref ext_id) = external_metadata_id {
|
||||||
let one_hour_ago = chrono::Utc::now().naive_utc() - Duration::hours(1);
|
let one_hour_ago = chrono::Utc::now().naive_utc() - Duration::hours(1);
|
||||||
if ctx
|
if deps
|
||||||
.repos
|
|
||||||
.watch_event
|
.watch_event
|
||||||
.find_duplicate(&user_id, ext_id, one_hour_ago)
|
.find_duplicate(&user_id, ext_id, one_hour_ago)
|
||||||
.await?
|
.await?
|
||||||
@@ -55,10 +52,9 @@ pub async fn execute(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.repos.watch_event.save(&event).await?;
|
deps.watch_event.save(&event).await?;
|
||||||
|
|
||||||
let _ = ctx
|
let _ = deps
|
||||||
.services
|
|
||||||
.event_publisher
|
.event_publisher
|
||||||
.publish(&DomainEvent::WatchEventIngested {
|
.publish(&DomainEvent::WatchEventIngested {
|
||||||
user_id: event.user_id().clone(),
|
user_id: event.user_id().clone(),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod cleanup;
|
pub mod cleanup;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod confirm;
|
pub mod confirm;
|
||||||
|
pub mod deps;
|
||||||
pub mod dismiss;
|
pub mod dismiss;
|
||||||
pub mod generate_token;
|
pub mod generate_token;
|
||||||
pub mod get_queue;
|
pub mod get_queue;
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
|
ports::WebhookTokenRepository,
|
||||||
value_objects::{UserId, WebhookTokenId},
|
value_objects::{UserId, WebhookTokenId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{context::AppContext, integrations::commands::RevokeWebhookTokenCommand};
|
use crate::integrations::commands::RevokeWebhookTokenCommand;
|
||||||
|
|
||||||
pub async fn execute(ctx: &AppContext, cmd: RevokeWebhookTokenCommand) -> Result<(), DomainError> {
|
pub async fn execute(
|
||||||
|
webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||||
|
cmd: RevokeWebhookTokenCommand,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
let user_id = UserId::from_uuid(cmd.user_id);
|
let user_id = UserId::from_uuid(cmd.user_id);
|
||||||
let token_id = WebhookTokenId::from_uuid(cmd.token_id);
|
let token_id = WebhookTokenId::from_uuid(cmd.token_id);
|
||||||
ctx.repos.webhook_token.delete(&token_id, &user_id).await
|
webhook_token.delete(&token_id, &user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use domain::ports::WatchEventRepository;
|
||||||
|
use domain::testing::InMemoryWatchEventRepository;
|
||||||
|
|
||||||
use crate::integrations::cleanup;
|
use crate::integrations::cleanup;
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn returns_zero_when_nothing_to_clean() {
|
async fn returns_zero_when_nothing_to_clean() {
|
||||||
let ctx = TestContextBuilder::new().build();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
|
|
||||||
let count = cleanup::execute(&ctx).await.unwrap();
|
let count = cleanup::execute(watch_events).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(count, 0);
|
assert_eq!(count, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::integrations::commands::{ConfirmWatchEventsCommand, WatchEventConfirmation};
|
use crate::integrations::commands::{ConfirmWatchEventsCommand, WatchEventConfirmation};
|
||||||
use crate::integrations::confirm;
|
use crate::integrations::confirm;
|
||||||
use crate::test_helpers::TestContextBuilder;
|
use crate::test_helpers::NoopReviewLogger;
|
||||||
|
|
||||||
|
fn noop_logger() -> Arc<dyn crate::ports::ReviewLogger> {
|
||||||
|
Arc::new(NoopReviewLogger)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_watch_event_via_review_logger() {
|
async fn confirms_watch_event_via_review_logger() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
|
|
||||||
let event = WatchEvent::new(
|
let event = WatchEvent::new(
|
||||||
@@ -28,13 +31,9 @@ async fn confirms_watch_event_via_review_logger() {
|
|||||||
let event_id = event.id().value();
|
let event_id = event.id().value();
|
||||||
watch_events.save(&event).await.unwrap();
|
watch_events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -52,10 +51,11 @@ async fn confirms_watch_event_via_review_logger() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn empty_confirmations_returns_zero() {
|
async fn empty_confirmations_returns_zero() {
|
||||||
let ctx = TestContextBuilder::new().build();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
confirmations: vec![],
|
confirmations: vec![],
|
||||||
@@ -69,8 +69,7 @@ async fn empty_confirmations_returns_zero() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
|
async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
|
|
||||||
let event = WatchEvent::new(
|
let event = WatchEvent::new(
|
||||||
@@ -85,13 +84,9 @@ async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
|
|||||||
let event_id = event.id().value();
|
let event_id = event.id().value();
|
||||||
watch_events.save(&event).await.unwrap();
|
watch_events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -109,7 +104,7 @@ async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn rejects_other_users_event() {
|
async fn rejects_other_users_event() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let owner = Uuid::new_v4();
|
let owner = Uuid::new_v4();
|
||||||
let intruder = Uuid::new_v4();
|
let intruder = Uuid::new_v4();
|
||||||
|
|
||||||
@@ -125,12 +120,9 @@ async fn rejects_other_users_event() {
|
|||||||
let event_id = event.id().value();
|
let event_id = event.id().value();
|
||||||
watch_events.save(&event).await.unwrap();
|
watch_events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: intruder,
|
user_id: intruder,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -147,10 +139,11 @@ async fn rejects_other_users_event() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn fails_when_event_not_found() {
|
async fn fails_when_event_not_found() {
|
||||||
let ctx = TestContextBuilder::new().build();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -167,7 +160,7 @@ async fn fails_when_event_not_found() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_event_with_movie_id() {
|
async fn confirms_event_with_movie_id() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
let events = NoopEventPublisher::new();
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
let movie_uuid = Uuid::new_v4();
|
let movie_uuid = Uuid::new_v4();
|
||||||
@@ -199,23 +192,18 @@ async fn confirms_event_with_movie_id() {
|
|||||||
// Build a real review logger
|
// Build a real review logger
|
||||||
let reviews = domain::testing::InMemoryReviewRepository::new();
|
let reviews = domain::testing::InMemoryReviewRepository::new();
|
||||||
let watchlist = domain::testing::InMemoryWatchlistRepository::new();
|
let watchlist = domain::testing::InMemoryWatchlistRepository::new();
|
||||||
let review_logger = std::sync::Arc::new(crate::diary::review_logger::DefaultReviewLogger::new(
|
let review_logger: Arc<dyn crate::ports::ReviewLogger> =
|
||||||
std::sync::Arc::clone(&movies) as _,
|
Arc::new(crate::diary::review_logger::DefaultReviewLogger::new(
|
||||||
std::sync::Arc::clone(&reviews) as _,
|
Arc::clone(&movies) as _,
|
||||||
std::sync::Arc::clone(&watchlist) as _,
|
Arc::clone(&reviews) as _,
|
||||||
std::sync::Arc::new(domain::testing::FakeMetadataClient) as _,
|
Arc::clone(&watchlist) as _,
|
||||||
std::sync::Arc::clone(&events) as _,
|
Arc::new(domain::testing::FakeMetadataClient) as _,
|
||||||
));
|
Arc::clone(&events) as _,
|
||||||
|
));
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(std::sync::Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(std::sync::Arc::clone(&events) as _)
|
|
||||||
.with_movies(std::sync::Arc::clone(&movies) as _)
|
|
||||||
.with_review_logger(review_logger as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
review_logger,
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -233,8 +221,7 @@ async fn confirms_event_with_movie_id() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
|
async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
|
|
||||||
let event = WatchEvent::new(
|
let event = WatchEvent::new(
|
||||||
@@ -249,13 +236,9 @@ async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
|
|||||||
let event_id = event.id().value();
|
let event_id = event.id().value();
|
||||||
watch_events.save(&event).await.unwrap();
|
watch_events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
@@ -273,8 +256,7 @@ async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_multiple_events() {
|
async fn confirms_multiple_events() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
|
|
||||||
let event1 = WatchEvent::new(
|
let event1 = WatchEvent::new(
|
||||||
@@ -302,13 +284,9 @@ async fn confirms_multiple_events() {
|
|||||||
watch_events.save(&event1).await.unwrap();
|
watch_events.save(&event1).await.unwrap();
|
||||||
watch_events.save(&event2).await.unwrap();
|
watch_events.save(&event2).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![
|
confirmations: vec![
|
||||||
@@ -333,14 +311,13 @@ async fn confirms_multiple_events() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn confirms_event_without_year() {
|
async fn confirms_event_without_year() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let events = NoopEventPublisher::new();
|
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
|
|
||||||
let event = WatchEvent::new(
|
let event = WatchEvent::new(
|
||||||
UserId::from_uuid(uid),
|
UserId::from_uuid(uid),
|
||||||
"No Year Movie".into(),
|
"No Year Movie".into(),
|
||||||
None, // no year
|
None,
|
||||||
None,
|
None,
|
||||||
WatchEventSource::Jellyfin,
|
WatchEventSource::Jellyfin,
|
||||||
chrono::Utc::now().naive_utc(),
|
chrono::Utc::now().naive_utc(),
|
||||||
@@ -349,13 +326,9 @@ async fn confirms_event_without_year() {
|
|||||||
let event_id = event.id().value();
|
let event_id = event.id().value();
|
||||||
watch_events.save(&event).await.unwrap();
|
watch_events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.with_event_publisher(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = confirm::execute(
|
let result = confirm::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
|
noop_logger(),
|
||||||
ConfirmWatchEventsCommand {
|
ConfirmWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
confirmations: vec![WatchEventConfirmation {
|
confirmations: vec![WatchEventConfirmation {
|
||||||
|
|||||||
@@ -7,17 +7,13 @@ use domain::value_objects::UserId;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::integrations::{commands::DismissWatchEventsCommand, dismiss};
|
use crate::integrations::{commands::DismissWatchEventsCommand, dismiss};
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn dismisses_empty_list_returns_zero() {
|
async fn dismisses_empty_list_returns_zero() {
|
||||||
let events = InMemoryWatchEventRepository::new();
|
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = dismiss::execute(
|
let result = dismiss::execute(
|
||||||
&ctx,
|
Arc::clone(&events),
|
||||||
DismissWatchEventsCommand {
|
DismissWatchEventsCommand {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
event_ids: vec![],
|
event_ids: vec![],
|
||||||
@@ -31,13 +27,10 @@ async fn dismisses_empty_list_returns_zero() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn fails_when_event_not_found() {
|
async fn fails_when_event_not_found() {
|
||||||
let events = InMemoryWatchEventRepository::new();
|
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = dismiss::execute(
|
let result = dismiss::execute(
|
||||||
&ctx,
|
Arc::clone(&events),
|
||||||
DismissWatchEventsCommand {
|
DismissWatchEventsCommand {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
event_ids: vec![Uuid::new_v4()],
|
event_ids: vec![Uuid::new_v4()],
|
||||||
@@ -50,7 +43,7 @@ async fn fails_when_event_not_found() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn dismisses_existing_events() {
|
async fn dismisses_existing_events() {
|
||||||
let watch_events = InMemoryWatchEventRepository::new();
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let uid = Uuid::new_v4();
|
let uid = Uuid::new_v4();
|
||||||
let user_id = UserId::from_uuid(uid);
|
let user_id = UserId::from_uuid(uid);
|
||||||
|
|
||||||
@@ -77,12 +70,8 @@ async fn dismisses_existing_events() {
|
|||||||
watch_events.save(&e1).await.unwrap();
|
watch_events.save(&e1).await.unwrap();
|
||||||
watch_events.save(&e2).await.unwrap();
|
watch_events.save(&e2).await.unwrap();
|
||||||
|
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&watch_events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = dismiss::execute(
|
let result = dismiss::execute(
|
||||||
&ctx,
|
Arc::clone(&watch_events),
|
||||||
DismissWatchEventsCommand {
|
DismissWatchEventsCommand {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
event_ids: vec![id1, id2],
|
event_ids: vec![id1, id2],
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
|
use domain::ports::WebhookTokenRepository;
|
||||||
use domain::testing::InMemoryWebhookTokenRepository;
|
use domain::testing::InMemoryWebhookTokenRepository;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::integrations::{commands::GenerateWebhookTokenCommand, generate_token};
|
use crate::integrations::{commands::GenerateWebhookTokenCommand, generate_token};
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn generates_token_and_saves() {
|
async fn generates_token_and_saves() {
|
||||||
let tokens = InMemoryWebhookTokenRepository::new();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_webhook_tokens(Arc::clone(&tokens) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
let result = generate_token::execute(
|
let result = generate_token::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GenerateWebhookTokenCommand {
|
GenerateWebhookTokenCommand {
|
||||||
user_id,
|
user_id,
|
||||||
provider: WatchEventSource::Jellyfin,
|
provider: WatchEventSource::Jellyfin,
|
||||||
@@ -28,9 +25,7 @@ async fn generates_token_and_saves() {
|
|||||||
|
|
||||||
assert!(!result.token_plaintext.is_empty());
|
assert!(!result.token_plaintext.is_empty());
|
||||||
|
|
||||||
let saved = ctx
|
let saved = tokens
|
||||||
.repos
|
|
||||||
.webhook_token
|
|
||||||
.list_by_user(&domain::value_objects::UserId::from_uuid(user_id))
|
.list_by_user(&domain::value_objects::UserId::from_uuid(user_id))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -8,17 +8,13 @@ use domain::value_objects::UserId;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::integrations::{get_queue, queries::GetWatchQueueQuery};
|
use crate::integrations::{get_queue, queries::GetWatchQueueQuery};
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn returns_empty_when_no_events() {
|
async fn returns_empty_when_no_events() {
|
||||||
let events = InMemoryWatchEventRepository::new();
|
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = get_queue::execute(
|
let result = get_queue::execute(
|
||||||
&ctx,
|
Arc::clone(&events),
|
||||||
GetWatchQueueQuery {
|
GetWatchQueueQuery {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
},
|
},
|
||||||
@@ -31,10 +27,7 @@ async fn returns_empty_when_no_events() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn returns_pending_events() {
|
async fn returns_pending_events() {
|
||||||
let events = InMemoryWatchEventRepository::new();
|
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_watch_events(Arc::clone(&events) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
let event = WatchEvent::new(
|
let event = WatchEvent::new(
|
||||||
@@ -48,7 +41,7 @@ async fn returns_pending_events() {
|
|||||||
);
|
);
|
||||||
events.save(&event).await.unwrap();
|
events.save(&event).await.unwrap();
|
||||||
|
|
||||||
let result = get_queue::execute(&ctx, GetWatchQueueQuery { user_id })
|
let result = get_queue::execute(Arc::clone(&events), GetWatchQueueQuery { user_id })
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
|
use domain::ports::WebhookTokenRepository;
|
||||||
use domain::testing::InMemoryWebhookTokenRepository;
|
use domain::testing::InMemoryWebhookTokenRepository;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -8,17 +9,13 @@ use crate::integrations::{
|
|||||||
commands::GenerateWebhookTokenCommand, generate_token, get_tokens,
|
commands::GenerateWebhookTokenCommand, generate_token, get_tokens,
|
||||||
queries::GetWebhookTokensQuery,
|
queries::GetWebhookTokensQuery,
|
||||||
};
|
};
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn returns_empty_when_no_tokens() {
|
async fn returns_empty_when_no_tokens() {
|
||||||
let tokens = InMemoryWebhookTokenRepository::new();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_webhook_tokens(Arc::clone(&tokens) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = get_tokens::execute(
|
let result = get_tokens::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GetWebhookTokensQuery {
|
GetWebhookTokensQuery {
|
||||||
user_id: Uuid::new_v4(),
|
user_id: Uuid::new_v4(),
|
||||||
},
|
},
|
||||||
@@ -31,15 +28,12 @@ async fn returns_empty_when_no_tokens() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn returns_tokens_after_generate() {
|
async fn returns_tokens_after_generate() {
|
||||||
let tokens = InMemoryWebhookTokenRepository::new();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_webhook_tokens(Arc::clone(&tokens) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
|
|
||||||
generate_token::execute(
|
generate_token::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GenerateWebhookTokenCommand {
|
GenerateWebhookTokenCommand {
|
||||||
user_id,
|
user_id,
|
||||||
provider: WatchEventSource::Jellyfin,
|
provider: WatchEventSource::Jellyfin,
|
||||||
@@ -50,7 +44,7 @@ async fn returns_tokens_after_generate() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
generate_token::execute(
|
generate_token::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GenerateWebhookTokenCommand {
|
GenerateWebhookTokenCommand {
|
||||||
user_id,
|
user_id,
|
||||||
provider: WatchEventSource::Plex,
|
provider: WatchEventSource::Plex,
|
||||||
@@ -60,7 +54,7 @@ async fn returns_tokens_after_generate() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let result = get_tokens::execute(&ctx, GetWebhookTokensQuery { user_id })
|
let result = get_tokens::execute(Arc::clone(&tokens), GetWebhookTokensQuery { user_id })
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
use domain::testing::InMemoryWebhookTokenRepository;
|
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
|
||||||
|
use domain::testing::{InMemoryWebhookTokenRepository, InMemoryWatchEventRepository, NoopEventPublisher};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::integrations::commands::GenerateWebhookTokenCommand;
|
use crate::integrations::commands::{GenerateWebhookTokenCommand, IngestWatchEventCommand};
|
||||||
use crate::integrations::{commands::IngestWatchEventCommand, generate_token, ingest};
|
use crate::integrations::deps::IngestWatchEventDeps;
|
||||||
use crate::test_helpers::TestContextBuilder;
|
use crate::integrations::{generate_token, ingest};
|
||||||
|
|
||||||
struct FakeParser;
|
struct FakeParser;
|
||||||
|
|
||||||
@@ -26,14 +27,13 @@ impl domain::ports::MediaServerParser for FakeParser {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ingests_watch_event() {
|
async fn ingests_watch_event() {
|
||||||
let tokens = InMemoryWebhookTokenRepository::new();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
.with_webhook_tokens(Arc::clone(&tokens) as _)
|
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
|
||||||
.build();
|
|
||||||
|
|
||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
let generated = generate_token::execute(
|
let generated = generate_token::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GenerateWebhookTokenCommand {
|
GenerateWebhookTokenCommand {
|
||||||
user_id,
|
user_id,
|
||||||
provider: WatchEventSource::Jellyfin,
|
provider: WatchEventSource::Jellyfin,
|
||||||
@@ -43,8 +43,14 @@ async fn ingests_watch_event() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let deps = IngestWatchEventDeps {
|
||||||
|
webhook_token: Arc::clone(&tokens),
|
||||||
|
watch_event: Arc::clone(&watch_events),
|
||||||
|
event_publisher: Arc::clone(&event_publisher),
|
||||||
|
};
|
||||||
|
|
||||||
let result = ingest::execute(
|
let result = ingest::execute(
|
||||||
&ctx,
|
&deps,
|
||||||
IngestWatchEventCommand {
|
IngestWatchEventCommand {
|
||||||
token: generated.token_plaintext,
|
token: generated.token_plaintext,
|
||||||
raw_payload: vec![],
|
raw_payload: vec![],
|
||||||
@@ -59,10 +65,18 @@ async fn ingests_watch_event() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn rejects_invalid_token() {
|
async fn rejects_invalid_token() {
|
||||||
let ctx = TestContextBuilder::new().build();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
|
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
|
||||||
|
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
|
||||||
|
|
||||||
|
let deps = IngestWatchEventDeps {
|
||||||
|
webhook_token: Arc::clone(&tokens),
|
||||||
|
watch_event: Arc::clone(&watch_events),
|
||||||
|
event_publisher: Arc::clone(&event_publisher),
|
||||||
|
};
|
||||||
|
|
||||||
let result = ingest::execute(
|
let result = ingest::execute(
|
||||||
&ctx,
|
&deps,
|
||||||
IngestWatchEventCommand {
|
IngestWatchEventCommand {
|
||||||
token: "bad-token".into(),
|
token: "bad-token".into(),
|
||||||
raw_payload: vec![],
|
raw_payload: vec![],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
|
use domain::ports::WebhookTokenRepository;
|
||||||
use domain::testing::InMemoryWebhookTokenRepository;
|
use domain::testing::InMemoryWebhookTokenRepository;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -10,19 +11,15 @@ use crate::integrations::{
|
|||||||
queries::GetWebhookTokensQuery,
|
queries::GetWebhookTokensQuery,
|
||||||
revoke_token,
|
revoke_token,
|
||||||
};
|
};
|
||||||
use crate::test_helpers::TestContextBuilder;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn revokes_existing_token() {
|
async fn revokes_existing_token() {
|
||||||
let tokens = InMemoryWebhookTokenRepository::new();
|
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
|
||||||
let ctx = TestContextBuilder::new()
|
|
||||||
.with_webhook_tokens(Arc::clone(&tokens) as _)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
|
|
||||||
let generated = generate_token::execute(
|
let generated = generate_token::execute(
|
||||||
&ctx,
|
Arc::clone(&tokens),
|
||||||
GenerateWebhookTokenCommand {
|
GenerateWebhookTokenCommand {
|
||||||
user_id,
|
user_id,
|
||||||
provider: WatchEventSource::Jellyfin,
|
provider: WatchEventSource::Jellyfin,
|
||||||
@@ -34,11 +31,14 @@ async fn revokes_existing_token() {
|
|||||||
|
|
||||||
let token_id = generated.token.id().value();
|
let token_id = generated.token.id().value();
|
||||||
|
|
||||||
revoke_token::execute(&ctx, RevokeWebhookTokenCommand { user_id, token_id })
|
revoke_token::execute(
|
||||||
.await
|
Arc::clone(&tokens),
|
||||||
.unwrap();
|
RevokeWebhookTokenCommand { user_id, token_id },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let remaining = get_tokens::execute(&ctx, GetWebhookTokensQuery { user_id })
|
let remaining = get_tokens::execute(Arc::clone(&tokens), GetWebhookTokensQuery { user_id })
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
|
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};
|
use domain::{errors::DomainError, ports::{PeriodicJob, WatchEventRepository}};
|
||||||
|
|
||||||
use crate::context::AppContext;
|
|
||||||
|
|
||||||
pub struct WatchEventCleanupJob {
|
pub struct WatchEventCleanupJob {
|
||||||
ctx: AppContext,
|
watch_event: Arc<dyn WatchEventRepository>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WatchEventCleanupJob {
|
impl WatchEventCleanupJob {
|
||||||
pub fn new(ctx: AppContext) -> Self {
|
pub fn new(watch_event: Arc<dyn WatchEventRepository>) -> Self {
|
||||||
Self { ctx }
|
Self { watch_event }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ impl PeriodicJob for WatchEventCleanupJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self) -> Result<(), DomainError> {
|
async fn run(&self) -> Result<(), DomainError> {
|
||||||
let n = crate::integrations::cleanup::execute(&self.ctx).await?;
|
let n = crate::integrations::cleanup::execute(self.watch_event.clone()).await?;
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
tracing::info!("watch event cleanup: removed {n} old entries");
|
tracing::info!("watch event cleanup: removed {n} old entries");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub async fn get_integrations_page(
|
|||||||
let query = GetWebhookTokensQuery {
|
let query = GetWebhookTokensQuery {
|
||||||
user_id: user_id.value(),
|
user_id: user_id.value(),
|
||||||
};
|
};
|
||||||
let tokens = get_webhook_tokens::execute(&state.app_ctx, query)
|
let tokens = get_webhook_tokens::execute(state.app_ctx.repos.webhook_token.clone(), query)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ pub async fn post_generate_token(
|
|||||||
label: form.label.filter(|l| !l.trim().is_empty()),
|
label: form.label.filter(|l| !l.trim().is_empty()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match generate_webhook_token::execute(&state.app_ctx, cmd).await {
|
match generate_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let encoded = percent_encoding::utf8_percent_encode(
|
let encoded = percent_encoding::utf8_percent_encode(
|
||||||
&result.token_plaintext,
|
&result.token_plaintext,
|
||||||
@@ -116,7 +116,7 @@ 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, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ pub async fn get_watch_queue_page(
|
|||||||
let query = GetWatchQueueQuery {
|
let query = GetWatchQueueQuery {
|
||||||
user_id: user_id.value(),
|
user_id: user_id.value(),
|
||||||
};
|
};
|
||||||
let events = get_watch_queue::execute(&state.app_ctx, query)
|
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event.clone(), query)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
@@ -175,7 +175,13 @@ pub async fn post_confirm_single(
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
match confirm_watch_events::execute(&state.app_ctx, cmd).await {
|
match confirm_watch_events::execute(
|
||||||
|
state.app_ctx.repos.watch_event.clone(),
|
||||||
|
state.app_ctx.services.review_logger.clone(),
|
||||||
|
cmd,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => Redirect::to("/watch-queue").into_response(),
|
Ok(_) => Redirect::to("/watch-queue").into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = encode_error(&e.to_string());
|
let msg = encode_error(&e.to_string());
|
||||||
@@ -200,7 +206,7 @@ pub async fn post_dismiss_single(
|
|||||||
event_ids: vec![event_id],
|
event_ids: vec![event_id],
|
||||||
};
|
};
|
||||||
|
|
||||||
match dismiss_watch_events::execute(&state.app_ctx, cmd).await {
|
match dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await {
|
||||||
Ok(_) => Redirect::to("/watch-queue").into_response(),
|
Ok(_) => Redirect::to("/watch-queue").into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = encode_error(&e.to_string());
|
let msg = encode_error(&e.to_string());
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ use application::integrations::{
|
|||||||
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
|
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
|
||||||
IngestWatchEventCommand, RevokeWebhookTokenCommand, WatchEventConfirmation,
|
IngestWatchEventCommand, RevokeWebhookTokenCommand, WatchEventConfirmation,
|
||||||
},
|
},
|
||||||
confirm as confirm_watch_events, dismiss as dismiss_watch_events,
|
confirm as confirm_watch_events, deps::IngestWatchEventDeps,
|
||||||
generate_token as generate_webhook_token, get_queue as get_watch_queue,
|
dismiss as dismiss_watch_events, generate_token as generate_webhook_token,
|
||||||
get_tokens as get_webhook_tokens, ingest as ingest_watch_event,
|
get_queue as get_watch_queue, get_tokens as get_webhook_tokens,
|
||||||
queries::{GetWatchQueueQuery, GetWebhookTokensQuery},
|
ingest as ingest_watch_event, queries::{GetWatchQueueQuery, GetWebhookTokensQuery},
|
||||||
revoke_token as revoke_webhook_token,
|
revoke_token as revoke_webhook_token,
|
||||||
};
|
};
|
||||||
use domain::models::WatchEventSource;
|
use domain::models::WatchEventSource;
|
||||||
@@ -126,7 +126,12 @@ async fn run_ingest(
|
|||||||
cmd: IngestWatchEventCommand,
|
cmd: IngestWatchEventCommand,
|
||||||
parser: &dyn domain::ports::MediaServerParser,
|
parser: &dyn domain::ports::MediaServerParser,
|
||||||
) -> StatusCode {
|
) -> StatusCode {
|
||||||
match ingest_watch_event::execute(&state.app_ctx, cmd, parser).await {
|
let deps = IngestWatchEventDeps {
|
||||||
|
webhook_token: state.app_ctx.repos.webhook_token.clone(),
|
||||||
|
watch_event: state.app_ctx.repos.watch_event.clone(),
|
||||||
|
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||||
|
};
|
||||||
|
match ingest_watch_event::execute(&deps, cmd, parser).await {
|
||||||
Ok(()) => StatusCode::OK,
|
Ok(()) => StatusCode::OK,
|
||||||
Err(e) => crate::errors::domain_error_status(&e),
|
Err(e) => crate::errors::domain_error_status(&e),
|
||||||
}
|
}
|
||||||
@@ -159,7 +164,7 @@ pub async fn post_generate_webhook_token(
|
|||||||
label: req.label,
|
label: req.label,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = generate_webhook_token::execute(&state.app_ctx, 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}");
|
||||||
@@ -186,7 +191,7 @@ 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, 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()
|
||||||
@@ -221,7 +226,7 @@ pub async fn delete_webhook_token(
|
|||||||
user_id: user.0.value(),
|
user_id: user.0.value(),
|
||||||
token_id: id,
|
token_id: id,
|
||||||
};
|
};
|
||||||
revoke_webhook_token::execute(&state.app_ctx, cmd).await?;
|
revoke_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +247,7 @@ pub async fn get_watch_queue(
|
|||||||
let query = GetWatchQueueQuery {
|
let query = GetWatchQueueQuery {
|
||||||
user_id: user.0.value(),
|
user_id: user.0.value(),
|
||||||
};
|
};
|
||||||
let events = get_watch_queue::execute(&state.app_ctx, query).await?;
|
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event.clone(), query).await?;
|
||||||
|
|
||||||
let dtos = events
|
let dtos = events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -287,7 +292,12 @@ pub async fn post_confirm_watch_events(
|
|||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let confirmed = confirm_watch_events::execute(&state.app_ctx, cmd).await?;
|
let confirmed = confirm_watch_events::execute(
|
||||||
|
state.app_ctx.repos.watch_event.clone(),
|
||||||
|
state.app_ctx.services.review_logger.clone(),
|
||||||
|
cmd,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(Json(ConfirmWatchResponse { confirmed }))
|
Ok(Json(ConfirmWatchResponse { confirmed }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +321,6 @@ 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, 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 }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
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(ctx.repos.import_session.clone())),
|
||||||
Arc::new(application::jobs::WatchEventCleanupJob::new(ctx.clone())),
|
Arc::new(application::jobs::WatchEventCleanupJob::new(ctx.repos.watch_event.clone())),
|
||||||
Arc::new(application::jobs::WrapUpAutoGenerateJob::new(ctx.clone())),
|
Arc::new(application::jobs::WrapUpAutoGenerateJob::new(ctx.clone())),
|
||||||
Arc::new(application::jobs::WrapUpCleanupJob::new(ctx.clone())),
|
Arc::new(application::jobs::WrapUpCleanupJob::new(ctx.clone())),
|
||||||
Arc::new(application::jobs::RefreshSessionCleanupJob::new(
|
Arc::new(application::jobs::RefreshSessionCleanupJob::new(
|
||||||
|
|||||||
Reference in New Issue
Block a user