use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; use crate::error::BoxedError; use crate::id::Id as BaseId; use crate::login::app::StoredHash; pub trait Provider { fn logins(&mut self) -> Logins; } impl<'c> Provider for Transaction<'c, Sqlite> { fn logins(&mut self) -> Logins { Logins(self) } } pub struct Logins<'t>(&'t mut SqliteConnection); // This also implements FromRequestParts (see `src/login/extract/login.rs`). As // a result, it can be used as an extractor. #[derive(Debug)] pub struct Login { pub id: Id, pub name: String, // The omission of the hashed password is deliberate, to minimize the // chance that it ends up tangled up in debug output or in some other chunk // of logic elsewhere. } impl<'c> Logins<'c> { pub async fn create( &mut self, name: &str, password_hash: &StoredHash, ) -> Result { let id = Id::generate(); let login = sqlx::query_as!( Login, r#" insert or fail into login (id, name, password_hash) values ($1, $2, $3) returning id as "id: Id", name "#, id, name, password_hash, ) .fetch_one(&mut *self.0) .await?; Ok(login) } /// Retrieves a login by name, plus its stored password hash for /// verification. If there's no login with the requested name, this will /// return [None]. pub async fn for_login( &mut self, name: &str, ) -> Result, BoxedError> { let found = sqlx::query!( r#" select id as "id: Id", name, password_hash as "password_hash: StoredHash" from login where name = $1 "#, name, ) .map(|rec| { ( Login { id: rec.id, name: rec.name, }, rec.password_hash, ) }) .fetch_optional(&mut *self.0) .await?; Ok(found) } } /// Stable identifier for a [Login]. Prefixed with `L`. #[derive(Debug, sqlx::Type)] #[sqlx(transparent)] pub struct Id(BaseId); impl From for Id { fn from(id: BaseId) -> Self { Self(id) } } impl Id { pub fn generate() -> Self { BaseId::generate("L") } }