mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -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>;
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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();
|
||||
|
109
frontend/rust-lib/flowy-user-pub/src/session.rs
Normal file
109
frontend/rust-lib/flowy-user-pub/src/session.rs
Normal 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()
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user