diff options
Diffstat (limited to 'src/repo')
| -rw-r--r-- | src/repo/channel.rs | 179 | ||||
| -rw-r--r-- | src/repo/error.rs | 23 | ||||
| -rw-r--r-- | src/repo/login/extract.rs | 15 | ||||
| -rw-r--r-- | src/repo/login/mod.rs | 4 | ||||
| -rw-r--r-- | src/repo/login/store.rs | 86 | ||||
| -rw-r--r-- | src/repo/message.rs | 33 | ||||
| -rw-r--r-- | src/repo/mod.rs | 6 | ||||
| -rw-r--r-- | src/repo/pool.rs | 18 | ||||
| -rw-r--r-- | src/repo/token.rs | 159 |
9 files changed, 0 insertions, 523 deletions
diff --git a/src/repo/channel.rs b/src/repo/channel.rs deleted file mode 100644 index 3c7468f..0000000 --- a/src/repo/channel.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::fmt; - -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; - -use crate::{ - clock::DateTime, - events::types::{self, Sequence}, - id::Id as BaseId, -}; - -pub trait Provider { - fn channels(&mut self) -> Channels; -} - -impl<'c> Provider for Transaction<'c, Sqlite> { - fn channels(&mut self) -> Channels { - Channels(self) - } -} - -pub struct Channels<'t>(&'t mut SqliteConnection); - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct Channel { - pub id: Id, - pub name: String, - #[serde(skip)] - pub created_at: DateTime, -} - -impl<'c> Channels<'c> { - pub async fn create( - &mut self, - name: &str, - created_at: &DateTime, - ) -> Result<Channel, sqlx::Error> { - let id = Id::generate(); - let sequence = Sequence::default(); - - let channel = sqlx::query_as!( - Channel, - r#" - insert - into channel (id, name, created_at, last_sequence) - values ($1, $2, $3, $4) - returning - id as "id: Id", - name, - created_at as "created_at: DateTime" - "#, - id, - name, - created_at, - sequence, - ) - .fetch_one(&mut *self.0) - .await?; - - Ok(channel) - } - - pub async fn by_id(&mut self, channel: &Id) -> Result<Channel, sqlx::Error> { - let channel = sqlx::query_as!( - Channel, - r#" - select - id as "id: Id", - name, - created_at as "created_at: DateTime" - from channel - where id = $1 - "#, - channel, - ) - .fetch_one(&mut *self.0) - .await?; - - Ok(channel) - } - - pub async fn all(&mut self) -> Result<Vec<Channel>, sqlx::Error> { - let channels = sqlx::query_as!( - Channel, - r#" - select - id as "id: Id", - name, - created_at as "created_at: DateTime" - from channel - order by channel.name - "#, - ) - .fetch_all(&mut *self.0) - .await?; - - Ok(channels) - } - - pub async fn delete_expired( - &mut self, - channel: &Channel, - sequence: Sequence, - deleted_at: &DateTime, - ) -> Result<types::ChannelEvent, sqlx::Error> { - let channel = channel.id.clone(); - sqlx::query_scalar!( - r#" - delete from channel - where id = $1 - returning 1 as "row: i64" - "#, - channel, - ) - .fetch_one(&mut *self.0) - .await?; - - Ok(types::ChannelEvent { - sequence, - at: *deleted_at, - data: types::DeletedEvent { channel }.into(), - }) - } - - pub async fn expired(&mut self, expired_at: &DateTime) -> Result<Vec<Channel>, sqlx::Error> { - let channels = sqlx::query_as!( - Channel, - r#" - select - channel.id as "id: Id", - channel.name, - channel.created_at as "created_at: DateTime" - from channel - left join message - where created_at < $1 - and message.id is null - "#, - expired_at, - ) - .fetch_all(&mut *self.0) - .await?; - - Ok(channels) - } -} - -// Stable identifier for a [Channel]. Prefixed with `C`. -#[derive( - Clone, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - sqlx::Type, - serde::Deserialize, - serde::Serialize, -)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("C") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} diff --git a/src/repo/error.rs b/src/repo/error.rs deleted file mode 100644 index a5961e2..0000000 --- a/src/repo/error.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub trait NotFound { - type Ok; - fn not_found<E, F>(self, map: F) -> Result<Self::Ok, E> - where - E: From<sqlx::Error>, - F: FnOnce() -> E; -} - -impl<T> NotFound for Result<T, sqlx::Error> { - type Ok = T; - - fn not_found<E, F>(self, map: F) -> Result<T, E> - where - E: From<sqlx::Error>, - F: FnOnce() -> E, - { - match self { - Err(sqlx::Error::RowNotFound) => Err(map()), - Err(other) => Err(other.into()), - Ok(value) => Ok(value), - } - } -} diff --git a/src/repo/login/extract.rs b/src/repo/login/extract.rs deleted file mode 100644 index ab61106..0000000 --- a/src/repo/login/extract.rs +++ /dev/null @@ -1,15 +0,0 @@ -use axum::{extract::FromRequestParts, http::request::Parts}; - -use super::Login; -use crate::{app::App, login::extract::Identity}; - -#[async_trait::async_trait] -impl FromRequestParts<App> for Login { - type Rejection = <Identity as FromRequestParts<App>>::Rejection; - - async fn from_request_parts(parts: &mut Parts, state: &App) -> Result<Self, Self::Rejection> { - let identity = Identity::from_request_parts(parts, state).await?; - - Ok(identity.login) - } -} diff --git a/src/repo/login/mod.rs b/src/repo/login/mod.rs deleted file mode 100644 index a1b4c6f..0000000 --- a/src/repo/login/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod extract; -mod store; - -pub use self::store::{Id, Login, Provider}; diff --git a/src/repo/login/store.rs b/src/repo/login/store.rs deleted file mode 100644 index b485941..0000000 --- a/src/repo/login/store.rs +++ /dev/null @@ -1,86 +0,0 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; - -use crate::{id::Id as BaseId, password::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 `./extract.rs`). As a result, it -// can be used as an extractor for endpoints that want to require login, or for -// endpoints that need to behave differently depending on whether the client is -// or is not logged in. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -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<Login, sqlx::Error> { - 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) - } -} - -impl<'t> From<&'t mut SqliteConnection> for Logins<'t> { - fn from(tx: &'t mut SqliteConnection) -> Self { - Self(tx) - } -} - -// Stable identifier for a [Login]. Prefixed with `L`. -#[derive(Clone, Debug, Eq, PartialEq, sqlx::Type, serde::Serialize)] -#[sqlx(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("L") - } -} - -impl std::fmt::Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} diff --git a/src/repo/message.rs b/src/repo/message.rs deleted file mode 100644 index a1f73d5..0000000 --- a/src/repo/message.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::fmt; - -use crate::id::Id as BaseId; - -// Stable identifier for a [Message]. Prefixed with `M`. -#[derive(Clone, Debug, Eq, Hash, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("M") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct Message { - pub id: Id, - pub body: String, -} diff --git a/src/repo/mod.rs b/src/repo/mod.rs deleted file mode 100644 index cb9d7c8..0000000 --- a/src/repo/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod channel; -pub mod error; -pub mod login; -pub mod message; -pub mod pool; -pub mod token; diff --git a/src/repo/pool.rs b/src/repo/pool.rs deleted file mode 100644 index b4aa6fc..0000000 --- a/src/repo/pool.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::str::FromStr; - -use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; - -pub async fn prepare(url: &str) -> sqlx::Result<SqlitePool> { - let pool = create(url).await?; - sqlx::migrate!().run(&pool).await?; - Ok(pool) -} - -async fn create(database_url: &str) -> sqlx::Result<SqlitePool> { - let options = SqliteConnectOptions::from_str(database_url)? - .create_if_missing(true) - .optimize_on_close(true, /* analysis_limit */ None); - - let pool = SqlitePoolOptions::new().connect_with(options).await?; - Ok(pool) -} diff --git a/src/repo/token.rs b/src/repo/token.rs deleted file mode 100644 index d96c094..0000000 --- a/src/repo/token.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::fmt; - -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; -use uuid::Uuid; - -use super::login::{self, Login}; -use crate::{clock::DateTime, id::Id as BaseId, login::extract::IdentitySecret}; - -pub trait Provider { - fn tokens(&mut self) -> Tokens; -} - -impl<'c> Provider for Transaction<'c, Sqlite> { - fn tokens(&mut self) -> Tokens { - Tokens(self) - } -} - -pub struct Tokens<'t>(&'t mut SqliteConnection); - -impl<'c> Tokens<'c> { - // Issue a new token for an existing login. The issued_at timestamp will - // be used to control expiry, until the token is actually used. - pub async fn issue( - &mut self, - login: &Login, - issued_at: &DateTime, - ) -> Result<IdentitySecret, sqlx::Error> { - let id = Id::generate(); - let secret = Uuid::new_v4().to_string(); - - let secret = sqlx::query_scalar!( - r#" - insert - into token (id, secret, login, issued_at, last_used_at) - values ($1, $2, $3, $4, $4) - returning secret as "secret!: IdentitySecret" - "#, - id, - secret, - login.id, - issued_at, - ) - .fetch_one(&mut *self.0) - .await?; - - Ok(secret) - } - - // Revoke a token by its secret. - pub async fn revoke(&mut self, token: &Id) -> Result<(), sqlx::Error> { - sqlx::query_scalar!( - r#" - delete - from token - where id = $1 - returning id as "id: Id" - "#, - token, - ) - .fetch_one(&mut *self.0) - .await?; - - Ok(()) - } - - // Expire and delete all tokens that haven't been used more recently than - // `expire_at`. - pub async fn expire(&mut self, expire_at: &DateTime) -> Result<Vec<Id>, sqlx::Error> { - let tokens = sqlx::query_scalar!( - r#" - delete - from token - where last_used_at < $1 - returning id as "id: Id" - "#, - expire_at, - ) - .fetch_all(&mut *self.0) - .await?; - - Ok(tokens) - } - - // Validate a token by its secret, retrieving the associated Login record. - // Will return [None] if the token is not valid. The token's last-used - // timestamp will be set to `used_at`. - pub async fn validate( - &mut self, - secret: &IdentitySecret, - used_at: &DateTime, - ) -> Result<(Id, Login), sqlx::Error> { - // I would use `update … returning` to do this in one query, but - // sqlite3, as of this writing, does not allow an update's `returning` - // clause to reference columns from tables joined into the update. Two - // queries is fine, but it feels untidy. - sqlx::query!( - r#" - update token - set last_used_at = $1 - where secret = $2 - "#, - used_at, - secret, - ) - .execute(&mut *self.0) - .await?; - - let login = sqlx::query!( - r#" - select - token.id as "token_id: Id", - login.id as "login_id: login::Id", - name as "login_name" - from login - join token on login.id = token.login - where token.secret = $1 - "#, - secret, - ) - .map(|row| { - ( - row.token_id, - Login { - id: row.login_id, - name: row.login_name, - }, - ) - }) - .fetch_one(&mut *self.0) - .await?; - - Ok(login) - } -} - -// Stable identifier for a token. Prefixed with `T`. -#[derive(Clone, Debug, Eq, Hash, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("T") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} |
