refactor: split up gpodder module files

main
Jef Roosens 2025-03-19 09:05:41 +01:00
parent 0cfcd90eba
commit 2a8917f21d
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
3 changed files with 169 additions and 162 deletions

View File

@ -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<dyn std::error::Error + Sync + Send>),
}
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<T> 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<Option<models::Session>, AuthErr>;
/// Retrieve the user with the given username
fn get_user(&self, username: &str) -> Result<Option<models::User>, 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<Utc>) -> Result<(), AuthErr>;
/// Remove any sessions whose last_seen timestamp is before the given minimum value
fn remove_old_sessions(&self, min_last_seen: DateTime<Utc>) -> Result<usize, AuthErr>;
}
pub trait DeviceRepository {
/// Return all devices associated with the user
fn devices_for_user(&self, user: &User) -> Result<Vec<Device>, 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<i64, AuthErr>;
/// 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<Utc>,
) -> 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<String>, Vec<Vec<String>>), AuthErr>;
}
pub trait SubscriptionRepository {
/// Return the subscriptions for the given device
fn subscriptions_for_device(
&self,
user: &User,
device_id: &str,
) -> Result<Vec<models::Subscription>, AuthErr>;
/// Return all subscriptions for a given user
fn subscriptions_for_user(&self, user: &User) -> Result<Vec<models::Subscription>, 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<String>,
time_changed: DateTime<Utc>,
) -> 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<String>,
remove: Vec<String>,
time_changed: DateTime<Utc>,
) -> Result<(), AuthErr>;
/// Returns the changes in subscriptions since the given timestamp.
fn subscription_updates_for_device(
&self,
user: &User,
device_id: &str,
since: DateTime<Utc>,
) -> Result<(Vec<Subscription>, Vec<Subscription>), AuthErr>;
}
pub trait EpisodeActionRepository {
/// Insert the given episode actions into the datastore.
fn add_episode_actions(
&self,
user: &User,
actions: Vec<EpisodeAction>,
time_changed: DateTime<Utc>,
) -> Result<(), AuthErr>;
/// Retrieve the list of episode actions for the given user.
fn episode_actions_for_user(
&self,
user: &User,
since: Option<DateTime<Utc>>,
podcast: Option<String>,
device: Option<String>,
aggregated: bool,
) -> Result<Vec<EpisodeAction>, AuthErr>;
}
pub use store::{
AuthErr, AuthStore, DeviceRepository, EpisodeActionRepository, Store, SubscriptionRepository,
};

View File

@ -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;

View File

@ -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<dyn std::error::Error + Sync + Send>),
}
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<T> 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<Option<Session>, AuthErr>;
/// Retrieve the user with the given username
fn get_user(&self, username: &str) -> Result<Option<User>, 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<Utc>) -> Result<(), AuthErr>;
/// Remove any sessions whose last_seen timestamp is before the given minimum value
fn remove_old_sessions(&self, min_last_seen: DateTime<Utc>) -> Result<usize, AuthErr>;
}
pub trait DeviceRepository {
/// Return all devices associated with the user
fn devices_for_user(&self, user: &User) -> Result<Vec<Device>, 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<i64, AuthErr>;
/// 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<Utc>,
) -> 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<String>, Vec<Vec<String>>), AuthErr>;
}
pub trait SubscriptionRepository {
/// Return the subscriptions for the given device
fn subscriptions_for_device(
&self,
user: &User,
device_id: &str,
) -> Result<Vec<Subscription>, AuthErr>;
/// Return all subscriptions for a given user
fn subscriptions_for_user(&self, user: &User) -> Result<Vec<Subscription>, 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<String>,
time_changed: DateTime<Utc>,
) -> 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<String>,
remove: Vec<String>,
time_changed: DateTime<Utc>,
) -> Result<(), AuthErr>;
/// Returns the changes in subscriptions since the given timestamp.
fn subscription_updates_for_device(
&self,
user: &User,
device_id: &str,
since: DateTime<Utc>,
) -> Result<(Vec<Subscription>, Vec<Subscription>), AuthErr>;
}
pub trait EpisodeActionRepository {
/// Insert the given episode actions into the datastore.
fn add_episode_actions(
&self,
user: &User,
actions: Vec<EpisodeAction>,
time_changed: DateTime<Utc>,
) -> Result<(), AuthErr>;
/// Retrieve the list of episode actions for the given user.
fn episode_actions_for_user(
&self,
user: &User,
since: Option<DateTime<Utc>>,
podcast: Option<String>,
device: Option<String>,
aggregated: bool,
) -> Result<Vec<EpisodeAction>, AuthErr>;
}