diff --git a/gpodder/src/lib.rs b/gpodder/src/lib.rs index e834bcf..0f31248 100644 --- a/gpodder/src/lib.rs +++ b/gpodder/src/lib.rs @@ -1,166 +1,9 @@ pub mod models; mod repository; +mod store; -use std::fmt::Display; - -use chrono::{DateTime, Utc}; pub use models::*; - pub use repository::GpodderRepository; - -#[derive(Debug)] -pub enum AuthErr { - UnknownSession, - UnknownUser, - InvalidPassword, - Other(Box), -} - -impl Display for AuthErr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnknownUser => write!(f, "unknown user"), - Self::UnknownSession => write!(f, "unknown session"), - Self::InvalidPassword => write!(f, "invalid password"), - Self::Other(err) => err.fmt(f), - } - } -} - -impl std::error::Error for AuthErr {} - -pub trait Store: - AuthStore + DeviceRepository + SubscriptionRepository + EpisodeActionRepository -{ -} - -impl Store for T where - T: AuthStore + DeviceRepository + SubscriptionRepository + EpisodeActionRepository -{ -} - -pub trait AuthStore { - /// Retrieve the session with the given session ID - fn get_session(&self, session_id: i64) -> Result, AuthErr>; - - /// Retrieve the user with the given username - fn get_user(&self, username: &str) -> Result, AuthErr>; - - /// Create a new session for a user with the given session ID - fn insert_session(&self, session: &Session) -> Result<(), AuthErr>; - - /// Remove the session with the given session ID - fn remove_session(&self, session_id: i64) -> Result<(), AuthErr>; - - /// Update the session's timestamp - fn refresh_session(&self, session: &Session, timestamp: DateTime) -> Result<(), AuthErr>; - - /// Remove any sessions whose last_seen timestamp is before the given minimum value - fn remove_old_sessions(&self, min_last_seen: DateTime) -> Result; -} - -pub trait DeviceRepository { - /// Return all devices associated with the user - fn devices_for_user(&self, user: &User) -> Result, AuthErr>; - - /// Update the information for the given device. If the device doesn't exist yet, it should be - /// created without a sync group. - fn update_device_info( - &self, - user: &User, - device_id: &str, - patch: DevicePatch, - ) -> Result<(), AuthErr>; - - /// Add the devices to the same sync group by: - /// - /// * Merging the sync groups of all devices already in a sync group - /// * Adding all devices not yet in a sync group to the newly merged sync group - /// - /// # Returns - /// - /// ID of the final sync group - fn merge_sync_groups(&self, user: &User, device_ids: Vec<&str>) -> Result; - - /// Synchronize the sync group by adding or removing subscriptions to each device so that each - /// device's subscription state is the same - fn synchronize_sync_group( - &self, - group_id: i64, - time_changed: DateTime, - ) -> Result<(), AuthErr>; - - /// Remove the devices from their respective sync groups - fn remove_from_sync_group(&self, user: &User, device_ids: Vec<&str>) -> Result<(), AuthErr>; - - /// Return all device IDs for the user, grouped per sync group - /// - /// # Returns - /// - /// A tuple (unsynced devices, sync groups) - fn devices_by_sync_group( - &self, - user: &User, - ) -> Result<(Vec, Vec>), AuthErr>; -} - -pub trait SubscriptionRepository { - /// Return the subscriptions for the given device - fn subscriptions_for_device( - &self, - user: &User, - device_id: &str, - ) -> Result, AuthErr>; - - /// Return all subscriptions for a given user - fn subscriptions_for_user(&self, user: &User) -> Result, AuthErr>; - - /// Replace the list of subscriptions for a device and all devices in its sync group with the - /// given list - fn set_subscriptions_for_device( - &self, - user: &User, - device_id: &str, - urls: Vec, - time_changed: DateTime, - ) -> Result<(), AuthErr>; - - /// Update the list of subscriptions for a device and all devices in its sync group by adding - /// and removing the given URLs - fn update_subscriptions_for_device( - &self, - user: &User, - device_id: &str, - add: Vec, - remove: Vec, - time_changed: DateTime, - ) -> Result<(), AuthErr>; - - /// Returns the changes in subscriptions since the given timestamp. - fn subscription_updates_for_device( - &self, - user: &User, - device_id: &str, - since: DateTime, - ) -> Result<(Vec, Vec), AuthErr>; -} - -pub trait EpisodeActionRepository { - /// Insert the given episode actions into the datastore. - fn add_episode_actions( - &self, - user: &User, - actions: Vec, - time_changed: DateTime, - ) -> Result<(), AuthErr>; - - /// Retrieve the list of episode actions for the given user. - fn episode_actions_for_user( - &self, - user: &User, - since: Option>, - podcast: Option, - device: Option, - aggregated: bool, - ) -> Result, AuthErr>; -} +pub use store::{ + AuthErr, AuthStore, DeviceRepository, EpisodeActionRepository, Store, SubscriptionRepository, +}; diff --git a/gpodder/src/repository.rs b/gpodder/src/repository.rs index 942ef26..b8a4cd7 100644 --- a/gpodder/src/repository.rs +++ b/gpodder/src/repository.rs @@ -4,7 +4,10 @@ use argon2::{Argon2, PasswordHash, PasswordVerifier}; use chrono::{DateTime, TimeDelta, Utc}; use rand::Rng; -use super::{models, AuthErr, Store}; +use crate::{ + models, + store::{AuthErr, Store}, +}; const MAX_SESSION_AGE: i64 = 60 * 60 * 24 * 7; diff --git a/gpodder/src/store.rs b/gpodder/src/store.rs new file mode 100644 index 0000000..4c9ddd7 --- /dev/null +++ b/gpodder/src/store.rs @@ -0,0 +1,161 @@ +use std::fmt::Display; + +use crate::models::*; +use chrono::{DateTime, Utc}; + +#[derive(Debug)] +pub enum AuthErr { + UnknownSession, + UnknownUser, + InvalidPassword, + Other(Box), +} + +impl Display for AuthErr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnknownUser => write!(f, "unknown user"), + Self::UnknownSession => write!(f, "unknown session"), + Self::InvalidPassword => write!(f, "invalid password"), + Self::Other(err) => err.fmt(f), + } + } +} + +impl std::error::Error for AuthErr {} + +pub trait Store: + AuthStore + DeviceRepository + SubscriptionRepository + EpisodeActionRepository +{ +} + +impl Store for T where + T: AuthStore + DeviceRepository + SubscriptionRepository + EpisodeActionRepository +{ +} + +pub trait AuthStore { + /// Retrieve the session with the given session ID + fn get_session(&self, session_id: i64) -> Result, AuthErr>; + + /// Retrieve the user with the given username + fn get_user(&self, username: &str) -> Result, AuthErr>; + + /// Create a new session for a user with the given session ID + fn insert_session(&self, session: &Session) -> Result<(), AuthErr>; + + /// Remove the session with the given session ID + fn remove_session(&self, session_id: i64) -> Result<(), AuthErr>; + + /// Update the session's timestamp + fn refresh_session(&self, session: &Session, timestamp: DateTime) -> Result<(), AuthErr>; + + /// Remove any sessions whose last_seen timestamp is before the given minimum value + fn remove_old_sessions(&self, min_last_seen: DateTime) -> Result; +} + +pub trait DeviceRepository { + /// Return all devices associated with the user + fn devices_for_user(&self, user: &User) -> Result, AuthErr>; + + /// Update the information for the given device. If the device doesn't exist yet, it should be + /// created without a sync group. + fn update_device_info( + &self, + user: &User, + device_id: &str, + patch: DevicePatch, + ) -> Result<(), AuthErr>; + + /// Add the devices to the same sync group by: + /// + /// * Merging the sync groups of all devices already in a sync group + /// * Adding all devices not yet in a sync group to the newly merged sync group + /// + /// # Returns + /// + /// ID of the final sync group + fn merge_sync_groups(&self, user: &User, device_ids: Vec<&str>) -> Result; + + /// Synchronize the sync group by adding or removing subscriptions to each device so that each + /// device's subscription state is the same + fn synchronize_sync_group( + &self, + group_id: i64, + time_changed: DateTime, + ) -> Result<(), AuthErr>; + + /// Remove the devices from their respective sync groups + fn remove_from_sync_group(&self, user: &User, device_ids: Vec<&str>) -> Result<(), AuthErr>; + + /// Return all device IDs for the user, grouped per sync group + /// + /// # Returns + /// + /// A tuple (unsynced devices, sync groups) + fn devices_by_sync_group( + &self, + user: &User, + ) -> Result<(Vec, Vec>), AuthErr>; +} + +pub trait SubscriptionRepository { + /// Return the subscriptions for the given device + fn subscriptions_for_device( + &self, + user: &User, + device_id: &str, + ) -> Result, AuthErr>; + + /// Return all subscriptions for a given user + fn subscriptions_for_user(&self, user: &User) -> Result, AuthErr>; + + /// Replace the list of subscriptions for a device and all devices in its sync group with the + /// given list + fn set_subscriptions_for_device( + &self, + user: &User, + device_id: &str, + urls: Vec, + time_changed: DateTime, + ) -> Result<(), AuthErr>; + + /// Update the list of subscriptions for a device and all devices in its sync group by adding + /// and removing the given URLs + fn update_subscriptions_for_device( + &self, + user: &User, + device_id: &str, + add: Vec, + remove: Vec, + time_changed: DateTime, + ) -> Result<(), AuthErr>; + + /// Returns the changes in subscriptions since the given timestamp. + fn subscription_updates_for_device( + &self, + user: &User, + device_id: &str, + since: DateTime, + ) -> Result<(Vec, Vec), AuthErr>; +} + +pub trait EpisodeActionRepository { + /// Insert the given episode actions into the datastore. + fn add_episode_actions( + &self, + user: &User, + actions: Vec, + time_changed: DateTime, + ) -> Result<(), AuthErr>; + + /// Retrieve the list of episode actions for the given user. + fn episode_actions_for_user( + &self, + user: &User, + since: Option>, + podcast: Option, + device: Option, + aggregated: bool, + ) -> Result, AuthErr>; +}