use chrono::DateTime; use diesel::prelude::*; use gpodder::AuthErr; use super::SqliteRepository; use crate::{ DbError, models::{ session::Session, signup_link::SignupLink, user::{NewUser, User}, }, schema::*, }; impl From for gpodder::User { fn from(value: User) -> Self { Self { id: value.id, username: value.username, password_hash: value.password_hash, admin: value.admin, } } } impl From for gpodder::SignupLink { fn from(value: SignupLink) -> Self { Self { id: value.id, time_created: DateTime::from_timestamp(value.created_at, 0).unwrap(), } } } impl gpodder::GpodderAuthStore for SqliteRepository { fn get_user(&self, username: &str) -> Result, AuthErr> { Ok(users::table .select(User::as_select()) .filter(users::username.eq(username)) .first(&mut self.pool.get().map_err(DbError::from)?) .optional() .map_err(DbError::from)? .map(gpodder::User::from)) } fn insert_user(&self, username: &str, password_hash: &str) -> Result { let conn = &mut self.pool.get().map_err(DbError::from)?; Ok(diesel::insert_into(users::table) .values(NewUser { username, password_hash, }) .returning(User::as_returning()) .get_result(conn) .map(gpodder::User::from) .map_err(DbError::from)?) } fn get_session(&self, session_id: i64) -> Result, AuthErr> { match sessions::table .inner_join(users::table) .filter(sessions::id.eq(session_id)) .select((Session::as_select(), User::as_select())) .get_result(&mut self.pool.get().map_err(DbError::from)?) .optional() { Ok(Some((session, user))) => Ok(Some(gpodder::Session { id: session.id, last_seen: DateTime::from_timestamp(session.last_seen, 0).unwrap(), user: user.into(), user_agent: session.user_agent.clone(), })), Ok(None) => Ok(None), Err(err) => Err(DbError::from(err).into()), } } fn remove_session(&self, session_id: i64) -> Result<(), AuthErr> { Ok( diesel::delete(sessions::table.filter(sessions::id.eq(session_id))) .execute(&mut self.pool.get().map_err(DbError::from)?) .map(|_| ()) .map_err(DbError::from)?, ) } fn insert_session(&self, session: &gpodder::Session) -> Result<(), AuthErr> { Ok(Session { id: session.id, user_id: session.user.id, last_seen: session.last_seen.timestamp(), user_agent: session.user_agent.clone(), } .insert_into(sessions::table) .execute(&mut self.pool.get().map_err(DbError::from)?) .map(|_| ()) .map_err(DbError::from)?) } fn refresh_session( &self, session: &gpodder::Session, timestamp: DateTime, ) -> Result<(), AuthErr> { if diesel::update(sessions::table.filter(sessions::id.eq(session.id))) .set(sessions::last_seen.eq(timestamp.timestamp())) .execute(&mut self.pool.get().map_err(DbError::from)?) .map_err(DbError::from)? == 0 { Err(AuthErr::UnknownSession) } else { Ok(()) } } fn remove_old_sessions(&self, min_last_seen: DateTime) -> Result { let min_last_seen = min_last_seen.timestamp(); Ok( diesel::delete(sessions::table.filter(sessions::last_seen.lt(min_last_seen))) .execute(&mut self.pool.get().map_err(DbError::from)?) .map_err(DbError::from)?, ) } fn paginated_sessions( &self, user: &gpodder::User, page: gpodder::Page, ) -> Result, AuthErr> { (|| { let sessions = sessions::table .filter(sessions::user_id.eq(user.id)) .order(sessions::last_seen.desc()) .offset((page.page * page.per_page) as i64) .limit(page.per_page as i64) .select(Session::as_select()) .get_results(&mut self.pool.get()?)? .into_iter() .map(|session| gpodder::Session { id: session.id, last_seen: DateTime::from_timestamp(session.last_seen, 0).unwrap(), user_agent: session.user_agent, user: user.clone(), }) .collect(); Ok::<_, DbError>(sessions) })() .map_err(AuthErr::from) } fn paginated_users( &self, page: gpodder::Page, filter: &gpodder::UserFilter, ) -> Result, AuthErr> { (|| { let mut query = users::table .select(User::as_select()) .order(users::username.asc()) .offset((page.page * page.per_page) as i64) .limit(page.per_page as i64) .into_boxed(); if let Some(username) = &filter.username { // Case insensitive by default for SQLite query = query.filter(users::username.like(format!("%{username}%"))); } Ok::<_, DbError>( query .load_iter(&mut self.pool.get()?)? .map(|res| res.map(gpodder::User::from)) .collect::, _>>()?, ) })() .map_err(AuthErr::from) } fn get_signup_link(&self, id: i64) -> Result, AuthErr> { match signup_links::table .find(id) .select(SignupLink::as_select()) .first(&mut self.pool.get().map_err(DbError::from)?) .optional() { Ok(Some(link)) => Ok(Some(gpodder::SignupLink::from(link))), Ok(None) => Ok(None), Err(err) => Err(DbError::from(err).into()), } } fn insert_signup_link(&self, link: &gpodder::SignupLink) -> Result<(), AuthErr> { diesel::insert_into(signup_links::table) .values(SignupLink { id: link.id, created_at: link.time_created.timestamp(), }) .execute(&mut self.pool.get().map_err(DbError::from)?) .map_err(DbError::from)?; Ok(()) } fn remove_signup_link(&self, id: i64) -> Result { match diesel::delete(signup_links::table.filter(signup_links::id.eq(id))) .execute(&mut self.pool.get().map_err(DbError::from)?) { Ok(0) => Ok(false), Ok(_) => Ok(true), Err(err) => Err(DbError::from(err).into()), } } }