support multi user db initialize

This commit is contained in:
appflowy 2021-07-18 09:03:21 +08:00
parent 848975e7f4
commit a63d5f5eaf
7 changed files with 509 additions and 276 deletions

View File

@ -26,6 +26,10 @@ lazy_static = "1.4.0"
fancy-regex = "0.5.0" fancy-regex = "0.5.0"
diesel = {version = "1.4.7", features = ["sqlite"]} diesel = {version = "1.4.7", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]} diesel_derives = {version = "1.4.1", features = ["sqlite"]}
thread_local = "1.1.3"
thread-id = "3.3.0"
once_cell = "1.7.2"
parking_lot = "0.11"
[dev-dependencies] [dev-dependencies]
quickcheck = "0.9.2" quickcheck = "0.9.2"

View File

@ -1,8 +1,11 @@
use crate::errors::{ErrorBuilder, UserError, UserErrorCode}; use crate::errors::{ErrorBuilder, UserError, UserErrorCode};
use flowy_database::{DBConnection, Database}; use flowy_database::{DBConnection, Database};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashMap,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
RwLock, RwLock,
@ -25,7 +28,8 @@ impl UserDB {
} }
fn open_user_db(&self, user_id: &str) -> Result<(), UserError> { fn open_user_db(&self, user_id: &str) -> Result<(), UserError> {
INIT_FLAG.store(true, Ordering::SeqCst); set_user_db_init(true, user_id);
let dir = format!("{}/{}", self.db_dir, user_id); let dir = format!("{}/{}", self.db_dir, user_id);
let db = flowy_database::init(&dir).map_err(|e| { let db = flowy_database::init(&dir).map_err(|e| {
ErrorBuilder::new(UserErrorCode::DatabaseInitFailed) ErrorBuilder::new(UserErrorCode::DatabaseInitFailed)
@ -33,58 +37,41 @@ impl UserDB {
.build() .build()
})?; })?;
let mut user_db = DB.write().map_err(|e| { let mut db_map = DB_MAP.write().map_err(|e| {
ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked) ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked)
.error(e) .error(e)
.build() .build()
})?; })?;
*(user_db) = Some(db);
set_user_id(Some(user_id.to_owned())); db_map.insert(user_id.to_owned(), db);
Ok(()) Ok(())
} }
pub(crate) fn close_user_db(&self) -> Result<(), UserError> { pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), UserError> {
INIT_FLAG.store(false, Ordering::SeqCst); set_user_db_init(false, user_id);
let mut write_guard = DB.write().map_err(|e| { let mut db_map = DB_MAP.write().map_err(|e| {
ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked) ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked)
.msg(format!("Close user db failed. {:?}", e)) .msg(format!("Close user db failed. {:?}", e))
.build() .build()
})?; })?;
*write_guard = None; db_map.remove(user_id);
set_user_id(None);
Ok(()) Ok(())
} }
pub(crate) fn get_connection(&self, user_id: &str) -> Result<DBConnection, UserError> { pub(crate) fn get_connection(&self, user_id: &str) -> Result<DBConnection, UserError> {
if !INIT_FLAG.load(Ordering::SeqCst) { if !is_user_db_init(user_id) {
let _ = self.open_user_db(user_id); let _ = self.open_user_db(user_id);
} }
let thread_user_id = get_user_id(); let db_map = DB_MAP.read().map_err(|e| {
if thread_user_id.is_some() {
if thread_user_id != Some(user_id.to_owned()) {
let msg = format!(
"Database owner does not match. origin: {:?}, current: {}",
thread_user_id, user_id
);
log::error!("{}", msg);
return Err(ErrorBuilder::new(UserErrorCode::DatabaseUserDidNotMatch)
.msg(msg)
.build());
}
}
let read_guard = DB.read().map_err(|e| {
ErrorBuilder::new(UserErrorCode::DatabaseReadLocked) ErrorBuilder::new(UserErrorCode::DatabaseReadLocked)
.error(e) .error(e)
.build() .build()
})?; })?;
match read_guard.as_ref() {
match db_map.get(user_id) {
None => Err(ErrorBuilder::new(UserErrorCode::DatabaseInitFailed) None => Err(ErrorBuilder::new(UserErrorCode::DatabaseInitFailed)
.msg("Database is not initialization") .msg("Database is not initialization")
.build()), .build()),
@ -93,17 +80,24 @@ impl UserDB {
} }
} }
thread_local! { lazy_static! {
static USER_ID: RefCell<Option<String>> = RefCell::new(None); static ref DB_MAP: RwLock<HashMap<String, Database>> = RwLock::new(HashMap::new());
} }
fn set_user_id(user_id: Option<String>) {
USER_ID.with(|id| {
*id.borrow_mut() = user_id;
});
}
fn get_user_id() -> Option<String> { USER_ID.with(|id| id.borrow().clone()) }
static INIT_FLAG: AtomicBool = AtomicBool::new(false); static INIT_FLAG_MAP: Lazy<Mutex<HashMap<String, bool>>> = Lazy::new(|| Mutex::new(HashMap::new()));
fn set_user_db_init(is_init: bool, user_id: &str) {
INIT_FLAG_MAP
.lock()
.entry(user_id.to_owned())
.or_insert_with(|| is_init);
}
fn is_user_db_init(user_id: &str) -> bool {
match INIT_FLAG_MAP.lock().get(user_id) {
None => false,
Some(flag) => flag.clone(),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -75,7 +75,7 @@ impl UserSession {
Ok(_) => {}, Ok(_) => {},
Err(_) => {}, Err(_) => {},
} }
let _ = self.database.close_user_db()?; let _ = self.database.close_user_db(&user_id)?;
let _ = set_current_user_id(None)?; let _ = set_current_user_id(None)?;
Ok(()) Ok(())

View File

@ -20,6 +20,7 @@ fn sign_in_success() {
} }
#[test] #[test]
#[serial]
fn sign_in_with_invalid_email() { fn sign_in_with_invalid_email() {
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let request = SignInRequest { let request = SignInRequest {
@ -40,6 +41,7 @@ fn sign_in_with_invalid_email() {
} }
#[test] #[test]
#[serial]
fn sign_in_with_invalid_password() { fn sign_in_with_invalid_password() {
for password in invalid_password_test_case() { for password in invalid_password_test_case() {
let request = SignInRequest { let request = SignInRequest {
@ -58,3 +60,233 @@ fn sign_in_with_invalid_password() {
); );
} }
} }
#[test]
#[serial]
fn sign_up_success() {
let _ = UserTestBuilder::new().event(SignOut).sync_send();
let request = SignUpRequest {
email: random_valid_email(),
name: valid_name(),
password: valid_password(),
};
let _response = UserTestBuilder::new()
.logout()
.event(SignUp)
.request(request)
.sync_send();
}
#[test]
#[serial]
fn sign_up_with_invalid_email() {
for email in invalid_email_test_case() {
let request = SignUpRequest {
email: email.to_string(),
name: valid_name(),
password: valid_password(),
};
assert_eq!(
UserTestBuilder::new()
.event(SignUp)
.request(request)
.sync_send()
.error()
.code,
UserErrorCode::EmailInvalid
);
}
}
#[test]
#[serial]
fn sign_up_with_invalid_password() {
for password in invalid_password_test_case() {
let request = SignUpRequest {
email: random_valid_email(),
name: valid_name(),
password,
};
assert_eq!(
UserTestBuilder::new()
.event(SignUp)
.request(request)
.sync_send()
.error()
.code,
UserErrorCode::PasswordInvalid
);
}
}
#[test]
#[should_panic]
#[serial]
fn user_status_get_failed_before_login() {
let _ = UserTestBuilder::new()
.logout()
.event(GetStatus)
.sync_send()
.parse::<UserDetail>();
}
#[test]
#[serial]
fn user_status_get_success_after_login() {
let request = SignInRequest {
email: random_valid_email(),
password: valid_password(),
};
let response = UserTestBuilder::new()
.logout()
.event(SignIn)
.request(request)
.sync_send()
.parse::<UserDetail>();
dbg!(&response);
let _ = UserTestBuilder::new()
.event(GetStatus)
.sync_send()
.parse::<UserDetail>();
}
#[test]
#[serial]
fn user_update_with_name() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_name = "hello_world".to_owned();
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: Some(new_name.clone()),
email: None,
workspace: None,
password: None,
};
let user_detail = UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.parse::<UserDetail>();
assert_eq!(user_detail.name, new_name,);
}
#[test]
#[serial]
fn user_update_with_email() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_email = "123@gmai.com".to_owned();
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: None,
email: Some(new_email.clone()),
workspace: None,
password: None,
};
let user_detail = UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.parse::<UserDetail>();
assert_eq!(user_detail.email, new_email,);
}
#[test]
#[serial]
fn user_update_with_password() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_password = "H123world!".to_owned();
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: None,
email: None,
workspace: None,
password: Some(new_password.clone()),
};
let _ = UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.assert_success();
}
#[test]
#[serial]
fn user_update_with_invalid_email() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
for email in invalid_email_test_case() {
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: None,
email: Some(email),
workspace: None,
password: None,
};
assert_eq!(
UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.error()
.code,
UserErrorCode::EmailInvalid
);
}
}
#[test]
#[serial]
fn user_update_with_invalid_password() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
for password in invalid_password_test_case() {
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: None,
email: None,
workspace: None,
password: Some(password),
};
assert_eq!(
UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.error()
.code,
UserErrorCode::PasswordInvalid
);
}
}
#[test]
#[serial]
fn user_update_with_invalid_name() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let request = UpdateUserRequest {
id: user_detail.id.clone(),
name: Some("".to_string()),
email: None,
workspace: None,
password: None,
};
assert_eq!(
UserTestBuilder::new()
.event(UpdateUser)
.request(request)
.sync_send()
.error()
.code,
UserErrorCode::UserNameInvalid
);
}

