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

@ -15,6 +15,7 @@ use flowy_error::{ErrorCode, FlowyError};
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use lib_infra::{if_native, if_wasm};
use crate::entities::{
AuthResponse, Authenticator, Role, UpdateUserProfileParams, UserCredentials, UserProfile,
@ -56,13 +57,21 @@ impl Display for UserCloudConfig {
}
}
if_native! {
pub trait UserCloudServiceProvider: UserCloudServiceProviderBase + Send + Sync + 'static {}
}
if_wasm! {
pub trait UserCloudServiceProvider: UserCloudServiceProviderBase + 'static {}
}
/// `UserCloudServiceProvider` defines a set of methods for managing user cloud services,
/// including token management, synchronization settings, network reachability, and authentication.
///
/// This trait is intended for implementation by providers that offer cloud-based services for users.
/// It includes methods for handling authentication tokens, enabling/disabling synchronization,
/// setting network reachability, managing encryption secrets, and accessing user-specific cloud services.
pub trait UserCloudServiceProvider: Send + Sync + 'static {
pub trait UserCloudServiceProviderBase {
/// Sets the authentication token for the cloud service.
///
/// # Arguments
@ -94,7 +103,7 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
fn get_user_authenticator(&self) -> Authenticator;
/// Sets the network reachability statset_user_authenticatorus.
/// Sets the network reachability
///
/// # Arguments
/// * `reachable`: A boolean indicating whether the network is reachable.
@ -139,9 +148,17 @@ pub trait UserCloudService: Send + Sync + 'static {
/// Currently, only use the admin client for testing
fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, FlowyError>;
fn create_user(&self, email: &str, password: &str) -> FutureResult<(), FlowyError>;
fn sign_in_with_password(
&self,
email: &str,
password: &str,
) -> FutureResult<UserProfile, FlowyError>;
/// When the user opens the OAuth URL, it redirects to the corresponding provider's OAuth web page.
/// After the user is authenticated, the browser will open a deep link to the AppFlowy app (iOS, macOS, etc.),
/// which will call [Client::sign_in_with_url] to sign in.
/// which will call [Client::sign_in_with_url]generate_sign_in_url_with_email to sign in.
///
/// For example, the OAuth URL on Google looks like `https://appflowy.io/authorize?provider=google`.
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, FlowyError>;

View File

@ -100,7 +100,7 @@ impl UserAuthResponse for AuthResponse {
#[derive(Clone, Debug)]
pub struct UserCredentials {
/// Currently, the token is only used when the [Authenticator] is AFCloud
/// Currently, the token is only used when the [Authenticator] is AppFlowyCloud
pub token: Option<String>,
/// The user id
@ -391,3 +391,7 @@ pub struct WorkspaceMember {
pub role: Role,
pub name: String,
}
pub fn awareness_oid_from_user_uuid(user_uuid: &Uuid) -> Uuid {
Uuid::new_v5(user_uuid, b"user_awareness")
}

View File

@ -1,5 +1,6 @@
pub mod cloud;
pub mod entities;
pub mod session;
pub mod workspace_service;
pub const DEFAULT_USER_NAME: fn() -> String = || "Me".to_string();

View File

@ -0,0 +1,109 @@
use crate::entities::{UserAuthResponse, UserWorkspace};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use chrono::Utc;
use serde::de::{MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize)]
pub struct Session {
pub user_id: i64,
pub user_uuid: Uuid,
pub user_workspace: UserWorkspace,
}
struct SessionVisitor;
impl<'de> Visitor<'de> for SessionVisitor {
type Value = Session;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Session")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut user_id = None;
let mut user_uuid = None;
// For historical reasons, the session used to contain a workspace_id field.
// This field is no longer used, and is replaced by user_workspace.
let mut workspace_id = None;
let mut user_workspace = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"user_id" => {
user_id = Some(map.next_value()?);
},
"user_uuid" => {
user_uuid = Some(map.next_value()?);
},
"workspace_id" => {
workspace_id = Some(map.next_value()?);
},
"user_workspace" => {
user_workspace = Some(map.next_value()?);
},
_ => {
let _ = map.next_value::<Value>();
},
}
}
let user_id = user_id.ok_or(serde::de::Error::missing_field("user_id"))?;
let user_uuid = user_uuid.ok_or(serde::de::Error::missing_field("user_uuid"))?;
if user_workspace.is_none() {
if let Some(workspace_id) = workspace_id {
user_workspace = Some(UserWorkspace {
id: workspace_id,
name: "My Workspace".to_string(),
created_at: Utc::now(),
// For historical reasons, the database_storage_id is constructed by the user_id.
database_view_tracker_id: STANDARD.encode(format!("{}:user:database", user_id)),
})
}
}
let session = Session {
user_id,
user_uuid,
user_workspace: user_workspace.ok_or(serde::de::Error::missing_field("user_workspace"))?,
};
Ok(session)
}
}
impl<'de> Deserialize<'de> for Session {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(SessionVisitor)
}
}
impl<T> From<&T> for Session
where
T: UserAuthResponse,
{
fn from(value: &T) -> Self {
Self {
user_id: value.user_id(),
user_uuid: *value.user_uuid(),
user_workspace: value.latest_workspace().clone(),
}
}
}
impl std::convert::From<Session> for String {
fn from(session: Session) -> Self {
serde_json::to_string(&session).unwrap_or_else(|e| {
tracing::error!("Serialize session to string failed: {:?}", e);
"".to_string()
})
}
}