domain: add RefreshSession model + repository port

This commit is contained in:
2026-06-11 14:29:43 +02:00
parent db285b513b
commit ef9ecbae06
12 changed files with 101 additions and 14 deletions

View File

@@ -4,10 +4,11 @@ use domain::ports::{
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, GoalRepository, AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, GoalRepository,
ImportProfileRepository, ImportSessionRepository, MetadataClient, MovieProfileRepository, ImportProfileRepository, ImportSessionRepository, MetadataClient, MovieProfileRepository,
MovieRepository, ObjectStorage, PasswordHasher, PersonCommand, PersonEnrichmentClient, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand, PersonEnrichmentClient,
PersonQuery, PosterFetcherClient, RemoteGoalRepository, RemoteWatchlistRepository, PersonQuery, PosterFetcherClient, RefreshSessionRepository, RemoteGoalRepository,
ReviewRepository, SearchCommand, SearchPort, SocialQueryPort, StatsRepository, RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort,
UserProfileFieldsRepository, UserRepository, UserSettingsRepository, WatchEventRepository, StatsRepository, UserProfileFieldsRepository, UserRepository, UserSettingsRepository,
WatchlistRepository, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery, WatchEventRepository, WatchlistRepository, WebhookTokenRepository, WrapUpRepository,
WrapUpStatsQuery,
}; };
use crate::config::AppConfig; use crate::config::AppConfig;
@@ -38,6 +39,7 @@ pub struct Repositories {
pub goal: Arc<dyn GoalRepository>, pub goal: Arc<dyn GoalRepository>,
pub user_settings: Arc<dyn UserSettingsRepository>, pub user_settings: Arc<dyn UserSettingsRepository>,
pub remote_goal: Arc<dyn RemoteGoalRepository>, pub remote_goal: Arc<dyn RemoteGoalRepository>,
pub refresh_session: Arc<dyn RefreshSessionRepository>,
} }
#[derive(Clone)] #[derive(Clone)]

View File

@@ -9,10 +9,10 @@ use domain::{
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher,
GoalRepository, ImportProfileRepository, ImportSessionRepository, MetadataClient, GoalRepository, ImportProfileRepository, ImportSessionRepository, MetadataClient,
MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand, MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher, PersonCommand,
PersonQuery, PosterFetcherClient, ReviewRepository, SearchCommand, SearchPort, PersonQuery, PosterFetcherClient, RefreshSessionRepository, ReviewRepository,
StatsRepository, UserProfileFieldsRepository, UserRepository, UserSettingsRepository, SearchCommand, SearchPort, StatsRepository, UserProfileFieldsRepository, UserRepository,
WatchEventRepository, WatchlistRepository, WebhookTokenRepository, WrapUpRepository, UserSettingsRepository, WatchEventRepository, WatchlistRepository,
WrapUpStatsQuery, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
}, },
testing::{ testing::{
FakeAuthService, FakeDiaryRepository, FakeDocumentParser, FakeMetadataClient, FakeAuthService, FakeDiaryRepository, FakeDocumentParser, FakeMetadataClient,
@@ -22,6 +22,7 @@ use domain::{
InMemoryReviewRepository, InMemoryUserRepository, InMemoryUserSettingsRepository, InMemoryReviewRepository, InMemoryUserRepository, InMemoryUserSettingsRepository,
InMemoryWatchEventRepository, InMemoryWatchlistRepository, InMemoryWebhookTokenRepository, InMemoryWatchEventRepository, InMemoryWatchlistRepository, InMemoryWebhookTokenRepository,
NoopEventPublisher, NoopObjectStorage, PanicDiaryExporter, PanicPersonCommand, NoopEventPublisher, NoopObjectStorage, PanicDiaryExporter, PanicPersonCommand,
PanicRefreshSessionRepository,
}, },
}; };
@@ -75,6 +76,7 @@ pub struct TestContextBuilder {
pub user_settings_repo: Arc<dyn UserSettingsRepository>, pub user_settings_repo: Arc<dyn UserSettingsRepository>,
pub review_logger: Arc<dyn ReviewLogger>, pub review_logger: Arc<dyn ReviewLogger>,
pub social_query: Arc<dyn domain::ports::SocialQueryPort>, pub social_query: Arc<dyn domain::ports::SocialQueryPort>,
pub refresh_session_repo: Arc<dyn RefreshSessionRepository>,
pub config: AppConfig, pub config: AppConfig,
} }
@@ -117,6 +119,7 @@ impl TestContextBuilder {
user_settings_repo: InMemoryUserSettingsRepository::new(), user_settings_repo: InMemoryUserSettingsRepository::new(),
review_logger: Arc::new(NoopReviewLogger), review_logger: Arc::new(NoopReviewLogger),
social_query: Arc::new(NoopSocialQueryPort), social_query: Arc::new(NoopSocialQueryPort),
refresh_session_repo: Arc::new(PanicRefreshSessionRepository),
config: AppConfig { config: AppConfig {
allow_registration: true, allow_registration: true,
base_url: "http://localhost:3000".into(), base_url: "http://localhost:3000".into(),
@@ -286,6 +289,7 @@ impl TestContextBuilder {
goal: self.goal_repo, goal: self.goal_repo,
user_settings: self.user_settings_repo, user_settings: self.user_settings_repo,
remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository), remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository),
refresh_session: self.refresh_session_repo,
}, },
services: Services { services: Services {
auth: self.auth_service, auth: self.auth_service,

View File

@@ -1,6 +1,7 @@
mod enrichment; mod enrichment;
mod feed; mod feed;
mod movie; mod movie;
mod refresh_session;
mod review; mod review;
mod stats; mod stats;
mod user; mod user;
@@ -43,6 +44,7 @@ pub use import::{
}; };
pub use import_profile::ImportProfile; pub use import_profile::ImportProfile;
pub use import_session::ImportSession; pub use import_session::ImportSession;
pub use refresh_session::RefreshSession;
pub use person::{ pub use person::{
CastCredit, CrewCredit, ExternalPersonId, Person, PersonCredits, PersonEnrichmentData, PersonId, CastCredit, CrewCredit, ExternalPersonId, Person, PersonCredits, PersonEnrichmentData, PersonId,
}; };

View File

@@ -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<Utc>,
pub created_at: DateTime<Utc>,
}

