otter/src/gpodder/repository.rs

215 lines
5.9 KiB
Rust

use std::sync::Arc;
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use chrono::{DateTime, TimeDelta, Utc};
use rand::Rng;
use super::{models, AuthErr, Store};
const MAX_SESSION_AGE: i64 = 60 * 60 * 24 * 7;
#[derive(Clone)]
pub struct GpodderRepository {
store: Arc<dyn Store + Send + Sync>,
}
impl GpodderRepository {
pub fn new(store: impl Store + Send + Sync + 'static) -> Self {
Self {
store: Arc::new(store),
}
}
pub fn get_session(&self, session_id: i64) -> Result<models::Session, AuthErr> {
let session = self
.store
.get_session(session_id)?
.ok_or(AuthErr::UnknownSession)?;
// Expired sessions still in the database are considered removed
if Utc::now() - session.last_seen > TimeDelta::new(MAX_SESSION_AGE, 0).unwrap() {
Err(AuthErr::UnknownSession)
} else {
Ok(session)
}
}
pub fn validate_credentials(
&self,
username: &str,
password: &str,
) -> Result<models::User, AuthErr> {
let user = self.store.get_user(username)?.ok_or(AuthErr::UnknownUser)?;
let password_hash = PasswordHash::new(&user.password_hash).unwrap();
if Argon2::default()
.verify_password(password.as_bytes(), &password_hash)
.is_ok()
{
Ok(user)
} else {
Err(AuthErr::InvalidPassword)
}
}
pub fn create_session(&self, user: &models::User) -> Result<models::Session, AuthErr> {
let session = models::Session {
id: rand::thread_rng().gen(),
last_seen: Utc::now(),
user: user.clone(),
};
self.store.insert_session(&session)?;
Ok(session)
}
pub fn refresh_session(&self, session: &models::Session) -> Result<(), AuthErr> {
let now = Utc::now();
self.store.refresh_session(session, now)
}
pub fn remove_session(&self, session_id: i64) -> Result<(), AuthErr> {
self.store.remove_session(session_id)
}
pub fn remove_old_sessions(&self) -> Result<usize, AuthErr> {
let min_last_seen = Utc::now() - TimeDelta::seconds(MAX_SESSION_AGE);
self.store.remove_old_sessions(min_last_seen)
}
pub fn devices_for_user(&self, user: &models::User) -> Result<Vec<models::Device>, AuthErr> {
self.store.devices_for_user(user)
}
pub fn update_device_info(
&self,
user: &models::User,
device_id: &str,
patch: models::DevicePatch,
) -> Result<(), AuthErr> {
self.store.update_device_info(user, device_id, patch)
}
pub fn update_device_sync_status(
&self,
user: &models::User,
sync: Vec<Vec<&str>>,
unsync: Vec<&str>,
) -> Result<(), AuthErr> {
todo!("perform diff devices to sync and unsync")
}
pub fn devices_by_sync_group(
&self,
user: &models::User,
) -> Result<(Vec<models::Device>, Vec<Vec<models::Device>>), AuthErr> {
self.store.devices_by_sync_group(user)
}
pub fn subscriptions_for_device(
&self,
user: &models::User,
device_id: &str,
) -> Result<Vec<models::Subscription>, AuthErr> {
self.store.subscriptions_for_device(user, device_id)
}
pub fn subscriptions_for_user(
&self,
user: &models::User,
) -> Result<Vec<models::Subscription>, AuthErr> {
self.store.subscriptions_for_user(user)
}
pub fn set_subscriptions_for_device(
&self,
user: &models::User,
device_id: &str,
urls: Vec<String>,
) -> Result<DateTime<Utc>, AuthErr> {
let time_changed = Utc::now();
self.store
.set_subscriptions_for_device(user, device_id, urls, time_changed)?;
Ok(time_changed + TimeDelta::seconds(1))
}
pub fn update_subscriptions_for_device(
&self,
user: &models::User,
device_id: &str,
add: Vec<String>,
remove: Vec<String>,
) -> Result<DateTime<Utc>, AuthErr> {
let time_changed = Utc::now();
self.store
.update_subscriptions_for_device(user, device_id, add, remove, time_changed)?;
Ok(time_changed + TimeDelta::seconds(1))
}
pub fn subscription_updates_for_device(
&self,
user: &models::User,
device_id: &str,
since: DateTime<Utc>,
) -> Result<
(
DateTime<Utc>,
Vec<models::Subscription>,
Vec<models::Subscription>,
),
AuthErr,
> {
let now = chrono::Utc::now();
let (added, removed) = self
.store
.subscription_updates_for_device(user, device_id, since)?;
let max_time_changed = added
.iter()
.chain(removed.iter())
.map(|s| s.time_changed)
.max()
.unwrap_or(now);
Ok((max_time_changed + TimeDelta::seconds(1), added, removed))
}
pub fn add_episode_actions(
&self,
user: &models::User,
actions: Vec<models::EpisodeAction>,
) -> Result<DateTime<Utc>, AuthErr> {
let time_changed = Utc::now();
self.store
.add_episode_actions(user, actions, time_changed)?;
Ok(time_changed + TimeDelta::seconds(1))
}
pub fn episode_actions_for_user(
&self,
user: &models::User,
since: Option<DateTime<Utc>>,
podcast: Option<String>,
device: Option<String>,
aggregated: bool,
) -> Result<(DateTime<Utc>, Vec<models::EpisodeAction>), AuthErr> {
let now = chrono::Utc::now();
let actions = self
.store
.episode_actions_for_user(user, since, podcast, device, aggregated)?;
let max_time_changed = actions.iter().map(|a| a.time_changed).max().unwrap_or(now);
Ok((max_time_changed + TimeDelta::seconds(1), actions))
}
}