refactor: folder with yrs

* feat: try using folder2

* feat: update

* feat: implement handlers

* fix: compile errors

* chore: add unsafe send + sync

* feat: remove unsafe impl

* fix: replace folder with foler2

* chore: dart compile errors

* test: fix test

* test: fix test

* test: bypass existing tests

* feat: open latest view

* chore: fix dart warnings

* chore: config notification

* fix: folder notification  bugs

* fix: doesn't open the new view after creating

* chore: rename struct

* refactor: user id

* test: fix test

* chore: remove unused user_id

* fix: fix read workspace views

* chore: rename appflowy data folder

* chore: update ref

* fix: tauri build
This commit is contained in:
Nathan.fooo
2023-04-04 08:41:16 +08:00
committed by GitHub
parent ddca659c77
commit e1c8135f5d
198 changed files with 4595 additions and 1736 deletions

View File

@ -13,6 +13,8 @@ user-model = { path = "../../../shared-lib/user-model" }
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-notification = { path = "../flowy-notification" }
lib-dispatch = { path = "../lib-dispatch" }
collab-persistence = { version = "0.1.0" }
tracing = { version = "0.1", features = ["log"] }
bytes = "1.4"

View File

@ -2,8 +2,7 @@ use crate::errors::ErrorCode;
use flowy_derive::ProtoBuf;
use std::convert::TryInto;
use user_model::{
UpdateUserProfileParams, UserEmail, UserIcon, UserId, UserName, UserOpenaiKey, UserPassword,
UserProfile,
UpdateUserProfileParams, UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword, UserProfile,
};
#[derive(Default, ProtoBuf)]
@ -21,7 +20,7 @@ pub struct UserSettingPB {
#[derive(ProtoBuf, Default, Debug, PartialEq, Eq, Clone)]
pub struct UserProfilePB {
#[pb(index = 1)]
pub id: String,
pub id: i64,
#[pb(index = 2)]
pub email: String,
@ -55,7 +54,7 @@ impl std::convert::From<UserProfile> for UserProfilePB {
#[derive(ProtoBuf, Default)]
pub struct UpdateUserProfilePayloadPB {
#[pb(index = 1)]
pub id: String,
pub id: i64,
#[pb(index = 2, one_of)]
pub name: Option<String>,
@ -74,9 +73,9 @@ pub struct UpdateUserProfilePayloadPB {
}
impl UpdateUserProfilePayloadPB {
pub fn new(id: &str) -> Self {
pub fn new(id: i64) -> Self {
Self {
id: id.to_owned(),
id,
..Default::default()
}
}
@ -111,8 +110,6 @@ impl TryInto<UpdateUserProfileParams> for UpdateUserProfilePayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<UpdateUserProfileParams, Self::Error> {
let id = UserId::parse(self.id)?.0;
let name = match self.name {
None => None,
Some(name) => Some(UserName::parse(name)?.0),
@ -139,7 +136,7 @@ impl TryInto<UpdateUserProfileParams> for UpdateUserProfilePayloadPB {
};
Ok(UpdateUserProfileParams {
id,
id: self.id,
name,
email,
password,

View File

@ -1,12 +1,41 @@
use crate::entities::{
AppearanceSettingsPB, UpdateUserProfilePayloadPB, UserProfilePB, UserSettingPB,
APPEARANCE_DEFAULT_THEME,
};
use crate::{errors::FlowyError, services::UserSession};
use crate::entities::*;
use crate::services::UserSession;
use flowy_error::FlowyError;
use flowy_sqlite::kv::KV;
use lib_dispatch::prelude::*;
use std::{convert::TryInto, sync::Arc};
use user_model::UpdateUserProfileParams;
use user_model::{SignInParams, SignUpParams, UpdateUserProfileParams};
// tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html
#[tracing::instrument(level = "debug", name = "sign_in", skip(data, session), fields(email = %data.email), err)]
pub async fn sign_in(
data: AFPluginData<SignInPayloadPB>,
session: AFPluginState<Arc<UserSession>>,
) -> DataResult<UserProfilePB, FlowyError> {
let params: SignInParams = data.into_inner().try_into()?;
let user_profile: UserProfilePB = session.sign_in(params).await?.into();
data_result_ok(user_profile)
}
#[tracing::instrument(
level = "debug",
name = "sign_up",
skip(data, session),
fields(
email = %data.email,
name = %data.name,
),
err
)]
pub async fn sign_up(
data: AFPluginData<SignUpPayloadPB>,
session: AFPluginState<Arc<UserSession>>,
) -> DataResult<UserProfilePB, FlowyError> {
let params: SignUpParams = data.into_inner().try_into()?;
let user_profile: UserProfilePB = session.sign_up(params).await?.into();
data_result_ok(user_profile)
}
#[tracing::instrument(level = "debug", skip(session))]
pub async fn init_user_handler(session: AFPluginState<Arc<UserSession>>) -> Result<(), FlowyError> {

View File

@ -1,9 +1,12 @@
use crate::entities::UserProfilePB;
use crate::{errors::FlowyError, handlers::*, services::UserSession};
use crate::event_handler::*;
use crate::{errors::FlowyError, services::UserSession};
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
use flowy_error::FlowyResult;
use lib_dispatch::prelude::*;
use lib_infra::future::{Fut, FutureResult};
use std::sync::Arc;
use strum_macros::Display;
use user_model::{
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile,
};
@ -25,9 +28,10 @@ pub fn init(user_session: Arc<UserSession>) -> AFPlugin {
}
pub trait UserStatusCallback: Send + Sync + 'static {
fn did_sign_in(&self, token: &str, user_id: &str) -> Fut<FlowyResult<()>>;
fn did_sign_in(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>>;
fn did_sign_up(&self, user_profile: &UserProfile) -> Fut<FlowyResult<()>>;
fn did_expired(&self, token: &str, user_id: &str) -> Fut<FlowyResult<()>>;
fn did_expired(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>>;
fn will_migrated(&self, token: &str, old_user_id: &str, user_id: i64) -> Fut<FlowyResult<()>>;
}
pub trait UserCloudService: Send + Sync {
@ -43,10 +47,6 @@ pub trait UserCloudService: Send + Sync {
fn ws_addr(&self) -> String;
}
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
use flowy_error::FlowyResult;
use strum_macros::Display;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
#[event_err = "FlowyError"]
pub enum UserEvent {

View File

@ -1,37 +0,0 @@
use crate::entities::*;
use crate::services::UserSession;
use flowy_error::FlowyError;
use lib_dispatch::prelude::*;
use std::{convert::TryInto, sync::Arc};
use user_model::{SignInParams, SignUpParams};
// tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html
#[tracing::instrument(level = "debug", name = "sign_in", skip(data, session), fields(email = %data.email), err)]
pub async fn sign_in(
data: AFPluginData<SignInPayloadPB>,
session: AFPluginState<Arc<UserSession>>,
) -> DataResult<UserProfilePB, FlowyError> {
let params: SignInParams = data.into_inner().try_into()?;
let user_profile: UserProfilePB = session.sign_in(params).await?.into();
data_result_ok(user_profile)
}
#[tracing::instrument(
level = "debug",
name = "sign_up",
skip(data, session),
fields(
email = %data.email,
name = %data.name,
),
err
)]
pub async fn sign_up(
data: AFPluginData<SignUpPayloadPB>,
session: AFPluginState<Arc<UserSession>>,
) -> DataResult<UserProfilePB, FlowyError> {
let params: SignUpParams = data.into_inner().try_into()?;
let user_profile: UserProfilePB = session.sign_up(params).await?.into();
data_result_ok(user_profile)
}

View File

@ -1,5 +0,0 @@
mod auth_handler;
mod user_handler;
pub use auth_handler::*;
pub use user_handler::*;

View File

@ -1,9 +1,10 @@
pub mod entities;
mod event_handler;
pub mod event_map;
mod handlers;
mod notification;
pub mod protobuf;
pub mod services;
pub mod uid;
// mod sql_tables;
#[macro_use]

View File

@ -1,4 +1,5 @@
use flowy_error::{ErrorCode, FlowyError};
use collab_persistence::CollabKV;
use flowy_error::FlowyError;
use flowy_sqlite::ConnectionPool;
use flowy_sqlite::{schema::user_table, DBConnection, Database};
use lazy_static::lazy_static;
@ -18,25 +19,21 @@ impl UserDB {
}
}
fn open_user_db_if_need(&self, user_id: &str) -> Result<Arc<ConnectionPool>, FlowyError> {
if user_id.is_empty() {
return Err(ErrorCode::UserIdIsEmpty.into());
}
if let Some(database) = DB_MAP.read().get(user_id) {
fn open_user_db_if_need(&self, user_id: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
if let Some(database) = DB_MAP.read().get(&user_id) {
return Ok(database.get_pool());
}
let mut write_guard = DB_MAP.write();
// The Write guard acquire exclusive access that will guarantee the user db only initialize once.
match write_guard.get(user_id) {
match write_guard.get(&user_id) {
None => {},
Some(database) => return Ok(database.get_pool()),
}
let mut dir = PathBuf::new();
dir.push(&self.db_dir);
dir.push(user_id);
dir.push(user_id.to_string());
let dir = dir.to_str().unwrap().to_owned();
tracing::trace!("open user db {} at path: {}", user_id, dir);
@ -50,29 +47,59 @@ impl UserDB {
Ok(pool)
}
pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), FlowyError> {
fn open_kv_db_if_need(&self, user_id: i64) -> Result<Arc<CollabKV>, FlowyError> {
if let Some(kv) = KVDB_MAP.read().get(&user_id) {
return Ok(kv.clone());
}
let mut write_guard = KVDB_MAP.write();
// The Write guard acquire exclusive access that will guarantee the user db only initialize once.
match write_guard.get(&user_id) {
None => {},
Some(kv) => return Ok(kv.clone()),
}
let mut dir = PathBuf::new();
dir.push(&self.db_dir);
dir.push(user_id.to_string());
tracing::trace!("open kv db {} at path: {:?}", user_id, dir);
let kv_db = CollabKV::open(dir).map_err(|err| FlowyError::internal().context(err))?;
let kv_db = Arc::new(kv_db);
write_guard.insert(user_id.to_owned(), kv_db.clone());
drop(write_guard);
Ok(kv_db)
}
pub(crate) fn close_user_db(&self, user_id: i64) -> Result<(), FlowyError> {
match DB_MAP.try_write_for(Duration::from_millis(300)) {
None => Err(FlowyError::internal().context("Acquire write lock to close user db failed")),
Some(mut write_guard) => {
write_guard.remove(user_id);
write_guard.remove(&user_id);
Ok(())
},
}
}
pub(crate) fn get_connection(&self, user_id: &str) -> Result<DBConnection, FlowyError> {
pub(crate) fn get_connection(&self, user_id: i64) -> Result<DBConnection, FlowyError> {
let conn = self.get_pool(user_id)?.get()?;
Ok(conn)
}
pub(crate) fn get_pool(&self, user_id: &str) -> Result<Arc<ConnectionPool>, FlowyError> {
pub(crate) fn get_pool(&self, user_id: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
let pool = self.open_user_db_if_need(user_id)?;
Ok(pool)
}
pub(crate) fn get_kv_db(&self, user_id: i64) -> Result<Arc<CollabKV>, FlowyError> {
let kv_db = self.open_kv_db_if_need(user_id)?;
Ok(kv_db)
}
}
lazy_static! {
static ref DB_MAP: RwLock<HashMap<String, Database>> = RwLock::new(HashMap::new());
static ref DB_MAP: RwLock<HashMap<i64, Database>> = RwLock::new(HashMap::new());
static ref KVDB_MAP: RwLock<HashMap<i64, Arc<CollabKV>>> = RwLock::new(HashMap::new());
}
#[derive(Clone, Default, Queryable, Identifiable, Insertable)]
@ -108,20 +135,20 @@ impl UserTable {
impl std::convert::From<SignUpResponse> for UserTable {
fn from(resp: SignUpResponse) -> Self {
UserTable::new(resp.user_id, resp.name, resp.email, resp.token)
UserTable::new(resp.user_id.to_string(), resp.name, resp.email, resp.token)
}
}
impl std::convert::From<SignInResponse> for UserTable {
fn from(resp: SignInResponse) -> Self {
UserTable::new(resp.user_id, resp.name, resp.email, resp.token)
UserTable::new(resp.user_id.to_string(), resp.name, resp.email, resp.token)
}
}
impl std::convert::From<UserTable> for UserProfile {
fn from(table: UserTable) -> Self {
UserProfile {
id: table.id,
id: table.id.parse::<i64>().unwrap_or(0),
email: table.email,
name: table.name,
token: table.token,
@ -145,7 +172,7 @@ pub struct UserTableChangeset {
impl UserTableChangeset {
pub fn new(params: UpdateUserProfileParams) -> Self {
UserTableChangeset {
id: params.id,
id: params.id.to_string(),
workspace: None,
name: params.name,
email: params.email,

View File

@ -1,11 +1,13 @@
use crate::entities::{UserProfilePB, UserSettingPB};
use crate::event_map::UserStatusCallback;
use crate::{
errors::{ErrorCode, FlowyError},
event_map::UserCloudService,
notification::*,
services::database::{UserDB, UserTable, UserTableChangeset},
};
use collab_persistence::CollabKV;
use flowy_sqlite::ConnectionPool;
use flowy_sqlite::{
kv::KV,
@ -13,6 +15,7 @@ use flowy_sqlite::{
schema::{user_table, user_table::dsl},
DBConnection, ExpressionMethods, UserDatabaseConnection,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
@ -20,6 +23,9 @@ use user_model::{
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile,
};
// lazy_static! {
// static ref ID_GEN: Mutex<UserIDGenerator> = Mutex::new(UserIDGenerator::new(1));
// }
pub struct UserSessionConfig {
root_dir: String,
@ -59,9 +65,45 @@ impl UserSession {
}
pub async fn init<C: UserStatusCallback + 'static>(&self, user_status_callback: C) {
// if let Some(old_session) = self.get_old_session() {
// let uid = ID_GEN.lock().next_id();
// let _ = user_status_callback
// .will_migrated(&old_session.token, &old_session.user_id, uid)
// .await;
//
// let new_session = Session {
// user_id: uid,
// token: old_session.token.clone(),
// email: old_session.email.clone(),
// name: old_session.name.clone(),
// };
// self.set_session(Some(new_session)).unwrap();
//
// if let Ok(db) = self.db_connection() {
// // Update db
// let _ = db.immediate_transaction(|| {
// // get the user data
// let mut user = dsl::user_table
// .filter(user_table::id.eq(&old_session.user_id))
// .first::<UserTable>(&*db)?;
//
// // delete the existing row
// let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&old_session.user_id)))
// .execute(&*db)?;
//
// // insert new row
// user.id = uid.to_string();
// let _ = diesel::insert_into(user_table::table)
// .values(user)
// .execute(&*db)?;
// Ok::<(), FlowyError>(())
// });
// }
// }
if let Ok(session) = self.get_session() {
let _ = user_status_callback
.did_sign_in(&session.token, &session.user_id)
.did_sign_in(&session.token, session.user_id)
.await;
}
*self.user_status_callback.write().await = Some(Arc::new(user_status_callback));
@ -69,7 +111,7 @@ impl UserSession {
pub fn db_connection(&self) -> Result<DBConnection, FlowyError> {
let user_id = self.get_session()?.user_id;
self.database.get_connection(&user_id)
self.database.get_connection(user_id)
}
// The caller will be not 'Sync' before of the return value,
@ -80,7 +122,12 @@ impl UserSession {
// let conn: PooledConnection<ConnectionManager> = pool.get()?;
pub fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError> {
let user_id = self.get_session()?.user_id;
self.database.get_pool(&user_id)
self.database.get_pool(user_id)
}
pub fn get_kv_db(&self) -> Result<Arc<CollabKV>, FlowyError> {
let user_id = self.get_session()?.user_id;
self.database.get_kv_db(user_id)
}
#[tracing::instrument(level = "debug", skip(self))]
@ -106,7 +153,7 @@ impl UserSession {
.await
.as_ref()
.unwrap()
.did_sign_in(&user_profile.token, &user_profile.id)
.did_sign_in(&user_profile.token, user_profile.id)
.await;
send_sign_in_notification()
.payload::<UserProfilePB>(user_profile.clone().into())
@ -140,9 +187,10 @@ impl UserSession {
#[tracing::instrument(level = "debug", skip(self))]
pub async fn sign_out(&self) -> Result<(), FlowyError> {
let session = self.get_session()?;
let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id)))
let uid = session.user_id.to_string();
let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&uid)))
.execute(&*(self.db_connection()?))?;
self.database.close_user_db(&session.user_id)?;
self.database.close_user_db(session.user_id)?;
self.set_session(None)?;
let _ = self
.user_status_callback
@ -150,7 +198,7 @@ impl UserSession {
.await
.as_ref()
.unwrap()
.did_expired(&session.token, &session.user_id)
.did_expired(&session.token, session.user_id)
.await;
self.sign_out_on_server(&session.token).await?;
@ -181,7 +229,7 @@ impl UserSession {
pub async fn check_user(&self) -> Result<UserProfile, FlowyError> {
let (user_id, token) = self.get_session()?.into_part();
let user_id = user_id.to_string();
let user = dsl::user_table
.filter(user_table::id.eq(&user_id))
.first::<UserTable>(&*(self.db_connection()?))?;
@ -192,6 +240,7 @@ impl UserSession {
pub async fn get_user_profile(&self) -> Result<UserProfile, FlowyError> {
let (user_id, token) = self.get_session()?.into_part();
let user_id = user_id.to_string();
let user = dsl::user_table
.filter(user_table::id.eq(&user_id))
.first::<UserTable>(&*(self.db_connection()?))?;
@ -212,7 +261,7 @@ impl UserSession {
Ok(user_setting)
}
pub fn user_id(&self) -> Result<String, FlowyError> {
pub fn user_id(&self) -> Result<i64, FlowyError> {
Ok(self.get_session()?.user_id)
}
@ -288,6 +337,11 @@ impl UserSession {
}
}
// fn get_old_session(&self) -> Option<OldSession> {
// let s = KV::get_str(&self.config.session_cache_key)?;
// serde_json::from_str::<OldSession>(&s).ok()
// }
fn is_user_login(&self, email: &str) -> bool {
match self.get_session() {
Ok(session) => session.email == email,
@ -315,7 +369,7 @@ impl UserDatabaseConnection for UserSession {
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
struct Session {
user_id: String,
user_id: i64,
token: String,
email: String,
#[serde(default)]
@ -345,7 +399,7 @@ impl std::convert::From<SignUpResponse> for Session {
}
impl Session {
pub fn into_part(self) -> (String, String) {
pub fn into_part(self) -> (i64, String) {
(self.user_id, self.token)
}
}
@ -372,3 +426,12 @@ impl std::convert::From<Session> for String {
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
struct OldSession {
user_id: String,
token: String,
email: String,
#[serde(default)]
name: String,
}

View File

@ -0,0 +1,58 @@
use std::time::SystemTime;
const EPOCH: u64 = 1637806706000;
const NODE_ID_BITS: u64 = 10;
const SEQUENCE_BITS: u64 = 12;
const NODE_ID_SHIFT: u64 = SEQUENCE_BITS;
const TIMESTAMP_SHIFT: u64 = NODE_ID_BITS + SEQUENCE_BITS;
const SEQUENCE_MASK: u64 = (1 << SEQUENCE_BITS) - 1;
pub struct UserIDGenerator {
node_id: u64,
sequence: u64,
last_timestamp: u64,
}
impl UserIDGenerator {
pub fn new(node_id: u64) -> UserIDGenerator {
UserIDGenerator {
node_id,
sequence: 0,
last_timestamp: 0,
}
}
pub fn next_id(&mut self) -> i64 {
let timestamp = self.timestamp();
if timestamp < self.last_timestamp {
panic!("Clock moved backwards!");
}
if timestamp == self.last_timestamp {
self.sequence = (self.sequence + 1) & SEQUENCE_MASK;
if self.sequence == 0 {
self.wait_next_millis();
}
} else {
self.sequence = 0;
}
self.last_timestamp = timestamp;
let id = (timestamp - EPOCH) << TIMESTAMP_SHIFT | self.node_id << NODE_ID_SHIFT | self.sequence;
id as i64
}
fn wait_next_millis(&self) {
let mut timestamp = self.timestamp();
while timestamp == self.last_timestamp {
timestamp = self.timestamp();
}
}
fn timestamp(&self) -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Clock moved backwards!")
.as_millis() as u64
}
}

View File

@ -33,7 +33,7 @@ async fn user_update_with_name() {
let sdk = FlowySDKTest::default();
let user = sdk.init_user().await;
let new_name = "hello_world".to_owned();
let request = UpdateUserProfilePayloadPB::new(&user.id).name(&new_name);
let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name);
let _ = UserModuleEventBuilder::new(sdk.clone())
.event(UpdateUserProfile)
.payload(request)
@ -53,7 +53,7 @@ async fn user_update_with_email() {
let sdk = FlowySDKTest::default();
let user = sdk.init_user().await;
let new_email = format!("{}@gmail.com", nanoid!(6));
let request = UpdateUserProfilePayloadPB::new(&user.id).email(&new_email);
let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email);
let _ = UserModuleEventBuilder::new(sdk.clone())
.event(UpdateUserProfile)
.payload(request)
@ -72,7 +72,7 @@ async fn user_update_with_password() {
let sdk = FlowySDKTest::default();
let user = sdk.init_user().await;
let new_password = "H123world!".to_owned();
let request = UpdateUserProfilePayloadPB::new(&user.id).password(&new_password);
let request = UpdateUserProfilePayloadPB::new(user.id).password(&new_password);
let _ = UserModuleEventBuilder::new(sdk.clone())
.event(UpdateUserProfile)
@ -86,7 +86,7 @@ async fn user_update_with_invalid_email() {
let test = FlowySDKTest::default();
let user = test.init_user().await;
for email in invalid_email_test_case() {
let request = UpdateUserProfilePayloadPB::new(&user.id).email(&email);
let request = UpdateUserProfilePayloadPB::new(user.id).email(&email);
assert_eq!(
UserModuleEventBuilder::new(test.clone())
.event(UpdateUserProfile)
@ -104,7 +104,7 @@ async fn user_update_with_invalid_password() {
let test = FlowySDKTest::default();
let user = test.init_user().await;
for password in invalid_password_test_case() {
let request = UpdateUserProfilePayloadPB::new(&user.id).password(&password);
let request = UpdateUserProfilePayloadPB::new(user.id).password(&password);
UserModuleEventBuilder::new(test.clone())
.event(UpdateUserProfile)
@ -118,7 +118,7 @@ async fn user_update_with_invalid_password() {
async fn user_update_with_invalid_name() {
let test = FlowySDKTest::default();
let user = test.init_user().await;
let request = UpdateUserProfilePayloadPB::new(&user.id).name("");
let request = UpdateUserProfilePayloadPB::new(user.id).name("");
UserModuleEventBuilder::new(test.clone())
.event(UpdateUserProfile)
.payload(request)