use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use super::{Id, Login}; use crate::{ db::NotFound, name::{self, Name}, password::StoredHash, }; pub trait Provider { fn logins(&mut self) -> Logins<'_>; } impl Provider for Transaction<'_, Sqlite> { fn logins(&mut self) -> Logins<'_> { Logins(self) } } pub struct Logins<'t>(&'t mut SqliteConnection); impl Logins<'_> { pub async fn create( &mut self, login: &Login, password: &StoredHash, ) -> Result<(), sqlx::Error> { let Login { id, name } = login; let display_name = name.display(); let canonical_name = name.canonical(); sqlx::query!( r#" insert into login (id, display_name, canonical_name, password) values ($1, $2, $3, $4) "#, id, display_name, canonical_name, password, ) .execute(&mut *self.0) .await?; Ok(()) } pub async fn by_id(&mut self, id: &Id) -> Result<(Login, StoredHash), LoadError> { let user = sqlx::query!( r#" select id as "id: Id", display_name, canonical_name, password as "password: StoredHash" from login where id = $1 "#, id, ) .map(|row| { Ok::<_, LoadError>(( Login { id: row.id, name: Name::new(row.display_name, row.canonical_name)?, }, row.password, )) }) .fetch_one(&mut *self.0) .await??; Ok(user) } pub async fn by_name(&mut self, name: &Name) -> Result<(Login, StoredHash), LoadError> { let canonical_name = name.canonical(); let (login, password) = sqlx::query!( r#" select id as "id: Id", display_name, canonical_name, password as "password: StoredHash" from login where canonical_name = $1 "#, canonical_name, ) .map(|row| { Ok::<_, LoadError>(( Login { id: row.id, name: Name::new(row.display_name, row.canonical_name)?, }, row.password, )) }) .fetch_one(&mut *self.0) .await??; Ok((login, password)) } pub async fn set_password( &mut self, login: &Login, password: &StoredHash, ) -> Result<(), sqlx::Error> { sqlx::query!( r#" update login set password = $1 where id = $2 "#, password, login.id, ) .execute(&mut *self.0) .await?; Ok(()) } } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub enum LoadError { Database(#[from] sqlx::Error), Name(#[from] name::Error), } impl NotFound for Result { type Ok = T; type Error = LoadError; fn optional(self) -> Result, LoadError> { match self { Ok(value) => Ok(Some(value)), Err(LoadError::Database(sqlx::Error::RowNotFound)) => Ok(None), Err(other) => Err(other), } } }