otter/gpodder_sqlite/src/repository/auth.rs

219 lines
7.0 KiB
Rust

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<User> 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<SignupLink> 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<Option<gpodder::models::User>, 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<gpodder::User, AuthErr> {
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<Option<gpodder::models::Session>, 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<chrono::Utc>,
) -> 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<chrono::Utc>) -> Result<usize, AuthErr> {
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<Vec<gpodder::Session>, 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<Vec<gpodder::User>, 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::<Result<Vec<_>, _>>()?,
)
})()
.map_err(AuthErr::from)
}
fn get_signup_link(&self, id: i64) -> Result<Option<gpodder::SignupLink>, 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<bool, AuthErr> {
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()),
}
}
}