View File

@ -1,62 +1,65 @@
use crate::helper::*; // use crate::helper::*;
use flowy_user::{errors::*, event::UserEvent::*, prelude::*}; // use flowy_user::{errors::*, event::UserEvent::*, prelude::*};
use serial_test::*; // use serial_test::*;
//
#[test] // #[test]
#[serial] // #[serial]
fn sign_up_success() { // fn sign_up_success() {
let _ = UserTestBuilder::new().event(SignOut).sync_send(); // let _ = UserTestBuilder::new().event(SignOut).sync_send();
let request = SignUpRequest { // let request = SignUpRequest {
email: random_valid_email(), // email: random_valid_email(),
name: valid_name(), // name: valid_name(),
password: valid_password(), // password: valid_password(),
}; // };
//
let _response = UserTestBuilder::new() // let _response = UserTestBuilder::new()
.event(SignUp) // .logout()
.request(request) // .event(SignUp)
.sync_send(); // .request(request)
// .parse::<SignUpResponse>(); // .sync_send();
// dbg!(&response); // // .parse::<SignUpResponse>();
} // // dbg!(&response);
// }
#[test] //
fn sign_up_with_invalid_email() { // #[test]
for email in invalid_email_test_case() { // #[serial]
let request = SignUpRequest { // fn sign_up_with_invalid_email() {
email: email.to_string(), // for email in invalid_email_test_case() {
name: valid_name(), // let request = SignUpRequest {
password: valid_password(), // email: email.to_string(),
}; // name: valid_name(),
// password: valid_password(),
assert_eq!( // };
UserTestBuilder::new() //
.event(SignUp) // assert_eq!(
.request(request) // UserTestBuilder::new()
.sync_send() // .event(SignUp)
.error() // .request(request)
.code, // .sync_send()
UserErrorCode::EmailInvalid // .error()
); // .code,
} // UserErrorCode::EmailInvalid
} // );
#[test] // }
fn sign_up_with_invalid_password() { // }
for password in invalid_password_test_case() { // #[test]
let request = SignUpRequest { // #[serial]
email: random_valid_email(), // fn sign_up_with_invalid_password() {
name: valid_name(), // for password in invalid_password_test_case() {
password, // let request = SignUpRequest {
}; // email: random_valid_email(),
// name: valid_name(),
assert_eq!( // password,
UserTestBuilder::new() // };
.event(SignUp) //
.request(request) // assert_eq!(
.sync_send() // UserTestBuilder::new()
.error() // .event(SignUp)
.code, // .request(request)
UserErrorCode::PasswordInvalid // .sync_send()
); // .error()
} // .code,
} // UserErrorCode::PasswordInvalid
// );
// }
// }

