diff --git a/crates/application/src/context.rs b/crates/application/src/context.rs index ab398f0..ee71715 100644 --- a/crates/application/src/context.rs +++ b/crates/application/src/context.rs @@ -4,10 +4,11 @@ use domain::ports::{ AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, GoalRepository, ImportProfileRepository, ImportSessionRepository, MetadataClient, MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand, PersonEnrichmentClient, - PersonQuery, PosterFetcherClient, RemoteGoalRepository, RemoteWatchlistRepository, - ReviewRepository, SearchCommand, SearchPort, SocialQueryPort, StatsRepository, - UserProfileFieldsRepository, UserRepository, UserSettingsRepository, WatchEventRepository, - WatchlistRepository, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery, + PersonQuery, PosterFetcherClient, RefreshSessionRepository, RemoteGoalRepository, + RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort, + StatsRepository, UserProfileFieldsRepository, UserRepository, UserSettingsRepository, + WatchEventRepository, WatchlistRepository, WebhookTokenRepository, WrapUpRepository, + WrapUpStatsQuery, }; use crate::config::AppConfig; @@ -38,6 +39,7 @@ pub struct Repositories { pub goal: Arc, pub user_settings: Arc, pub remote_goal: Arc, + pub refresh_session: Arc, } #[derive(Clone)] diff --git a/crates/application/src/test_helpers.rs b/crates/application/src/test_helpers.rs index 01c318e..5fc69cb 100644 --- a/crates/application/src/test_helpers.rs +++ b/crates/application/src/test_helpers.rs @@ -9,10 +9,10 @@ use domain::{ AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, GoalRepository, ImportProfileRepository, ImportSessionRepository, MetadataClient, MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand, - PersonQuery, PosterFetcherClient, ReviewRepository, SearchCommand, SearchPort, - StatsRepository, UserProfileFieldsRepository, UserRepository, UserSettingsRepository, - WatchEventRepository, WatchlistRepository, WebhookTokenRepository, WrapUpRepository, - WrapUpStatsQuery, + PersonQuery, PosterFetcherClient, RefreshSessionRepository, ReviewRepository, + SearchCommand, SearchPort, StatsRepository, UserProfileFieldsRepository, UserRepository, + UserSettingsRepository, WatchEventRepository, WatchlistRepository, + WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery, }, testing::{ FakeAuthService, FakeDiaryRepository, FakeDocumentParser, FakeMetadataClient, @@ -22,6 +22,7 @@ use domain::{ InMemoryReviewRepository, InMemoryUserRepository, InMemoryUserSettingsRepository, InMemoryWatchEventRepository, InMemoryWatchlistRepository, InMemoryWebhookTokenRepository, NoopEventPublisher, NoopObjectStorage, PanicDiaryExporter, PanicPersonCommand, + PanicRefreshSessionRepository, }, }; @@ -75,6 +76,7 @@ pub struct TestContextBuilder { pub user_settings_repo: Arc, pub review_logger: Arc, pub social_query: Arc, + pub refresh_session_repo: Arc, pub config: AppConfig, } @@ -117,6 +119,7 @@ impl TestContextBuilder { user_settings_repo: InMemoryUserSettingsRepository::new(), review_logger: Arc::new(NoopReviewLogger), social_query: Arc::new(NoopSocialQueryPort), + refresh_session_repo: Arc::new(PanicRefreshSessionRepository), config: AppConfig { allow_registration: true, base_url: "http://localhost:3000".into(), @@ -286,6 +289,7 @@ impl TestContextBuilder { 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, diff --git a/crates/domain/src/models/mod.rs b/crates/domain/src/models/mod.rs index e7c9cd0..621eb72 100644 --- a/crates/domain/src/models/mod.rs +++ b/crates/domain/src/models/mod.rs @@ -1,6 +1,7 @@ mod enrichment; mod feed; mod movie; +mod refresh_session; mod review; mod stats; mod user; @@ -43,6 +44,7 @@ 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, }; diff --git a/crates/domain/src/models/refresh_session.rs b/crates/domain/src/models/refresh_session.rs new file mode 100644 index 0000000..9d503a0 --- /dev/null +++ b/crates/domain/src/models/refresh_session.rs @@ -0,0 +1,13 @@ +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +use crate::value_objects::UserId; + +#[derive(Clone, Debug)] +pub struct RefreshSession { + pub id: Uuid, + pub user_id: UserId, + pub token: String, + pub expires_at: DateTime, + pub created_at: DateTime, +} diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index c5c85b1..78ef750 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -8,7 +8,7 @@ use crate::{ models::wrapup::WrapUpReport, models::{ AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId, - FeedEntry, FieldMapping, FileFormat, Goal, ImportError, ImportProfile, ImportSession, + FeedEntry, FieldMapping, FileFormat, Goal, ImportError, ImportProfile, ImportSession, RefreshSession, IndexableDocument, Movie, MovieFilter, MovieProfile, MovieStats, MovieSummary, ParsedFile, ParsedPlaybackEvent, Person, PersonCredits, PersonEnrichmentData, PersonId, RemoteGoalEntry, RemoteWatchlistEntry, Review, ReviewHistory, SearchQuery, SearchResults, @@ -311,6 +311,15 @@ pub trait ImportSessionRepository: Send + Sync { async fn delete_expired_for_user(&self, user_id: &UserId) -> Result<(), DomainError>; } +#[async_trait] +pub trait RefreshSessionRepository: Send + Sync { + async fn create(&self, session: &RefreshSession) -> Result<(), DomainError>; + async fn get_by_token(&self, token: &str) -> Result, DomainError>; + async fn revoke(&self, token: &str) -> Result<(), DomainError>; + async fn revoke_all_for_user(&self, user_id: &UserId) -> Result<(), DomainError>; + async fn delete_expired(&self) -> Result; +} + #[async_trait] pub trait ImportProfileRepository: Send + Sync { async fn save(&self, profile: &ImportProfile) -> Result<(), DomainError>; diff --git a/crates/domain/src/testing/panics.rs b/crates/domain/src/testing/panics.rs index e5ba08c..5484f63 100644 --- a/crates/domain/src/testing/panics.rs +++ b/crates/domain/src/testing/panics.rs @@ -6,15 +6,15 @@ use crate::{ AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId, FeedEntry, FieldMapping, FileFormat, ImportError, ImportProfile, ImportSession, IndexableDocument, MovieProfile, MovieStats, ParsedFile, Person, PersonCredits, - PersonEnrichmentData, PersonId, ReviewHistory, SearchQuery, SearchResults, UserStats, - UserTrends, + PersonEnrichmentData, PersonId, RefreshSession, ReviewHistory, SearchQuery, SearchResults, + UserStats, UserTrends, collections::{PageParams, Paginated}, }, ports::{ DiaryExporter, DiaryRepository, DocumentParser, FeedSortBy, FollowingFilter, ImportProfileRepository, ImportSessionRepository, MovieProfileRepository, PersonCommand, - PersonQuery, PosterFetcherClient, SearchCommand, SearchPort, StatsRepository, - UserProfileFieldsRepository, + PersonQuery, PosterFetcherClient, RefreshSessionRepository, SearchCommand, SearchPort, + StatsRepository, UserProfileFieldsRepository, }, value_objects::{ImportProfileId, ImportSessionId, MovieId, PosterUrl, UserId}, }; @@ -104,6 +104,27 @@ impl ImportSessionRepository for PanicImportSessionRepository { } } +pub struct PanicRefreshSessionRepository; + +#[async_trait] +impl RefreshSessionRepository for PanicRefreshSessionRepository { + async fn create(&self, _: &RefreshSession) -> Result<(), DomainError> { + panic!("PanicRefreshSessionRepository called") + } + async fn get_by_token(&self, _: &str) -> Result, DomainError> { + panic!("PanicRefreshSessionRepository called") + } + async fn revoke(&self, _: &str) -> Result<(), DomainError> { + panic!("PanicRefreshSessionRepository called") + } + async fn revoke_all_for_user(&self, _: &UserId) -> Result<(), DomainError> { + panic!("PanicRefreshSessionRepository called") + } + async fn delete_expired(&self) -> Result { + panic!("PanicRefreshSessionRepository called") + } +} + pub struct PanicImportProfileRepository; #[async_trait] diff --git a/crates/presentation/src/factory.rs b/crates/presentation/src/factory.rs index 7e2e900..96ab018 100644 --- a/crates/presentation/src/factory.rs +++ b/crates/presentation/src/factory.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use anyhow::Context; use domain::ports::{ AuthService, LocalApContentQuery, MetadataClient, ObjectStorage, PasswordHasher, - PosterFetcherClient, UserProfileFieldsRepository, WatchEventRepository, WebhookTokenRepository, + PosterFetcherClient, RefreshSessionRepository, UserProfileFieldsRepository, + WatchEventRepository, WebhookTokenRepository, }; pub enum DbPool { @@ -36,6 +37,7 @@ pub struct DatabaseOutput { pub goal: Arc, pub user_settings: Arc, pub remote_goal: Arc, + pub refresh_session: Arc, pub db_pool: DbPool, } @@ -77,6 +79,7 @@ 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(domain::testing::PanicRefreshSessionRepository) as _, db_pool: DbPool::Postgres(w.pool), }) } @@ -115,6 +118,7 @@ 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(domain::testing::PanicRefreshSessionRepository) as _, db_pool: DbPool::Sqlite(w.pool), }) } diff --git a/crates/presentation/src/main.rs b/crates/presentation/src/main.rs index 900c770..33dff87 100644 --- a/crates/presentation/src/main.rs +++ b/crates/presentation/src/main.rs @@ -207,6 +207,7 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> { goal: db.goal, user_settings: db.user_settings, remote_goal: db.remote_goal, + refresh_session: db.refresh_session, }, services: Services { auth: auth_service, diff --git a/crates/presentation/src/tests/extractors.rs b/crates/presentation/src/tests/extractors.rs index 8506526..5977b4c 100644 --- a/crates/presentation/src/tests/extractors.rs +++ b/crates/presentation/src/tests/extractors.rs @@ -729,6 +729,31 @@ impl domain::ports::RemoteGoalRepository for Panic { } } +#[async_trait::async_trait] +impl domain::ports::RefreshSessionRepository for Panic { + async fn create(&self, _: &domain::models::RefreshSession) -> Result<(), DomainError> { + panic!() + } + async fn get_by_token( + &self, + _: &str, + ) -> Result, DomainError> { + panic!() + } + async fn revoke(&self, _: &str) -> Result<(), DomainError> { + panic!() + } + async fn revoke_all_for_user( + &self, + _: &domain::value_objects::UserId, + ) -> Result<(), DomainError> { + panic!() + } + async fn delete_expired(&self) -> Result { + panic!() + } +} + #[async_trait::async_trait] impl application::ports::ReviewLogger for Panic { async fn log_review( @@ -769,6 +794,7 @@ pub fn make_test_state(auth_service: Arc) -> crate::state::AppS goal: Arc::clone(&repo) as _, user_settings: Arc::clone(&repo) as _, remote_goal: Arc::clone(&repo) as _, + refresh_session: Arc::clone(&repo) as _, }, services: Services { auth: auth_service, diff --git a/crates/presentation/tests/api_test.rs b/crates/presentation/tests/api_test.rs index 89276a9..3681965 100644 --- a/crates/presentation/tests/api_test.rs +++ b/crates/presentation/tests/api_test.rs @@ -458,6 +458,7 @@ async fn test_app() -> Router { goal: Arc::new(domain::testing::NoopGoalRepository), user_settings: Arc::new(domain::testing::NoopUserSettingsRepository), remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository), + refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository), }, services: Services { auth: Arc::new(PanicAuth), diff --git a/crates/worker/src/db.rs b/crates/worker/src/db.rs index 6009520..7953bc7 100644 --- a/crates/worker/src/db.rs +++ b/crates/worker/src/db.rs @@ -41,6 +41,7 @@ pub struct WorkerDbOutput { pub goal: Arc, pub user_settings: Arc, pub remote_goal: Arc, + pub refresh_session: Arc, pub db_pool: DbPool, } @@ -86,6 +87,7 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result anyhow::Result anyhow::Result<()> { goal: db.goal, user_settings: db.user_settings, remote_goal: db.remote_goal, + refresh_session: db.refresh_session, }, services: Services { auth: auth_service,