manager.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. use std::string::ToString;
  2. use std::sync::{Arc, Weak};
  3. use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
  4. use appflowy_integrate::RocksCollabDB;
  5. use collab_user::core::MutexUserAwareness;
  6. use serde_json::Value;
  7. use tokio::sync::{Mutex, RwLock};
  8. use uuid::Uuid;
  9. use flowy_error::{internal_error, ErrorCode, FlowyResult};
  10. use flowy_sqlite::kv::StorePreferences;
  11. use flowy_sqlite::schema::user_table;
  12. use flowy_sqlite::ConnectionPool;
  13. use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
  14. use flowy_user_deps::cloud::UserUpdate;
  15. use flowy_user_deps::entities::*;
  16. use lib_infra::box_any::BoxAny;
  17. use crate::entities::{AuthStateChangedPB, AuthStatePB, UserProfilePB, UserSettingPB};
  18. use crate::event_map::{DefaultUserStatusCallback, UserCloudServiceProvider, UserStatusCallback};
  19. use crate::migrations::historical_document::HistoricalEmptyDocumentMigration;
  20. use crate::migrations::migrate_to_new_user::migration_local_user_on_sign_up;
  21. use crate::migrations::migration::UserLocalDataMigration;
  22. use crate::migrations::sync_new_user::sync_user_data_to_cloud;
  23. use crate::migrations::MigrationUser;
  24. use crate::services::cloud_config::get_cloud_config;
  25. use crate::services::database::UserDB;
  26. use crate::services::entities::{ResumableSignUp, Session};
  27. use crate::services::user_awareness::UserAwarenessDataSource;
  28. use crate::services::user_sql::{UserTable, UserTableChangeset};
  29. use crate::services::user_workspace::save_user_workspaces;
  30. use crate::{errors::FlowyError, notification::*};
  31. pub struct UserSessionConfig {
  32. root_dir: String,
  33. /// Used as the key of `Session` when saving session information to KV.
  34. session_cache_key: String,
  35. }
  36. impl UserSessionConfig {
  37. /// The `root_dir` represents as the root of the user folders. It must be unique for each
  38. /// users.
  39. pub fn new(name: &str, root_dir: &str) -> Self {
  40. let session_cache_key = format!("{}_session_cache", name);
  41. Self {
  42. root_dir: root_dir.to_owned(),
  43. session_cache_key,
  44. }
  45. }
  46. }
  47. pub struct UserManager {
  48. database: UserDB,
  49. session_config: UserSessionConfig,
  50. pub(crate) cloud_services: Arc<dyn UserCloudServiceProvider>,
  51. pub(crate) store_preferences: Arc<StorePreferences>,
  52. pub(crate) user_awareness: Arc<Mutex<Option<MutexUserAwareness>>>,
  53. pub(crate) user_status_callback: RwLock<Arc<dyn UserStatusCallback>>,
  54. pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>,
  55. resumable_sign_up: Mutex<Option<ResumableSignUp>>,
  56. current_session: parking_lot::RwLock<Option<Session>>,
  57. }
  58. impl UserManager {
  59. pub fn new(
  60. session_config: UserSessionConfig,
  61. cloud_services: Arc<dyn UserCloudServiceProvider>,
  62. store_preferences: Arc<StorePreferences>,
  63. collab_builder: Weak<AppFlowyCollabBuilder>,
  64. ) -> Arc<Self> {
  65. let database = UserDB::new(&session_config.root_dir);
  66. let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> =
  67. RwLock::new(Arc::new(DefaultUserStatusCallback));
  68. let user_manager = Arc::new(Self {
  69. database,
  70. session_config,
  71. cloud_services,
  72. store_preferences,
  73. user_awareness: Arc::new(Default::default()),
  74. user_status_callback,
  75. collab_builder,
  76. resumable_sign_up: Default::default(),
  77. current_session: Default::default(),
  78. });
  79. let weak_user_manager = Arc::downgrade(&user_manager);
  80. if let Ok(user_service) = user_manager.cloud_services.get_user_service() {
  81. if let Some(mut rx) = user_service.subscribe_user_update() {
  82. tokio::spawn(async move {
  83. while let Ok(update) = rx.recv().await {
  84. if let Some(user_manager) = weak_user_manager.upgrade() {
  85. if let Err(err) = user_manager.handler_user_update(update).await {
  86. tracing::error!("handler_user_update failed: {:?}", err);
  87. }
  88. }
  89. }
  90. });
  91. }
  92. }
  93. user_manager
  94. }
  95. pub fn get_store_preferences(&self) -> Weak<StorePreferences> {
  96. Arc::downgrade(&self.store_preferences)
  97. }
  98. /// Initializes the user session, including data migrations and user awareness configuration.
  99. ///
  100. /// This asynchronous function starts by retrieving the current session. If the session is successfully obtained,
  101. /// it will attempt a local data migration for the user. After ensuring the user's data is migrated and up-to-date,
  102. /// the function will set up the collaboration configuration and initialize the user's awareness. Upon successful
  103. /// completion, a user status callback is invoked to signify that the initialization process is complete.
  104. pub async fn init<C: UserStatusCallback + 'static>(&self, user_status_callback: C) {
  105. if let Ok(session) = self.get_session() {
  106. // Do the user data migration if needed
  107. match (
  108. self.database.get_collab_db(session.user_id),
  109. self.database.get_pool(session.user_id),
  110. ) {
  111. (Ok(collab_db), Ok(sqlite_pool)) => {
  112. match UserLocalDataMigration::new(session.clone(), collab_db, sqlite_pool)
  113. .run(vec![Box::new(HistoricalEmptyDocumentMigration)])
  114. {
  115. Ok(applied_migrations) => {
  116. if !applied_migrations.is_empty() {
  117. tracing::info!("Did apply migrations: {:?}", applied_migrations);
  118. }
  119. },
  120. Err(e) => tracing::error!("User data migration failed: {:?}", e),
  121. }
  122. },
  123. _ => tracing::error!("Failed to get collab db or sqlite pool"),
  124. }
  125. self.set_collab_config(&session);
  126. // Init the user awareness
  127. self
  128. .initialize_user_awareness(&session, UserAwarenessDataSource::Local)
  129. .await;
  130. let cloud_config = get_cloud_config(session.user_id, &self.store_preferences);
  131. if let Err(e) = user_status_callback
  132. .did_init(
  133. session.user_id,
  134. &cloud_config,
  135. &session.user_workspace,
  136. &session.device_id,
  137. )
  138. .await
  139. {
  140. tracing::error!("Failed to call did_init callback: {:?}", e);
  141. }
  142. }
  143. *self.user_status_callback.write().await = Arc::new(user_status_callback);
  144. }
  145. pub fn db_connection(&self, uid: i64) -> Result<DBConnection, FlowyError> {
  146. self.database.get_connection(uid)
  147. }
  148. pub fn db_pool(&self, uid: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
  149. self.database.get_pool(uid)
  150. }
  151. pub fn get_collab_db(&self, uid: i64) -> Result<Weak<RocksCollabDB>, FlowyError> {
  152. self
  153. .database
  154. .get_collab_db(uid)
  155. .map(|collab_db| Arc::downgrade(&collab_db))
  156. }
  157. /// Performs a user sign-in, initializing user awareness and sending relevant notifications.
  158. ///
  159. /// This asynchronous function interacts with an external user service to authenticate and sign in a user
  160. /// based on provided parameters. Once signed in, it updates the collaboration configuration, logs the user,
  161. /// saves their workspaces, and initializes their user awareness.
  162. ///
  163. /// A sign-in notification is also sent after a successful sign-in.
  164. ///
  165. #[tracing::instrument(level = "debug", skip(self, params))]
  166. pub async fn sign_in(
  167. &self,
  168. params: BoxAny,
  169. auth_type: AuthType,
  170. ) -> Result<UserProfile, FlowyError> {
  171. self.update_auth_type(&auth_type).await;
  172. let response: SignInResponse = self
  173. .cloud_services
  174. .get_user_service()?
  175. .sign_in(params)
  176. .await?;
  177. let session = Session::from(&response);
  178. self.set_collab_config(&session);
  179. let latest_workspace = response.latest_workspace.clone();
  180. let user_profile = UserProfile::from((&response, &auth_type));
  181. self.save_auth_data(&response, &auth_type, &session).await?;
  182. let _ = self
  183. .initialize_user_awareness(&session, UserAwarenessDataSource::Remote)
  184. .await;
  185. if let Err(e) = self
  186. .user_status_callback
  187. .read()
  188. .await
  189. .did_sign_in(user_profile.uid, &latest_workspace, &session.device_id)
  190. .await
  191. {
  192. tracing::error!("Failed to call did_sign_in callback: {:?}", e);
  193. }
  194. send_auth_state_notification(AuthStateChangedPB {
  195. state: AuthStatePB::AuthStateSignIn,
  196. })
  197. .send();
  198. Ok(user_profile)
  199. }
  200. pub(crate) async fn update_auth_type(&self, auth_type: &AuthType) {
  201. self
  202. .user_status_callback
  203. .read()
  204. .await
  205. .auth_type_did_changed(auth_type.clone());
  206. self.cloud_services.set_auth_type(auth_type.clone());
  207. }
  208. /// Manages the user sign-up process, potentially migrating data if necessary.
  209. ///
  210. /// This asynchronous function interacts with an external authentication service to register and sign up a user
  211. /// based on the provided parameters. Following a successful sign-up, it handles configuration updates, logging,
  212. /// and saving workspace information. If a user is signing up with a new profile and previously had guest data,
  213. /// this function may migrate that data over to the new account.
  214. ///
  215. #[tracing::instrument(level = "info", skip(self, params))]
  216. pub async fn sign_up(
  217. &self,
  218. auth_type: AuthType,
  219. params: BoxAny,
  220. ) -> Result<UserProfile, FlowyError> {
  221. self.update_auth_type(&auth_type).await;
  222. let migration_user = self.get_migration_user(&auth_type).await;
  223. let auth_service = self.cloud_services.get_user_service()?;
  224. let response: SignUpResponse = auth_service.sign_up(params).await?;
  225. let user_profile = UserProfile::from((&response, &auth_type));
  226. if user_profile.encryption_type.is_need_encrypt_secret() {
  227. self
  228. .resumable_sign_up
  229. .lock()
  230. .await
  231. .replace(ResumableSignUp {
  232. user_profile: user_profile.clone(),
  233. migration_user,
  234. response,
  235. auth_type,
  236. });
  237. } else {
  238. self
  239. .continue_sign_up(&user_profile, migration_user, response, &auth_type)
  240. .await?;
  241. }
  242. Ok(user_profile)
  243. }
  244. #[tracing::instrument(level = "info", skip(self))]
  245. pub async fn resume_sign_up(&self) -> Result<(), FlowyError> {
  246. let ResumableSignUp {
  247. user_profile,
  248. migration_user,
  249. response,
  250. auth_type,
  251. } = self
  252. .resumable_sign_up
  253. .lock()
  254. .await
  255. .clone()
  256. .ok_or(FlowyError::new(
  257. ErrorCode::Internal,
  258. "No resumable sign up data",
  259. ))?;
  260. self
  261. .continue_sign_up(&user_profile, migration_user, response, &auth_type)
  262. .await?;
  263. Ok(())
  264. }
  265. #[tracing::instrument(level = "info", skip_all, err)]
  266. async fn continue_sign_up(
  267. &self,
  268. user_profile: &UserProfile,
  269. migration_user: Option<MigrationUser>,
  270. response: SignUpResponse,
  271. auth_type: &AuthType,
  272. ) -> FlowyResult<()> {
  273. let new_session = Session::from(&response);
  274. self.set_collab_config(&new_session);
  275. let user_awareness_source = if response.is_new_user {
  276. UserAwarenessDataSource::Local
  277. } else {
  278. UserAwarenessDataSource::Remote
  279. };
  280. if response.is_new_user {
  281. if let Some(old_user) = migration_user {
  282. let new_user = MigrationUser {
  283. user_profile: user_profile.clone(),
  284. session: new_session.clone(),
  285. };
  286. tracing::info!(
  287. "Migrate old user data from {:?} to {:?}",
  288. old_user.user_profile.uid,
  289. new_user.user_profile.uid
  290. );
  291. self
  292. .migrate_local_user_to_cloud(&old_user, &new_user)
  293. .await?;
  294. let _ = self.database.close(old_user.session.user_id);
  295. }
  296. }
  297. self
  298. .initialize_user_awareness(&new_session, user_awareness_source)
  299. .await;
  300. self
  301. .save_auth_data(&response, auth_type, &new_session)
  302. .await?;
  303. self
  304. .user_status_callback
  305. .read()
  306. .await
  307. .did_sign_up(
  308. response.is_new_user,
  309. user_profile,
  310. &new_session.user_workspace,
  311. &new_session.device_id,
  312. )
  313. .await?;
  314. send_auth_state_notification(AuthStateChangedPB {
  315. state: AuthStatePB::AuthStateSignIn,
  316. })
  317. .send();
  318. Ok(())
  319. }
  320. #[tracing::instrument(level = "info", skip(self))]
  321. pub async fn sign_out(&self) -> Result<(), FlowyError> {
  322. let session = self.get_session()?;
  323. self.database.close(session.user_id)?;
  324. self.set_session(None)?;
  325. let server = self.cloud_services.get_user_service()?;
  326. tokio::spawn(async move {
  327. match server.sign_out(None).await {
  328. Ok(_) => {},
  329. Err(e) => tracing::error!("Sign out failed: {:?}", e),
  330. }
  331. });
  332. Ok(())
  333. }
  334. /// Updates the user's profile with the given parameters.
  335. ///
  336. /// This function modifies the user's profile based on the provided update parameters. After updating, it
  337. /// sends a notification about the change. It's also responsible for handling interactions with the underlying
  338. /// database and updates user profile.
  339. ///
  340. #[tracing::instrument(level = "debug", skip(self))]
  341. pub async fn update_user_profile(
  342. &self,
  343. params: UpdateUserProfileParams,
  344. ) -> Result<(), FlowyError> {
  345. let changeset = UserTableChangeset::new(params.clone());
  346. let session = self.get_session()?;
  347. save_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?;
  348. self.update_user(session.user_id, None, params).await?;
  349. Ok(())
  350. }
  351. pub async fn init_user(&self) -> Result<(), FlowyError> {
  352. Ok(())
  353. }
  354. pub async fn check_user(&self) -> Result<(), FlowyError> {
  355. let user_id = self.get_session()?.user_id;
  356. let credential = UserCredentials::from_uid(user_id);
  357. let auth_service = self.cloud_services.get_user_service()?;
  358. auth_service.check_user(credential).await?;
  359. Ok(())
  360. }
  361. pub async fn check_user_with_uuid(&self, uuid: &Uuid) -> Result<(), FlowyError> {
  362. let credential = UserCredentials::from_uuid(uuid.to_string());
  363. let auth_service = self.cloud_services.get_user_service()?;
  364. auth_service.check_user(credential).await?;
  365. Ok(())
  366. }
  367. /// Fetches the user profile for the given user ID.
  368. pub async fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError> {
  369. let user: UserProfile = user_table::dsl::user_table
  370. .filter(user_table::id.eq(&uid.to_string()))
  371. .first::<UserTable>(&*(self.db_connection(uid)?))?
  372. .into();
  373. Ok(user)
  374. }
  375. #[tracing::instrument(level = "info", skip_all)]
  376. pub async fn refresh_user_profile(
  377. &self,
  378. old_user_profile: &UserProfile,
  379. ) -> FlowyResult<UserProfile> {
  380. let uid = old_user_profile.uid;
  381. let new_user_profile: UserProfile = self
  382. .cloud_services
  383. .get_user_service()?
  384. .get_user_profile(UserCredentials::from_uid(uid))
  385. .await?
  386. .ok_or_else(|| FlowyError::new(ErrorCode::RecordNotFound, "User not found"))?;
  387. if !is_user_encryption_sign_valid(old_user_profile, &new_user_profile.encryption_type.sign()) {
  388. return Err(FlowyError::new(
  389. ErrorCode::InvalidEncryptSecret,
  390. "Invalid encryption sign",
  391. ));
  392. }
  393. let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone());
  394. let _ = save_user_profile_change(uid, self.database.get_pool(uid)?, changeset);
  395. Ok(new_user_profile)
  396. }
  397. pub fn user_dir(&self, uid: i64) -> String {
  398. format!("{}/{}", self.session_config.root_dir, uid)
  399. }
  400. pub fn user_setting(&self) -> Result<UserSettingPB, FlowyError> {
  401. let session = self.get_session()?;
  402. let user_setting = UserSettingPB {
  403. user_folder: self.user_dir(session.user_id),
  404. };
  405. Ok(user_setting)
  406. }
  407. pub fn user_id(&self) -> Result<i64, FlowyError> {
  408. Ok(self.get_session()?.user_id)
  409. }
  410. pub fn token(&self) -> Result<Option<String>, FlowyError> {
  411. Ok(None)
  412. }
  413. async fn update_user(
  414. &self,
  415. uid: i64,
  416. token: Option<String>,
  417. params: UpdateUserProfileParams,
  418. ) -> Result<(), FlowyError> {
  419. let server = self.cloud_services.get_user_service()?;
  420. let token = token.to_owned();
  421. tokio::spawn(async move {
  422. let credentials = UserCredentials::new(token, Some(uid), None);
  423. server.update_user(credentials, params).await
  424. })
  425. .await
  426. .map_err(internal_error)??;
  427. Ok(())
  428. }
  429. async fn save_user(&self, uid: i64, user: UserTable) -> Result<(), FlowyError> {
  430. let conn = self.db_connection(uid)?;
  431. conn.immediate_transaction(|| {
  432. // delete old user if exists
  433. diesel::delete(user_table::dsl::user_table.filter(user_table::dsl::id.eq(&user.id)))
  434. .execute(&*conn)?;
  435. let _ = diesel::insert_into(user_table::table)
  436. .values(user)
  437. .execute(&*conn)?;
  438. Ok::<(), FlowyError>(())
  439. })?;
  440. Ok(())
  441. }
  442. pub async fn receive_realtime_event(&self, json: Value) {
  443. if let Ok(user_service) = self.cloud_services.get_user_service() {
  444. user_service.receive_realtime_event(json)
  445. }
  446. }
  447. /// Returns the current user session.
  448. pub fn get_session(&self) -> Result<Session, FlowyError> {
  449. if let Some(session) = (self.current_session.read()).clone() {
  450. return Ok(session);
  451. }
  452. match self
  453. .store_preferences
  454. .get_object::<Session>(&self.session_config.session_cache_key)
  455. {
  456. None => Err(FlowyError::new(
  457. ErrorCode::RecordNotFound,
  458. "User is not logged in",
  459. )),
  460. Some(session) => {
  461. self.current_session.write().replace(session.clone());
  462. Ok(session)
  463. },
  464. }
  465. }
  466. pub(crate) fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
  467. tracing::debug!("Set current user: {:?}", session);
  468. match &session {
  469. None => {
  470. self.current_session.write().take();
  471. self
  472. .store_preferences
  473. .remove(&self.session_config.session_cache_key)
  474. },
  475. Some(session) => {
  476. self.current_session.write().replace(session.clone());
  477. self
  478. .store_preferences
  479. .set_object(&self.session_config.session_cache_key, session.clone())
  480. .map_err(internal_error)?;
  481. },
  482. }
  483. Ok(())
  484. }
  485. async fn save_auth_data(
  486. &self,
  487. response: &impl UserAuthResponse,
  488. auth_type: &AuthType,
  489. session: &Session,
  490. ) -> Result<(), FlowyError> {
  491. let user_profile = UserProfile::from((response, auth_type));
  492. let uid = user_profile.uid;
  493. self.add_historical_user(
  494. uid,
  495. response.device_id(),
  496. response.user_name().to_string(),
  497. auth_type,
  498. self.user_dir(uid),
  499. );
  500. save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?;
  501. self
  502. .save_user(uid, (user_profile, auth_type.clone()).into())
  503. .await?;
  504. self.set_session(Some(session.clone()))?;
  505. Ok(())
  506. }
  507. fn set_collab_config(&self, session: &Session) {
  508. let collab_builder = self.collab_builder.upgrade().unwrap();
  509. collab_builder.set_sync_device(session.device_id.clone());
  510. collab_builder.initialize(session.user_workspace.id.clone());
  511. self.cloud_services.set_device_id(&session.device_id);
  512. }
  513. async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> {
  514. let session = self.get_session()?;
  515. if session.user_id == user_update.uid {
  516. tracing::debug!("Receive user update: {:?}", user_update);
  517. let user_profile = self.get_user_profile(user_update.uid).await?;
  518. if !is_user_encryption_sign_valid(&user_profile, &user_update.encryption_sign) {
  519. return Ok(());
  520. }
  521. // Save the user profile change
  522. save_user_profile_change(
  523. user_update.uid,
  524. self.db_pool(user_update.uid)?,
  525. UserTableChangeset::from(user_update),
  526. )?;
  527. }
  528. Ok(())
  529. }
  530. async fn migrate_local_user_to_cloud(
  531. &self,
  532. old_user: &MigrationUser,
  533. new_user: &MigrationUser,
  534. ) -> Result<(), FlowyError> {
  535. let old_collab_db = self.database.get_collab_db(old_user.session.user_id)?;
  536. let new_collab_db = self.database.get_collab_db(new_user.session.user_id)?;
  537. migration_local_user_on_sign_up(old_user, &old_collab_db, new_user, &new_collab_db)?;
  538. if let Err(err) = sync_user_data_to_cloud(
  539. self.cloud_services.get_user_service()?,
  540. new_user,
  541. &new_collab_db,
  542. )
  543. .await
  544. {
  545. tracing::error!("Sync user data to cloud failed: {:?}", err);
  546. }
  547. // Save the old user workspace setting.
  548. save_user_workspaces(
  549. old_user.session.user_id,
  550. self.database.get_pool(old_user.session.user_id)?,
  551. &[old_user.session.user_workspace.clone()],
  552. )?;
  553. Ok(())
  554. }
  555. }
  556. fn is_user_encryption_sign_valid(user_profile: &UserProfile, encryption_sign: &str) -> bool {
  557. // If the local user profile's encryption sign is not equal to the user update's encryption sign,
  558. // which means the user enable encryption in another device, we should logout the current user.
  559. let is_valid = user_profile.encryption_type.sign() == encryption_sign;
  560. if !is_valid {
  561. send_auth_state_notification(AuthStateChangedPB {
  562. state: AuthStatePB::AuthStateForceSignOut,
  563. })
  564. .send();
  565. }
  566. is_valid
  567. }
  568. fn save_user_profile_change(
  569. uid: i64,
  570. pool: Arc<ConnectionPool>,
  571. changeset: UserTableChangeset,
  572. ) -> FlowyResult<()> {
  573. let conn = pool.get()?;
  574. diesel_update_table!(user_table, changeset, &*conn);
  575. let user: UserProfile = user_table::dsl::user_table
  576. .filter(user_table::id.eq(&uid.to_string()))
  577. .first::<UserTable>(&*conn)?
  578. .into();
  579. send_notification(&uid.to_string(), UserNotification::DidUpdateUserProfile)
  580. .payload(UserProfilePB::from(user))
  581. .send();
  582. Ok(())
  583. }