use std::str::FromStr; use std::sync::{Arc, Weak}; use anyhow::Error; use collab_plugins::cloud_storage::CollabObject; use parking_lot::RwLock; use serde_json::Value; use tokio::sync::oneshot::channel; use uuid::Uuid; use flowy_user_deps::cloud::*; use flowy_user_deps::entities::*; use flowy_user_deps::DEFAULT_USER_NAME; use lib_infra::box_any::BoxAny; use lib_infra::future::FutureResult; use crate::supabase::api::request::FetchObjectUpdateAction; use crate::supabase::api::util::{ ExtendedResponse, InsertParamsBuilder, RealtimeBinaryColumnDecoder, SupabaseBinaryColumnDecoder, }; use crate::supabase::api::{send_update, PostgresWrapper, SupabaseServerService}; use crate::supabase::define::*; use crate::supabase::entities::UserProfileResponse; use crate::supabase::entities::{GetUserProfileParams, RealtimeUserEvent}; use crate::supabase::entities::{RealtimeCollabUpdateEvent, RealtimeEvent, UidResponse}; use crate::supabase::CollabUpdateSenderByOid; use crate::AppFlowyEncryption; pub struct SupabaseUserServiceImpl { server: T, realtime_event_handlers: Vec>, user_update_tx: Option, } impl SupabaseUserServiceImpl { pub fn new( server: T, realtime_event_handlers: Vec>, user_update_tx: Option, ) -> Self { Self { server, realtime_event_handlers, user_update_tx, } } } impl UserService for SupabaseUserServiceImpl where T: SupabaseServerService, { fn sign_up(&self, params: BoxAny) -> FutureResult { let try_get_postgrest = self.server.try_get_postgrest(); FutureResult::new(async move { let postgrest = try_get_postgrest?; let params = third_party_params_from_box_any(params)?; let is_new_user = postgrest .from(USER_TABLE) .select("uid") .eq("uuid", params.uuid.to_string()) .execute() .await? .get_value::>() .await? .is_empty(); // Insert the user if it's a new user. After the user is inserted, we can query the user profile // and workspaces. The profile and workspaces are created by the database trigger. if is_new_user { let insert_params = InsertParamsBuilder::new() .insert(USER_UUID, params.uuid.to_string()) .insert(USER_EMAIL, params.email) .build(); let resp = postgrest .from(USER_TABLE) .insert(insert_params) .execute() .await? .success_with_body() .await?; tracing::debug!("Create user response: {:?}", resp); } // Query the user profile and workspaces tracing::debug!( "user uuid: {}, device_id: {}", params.uuid, params.device_id ); let user_profile = get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(params.uuid)) .await? .unwrap(); let user_workspaces = get_user_workspaces(postgrest.clone(), user_profile.uid).await?; let latest_workspace = user_workspaces .iter() .find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id) .cloned(); let user_name = if user_profile.name.is_empty() { DEFAULT_USER_NAME() } else { user_profile.name }; Ok(SignUpResponse { user_id: user_profile.uid, name: user_name, latest_workspace: latest_workspace.unwrap(), user_workspaces, is_new_user, email: Some(user_profile.email), token: None, device_id: params.device_id, encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign), }) }) } fn sign_in(&self, params: BoxAny) -> FutureResult { let try_get_postgrest = self.server.try_get_postgrest(); FutureResult::new(async move { let postgrest = try_get_postgrest?; let params = third_party_params_from_box_any(params)?; let uuid = params.uuid; let response = get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(uuid)) .await? .unwrap(); let user_workspaces = get_user_workspaces(postgrest.clone(), response.uid).await?; let latest_workspace = user_workspaces .iter() .find(|user_workspace| user_workspace.id == response.latest_workspace_id) .cloned(); Ok(SignInResponse { user_id: response.uid, name: DEFAULT_USER_NAME(), latest_workspace: latest_workspace.unwrap(), user_workspaces, email: None, token: None, device_id: params.device_id, encryption_type: EncryptionType::from_sign(&response.encryption_sign), }) }) } fn sign_out(&self, _token: Option) -> FutureResult<(), Error> { FutureResult::new(async { Ok(()) }) } fn update_user( &self, _credential: UserCredentials, params: UpdateUserProfileParams, ) -> FutureResult<(), Error> { let try_get_postgrest = self.server.try_get_postgrest(); FutureResult::new(async move { let postgrest = try_get_postgrest?; update_user_profile(postgrest, params).await?; Ok(()) }) } fn get_user_profile( &self, credential: UserCredentials, ) -> FutureResult, Error> { let try_get_postgrest = self.server.try_get_postgrest(); let uid = credential .uid .ok_or(anyhow::anyhow!("uid is required")) .unwrap(); FutureResult::new(async move { let postgrest = try_get_postgrest?; let user_profile_resp = get_user_profile(postgrest, GetUserProfileParams::Uid(uid)).await?; match user_profile_resp { None => Ok(None), Some(response) => Ok(Some(UserProfile { uid: response.uid, email: response.email, name: response.name, token: "".to_string(), icon_url: "".to_string(), openai_key: "".to_string(), workspace_id: response.latest_workspace_id, auth_type: AuthType::Supabase, encryption_type: EncryptionType::from_sign(&response.encryption_sign), })), } }) } fn get_user_workspaces(&self, uid: i64) -> FutureResult, Error> { let try_get_postgrest = self.server.try_get_postgrest(); FutureResult::new(async move { let postgrest = try_get_postgrest?; let user_workspaces = get_user_workspaces(postgrest, uid).await?; Ok(user_workspaces) }) } fn check_user(&self, credential: UserCredentials) -> FutureResult<(), Error> { let try_get_postgrest = self.server.try_get_postgrest(); let uuid = credential.uuid.and_then(|uuid| Uuid::from_str(&uuid).ok()); let uid = credential.uid; FutureResult::new(async move { let postgrest = try_get_postgrest?; check_user(postgrest, uid, uuid).await?; Ok(()) }) } fn add_workspace_member( &self, _user_email: String, _workspace_id: String, ) -> FutureResult<(), Error> { todo!() } fn remove_workspace_member( &self, _user_email: String, _workspace_id: String, ) -> FutureResult<(), Error> { todo!() } fn get_user_awareness_updates(&self, uid: i64) -> FutureResult>, Error> { let try_get_postgrest = self.server.try_get_weak_postgrest(); let awareness_id = uid.to_string(); let (tx, rx) = channel(); tokio::spawn(async move { tx.send( async move { let postgrest = try_get_postgrest?; let action = FetchObjectUpdateAction::new(awareness_id, CollabType::UserAwareness, postgrest); action.run_with_fix_interval(3, 3).await } .await, ) }); FutureResult::new(async { rx.await? }) } fn receive_realtime_event(&self, json: Value) { match serde_json::from_value::(json) { Ok(event) => { tracing::trace!("Realtime event: {}", event); for handler in &self.realtime_event_handlers { if event.table.as_str().starts_with(handler.table_name()) { handler.handler_event(&event); } } }, Err(e) => { tracing::error!("parser realtime event error: {}", e); }, } } fn subscribe_user_update(&self) -> Option { self.user_update_tx.as_ref().map(|tx| tx.subscribe()) } fn create_collab_object( &self, collab_object: &CollabObject, data: Vec, ) -> FutureResult<(), Error> { let try_get_postgrest = self.server.try_get_weak_postgrest(); let cloned_collab_object = collab_object.clone(); let (tx, rx) = channel(); tokio::spawn(async move { tx.send( async move { let workspace_id = cloned_collab_object .get_workspace_id() .ok_or(anyhow::anyhow!("Invalid workspace id"))?; let postgrest = try_get_postgrest? .upgrade() .ok_or(anyhow::anyhow!("postgrest is not available"))?; let encryption_secret = postgrest.secret(); send_update( workspace_id, &cloned_collab_object, data, &postgrest, &encryption_secret, ) .await?; Ok(()) } .await, ) }); FutureResult::new(async { rx.await? }) } } async fn get_user_profile( postgrest: Arc, params: GetUserProfileParams, ) -> Result, Error> { let mut builder = postgrest .from(USER_PROFILE_VIEW) .select("uid, email, name, encryption_sign, latest_workspace_id"); match params { GetUserProfileParams::Uid(uid) => builder = builder.eq("uid", uid.to_string()), GetUserProfileParams::Uuid(uuid) => builder = builder.eq("uuid", uuid.to_string()), } let mut profiles = builder .execute() .await? .error_for_status()? .get_value::>() .await?; match profiles.len() { 0 => Ok(None), 1 => Ok(Some(profiles.swap_remove(0))), _ => { tracing::error!("multiple user profile found"); Ok(None) }, } } async fn get_user_workspaces( postgrest: Arc, uid: i64, ) -> Result, Error> { postgrest .from(WORKSPACE_TABLE) .select("id:workspace_id, name:workspace_name, created_at, database_storage_id") .eq("owner_uid", uid.to_string()) .execute() .await? .error_for_status()? .get_value::>() .await } async fn update_user_profile( postgrest: Arc, params: UpdateUserProfileParams, ) -> Result<(), Error> { if params.is_empty() { anyhow::bail!("no params to update"); } // check if user exists let exists = !postgrest .from(USER_TABLE) .select("uid") .eq("uid", params.uid.to_string()) .execute() .await? .error_for_status()? .get_value::>() .await? .is_empty(); if !exists { anyhow::bail!("user uid {} does not exist", params.uid); } let mut update_params = serde_json::Map::new(); if let Some(name) = params.name { update_params.insert("name".to_string(), serde_json::json!(name)); } if let Some(email) = params.email { update_params.insert("email".to_string(), serde_json::json!(email)); } if let Some(encrypt_sign) = params.encryption_sign { update_params.insert( "encryption_sign".to_string(), serde_json::json!(encrypt_sign), ); } let update_payload = serde_json::to_string(&update_params).unwrap(); let resp = postgrest .from(USER_TABLE) .update(update_payload) .eq("uid", params.uid.to_string()) .execute() .await? .success_with_body() .await?; tracing::trace!("update user profile resp: {:?}", resp); Ok(()) } async fn check_user( postgrest: Arc, uid: Option, uuid: Option, ) -> Result<(), Error> { let mut builder = postgrest.from(USER_TABLE); if let Some(uid) = uid { builder = builder.eq("uid", uid.to_string()); } else if let Some(uuid) = uuid { builder = builder.eq("uuid", uuid.to_string()); } else { anyhow::bail!("uid or uuid is required"); } let exists = !builder .execute() .await? .error_for_status()? .get_value::>() .await? .is_empty(); if !exists { anyhow::bail!("user does not exist, uid: {:?}, uuid: {:?}", uid, uuid); } Ok(()) } pub trait RealtimeEventHandler: Send + Sync + 'static { fn table_name(&self) -> &str; fn handler_event(&self, event: &RealtimeEvent); } pub struct RealtimeUserHandler(pub UserUpdateSender); impl RealtimeEventHandler for RealtimeUserHandler { fn table_name(&self) -> &str { "af_user" } fn handler_event(&self, event: &RealtimeEvent) { if let Ok(user_event) = serde_json::from_value::(event.new.clone()) { let _ = self.0.send(UserUpdate { uid: user_event.uid, name: user_event.name, email: user_event.email, encryption_sign: user_event.encryption_sign, }); } } } pub struct RealtimeCollabUpdateHandler { sender_by_oid: Weak, device_id: Arc>, encryption: Weak, } impl RealtimeCollabUpdateHandler { pub fn new( sender_by_oid: Weak, device_id: Arc>, encryption: Weak, ) -> Self { Self { sender_by_oid, device_id, encryption, } } } impl RealtimeEventHandler for RealtimeCollabUpdateHandler { fn table_name(&self) -> &str { "af_collab_update" } fn handler_event(&self, event: &RealtimeEvent) { if let Ok(collab_update) = serde_json::from_value::(event.new.clone()) { if let Some(sender_by_oid) = self.sender_by_oid.upgrade() { if let Some(sender) = sender_by_oid.read().get(collab_update.oid.as_str()) { tracing::trace!( "current device: {}, event device: {}", self.device_id.read(), collab_update.did.as_str() ); if *self.device_id.read() != collab_update.did.as_str() { let encryption_secret = self .encryption .upgrade() .and_then(|encryption| encryption.get_secret()); tracing::trace!( "Parse collab update with len: {}, encrypt: {}", collab_update.value.len(), collab_update.encrypt, ); match SupabaseBinaryColumnDecoder::decode::<_, RealtimeBinaryColumnDecoder>( collab_update.value.as_str(), collab_update.encrypt, &encryption_secret, ) { Ok(value) => { if let Err(e) = sender.send(value) { tracing::debug!("send realtime update error: {}", e); } }, Err(err) => { tracing::error!("decode collab update error: {}", err); }, } } } } } } }