refactor: split jobs.rs into per-context modules
This commit is contained in:
@@ -40,8 +40,8 @@ pub use import_profile::PostgresImportProfileRepository;
|
||||
pub use import_session::PostgresImportSessionRepository;
|
||||
pub use persons::{PostgresPersonAdapter, create_person_adapter};
|
||||
pub use profile::PostgresMovieProfileRepository;
|
||||
pub use refresh_sessions::PostgresRefreshSessionAdapter;
|
||||
pub use profile_fields::PostgresProfileFieldsRepository;
|
||||
pub use refresh_sessions::PostgresRefreshSessionAdapter;
|
||||
pub use users::PostgresUserRepository;
|
||||
pub use watch_event::{PostgresWatchEventRepository, PostgresWebhookTokenRepository};
|
||||
pub use watchlist::PostgresWatchlistRepository;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::DateTime;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::RefreshSession,
|
||||
ports::RefreshSessionRepository,
|
||||
errors::DomainError, models::RefreshSession, ports::RefreshSessionRepository,
|
||||
value_objects::UserId,
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -41,8 +41,8 @@ pub use import_profile::SqliteImportProfileRepository;
|
||||
pub use import_session::SqliteImportSessionRepository;
|
||||
pub use persons::{SqlitePersonAdapter, create_person_adapter};
|
||||
pub use profile::SqliteMovieProfileRepository;
|
||||
pub use refresh_sessions::SqliteRefreshSessionAdapter;
|
||||
pub use profile_fields::SqliteProfileFieldsRepository;
|
||||
pub use refresh_sessions::SqliteRefreshSessionAdapter;
|
||||
pub use users::SqliteUserRepository;
|
||||
pub use watch_event::{SqliteWatchEventRepository, SqliteWebhookTokenRepository};
|
||||
pub use watchlist::SqliteWatchlistRepository;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::DateTime;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::RefreshSession,
|
||||
ports::RefreshSessionRepository,
|
||||
errors::DomainError, models::RefreshSession, ports::RefreshSessionRepository,
|
||||
value_objects::UserId,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
39
crates/application/src/jobs/enrichment_staleness.rs
Normal file
39
crates/application/src/jobs/enrichment_staleness.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, events::DomainEvent, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct EnrichmentStalenessJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl EnrichmentStalenessJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for EnrichmentStalenessJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let stale = self.ctx.repos.movie_profile.list_stale().await?;
|
||||
if stale.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
tracing::info!("enrichment scan: {} stale movies", stale.len());
|
||||
for (movie_id, external_metadata_id) in stale {
|
||||
let event = DomainEvent::MovieEnrichmentRequested {
|
||||
movie_id,
|
||||
external_metadata_id,
|
||||
};
|
||||
self.ctx.services.event_publisher.publish(&event).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
29
crates/application/src/jobs/import_cleanup.rs
Normal file
29
crates/application/src/jobs/import_cleanup.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct ImportSessionCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl ImportSessionCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for ImportSessionCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = crate::import::cleanup::execute(&self.ctx).await?;
|
||||
tracing::info!("import session cleanup: removed {} expired sessions", n);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
crates/application/src/jobs/mod.rs
Normal file
11
crates/application/src/jobs/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod enrichment_staleness;
|
||||
mod import_cleanup;
|
||||
mod refresh_session_cleanup;
|
||||
mod watch_event_cleanup;
|
||||
mod wrapup;
|
||||
|
||||
pub use enrichment_staleness::EnrichmentStalenessJob;
|
||||
pub use import_cleanup::ImportSessionCleanupJob;
|
||||
pub use refresh_session_cleanup::RefreshSessionCleanupJob;
|
||||
pub use watch_event_cleanup::WatchEventCleanupJob;
|
||||
pub use wrapup::{WrapUpAutoGenerateJob, WrapUpCleanupJob};
|
||||
31
crates/application/src/jobs/refresh_session_cleanup.rs
Normal file
31
crates/application/src/jobs/refresh_session_cleanup.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct RefreshSessionCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl RefreshSessionCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for RefreshSessionCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(86400)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = self.ctx.repos.refresh_session.delete_expired().await?;
|
||||
if n > 0 {
|
||||
tracing::info!("refresh session cleanup: removed {n} expired sessions");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
31
crates/application/src/jobs/watch_event_cleanup.rs
Normal file
31
crates/application/src/jobs/watch_event_cleanup.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct WatchEventCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl WatchEventCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for WatchEventCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(86400)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = crate::integrations::cleanup::execute(&self.ctx).await?;
|
||||
if n > 0 {
|
||||
tracing::info!("watch event cleanup: removed {n} old entries");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,91 +2,10 @@ use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Datelike;
|
||||
use domain::{errors::DomainError, events::DomainEvent, ports::PeriodicJob};
|
||||
use domain::{errors::DomainError, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct ImportSessionCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl ImportSessionCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for ImportSessionCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = crate::import::cleanup::execute(&self.ctx).await?;
|
||||
tracing::info!("import session cleanup: removed {} expired sessions", n);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WatchEventCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl WatchEventCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for WatchEventCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(86400)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = crate::integrations::cleanup::execute(&self.ctx).await?;
|
||||
if n > 0 {
|
||||
tracing::info!("watch event cleanup: removed {n} old entries");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnrichmentStalenessJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl EnrichmentStalenessJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for EnrichmentStalenessJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let stale = self.ctx.repos.movie_profile.list_stale().await?;
|
||||
if stale.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
tracing::info!("enrichment scan: {} stale movies", stale.len());
|
||||
for (movie_id, external_metadata_id) in stale {
|
||||
let event = DomainEvent::MovieEnrichmentRequested {
|
||||
movie_id,
|
||||
external_metadata_id,
|
||||
};
|
||||
self.ctx.services.event_publisher.publish(&event).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WrapUpAutoGenerateJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
@@ -105,7 +24,6 @@ impl PeriodicJob for WrapUpAutoGenerateJob {
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
// Only run in January
|
||||
if now.month() != 1 {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -140,7 +58,6 @@ impl PeriodicJob for WrapUpAutoGenerateJob {
|
||||
}
|
||||
}
|
||||
|
||||
// Global wrap-up
|
||||
let existing = self
|
||||
.ctx
|
||||
.repos
|
||||
@@ -162,31 +79,6 @@ impl PeriodicJob for WrapUpAutoGenerateJob {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefreshSessionCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl RefreshSessionCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for RefreshSessionCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(86400)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = self.ctx.repos.refresh_session.delete_expired().await?;
|
||||
if n > 0 {
|
||||
tracing::info!("refresh session cleanup: removed {n} expired sessions");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WrapUpCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
@@ -11,8 +11,8 @@ use domain::{
|
||||
MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand,
|
||||
PersonQuery, PosterFetcherClient, RefreshSessionRepository, ReviewRepository,
|
||||
SearchCommand, SearchPort, StatsRepository, UserProfileFieldsRepository, UserRepository,
|
||||
UserSettingsRepository, WatchEventRepository, WatchlistRepository,
|
||||
WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
|
||||
UserSettingsRepository, WatchEventRepository, WatchlistRepository, WebhookTokenRepository,
|
||||
WrapUpRepository, WrapUpStatsQuery,
|
||||
},
|
||||
testing::{
|
||||
FakeAuthService, FakeDiaryRepository, FakeDocumentParser, FakeMetadataClient,
|
||||
@@ -21,8 +21,8 @@ use domain::{
|
||||
InMemoryMovieProfileRepository, InMemoryMovieRepository, InMemoryProfileFieldsRepo,
|
||||
InMemoryRefreshSessionRepository, InMemoryReviewRepository, InMemoryUserRepository,
|
||||
InMemoryUserSettingsRepository, InMemoryWatchEventRepository, InMemoryWatchlistRepository,
|
||||
InMemoryWebhookTokenRepository, NoopEventPublisher, NoopObjectStorage,
|
||||
PanicDiaryExporter, PanicPersonCommand,
|
||||
InMemoryWebhookTokenRepository, NoopEventPublisher, NoopObjectStorage, PanicDiaryExporter,
|
||||
PanicPersonCommand,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -44,10 +44,10 @@ pub use import::{
|
||||
};
|
||||
pub use import_profile::ImportProfile;
|
||||
pub use import_session::ImportSession;
|
||||
pub use refresh_session::RefreshSession;
|
||||
pub use person::{
|
||||
CastCredit, CrewCredit, ExternalPersonId, Person, PersonCredits, PersonEnrichmentData, PersonId,
|
||||
};
|
||||
pub use refresh_session::RefreshSession;
|
||||
pub use search::{
|
||||
EntityType, IndexableDocument, MovieSearchHit, PersonSearchHit, SearchFilters, SearchQuery,
|
||||
SearchResults,
|
||||
|
||||
@@ -8,9 +8,9 @@ use crate::{
|
||||
models::wrapup::WrapUpReport,
|
||||
models::{
|
||||
AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId,
|
||||
FeedEntry, FieldMapping, FileFormat, Goal, ImportError, ImportProfile, ImportSession, RefreshSession,
|
||||
FeedEntry, FieldMapping, FileFormat, Goal, ImportError, ImportProfile, ImportSession,
|
||||
IndexableDocument, Movie, MovieFilter, MovieProfile, MovieStats, MovieSummary, ParsedFile,
|
||||
ParsedPlaybackEvent, Person, PersonCredits, PersonEnrichmentData, PersonId,
|
||||
ParsedPlaybackEvent, Person, PersonCredits, PersonEnrichmentData, PersonId, RefreshSession,
|
||||
RemoteGoalEntry, RemoteWatchlistEntry, Review, ReviewHistory, SearchQuery, SearchResults,
|
||||
User, UserSettings, UserStats, UserSummary, UserTrends, WatchEvent, WatchEventStatus,
|
||||
WatchlistEntry, WatchlistWithMovie, WebhookToken,
|
||||
|
||||
@@ -818,10 +818,7 @@ impl RefreshSessionRepository for InMemoryRefreshSessionRepository {
|
||||
}
|
||||
|
||||
async fn revoke_all_for_user(&self, user_id: &UserId) -> Result<(), DomainError> {
|
||||
self.store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|s| s.user_id != *user_id);
|
||||
self.store.lock().unwrap().retain(|s| s.user_id != *user_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
goal: w.goal,
|
||||
user_settings: w.user_settings,
|
||||
remote_goal: w.remote_goal,
|
||||
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(w.pool.clone())) as _,
|
||||
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(
|
||||
w.pool.clone(),
|
||||
)) as _,
|
||||
db_pool: DbPool::Postgres(w.pool),
|
||||
})
|
||||
}
|
||||
@@ -118,7 +120,8 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
goal: w.goal,
|
||||
user_settings: w.user_settings,
|
||||
remote_goal: w.remote_goal,
|
||||
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone())) as _,
|
||||
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone()))
|
||||
as _,
|
||||
db_pool: DbPool::Sqlite(w.pool),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ use crate::{
|
||||
render::render_page,
|
||||
state::AppState,
|
||||
};
|
||||
use api_types::{LoginRequest, LoginResponse, LogoutRequest, RefreshRequest, RefreshResponse, RegisterRequest};
|
||||
use api_types::{
|
||||
LoginRequest, LoginResponse, LogoutRequest, RefreshRequest, RefreshResponse, RegisterRequest,
|
||||
};
|
||||
use application::ports::HtmlPageContext;
|
||||
use template_askama::{LoginTemplate, RegisterTemplate};
|
||||
|
||||
|
||||
@@ -87,7 +87,9 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
|
||||
goal: w.goal,
|
||||
user_settings: w.user_settings,
|
||||
remote_goal: w.remote_goal,
|
||||
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(w.pool.clone())) as _,
|
||||
refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new(
|
||||
w.pool.clone(),
|
||||
)) as _,
|
||||
db_pool: DbPool::Postgres(w.pool),
|
||||
})
|
||||
}
|
||||
@@ -130,7 +132,8 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
|
||||
goal: w.goal,
|
||||
user_settings: w.user_settings,
|
||||
remote_goal: w.remote_goal,
|
||||
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone())) as _,
|
||||
refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone()))
|
||||
as _,
|
||||
db_pool: DbPool::Sqlite(w.pool),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,7 +178,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::new(application::jobs::WatchEventCleanupJob::new(ctx.clone())),
|
||||
Arc::new(application::jobs::WrapUpAutoGenerateJob::new(ctx.clone())),
|
||||
Arc::new(application::jobs::WrapUpCleanupJob::new(ctx.clone())),
|
||||
Arc::new(application::jobs::RefreshSessionCleanupJob::new(ctx.clone())),
|
||||
Arc::new(application::jobs::RefreshSessionCleanupJob::new(
|
||||
ctx.clone(),
|
||||
)),
|
||||
];
|
||||
if let Some(job) = enrichment_job {
|
||||
periodic_jobs.push(job);
|
||||
|
||||
Reference in New Issue
Block a user