Add an in-memory storage adaptor
This commit is contained in:
parent
bf5bcf9992
commit
1a8db405de
12
backend/Cargo.lock
generated
12
backend/Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
10
backend/adaptors/memory/Cargo.toml
Normal file
10
backend/adaptors/memory/Cargo.toml
Normal 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"] }
|
||||||
146
backend/adaptors/memory/src/lib.rs
Normal file
146
backend/adaptors/memory/src/lib.rs
Normal 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 {}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
|
|
|
||||||
|
|
@ -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
13
backend/src/adaptors.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue