From c2268c285648ef02ece04de0d9df0813c6d70ff8 Mon Sep 17 00:00:00 2001 From: benj Date: Sat, 24 Dec 2022 00:43:38 -0800 Subject: refactor everything with more abstraction and a nicer interface --- crates/secd/src/client/store/mod.rs | 190 ++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 crates/secd/src/client/store/mod.rs (limited to 'crates/secd/src/client/store/mod.rs') diff --git a/crates/secd/src/client/store/mod.rs b/crates/secd/src/client/store/mod.rs new file mode 100644 index 0000000..b93fd84 --- /dev/null +++ b/crates/secd/src/client/store/mod.rs @@ -0,0 +1,190 @@ +pub(crate) mod sql_db; + +use email_address::EmailAddress; +use sqlx::{Postgres, Sqlite}; +use std::sync::Arc; +use time::OffsetDateTime; +use uuid::Uuid; + +use crate::{ + util, Address, AddressType, AddressValidation, Identity, IdentityId, PhoneNumber, Session, + SessionToken, +}; + +use self::sql_db::SqlClient; + +#[derive(Debug, thiserror::Error, derive_more::Display)] +pub enum StoreError { + SqlClientError(#[from] sqlx::Error), + StoreValueCannotBeParsedInvariant, + IdempotentCheckAlreadyExists, +} + +#[async_trait::async_trait(?Send)] +pub trait Store { + fn get_type(&self) -> StoreType; +} + +pub enum StoreType { + Postgres { c: Arc> }, + Sqlite { c: Arc> }, +} + +#[async_trait::async_trait(?Send)] +pub(crate) trait Storable<'a> { + type Item; + type Lens; + + async fn write(&self, store: Arc) -> Result<(), StoreError>; + async fn find( + store: Arc, + lens: &'a Self::Lens, + ) -> Result, StoreError>; +} + +pub(crate) trait Lens {} + +pub(crate) struct AddressLens<'a> { + pub id: Option<&'a Uuid>, + pub t: Option<&'a AddressType>, +} +impl<'a> Lens for AddressLens<'a> {} + +pub(crate) struct AddressValidationLens<'a> { + pub id: Option<&'a Uuid>, +} +impl<'a> Lens for AddressValidationLens<'a> {} + +pub(crate) struct IdentityLens<'a> { + pub id: Option<&'a Uuid>, + pub address_type: Option<&'a AddressType>, + pub validated_address: Option, + pub session_token_hash: Option>, +} +impl<'a> Lens for IdentityLens<'a> {} + +pub(crate) struct SessionLens<'a> { + pub token_hash: Option<&'a Vec>, + pub identity_id: Option<&'a IdentityId>, +} +impl<'a> Lens for SessionLens<'a> {} + +#[async_trait::async_trait(?Send)] +impl<'a> Storable<'a> for Address { + type Item = Address; + type Lens = AddressLens<'a>; + + async fn write(&self, store: Arc) -> Result<(), StoreError> { + match store.get_type() { + StoreType::Postgres { c } => c.write_address(self).await?, + StoreType::Sqlite { c } => c.write_address(self).await?, + } + Ok(()) + } + async fn find( + store: Arc, + lens: &'a Self::Lens, + ) -> Result, StoreError> { + let typ = lens.t.map(|at| at.to_string().clone()); + let typ = typ.as_deref(); + + let val = lens.t.and_then(|at| at.get_value()); + let val = val.as_deref(); + + Ok(match store.get_type() { + StoreType::Postgres { c } => c.find_address(lens.id, typ, val).await?, + StoreType::Sqlite { c } => c.find_address(lens.id, typ, val).await?, + }) + } +} + +#[async_trait::async_trait(?Send)] +impl<'a> Storable<'a> for AddressValidation { + type Item = AddressValidation; + type Lens = AddressValidationLens<'a>; + + async fn write(&self, store: Arc) -> Result<(), StoreError> { + match store.get_type() { + StoreType::Sqlite { c } => c.write_address_validation(self).await?, + StoreType::Postgres { c } => c.write_address_validation(self).await?, + } + Ok(()) + } + async fn find( + store: Arc, + lens: &'a Self::Lens, + ) -> Result, StoreError> { + Ok(match store.get_type() { + StoreType::Postgres { c } => c.find_address_validation(lens.id).await?, + StoreType::Sqlite { c } => c.find_address_validation(lens.id).await?, + }) + } +} + +#[async_trait::async_trait(?Send)] +impl<'a> Storable<'a> for Identity { + type Item = Identity; + type Lens = IdentityLens<'a>; + + async fn write(&self, store: Arc) -> Result<(), StoreError> { + match store.get_type() { + StoreType::Postgres { c } => c.write_identity(self).await?, + StoreType::Sqlite { c } => c.write_identity(self).await?, + } + Ok(()) + } + async fn find( + store: Arc, + lens: &'a Self::Lens, + ) -> Result, StoreError> { + let val = lens.address_type.and_then(|at| at.get_value()); + let val = val.as_deref(); + + Ok(match store.get_type() { + StoreType::Postgres { c } => { + c.find_identity( + lens.id, + val, + lens.validated_address, + &lens.session_token_hash, + ) + .await? + } + StoreType::Sqlite { c } => { + c.find_identity( + lens.id, + val, + lens.validated_address, + &lens.session_token_hash, + ) + .await? + } + }) + } +} + +#[async_trait::async_trait(?Send)] +impl<'a> Storable<'a> for Session { + type Item = Session; + type Lens = SessionLens<'a>; + + async fn write(&self, store: Arc) -> Result<(), StoreError> { + let token_hash = util::hash(&self.token); + match store.get_type() { + StoreType::Postgres { c } => c.write_session(self, &token_hash).await?, + StoreType::Sqlite { c } => c.write_session(self, &token_hash).await?, + } + Ok(()) + } + async fn find( + store: Arc, + lens: &'a Self::Lens, + ) -> Result, StoreError> { + let token = lens.token_hash.map(|t| t.clone()).unwrap_or(vec![]); + + Ok(match store.get_type() { + StoreType::Postgres { c } => c.find_session(token, lens.identity_id).await?, + StoreType::Sqlite { c } => c.find_session(token, lens.identity_id).await?, + }) + } +} -- cgit v1.2.3