otter/gpodder/src/store.rs

206 lines
6.6 KiB
Rust

use std::fmt::Display;
use crate::models::*;
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub enum AuthErr {
UnknownSession,
UnknownUser,
InvalidPassword,
NotAnAdmin,
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::NotAnAdmin => write!(f, "not an admin"),
Self::Other(err) => err.fmt(f),
}
}
}
impl std::error::Error for AuthErr {}
/// API abstraction providing methods for serving the Gpodder API
pub trait GpodderStore:
GpodderAuthStore + GpodderDeviceStore + GpodderSubscriptionStore + GpodderEpisodeActionStore
{
}
impl<T> GpodderStore for T where
T: GpodderAuthStore + GpodderDeviceStore + GpodderSubscriptionStore + GpodderEpisodeActionStore
{
}
pub trait GpodderAuthStore {
/// Retrieve the session with the given session ID
fn get_session(&self, session_id: i64) -> Result<Option<Session>, AuthErr>;
/// Retrieve a paginated list of the given user's sessions, ordered descending by the last seen
/// value.
fn paginated_sessions(&self, user: &User, page: Page) -> Result<Vec<Session>, AuthErr>;
/// Retrieve the user with the given username
fn get_user(&self, username: &str) -> Result<Option<User>, AuthErr>;
/// Insert a new user into the data store
fn insert_user(&self, username: &str, password_hash: &str) -> Result<User, AuthErr>;
/// Create a new session for a user with the given session ID
///
/// The `last_seen` timestamp's precision should be at least accurate to the second
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>;
/// Return the given page of users, ordered by username
fn paginated_users(&self, page: Page, filter: &UserFilter) -> Result<Vec<User>, AuthErr>;
/// Insert the signup link into the database.
///
/// # Errors
///
/// If a database failure occurs
fn insert_signup_link(&self, link: &SignupLink) -> Result<(), AuthErr>;
/// Get the signup link associated with the given ID
///
/// # Returns
///
/// Some(link) if the ID corresponds to an existing signup link; None otherwise
///
/// # Errors
///
/// If a database failure occurs
fn get_signup_link(&self, id: i64) -> Result<Option<SignupLink>, AuthErr>;
/// Remove the signup link with the given ID.
///
/// # Returns
///
/// True if the ID existed in the database; false otherwise
///
/// # Errors
///
/// If a database failure occurs
fn remove_signup_link(&self, id: i64) -> Result<bool, AuthErr>;
}
pub trait GpodderDeviceStore {
/// 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 GpodderSubscriptionStore {
/// 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 GpodderEpisodeActionStore {
/// 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>;
}