feat: calling user event from web (#4535)

* refactor: user manager

* refactor: user manager

* refactor: session location

* refactor: user manager

* chore: gen ts files

* feat: implement indexeddb persistence

* chore: integrate user manager

* chore: update

* chore: run on web thread

* chore: run on web thread

* chore: fix test

* chore: add test

* chore: add test

* chore: add user & sign in with password

* chore: fix test

* chore: update docs

* chore: fix warnings

* chore: gen files

* chore: add user

* chore: add files

* chore: update config

* chore: update scirpt

* chore: update scirpt

* fix: build

* chore: update command

* fix: ci

* ci: fix

* fix: compile

* fix: compile

* fix: ci

* fix: compile

* fix: tauri build

* chore: fix test

* chore: fix test
This commit is contained in:
Nathan.fooo
2024-01-30 05:36:27 +08:00
committed by GitHub
parent 86a0569d84
commit 55c97b56a3
164 changed files with 9334 additions and 2885 deletions

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
tracing.workspace = true
@ -23,7 +26,7 @@ anyhow.workspace = true
uuid.workspace = true
chrono = { workspace = true, default-features = false, features = ["clock", "serde"] }
collab = { version = "0.1.0" }
collab-plugins = { version = "0.1.0", features = ["postgres_plugin"] }
collab-plugins = { version = "0.1.0"}
collab-document = { version = "0.1.0" }
collab-entity = { version = "0.1.0" }
hex = "0.4.3"
@ -53,3 +56,7 @@ yrs = "0.17.1"
assert-json-diff = "2.0.2"
serde_json.workspace = true
client-api = { version = "0.1.0" }
[features]
enable_wasm = ["collab/async-plugin"]
enable_supabase = ["collab-plugins/postgres_plugin"]

View File

@ -0,0 +1,4 @@
pub const USER_SIGN_IN_URL: &str = "sign_in_url";
pub const USER_UUID: &str = "uuid";
pub const USER_EMAIL: &str = "email";
pub const USER_DEVICE_ID: &str = "device_id";

View File

@ -4,23 +4,23 @@ use std::sync::Arc;
use anyhow::{anyhow, Error};
use client_api::entity::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
use client_api::entity::{AFRole, AFWorkspace, AuthProvider, CollabParams, CreateCollabParams};
use client_api::ClientConfiguration;
use client_api::{Client, ClientConfiguration};
use collab::core::collab::CollabDocState;
use collab_entity::CollabObject;
use parking_lot::RwLock;
use flowy_error::{ErrorCode, FlowyError};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver};
use flowy_user_pub::entities::*;
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use crate::af_cloud::define::USER_SIGN_IN_URL;
use crate::af_cloud::impls::user::dto::{
af_update_from_update_params, from_af_workspace_member, to_af_role, user_profile_from_af_profile,
};
use crate::af_cloud::impls::user::util::encryption_type_from_profile;
use crate::af_cloud::{AFCloudClient, AFServer};
use crate::supabase::define::USER_SIGN_IN_URL;
pub(crate) struct AFCloudUserAuthServiceImpl<T> {
server: T,
@ -71,32 +71,46 @@ where
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let admin_email = std::env::var("GOTRUE_ADMIN_EMAIL").map_err(|_| {
anyhow!(
"GOTRUE_ADMIN_EMAIL is not set. Please set it to the admin email for the test server"
)
})?;
let admin_password = std::env::var("GOTRUE_ADMIN_PASSWORD").map_err(|_| {
anyhow!(
"GOTRUE_ADMIN_PASSWORD is not set. Please set it to the admin password for the test server"
)
})?;
let admin_client = client_api::Client::new(
client.base_url(),
client.ws_addr(),
client.gotrue_url(),
ClientConfiguration::default(),
);
admin_client
.sign_in_password(&admin_email, &admin_password)
.await?;
let admin_client = get_admin_client(&client).await?;
let action_link = admin_client.generate_sign_in_action_link(&email).await?;
let sign_in_url = client.extract_sign_in_url(&action_link).await?;
Ok(sign_in_url)
})
}
fn create_user(&self, email: &str, password: &str) -> FutureResult<(), FlowyError> {
let password = password.to_string();
let email = email.to_string();
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let admin_client = get_admin_client(&client).await?;
admin_client
.create_email_verified_user(&email, &password)
.await?;
Ok(())
})
}
fn sign_in_with_password(
&self,
email: &str,
password: &str,
) -> FutureResult<UserProfile, FlowyError> {
let password = password.to_string();
let email = email.to_string();
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
client.sign_in_password(&email, &password).await?;
let profile = client.get_profile().await?;
let token = client.get_token()?;
let profile = user_profile_from_af_profile(token, profile)?;
Ok(profile)
})
}
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, FlowyError> {
let provider = AuthProvider::from(provider);
let try_get_client = self.server.try_get_client();
@ -282,6 +296,23 @@ where
}
}
async fn get_admin_client(client: &Arc<AFCloudClient>) -> FlowyResult<Client> {
let admin_email =
std::env::var("GOTRUE_ADMIN_EMAIL").unwrap_or_else(|_| "admin@example.com".to_string());
let admin_password =
std::env::var("GOTRUE_ADMIN_PASSWORD").unwrap_or_else(|_| "password".to_string());
let admin_client = client_api::Client::new(
client.base_url(),
client.ws_addr(),
client.gotrue_url(),
ClientConfiguration::default(),
);
admin_client
.sign_in_password(&admin_email, &admin_password)
.await?;
Ok(admin_client)
}
pub async fn user_sign_up_request(
client: Arc<AFCloudClient>,
params: AFCloudOAuthParams,

View File

@ -1,4 +1,5 @@
pub use server::*;
pub mod define;
pub mod impls;
mod server;

View File

@ -5,10 +5,10 @@ use anyhow::Error;
use client_api::collab_sync::collab_msg::CollabMessage;
use client_api::entity::UserMessage;
use client_api::notify::{TokenState, TokenStateReceiver};
use client_api::{Client, ClientConfiguration};
use client_api::{
use client_api::ws::{
ConnectState, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel,
};
use client_api::{Client, ClientConfiguration};
use flowy_storage::ObjectStorageService;
use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream;
@ -137,7 +137,7 @@ impl AppFlowyServer for AppFlowyCloudServer {
};
let mut user_change = self.ws_client.subscribe_user_changed();
let (tx, rx) = tokio::sync::mpsc::channel(1);
tokio::spawn(async move {
af_spawn(async move {
while let Ok(user_message) = user_change.recv().await {
if let UserMessage::ProfileChange(change) = user_message {
let user_update = UserUpdate {

View File

@ -5,5 +5,8 @@ pub mod local_server;
// mod request;
mod response;
mod server;
#[cfg(feature = "enable_supabase")]
pub mod supabase;
pub mod util;

View File

@ -87,11 +87,28 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async {
Err(
FlowyError::internal().with_context("Can't generate callback url when using offline mode"),
FlowyError::local_version_not_support()
.with_context("Not support generate sign in url with email"),
)
})
}
fn create_user(&self, _email: &str, _password: &str) -> FutureResult<(), FlowyError> {
FutureResult::new(async {
Err(FlowyError::local_version_not_support().with_context("Not support create user"))
})
}
fn sign_in_with_password(
&self,
_email: &str,
_password: &str,
) -> FutureResult<UserProfile, FlowyError> {
FutureResult::new(async {
Err(FlowyError::local_version_not_support().with_context("Not support"))
})
}
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async {
Err(FlowyError::internal().with_context("Can't oauth url when using offline mode"))

View File

@ -3,11 +3,11 @@ use std::sync::Arc;
use anyhow::Error;
use client_api::collab_sync::collab_msg::CollabMessage;
use client_api::{ConnectState, WSConnectStateReceiver, WebSocketChannel};
use collab_entity::CollabObject;
use collab_plugins::cloud_storage::RemoteCollabStorage;
use client_api::ws::{ConnectState, WSConnectStateReceiver, WebSocketChannel};
use parking_lot::RwLock;
use tokio_stream::wrappers::WatchStream;
#[cfg(feature = "enable_supabase")]
use {collab_entity::CollabObject, collab_plugins::cloud_storage::RemoteCollabStorage};
use flowy_database_pub::cloud::DatabaseCloudService;
use flowy_document_pub::cloud::DocumentCloudService;
@ -104,6 +104,7 @@ pub trait AppFlowyServer: Send + Sync + 'static {
/// # Returns
///
/// An `Option` that might contain an `Arc` wrapping the `RemoteCollabStorage` interface.
#[cfg(feature = "enable_supabase")]
fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
None
}

View File

@ -171,6 +171,22 @@ where
})
}
fn create_user(&self, _email: &str, _password: &str) -> FutureResult<(), FlowyError> {
FutureResult::new(async {
Err(FlowyError::not_support().with_context("Can't create user when using supabase"))
})
}
fn sign_in_with_password(
&self,
_email: &str,
_password: &str,
) -> FutureResult<UserProfile, FlowyError> {
FutureResult::new(async {
Err(FlowyError::not_support().with_context("Can't sign in with password when using supabase"))
})
}
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async {
Err(FlowyError::internal().with_context("Can't generate oauth url when using supabase"))

View File

@ -12,13 +12,14 @@ pub const AF_COLLAB_SNAPSHOT_TABLE: &str = "af_collab_snapshot";
pub const USER_UUID: &str = "uuid";
pub const USER_SIGN_IN_URL: &str = "sign_in_url";
pub const USER_EMAIL: &str = "email";
pub const USER_DEVICE_ID: &str = "device_id";
pub const USER_TABLE: &str = "af_user";
pub const USER_UID: &str = "uid";
pub const OWNER_USER_UID: &str = "owner_uid";
pub const USER_EMAIL: &str = "email";
pub const USER_TABLE: &str = "af_user";
pub const WORKSPACE_TABLE: &str = "af_workspace";
pub const USER_PROFILE_VIEW: &str = "af_user_profile_view";
pub const USER_DEVICE_ID: &str = "device_id";
pub(crate) const WORKSPACE_ID: &str = "workspace_id";
pub(crate) const WORKSPACE_NAME: &str = "workspace_name";

View File

@ -2,6 +2,7 @@ use serde::{Deserialize, Deserializer};
/// Handles the case where the value is null. If the value is null, return the default value of the
/// type. Otherwise, deserialize the value.
#[allow(dead_code)]
pub(crate) fn deserialize_null_or_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
T: Default + Deserialize<'de>,