View File

@@ -8,7 +8,7 @@ use crate::{
models::wrapup::WrapUpReport, models::wrapup::WrapUpReport,
models::{ models::{
AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId, 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, IndexableDocument, Movie, MovieFilter, MovieProfile, MovieStats, MovieSummary, ParsedFile,
ParsedPlaybackEvent, Person, PersonCredits, PersonEnrichmentData, PersonId, ParsedPlaybackEvent, Person, PersonCredits, PersonEnrichmentData, PersonId,
RemoteGoalEntry, RemoteWatchlistEntry, Review, ReviewHistory, SearchQuery, SearchResults, 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 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<Option<RefreshSession>, 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<u64, DomainError>;
}
#[async_trait] #[async_trait]
pub trait ImportProfileRepository: Send + Sync { pub trait ImportProfileRepository: Send + Sync {
async fn save(&self, profile: &ImportProfile) -> Result<(), DomainError>; async fn save(&self, profile: &ImportProfile) -> Result<(), DomainError>;

View File

@@ -6,15 +6,15 @@ use crate::{
AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId, AnnotatedRow, DiaryEntry, DiaryFilter, EntityType, ExportFormat, ExternalPersonId,
FeedEntry, FieldMapping, FileFormat, ImportError, ImportProfile, ImportSession, FeedEntry, FieldMapping, FileFormat, ImportError, ImportProfile, ImportSession,
IndexableDocument, MovieProfile, MovieStats, ParsedFile, Person, PersonCredits, IndexableDocument, MovieProfile, MovieStats, ParsedFile, Person, PersonCredits,
PersonEnrichmentData, PersonId, ReviewHistory, SearchQuery, SearchResults, UserStats, PersonEnrichmentData, PersonId, RefreshSession, ReviewHistory, SearchQuery, SearchResults,
UserTrends, UserStats, UserTrends,
collections::{PageParams, Paginated}, collections::{PageParams, Paginated},
}, },
ports::{ ports::{
DiaryExporter, DiaryRepository, DocumentParser, FeedSortBy, FollowingFilter, DiaryExporter, DiaryRepository, DocumentParser, FeedSortBy, FollowingFilter,
ImportProfileRepository, ImportSessionRepository, MovieProfileRepository, PersonCommand, ImportProfileRepository, ImportSessionRepository, MovieProfileRepository, PersonCommand,
PersonQuery, PosterFetcherClient, SearchCommand, SearchPort, StatsRepository, PersonQuery, PosterFetcherClient, RefreshSessionRepository, SearchCommand, SearchPort,
UserProfileFieldsRepository, StatsRepository, UserProfileFieldsRepository,
}, },
value_objects::{ImportProfileId, ImportSessionId, MovieId, PosterUrl, UserId}, 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<Option<RefreshSession>, 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<u64, DomainError> {
panic!("PanicRefreshSessionRepository called")
}
}
pub struct PanicImportProfileRepository; pub struct PanicImportProfileRepository;
#[async_trait] #[async_trait]

View File

@@ -3,7 +3,8 @@ use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use domain::ports::{ use domain::ports::{
AuthService, LocalApContentQuery, MetadataClient, ObjectStorage, PasswordHasher, AuthService, LocalApContentQuery, MetadataClient, ObjectStorage, PasswordHasher,
PosterFetcherClient, UserProfileFieldsRepository, WatchEventRepository, WebhookTokenRepository, PosterFetcherClient, RefreshSessionRepository, UserProfileFieldsRepository,
WatchEventRepository, WebhookTokenRepository,
}; };
pub enum DbPool { pub enum DbPool {
@@ -36,6 +37,7 @@ pub struct DatabaseOutput {
pub goal: Arc<dyn domain::ports::GoalRepository>, pub goal: Arc<dyn domain::ports::GoalRepository>,
pub user_settings: Arc<dyn domain::ports::UserSettingsRepository>, 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 RefreshSessionRepository>,
pub db_pool: DbPool, pub db_pool: DbPool,
} }
@@ -77,6 +79,7 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
goal: w.goal, goal: w.goal,
user_settings: w.user_settings, user_settings: w.user_settings,
remote_goal: w.remote_goal, remote_goal: w.remote_goal,
refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository) as _,
db_pool: DbPool::Postgres(w.pool), 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, goal: w.goal,
user_settings: w.user_settings, user_settings: w.user_settings,
remote_goal: w.remote_goal, remote_goal: w.remote_goal,
refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository) as _,
db_pool: DbPool::Sqlite(w.pool), db_pool: DbPool::Sqlite(w.pool),
}) })
} }

