feat: per-entity federation privacy toggles for reviews and watchlist

- add federate_reviews + federate_watchlist to UserSettings (default true)
- new UserFederationSettingsQuery port with FederationFlags struct
- remove get_user_federate_goals from LocalApContentQuery
- gate ReviewLogged, ReviewUpdated, WatchlistEntryAdded, on_poster_synced on flags
- goals gating migrated to UserFederationSettingsQuery
- ReviewDeleted and WatchlistEntryRemoved ungated (tombstones always fire)
- sqlite + postgres migrations and adapter impls
- settings API and SPA toggles
This commit is contained in:
2026-06-12 02:26:01 +02:00
parent 33aa5bdab3
commit ca7ca51949
25 changed files with 372 additions and 113 deletions

View File

@@ -4,27 +4,34 @@ use crate::value_objects::UserId;
pub struct UserSettings {
user_id: UserId,
federate_goals: bool,
federate_reviews: bool,
federate_watchlist: bool,
}
impl UserSettings {
pub fn new(user_id: UserId) -> Self {
Self {
user_id,
federate_goals: false,
federate_goals: true,
federate_reviews: true,
federate_watchlist: true,
}
}
pub fn from_persistence(user_id: UserId, federate_goals: bool) -> Self {
pub fn from_persistence(
user_id: UserId,
federate_goals: bool,
federate_reviews: bool,
federate_watchlist: bool,
) -> Self {
Self {
user_id,
federate_goals,
federate_reviews,
federate_watchlist,
}
}
pub fn set_federate_goals(&mut self, value: bool) {
self.federate_goals = value;
}
pub fn user_id(&self) -> &UserId {
&self.user_id
}
@@ -32,4 +39,24 @@ impl UserSettings {
pub fn federate_goals(&self) -> bool {
self.federate_goals
}
pub fn set_federate_goals(&mut self, value: bool) {
self.federate_goals = value;
}
pub fn federate_reviews(&self) -> bool {
self.federate_reviews
}
pub fn set_federate_reviews(&mut self, value: bool) {
self.federate_reviews = value;
}
pub fn federate_watchlist(&self) -> bool {
self.federate_watchlist
}
pub fn set_federate_watchlist(&mut self, value: bool) {
self.federate_watchlist = value;
}
}

View File

@@ -456,6 +456,17 @@ pub trait UserSettingsRepository: Send + Sync {
async fn save(&self, settings: &UserSettings) -> Result<(), DomainError>;
}
pub struct FederationFlags {
pub goals: bool,
pub reviews: bool,
pub watchlist: bool,
}
#[async_trait]
pub trait UserFederationSettingsQuery: Send + Sync {
async fn get_federation_flags(&self, user_id: &UserId) -> Result<FederationFlags, DomainError>;
}
#[async_trait]
pub trait RemoteGoalRepository: Send + Sync {
async fn save(&self, entry: RemoteGoalEntry) -> Result<(), DomainError>;
@@ -502,8 +513,6 @@ pub trait LocalApContentQuery: Send + Sync {
limit: usize,
) -> Result<Vec<DiaryEntry>, DomainError>;
async fn get_user_federate_goals(&self, user_id: &UserId) -> Result<bool, DomainError>;
async fn get_goal_with_progress(
&self,
user_id: &UserId,

View File

@@ -18,9 +18,10 @@ use crate::{
collections::{PageParams, Paginated},
},
ports::{
GoalRepository, ImportProfileRepository, ImportSessionRepository, MovieProfileRepository,
MovieRepository, RefreshSessionRepository, ReviewRepository, UserProfileFieldsRepository,
UserRepository, UserSettingsRepository, WatchEventRepository, WatchlistRepository,
FederationFlags, GoalRepository, ImportProfileRepository, ImportSessionRepository,
MovieProfileRepository, MovieRepository, RefreshSessionRepository, ReviewRepository,
UserFederationSettingsQuery, UserProfileFieldsRepository, UserRepository,
UserSettingsRepository, WatchEventRepository, WatchlistRepository,
WebhookTokenRepository,
},
value_objects::{
@@ -441,6 +442,22 @@ impl UserSettingsRepository for InMemoryUserSettingsRepository {
}
}
#[async_trait]
impl UserFederationSettingsQuery for InMemoryUserSettingsRepository {
async fn get_federation_flags(&self, user_id: &UserId) -> Result<FederationFlags, DomainError> {
let store = self.store.lock().unwrap();
let settings = store
.get(&user_id.value())
.cloned()
.unwrap_or_else(|| UserSettings::new(user_id.clone()));
Ok(FederationFlags {
goals: settings.federate_goals(),
reviews: settings.federate_reviews(),
watchlist: settings.federate_watchlist(),
})
}
}
// ── InMemoryWebhookTokenRepository ──────────────────────────────────────────
pub struct InMemoryWebhookTokenRepository {