AppFlowy/frontend/rust-lib/flowy-user/src/services/user_session.rs

352 lines
11 KiB
Rust
Raw Normal View History

2022-07-04 06:53:35 +00:00
use crate::entities::{
2022-11-11 09:24:10 +00:00
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, UserSettingPB,
2022-07-04 06:53:35 +00:00
};
2022-01-11 05:34:45 +00:00
use crate::{
2022-01-13 03:16:26 +00:00
dart_notification::*,
2022-01-11 05:34:45 +00:00
errors::{ErrorCode, FlowyError},
2022-01-28 02:56:55 +00:00
event_map::UserCloudService,
2022-01-11 05:34:45 +00:00
services::{
database::{UserDB, UserTable, UserTableChangeset},
notifier::UserNotifier,
},
};
2022-03-19 08:23:34 +00:00
use flowy_database::ConnectionPool;
use flowy_database::{
2021-12-12 13:18:23 +00:00
kv::KV,
query_dsl::*,
schema::{user_table, user_table::dsl},
2022-01-23 04:14:00 +00:00
DBConnection, ExpressionMethods, UserDatabaseConnection,
};
2022-01-10 15:45:59 +00:00
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::mpsc;
pub struct UserSessionConfig {
root_dir: String,
feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 03:14:42 +00:00
/// Used as the key of `Session` when saving session information to KV.
2021-11-09 09:50:32 +00:00
session_cache_key: String,
}
impl UserSessionConfig {
feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 03:14:42 +00:00
/// The `root_dir` represents as the root of the user folders. It must be unique for each
/// users.
pub fn new(name: &str, root_dir: &str) -> Self {
let session_cache_key = format!("{}_session_cache", name);
Self {
root_dir: root_dir.to_owned(),
feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 03:14:42 +00:00
session_cache_key,
}
}
}
pub struct UserSession {
database: UserDB,
config: UserSessionConfig,
2022-01-10 15:45:59 +00:00
cloud_service: Arc<dyn UserCloudService>,
2021-12-04 15:54:14 +00:00
pub notifier: UserNotifier,
}
impl UserSession {
2022-01-10 15:45:59 +00:00
pub fn new(config: UserSessionConfig, cloud_service: Arc<dyn UserCloudService>) -> Self {
let db = UserDB::new(&config.root_dir);
2021-12-04 15:54:14 +00:00
let notifier = UserNotifier::new();
2021-11-27 11:19:41 +00:00
Self {
database: db,
config,
2022-01-10 15:45:59 +00:00
cloud_service,
2021-12-04 15:54:14 +00:00
notifier,
2021-11-27 11:19:41 +00:00
}
}
pub fn init(&self) {
2021-11-27 11:19:41 +00:00
if let Ok(session) = self.get_session() {
2022-01-07 15:00:23 +00:00
self.notifier.notify_login(&session.token, &session.user_id);
}
}
2021-12-14 10:04:51 +00:00
pub fn db_connection(&self) -> Result<DBConnection, FlowyError> {
let user_id = self.get_session()?.user_id;
2021-07-16 15:18:12 +00:00
self.database.get_connection(&user_id)
}
// The caller will be not 'Sync' before of the return value,
// PooledConnection<ConnectionManager> is not sync. You can use
// db_connection_pool function to require the ConnectionPool that is 'Sync'.
//
// let pool = self.db_connection_pool()?;
// let conn: PooledConnection<ConnectionManager> = pool.get()?;
2021-12-14 10:04:51 +00:00
pub fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError> {
let user_id = self.get_session()?.user_id;
2021-08-31 15:01:46 +00:00
self.database.get_pool(&user_id)
}
2021-09-06 08:18:34 +00:00
#[tracing::instrument(level = "debug", skip(self))]
2022-07-19 06:40:56 +00:00
pub async fn sign_in(&self, params: SignInParams) -> Result<UserProfilePB, FlowyError> {
2022-01-24 08:27:40 +00:00
if self.is_user_login(&params.email) {
2022-07-04 02:59:08 +00:00
self.get_user_profile().await
} else {
2022-01-10 15:45:59 +00:00
let resp = self.cloud_service.sign_in(params).await?;
2021-12-09 13:39:53 +00:00
let session: Session = resp.clone().into();
let _ = self.set_session(Some(session))?;
let user_table = self.save_user(resp.into()).await?;
2022-07-19 06:40:56 +00:00
let user_profile: UserProfilePB = user_table.into();
2022-01-07 15:00:23 +00:00
self.notifier.notify_login(&user_profile.token, &user_profile.id);
2021-09-04 08:53:58 +00:00
Ok(user_profile)
}
}
2021-09-06 08:18:34 +00:00
#[tracing::instrument(level = "debug", skip(self))]
2022-07-19 06:40:56 +00:00
pub async fn sign_up(&self, params: SignUpParams) -> Result<UserProfilePB, FlowyError> {
2022-01-24 08:27:40 +00:00
if self.is_user_login(&params.email) {
2022-07-04 02:59:08 +00:00
self.get_user_profile().await
2021-09-04 01:00:15 +00:00
} else {
2022-01-10 15:45:59 +00:00
let resp = self.cloud_service.sign_up(params).await?;
2021-12-09 13:39:53 +00:00
let session: Session = resp.clone().into();
2021-09-04 01:00:15 +00:00
let _ = self.set_session(Some(session))?;
let user_table = self.save_user(resp.into()).await?;
2022-07-19 06:40:56 +00:00
let user_profile: UserProfilePB = user_table.into();
let (ret, mut tx) = mpsc::channel(1);
2021-12-04 15:54:14 +00:00
self.notifier.notify_sign_up(ret, &user_profile);
let _ = tx.recv().await;
2021-09-04 08:53:58 +00:00
Ok(user_profile)
2021-09-04 01:00:15 +00:00
}
}
2021-09-06 08:18:34 +00:00
#[tracing::instrument(level = "debug", skip(self))]
2021-12-14 10:04:51 +00:00
pub async fn sign_out(&self) -> Result<(), FlowyError> {
2021-09-01 08:08:32 +00:00
let session = self.get_session()?;
2021-09-27 15:23:23 +00:00
let _ =
diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_connection()?))?;
2021-09-01 08:08:32 +00:00
let _ = self.database.close_user_db(&session.user_id)?;
let _ = self.set_session(None)?;
feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 03:14:42 +00:00
self.notifier.notify_logout(&session.token, &session.user_id);
2021-09-04 09:26:04 +00:00
let _ = self.sign_out_on_server(&session.token).await?;
2021-07-11 09:38:03 +00:00
Ok(())
}
2021-09-06 08:18:34 +00:00
#[tracing::instrument(level = "debug", skip(self))]
pub async fn update_user_profile(&self, params: UpdateUserProfileParams) -> Result<(), FlowyError> {
2021-09-01 09:57:06 +00:00
let session = self.get_session()?;
2021-09-04 09:26:04 +00:00
let changeset = UserTableChangeset::new(params.clone());
diesel_update_table!(user_table, changeset, &*self.db_connection()?);
2021-09-01 09:57:06 +00:00
2022-07-04 02:59:08 +00:00
let user_profile = self.get_user_profile().await?;
dart_notify(&session.token, UserNotification::UserProfileUpdated)
.payload(user_profile)
.send();
2021-09-04 09:26:04 +00:00
let _ = self.update_user_on_server(&session.token, params).await?;
2021-08-31 15:01:46 +00:00
Ok(())
}
2022-01-23 04:14:00 +00:00
pub async fn init_user(&self) -> Result<(), FlowyError> {
Ok(())
}
2022-07-19 06:40:56 +00:00
pub async fn check_user(&self) -> Result<UserProfilePB, FlowyError> {
2021-09-17 11:03:46 +00:00
let (user_id, token) = self.get_session()?.into_part();
let user = dsl::user_table
.filter(user_table::id.eq(&user_id))
.first::<UserTable>(&*(self.db_connection()?))?;
let _ = self.read_user_profile_on_server(&token)?;
Ok(user.into())
2021-09-17 11:03:46 +00:00
}
2022-07-19 06:40:56 +00:00
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {
2021-09-07 15:30:43 +00:00
let (user_id, token) = self.get_session()?.into_part();
2021-09-01 08:37:46 +00:00
let user = dsl::user_table
2021-09-07 15:30:43 +00:00
.filter(user_table::id.eq(&user_id))
.first::<UserTable>(&*(self.db_connection()?))?;
2021-09-01 08:37:46 +00:00
2021-09-17 11:03:46 +00:00
let _ = self.read_user_profile_on_server(&token)?;
Ok(user.into())
}
2021-12-14 10:04:51 +00:00
pub fn user_dir(&self) -> Result<String, FlowyError> {
let session = self.get_session()?;
Ok(format!("{}/{}", self.config.root_dir, session.user_id))
2021-07-23 09:30:33 +00:00
}
2022-11-11 09:24:10 +00:00
pub fn user_setting(&self) -> Result<UserSettingPB, FlowyError> {
let user_setting = UserSettingPB {
user_folder: self.user_dir()?,
};
Ok(user_setting)
}
2022-01-23 04:14:00 +00:00
pub fn user_id(&self) -> Result<String, FlowyError> {
Ok(self.get_session()?.user_id)
}
2021-09-01 08:08:32 +00:00
2022-01-23 04:14:00 +00:00
pub fn user_name(&self) -> Result<String, FlowyError> {
Ok(self.get_session()?.name)
}
2021-12-09 14:28:11 +00:00
2022-01-23 04:14:00 +00:00
pub fn token(&self) -> Result<String, FlowyError> {
Ok(self.get_session()?.token)
}
2021-09-01 08:08:32 +00:00
}
impl UserSession {
fn read_user_profile_on_server(&self, _token: &str) -> Result<(), FlowyError> {
// let server = self.cloud_service.clone();
// let token = token.to_owned();
// tokio::spawn(async move {
// match server.get_user(&token).await {
// Ok(profile) => {
// dart_notify(&token, UserNotification::UserProfileUpdated)
// .payload(profile)
// .send();
// }
// Err(e) => {
// dart_notify(&token, UserNotification::UserProfileUpdated)
// .error(e)
// .send();
// }
// }
// });
2021-09-04 09:26:04 +00:00
Ok(())
}
async fn update_user_on_server(&self, token: &str, params: UpdateUserProfileParams) -> Result<(), FlowyError> {
2022-01-10 15:45:59 +00:00
let server = self.cloud_service.clone();
2021-09-04 09:26:04 +00:00
let token = token.to_owned();
2021-09-06 08:18:34 +00:00
let _ = tokio::spawn(async move {
2021-09-04 09:26:04 +00:00
match server.update_user(&token, params).await {
2022-01-23 04:14:00 +00:00
Ok(_) => {}
2021-09-04 09:26:04 +00:00
Err(e) => {
// TODO: retry?
log::error!("update user profile failed: {:?}", e);
2022-01-23 04:14:00 +00:00
}
2021-09-04 09:26:04 +00:00
}
2021-09-05 05:50:23 +00:00
})
.await;
2021-09-04 09:26:04 +00:00
Ok(())
}
2021-12-14 10:04:51 +00:00
async fn sign_out_on_server(&self, token: &str) -> Result<(), FlowyError> {
2022-01-10 15:45:59 +00:00
let server = self.cloud_service.clone();
2021-09-04 09:26:04 +00:00
let token = token.to_owned();
2021-09-06 08:18:34 +00:00
let _ = tokio::spawn(async move {
2021-09-04 09:26:04 +00:00
match server.sign_out(&token).await {
2022-01-23 04:14:00 +00:00
Ok(_) => {}
2021-09-04 09:26:04 +00:00
Err(e) => log::error!("Sign out failed: {:?}", e),
}
2021-09-05 05:50:23 +00:00
})
.await;
2021-09-04 09:26:04 +00:00
Ok(())
}
2021-12-14 10:04:51 +00:00
async fn save_user(&self, user: UserTable) -> Result<UserTable, FlowyError> {
let conn = self.db_connection()?;
2021-09-27 15:23:23 +00:00
let _ = diesel::insert_into(user_table::table)
.values(user.clone())
.execute(&*conn)?;
2021-09-01 08:08:32 +00:00
Ok(user)
}
2021-12-14 10:04:51 +00:00
fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
2021-11-03 07:37:38 +00:00
tracing::debug!("Set user session: {:?}", session);
match &session {
2021-12-14 10:04:51 +00:00
None => KV::remove(&self.config.session_cache_key).map_err(|e| FlowyError::new(ErrorCode::Internal, &e))?,
2021-11-09 09:50:32 +00:00
Some(session) => KV::set_str(&self.config.session_cache_key, session.clone().into()),
}
Ok(())
}
2021-12-14 10:04:51 +00:00
fn get_session(&self) -> Result<Session, FlowyError> {
feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 03:14:42 +00:00
match KV::get_str(&self.config.session_cache_key) {
2021-12-14 10:04:51 +00:00
None => Err(FlowyError::unauthorized()),
2022-12-20 06:45:51 +00:00
Some(s) => Ok(Session::from(s)),
}
}
2022-01-24 08:27:40 +00:00
fn is_user_login(&self, email: &str) -> bool {
match self.get_session() {
Ok(session) => session.email == email,
Err(_) => false,
}
}
2021-08-31 15:01:46 +00:00
}
2021-09-27 15:23:23 +00:00
pub async fn update_user(
2022-01-10 15:45:59 +00:00
_cloud_service: Arc<dyn UserCloudService>,
2021-09-27 15:23:23 +00:00
pool: Arc<ConnectionPool>,
params: UpdateUserProfileParams,
2021-12-14 10:04:51 +00:00
) -> Result<(), FlowyError> {
2021-08-31 15:01:46 +00:00
let changeset = UserTableChangeset::new(params);
let conn = pool.get()?;
2021-09-07 09:12:03 +00:00
diesel_update_table!(user_table, changeset, &*conn);
2021-08-31 15:01:46 +00:00
Ok(())
2021-07-16 15:18:12 +00:00
}
2021-07-11 09:38:03 +00:00
2021-07-13 15:08:20 +00:00
impl UserDatabaseConnection for UserSession {
2022-01-23 04:14:00 +00:00
fn get_connection(&self) -> Result<DBConnection, String> {
self.db_connection().map_err(|e| format!("{:?}", e))
}
2021-07-13 15:08:20 +00:00
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
struct Session {
user_id: String,
token: String,
email: String,
#[serde(default)]
2021-12-09 13:39:53 +00:00
name: String,
}
2021-12-09 13:39:53 +00:00
impl std::convert::From<SignInResponse> for Session {
fn from(resp: SignInResponse) -> Self {
Session {
user_id: resp.user_id,
token: resp.token,
email: resp.email,
name: resp.name,
}
}
}
impl std::convert::From<SignUpResponse> for Session {
fn from(resp: SignUpResponse) -> Self {
Session {
user_id: resp.user_id,
token: resp.token,
email: resp.email,
name: resp.name,
}
}
2021-12-09 13:39:53 +00:00
}
2021-09-07 15:30:43 +00:00
2021-12-09 13:39:53 +00:00
impl Session {
2022-01-23 04:14:00 +00:00
pub fn into_part(self) -> (String, String) {
(self.user_id, self.token)
}
}
impl std::convert::From<String> for Session {
fn from(s: String) -> Self {
match serde_json::from_str(&s) {
Ok(s) => s,
Err(e) => {
2021-09-01 08:08:32 +00:00
log::error!("Deserialize string to Session failed: {:?}", e);
Session::default()
2022-01-23 04:14:00 +00:00
}
}
}
}
2021-11-27 11:19:41 +00:00
impl std::convert::From<Session> for String {
fn from(session: Session) -> Self {
match serde_json::to_string(&session) {
Ok(s) => s,
Err(e) => {
2021-09-01 08:08:32 +00:00
log::error!("Serialize session to string failed: {:?}", e);
"".to_string()
2022-01-23 04:14:00 +00:00
}
}
}
}