View File

@@ -207,6 +207,7 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
goal: db.goal, goal: db.goal,
user_settings: db.user_settings, user_settings: db.user_settings,
remote_goal: db.remote_goal, remote_goal: db.remote_goal,
refresh_session: db.refresh_session,
}, },
services: Services { services: Services {
auth: auth_service, auth: auth_service,

View File

@@ -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<Option<domain::models::RefreshSession>, 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<u64, DomainError> {
panic!()
}
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl application::ports::ReviewLogger for Panic { impl application::ports::ReviewLogger for Panic {
async fn log_review( async fn log_review(
@@ -769,6 +794,7 @@ pub fn make_test_state(auth_service: Arc<dyn AuthService>) -> crate::state::AppS
goal: Arc::clone(&repo) as _, goal: Arc::clone(&repo) as _,
user_settings: Arc::clone(&repo) as _, user_settings: Arc::clone(&repo) as _,
remote_goal: Arc::clone(&repo) as _, remote_goal: Arc::clone(&repo) as _,
refresh_session: Arc::clone(&repo) as _,
}, },
services: Services { services: Services {
auth: auth_service, auth: auth_service,

View File

@@ -458,6 +458,7 @@ async fn test_app() -> Router {
goal: Arc::new(domain::testing::NoopGoalRepository), goal: Arc::new(domain::testing::NoopGoalRepository),
user_settings: Arc::new(domain::testing::NoopUserSettingsRepository), user_settings: Arc::new(domain::testing::NoopUserSettingsRepository),
remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository), remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository),
refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository),
}, },
services: Services { services: Services {
auth: Arc::new(PanicAuth), auth: Arc::new(PanicAuth),

View File

@@ -41,6 +41,7 @@ pub struct WorkerDbOutput {
pub goal: Arc<dyn domain::ports::GoalRepository>, pub goal: Arc<dyn domain::ports::GoalRepository>,
pub user_settings: Arc<dyn domain::ports::UserSettingsRepository>, 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 db_pool: DbPool, pub db_pool: DbPool,
} }
@@ -86,6 +87,7 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
goal: w.goal, goal: w.goal,
user_settings: w.user_settings, user_settings: w.user_settings,
remote_goal: w.remote_goal, remote_goal: w.remote_goal,
refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository) as _,
db_pool: DbPool::Postgres(w.pool), db_pool: DbPool::Postgres(w.pool),
}) })
} }
@@ -128,6 +130,7 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result<Worker
goal: w.goal, goal: w.goal,
user_settings: w.user_settings, user_settings: w.user_settings,
remote_goal: w.remote_goal, remote_goal: w.remote_goal,
refresh_session: Arc::new(domain::testing::PanicRefreshSessionRepository) as _,
db_pool: DbPool::Sqlite(w.pool), db_pool: DbPool::Sqlite(w.pool),
}) })
} }

View File

@@ -108,6 +108,7 @@ async fn main() -> anyhow::Result<()> {
goal: db.goal, goal: db.goal,
user_settings: db.user_settings, user_settings: db.user_settings,
remote_goal: db.remote_goal, remote_goal: db.remote_goal,
refresh_session: db.refresh_session,
}, },
services: Services { services: Services {
auth: auth_service, auth: auth_service,