Add an in-memory storage adaptor

This commit is contained in:
Ben Grant 2023-05-15 18:18:26 +10:00
parent bf5bcf9992
commit 1a8db405de
9 changed files with 190 additions and 2 deletions

12
backend/Cargo.lock generated
View file

@ -616,7 +616,9 @@ dependencies = [
"bcrypt", "bcrypt",
"chrono", "chrono",
"common", "common",
"datastore-adaptor",
"dotenv", "dotenv",
"memory-adaptor",
"punycode", "punycode",
"rand", "rand",
"regex", "regex",
@ -1573,6 +1575,16 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memory-adaptor"
version = "0.1.0"
dependencies = [
"async-trait",
"chrono",
"common",
"tokio",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"

View file

@ -14,6 +14,8 @@ serde = { version = "1.0.162", features = ["derive"] }
tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] }
common = { path = "common" } common = { path = "common" }
sql-adaptor = { path = "adaptors/sql" } sql-adaptor = { path = "adaptors/sql" }
datastore-adaptor = { path = "adaptors/datastore" }
memory-adaptor = { path = "adaptors/memory" }
dotenv = "0.15.0" dotenv = "0.15.0"
serde_json = "1.0.96" serde_json = "1.0.96"
rand = "0.8.5" rand = "0.8.5"

View file

@ -0,0 +1,10 @@
[package]
name = "memory-adaptor"
version = "0.1.0"
edition = "2021"
[dependencies]
async-trait = "0.1.68"
chrono = "0.4.24"
common = { path = "../../common" }
tokio = { version = "1.28.1", features = ["rt-multi-thread"] }

View file

@ -0,0 +1,146 @@
use std::{collections::HashMap, error::Error, fmt::Display};
use async_trait::async_trait;
use chrono::Utc;
use common::{
adaptor::Adaptor,
event::{Event, EventDeletion},
person::Person,
stats::Stats,
};
use tokio::sync::Mutex;
struct State {
stats: Stats,
events: HashMap<String, Event>,
people: HashMap<(String, String), Person>,
}
pub struct MemoryAdaptor {
state: Mutex<State>,
}
#[async_trait]
impl Adaptor for MemoryAdaptor {
type Error = MemoryAdaptorError;
async fn get_stats(&self) -> Result<Stats, Self::Error> {
let state = self.state.lock().await;
Ok(state.stats.clone())
}
async fn increment_stat_event_count(&self) -> Result<i64, Self::Error> {
let mut state = self.state.lock().await;
state.stats.event_count += 1;
Ok(state.stats.event_count)
}
async fn increment_stat_person_count(&self) -> Result<i64, Self::Error> {
let mut state = self.state.lock().await;
state.stats.person_count += 1;
Ok(state.stats.person_count)
}
async fn get_people(&self, event_id: String) -> Result<Option<Vec<Person>>, Self::Error> {
let state = self.state.lock().await;
// Event doesn't exist
if state.events.get(&event_id).is_none() {
return Ok(None);
}
Ok(Some(
state
.people
.clone()
.into_iter()
.filter_map(|((p_event_id, _), p)| {
if p_event_id == event_id {
Some(p)
} else {
None
}
})
.collect(),
))
}
async fn upsert_person(&self, event_id: String, person: Person) -> Result<Person, Self::Error> {
let mut state = self.state.lock().await;
state
.people
.insert((event_id, person.name.clone()), person.clone());
Ok(person)
}
async fn get_event(&self, id: String) -> Result<Option<Event>, Self::Error> {
let mut state = self.state.lock().await;
let event = state.events.get(&id).cloned();
if let Some(mut event) = event.clone() {
event.visited_at = Utc::now();
state.events.insert(id, event);
}
Ok(event)
}
async fn create_event(&self, event: Event) -> Result<Event, Self::Error> {
let mut state = self.state.lock().await;
state.events.insert(event.id.clone(), event.clone());
Ok(event)
}
async fn delete_event(&self, id: String) -> Result<EventDeletion, Self::Error> {
let mut state = self.state.lock().await;
let mut person_count: u64 = state.people.len() as u64;
state.people = state
.people
.clone()
.into_iter()
.filter(|((event_id, _), _)| event_id != &id)
.collect();
person_count -= state.people.len() as u64;
state.events.remove(&id);
Ok(EventDeletion { id, person_count })
}
}
impl MemoryAdaptor {
pub async fn new() -> Self {
println!("🧠 Using in-memory storage");
println!("🚨 WARNING: All data will be lost when the process ends. Make sure you choose a database adaptor before deploying.");
let state = Mutex::new(State {
stats: Stats {
event_count: 0,
person_count: 0,
},
events: HashMap::new(),
people: HashMap::new(),
});
Self { state }
}
}
#[derive(Debug)]
pub enum MemoryAdaptorError {}
impl Display for MemoryAdaptorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Memory adaptor error")
}
}
impl Error for MemoryAdaptorError {}

View file

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[derive(Clone)]
pub struct Event { pub struct Event {
pub id: String, pub id: String,
pub name: String, pub name: String,
@ -9,6 +10,7 @@ pub struct Event {
pub timezone: String, pub timezone: String,
} }
#[derive(Clone)]
/// Info about a deleted event /// Info about a deleted event
pub struct EventDeletion { pub struct EventDeletion {
pub id: String, pub id: String,

View file

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[derive(Clone)]
pub struct Person { pub struct Person {
pub name: String, pub name: String,
pub password_hash: Option<String>, pub password_hash: Option<String>,

View file

@ -1,3 +1,4 @@
#[derive(Clone)]
pub struct Stats { pub struct Stats {
pub event_count: i64, pub event_count: i64,
pub person_count: i64, pub person_count: i64,

13
backend/src/adaptors.rs Normal file
View file

@ -0,0 +1,13 @@
#[cfg(feature = "sql-adaptor")]
pub async fn create_adaptor() -> sql_adaptor::SqlAdaptor {
sql_adaptor::SqlAdaptor::new().await
}
#[cfg(feature = "datastore-adaptor")]
pub async fn create_adaptor() -> datastore_adaptor::DatastoreAdaptor {
datastore_adaptor::DatastoreAdaptor::new().await
}
pub async fn create_adaptor() -> memory_adaptor::MemoryAdaptor {
memory_adaptor::MemoryAdaptor::new().await
}

View file

@ -8,7 +8,6 @@ use axum::{
BoxError, Router, Server, BoxError, Router, Server,
}; };
use routes::*; use routes::*;
use sql_adaptor::SqlAdaptor;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer};
@ -16,8 +15,10 @@ use tower_http::{cors::CorsLayer, trace::TraceLayer};
use utoipa::OpenApi; use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;
use crate::adaptors::create_adaptor;
use crate::docs::ApiDoc; use crate::docs::ApiDoc;
mod adaptors;
mod docs; mod docs;
mod errors; mod errors;
mod payloads; mod payloads;
@ -37,7 +38,7 @@ async fn main() {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
let shared_state = Arc::new(Mutex::new(ApiState { let shared_state = Arc::new(Mutex::new(ApiState {
adaptor: SqlAdaptor::new().await, adaptor: create_adaptor().await,
})); }));
// CORS configuration // CORS configuration