diff --git a/src/main.rs b/src/main.rs index 9c1f105..635de62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ extern crate serde; extern crate crypto; extern crate scrypt; #[macro_use] -#[cfg(test)] extern crate lazy_static; mod schema; @@ -31,9 +30,33 @@ use rocket::Rocket; use rocket::config::{Config, Environment, Value}; use std::collections::HashMap; use std::env; +use std::sync::RwLock; embed_migrations!(); +// We need a global RwLock for SQLite +// This is unfortunate when we still use SQLite +// but should be mostly fine for our purpose +lazy_static! { + pub static ref DB_LOCK: RwLock<()> = RwLock::new(()); +} + +#[macro_export] +macro_rules! lock_db_write { + () => { + crate::DB_LOCK.write() + .map_err(|_| "Cannot lock database for writing".into()) + }; +} + +#[macro_export] +macro_rules! lock_db_read { + () => { + crate::DB_LOCK.read() + .map_err(|_| "Cannot lock database for reading".into()) + }; +} + #[database("db")] pub struct DbConn(SqliteConnection); diff --git a/src/user.rs b/src/user.rs index cedbbd7..eb21354 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,5 +1,6 @@ use crate::schema::users; use crate::schema::users::dsl::*; +use crate::{lock_db_write, lock_db_read}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use serde::Deserialize; @@ -15,6 +16,12 @@ impl UserOpError { } } +impl Into for &str { + fn into(self) -> UserOpError { + UserOpError::new(self) + } +} + // Password should ALWAYS be hashed #[derive(Debug)] pub struct Password(String); @@ -104,19 +111,21 @@ impl User { match Self::find_user_by_email(db, &new_user.email) { Ok(_) => Err(UserOpError::new("User already registered")), - Err(_) => diesel::insert_into(users::table) - .values(user_hashed) - .execute(db) - .map(|_| ()) - .map_err(|_| UserOpError::new("Database error")) + Err(_) => lock_db_write!() + .and_then(|_| diesel::insert_into(users::table) + .values(user_hashed) + .execute(db) + .map(|_| ()) + .map_err(|_| UserOpError::new("Database error"))) } } pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result { - let mut results = users.filter(email.eq(user_email)) - .limit(1) - .load::(db) - .map_err(|_| UserOpError::new("Database error"))?; + let mut results = lock_db_read!() + .and_then(|_| users.filter(email.eq(user_email)) + .limit(1) + .load::(db) + .map_err(|_| UserOpError::new("Database error")))?; if results.is_empty() { Result::Err(UserOpError::new("No matching user found")) } else { @@ -157,11 +166,12 @@ impl User { // Update database // TODO: Maybe we should revoke all JWTs somehow? // maybe we can record when the user last changed? - diesel::update(users.find(self.id)) - .set(password.eq::(Password::new(new_passwd).into())) - .execute(db) - .map(|_| ()) - .map_err(|_| UserOpError::new("Database error")) + lock_db_write!() + .and_then(|_| diesel::update(users.find(self.id)) + .set(password.eq::(Password::new(new_passwd).into())) + .execute(db) + .map(|_| ()) + .map_err(|_| UserOpError::new("Database error"))) } } } \ No newline at end of file