add global RwLock for SQLite

now tests pass
This commit is contained in:
Peter Cai 2020-02-21 09:02:52 +08:00
parent f611c5378d
commit 2893bbd595
No known key found for this signature in database
GPG key ID: 71F5FB4E4F3FD54F
2 changed files with 48 additions and 15 deletions

View file

@ -14,7 +14,6 @@ extern crate serde;
extern crate crypto; extern crate crypto;
extern crate scrypt; extern crate scrypt;
#[macro_use] #[macro_use]
#[cfg(test)]
extern crate lazy_static; extern crate lazy_static;
mod schema; mod schema;
@ -31,9 +30,33 @@ use rocket::Rocket;
use rocket::config::{Config, Environment, Value}; use rocket::config::{Config, Environment, Value};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::sync::RwLock;
embed_migrations!(); 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")] #[database("db")]
pub struct DbConn(SqliteConnection); pub struct DbConn(SqliteConnection);

View file

@ -1,5 +1,6 @@
use crate::schema::users; use crate::schema::users;
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
use crate::{lock_db_write, lock_db_read};
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sqlite::SqliteConnection; use diesel::sqlite::SqliteConnection;
use serde::Deserialize; use serde::Deserialize;
@ -15,6 +16,12 @@ impl UserOpError {
} }
} }
impl Into<UserOpError> for &str {
fn into(self) -> UserOpError {
UserOpError::new(self)
}
}
// Password should ALWAYS be hashed // Password should ALWAYS be hashed
#[derive(Debug)] #[derive(Debug)]
pub struct Password(String); pub struct Password(String);
@ -104,19 +111,21 @@ impl User {
match Self::find_user_by_email(db, &new_user.email) { match Self::find_user_by_email(db, &new_user.email) {
Ok(_) => Err(UserOpError::new("User already registered")), Ok(_) => Err(UserOpError::new("User already registered")),
Err(_) => diesel::insert_into(users::table) Err(_) => lock_db_write!()
.values(user_hashed) .and_then(|_| diesel::insert_into(users::table)
.execute(db) .values(user_hashed)
.map(|_| ()) .execute(db)
.map_err(|_| UserOpError::new("Database error")) .map(|_| ())
.map_err(|_| UserOpError::new("Database error")))
} }
} }
pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result<User, UserOpError> { pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result<User, UserOpError> {
let mut results = users.filter(email.eq(user_email)) let mut results = lock_db_read!()
.limit(1) .and_then(|_| users.filter(email.eq(user_email))
.load::<UserQuery>(db) .limit(1)
.map_err(|_| UserOpError::new("Database error"))?; .load::<UserQuery>(db)
.map_err(|_| UserOpError::new("Database error")))?;
if results.is_empty() { if results.is_empty() {
Result::Err(UserOpError::new("No matching user found")) Result::Err(UserOpError::new("No matching user found"))
} else { } else {
@ -157,11 +166,12 @@ impl User {
// Update database // Update database
// TODO: Maybe we should revoke all JWTs somehow? // TODO: Maybe we should revoke all JWTs somehow?
// maybe we can record when the user last changed? // maybe we can record when the user last changed?
diesel::update(users.find(self.id)) lock_db_write!()
.set(password.eq::<String>(Password::new(new_passwd).into())) .and_then(|_| diesel::update(users.find(self.id))
.execute(db) .set(password.eq::<String>(Password::new(new_passwd).into()))
.map(|_| ()) .execute(db)
.map_err(|_| UserOpError::new("Database error")) .map(|_| ())
.map_err(|_| UserOpError::new("Database error")))
} }
} }
} }