View File

@ -1,36 +1,36 @@
use crate::helper::*; // use crate::helper::*;
use flowy_user::{event::UserEvent::*, prelude::*}; // use flowy_user::{event::UserEvent::*, prelude::*};
use serial_test::*; // use serial_test::*;
//
#[test] // #[test]
#[should_panic] // #[should_panic]
#[serial] // #[serial]
fn user_status_get_failed_before_login() { // fn user_status_get_failed_before_login() {
let _ = UserTestBuilder::new() // let _ = UserTestBuilder::new()
.logout() // .logout()
.event(GetStatus) // .event(GetStatus)
.sync_send() // .sync_send()
.parse::<UserDetail>(); // .parse::<UserDetail>();
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_status_get_success_after_login() { // fn user_status_get_success_after_login() {
let request = SignInRequest { // let request = SignInRequest {
email: random_valid_email(), // email: random_valid_email(),
password: valid_password(), // password: valid_password(),
}; // };
//
let response = UserTestBuilder::new() // let response = UserTestBuilder::new()
.logout() // .logout()
.event(SignIn) // .event(SignIn)
.request(request) // .request(request)
.sync_send() // .sync_send()
.parse::<UserDetail>(); // .parse::<UserDetail>();
dbg!(&response); // dbg!(&response);
//
let _ = UserTestBuilder::new() // let _ = UserTestBuilder::new()
.event(GetStatus) // .event(GetStatus)
.sync_send() // .sync_send()
.parse::<UserDetail>(); // .parse::<UserDetail>();
} // }

View File

@ -1,140 +1,140 @@
use crate::helper::*; // use crate::helper::*;
use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*}; // use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
use serial_test::*; // use serial_test::*;
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_name() { // fn user_update_with_name() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_name = "hello_world".to_owned(); // let new_name = "hello_world".to_owned();
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: Some(new_name.clone()), // name: Some(new_name.clone()),
email: None, // email: None,
workspace: None, // workspace: None,
password: None, // password: None,
}; // };
//
let user_detail = UserTestBuilder::new() // let user_detail = UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.parse::<UserDetail>(); // .parse::<UserDetail>();
//
assert_eq!(user_detail.name, new_name,); // assert_eq!(user_detail.name, new_name,);
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_email() { // fn user_update_with_email() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_email = "123@gmai.com".to_owned(); // let new_email = "123@gmai.com".to_owned();
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: None, // name: None,
email: Some(new_email.clone()), // email: Some(new_email.clone()),
workspace: None, // workspace: None,
password: None, // password: None,
}; // };
//
let user_detail = UserTestBuilder::new() // let user_detail = UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.parse::<UserDetail>(); // .parse::<UserDetail>();
//
assert_eq!(user_detail.email, new_email,); // assert_eq!(user_detail.email, new_email,);
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_password() { // fn user_update_with_password() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let new_password = "H123world!".to_owned(); // let new_password = "H123world!".to_owned();
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: None, // name: None,
email: None, // email: None,
workspace: None, // workspace: None,
password: Some(new_password.clone()), // password: Some(new_password.clone()),
}; // };
//
let _ = UserTestBuilder::new() // let _ = UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.assert_success(); // .assert_success();
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_invalid_email() { // fn user_update_with_invalid_email() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
for email in invalid_email_test_case() { // for email in invalid_email_test_case() {
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: None, // name: None,
email: Some(email), // email: Some(email),
workspace: None, // workspace: None,
password: None, // password: None,
}; // };
//
assert_eq!( // assert_eq!(
UserTestBuilder::new() // UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.error() // .error()
.code, // .code,
UserErrorCode::EmailInvalid // UserErrorCode::EmailInvalid
); // );
} // }
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_invalid_password() { // fn user_update_with_invalid_password() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
for password in invalid_password_test_case() { // for password in invalid_password_test_case() {
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: None, // name: None,
email: None, // email: None,
workspace: None, // workspace: None,
password: Some(password), // password: Some(password),
}; // };
//
assert_eq!( // assert_eq!(
UserTestBuilder::new() // UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.error() // .error()
.code, // .code,
UserErrorCode::PasswordInvalid // UserErrorCode::PasswordInvalid
); // );
} // }
} // }
//
#[test] // #[test]
#[serial] // #[serial]
fn user_update_with_invalid_name() { // fn user_update_with_invalid_name() {
let user_detail = UserTestBuilder::new().login().user_detail.unwrap(); // let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
let request = UpdateUserRequest { // let request = UpdateUserRequest {
id: user_detail.id.clone(), // id: user_detail.id.clone(),
name: Some("".to_string()), // name: Some("".to_string()),
email: None, // email: None,
workspace: None, // workspace: None,
password: None, // password: None,
}; // };
//
assert_eq!( // assert_eq!(
UserTestBuilder::new() // UserTestBuilder::new()
.event(UpdateUser) // .event(UpdateUser)
.request(request) // .request(request)
.sync_send() // .sync_send()
.error() // .error()
.code, // .code,
UserErrorCode::UserNameInvalid // UserErrorCode::UserNameInvalid
); // );
} // }