chore: workspace usage

This commit is contained in:
Nathan 2025-04-20 15:54:37 +08:00
parent f72739d98d
commit fa798f3ecd
16 changed files with 241 additions and 84 deletions

View file

@ -389,7 +389,7 @@ class _CreateWorkspaceButton extends StatelessWidget {
workspaceBloc.add(
UserWorkspaceEvent.createWorkspace(
name,
AuthTypePB.Server,
AuthTypePB.Local,
),
);
},

View file

@ -1,6 +1,7 @@
#![allow(unused_doc_comments)]
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_plugins::CollabKVDB;
use flowy_ai::ai_manager::AIManager;
use flowy_database2::DatabaseManager;
use flowy_document::manager::DocumentManager;
@ -342,6 +343,10 @@ impl LoggedUser for ServerUserImpl {
self.upgrade_user()?.get_sqlite_connection(uid)
}
fn get_collab_db(&self, uid: i64) -> Result<Weak<CollabKVDB>, FlowyError> {
self.upgrade_user()?.get_collab_db(uid)
}
fn application_root_dir(&self) -> Result<PathBuf, FlowyError> {
Ok(PathBuf::from(
self.upgrade_user()?.get_application_root_dir(),

View file

@ -1,3 +1,4 @@
use collab_plugins::CollabKVDB;
use flowy_ai::ai_manager::AIUserService;
use flowy_error::{FlowyError, FlowyResult};
use flowy_sqlite::DBConnection;
@ -21,6 +22,9 @@ pub trait LoggedUser: Send + Sync {
async fn is_local_mode(&self) -> FlowyResult<bool>;
fn get_sqlite_db(&self, uid: i64) -> Result<DBConnection, FlowyError>;
fn get_collab_db(&self, uid: i64) -> Result<Weak<CollabKVDB>, FlowyError>;
fn application_root_dir(&self) -> Result<PathBuf, FlowyError>;
}

View file

@ -28,14 +28,14 @@ use tracing::trace;
use uuid::Uuid;
pub struct LocalChatServiceImpl {
pub user: Arc<dyn LoggedUser>,
pub logged_user: Arc<dyn LoggedUser>,
pub local_ai: Arc<LocalAIController>,
}
impl LocalChatServiceImpl {
fn get_message_content(&self, message_id: i64) -> FlowyResult<String> {
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
let content = select_message_content(db, message_id)?.ok_or_else(|| {
FlowyError::record_not_found().with_context(format!("Message not found: {}", message_id))
})?;
@ -43,8 +43,8 @@ impl LocalChatServiceImpl {
}
async fn upsert_message(&self, chat_id: &Uuid, message: ChatMessage) -> Result<(), FlowyError> {
let uid = self.user.user_id()?;
let conn = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let conn = self.logged_user.get_sqlite_db(uid)?;
let row = ChatMessageTable::from_message(chat_id.to_string(), message, true);
upsert_chat_messages(conn, &[row])?;
Ok(())
@ -62,8 +62,8 @@ impl ChatCloudService for LocalChatServiceImpl {
_name: &str,
metadata: Value,
) -> Result<(), FlowyError> {
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
let row = ChatTable::new(chat_id.to_string(), metadata, rag_ids, true);
upsert_chat(db, &row)?;
Ok(())
@ -139,8 +139,8 @@ impl ChatCloudService for LocalChatServiceImpl {
chat_id: &Uuid,
question_id: i64,
) -> Result<ChatMessage, FlowyError> {
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
match select_answer_where_match_reply_message_id(db, &chat_id.to_string(), question_id)? {
None => Err(FlowyError::record_not_found()),
@ -156,8 +156,8 @@ impl ChatCloudService for LocalChatServiceImpl {
limit: u64,
) -> Result<RepeatedChatMessage, FlowyError> {
let chat_id = chat_id.to_string();
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
let result = select_chat_messages(db, &chat_id, limit, offset)?;
let messages = result
@ -180,8 +180,8 @@ impl ChatCloudService for LocalChatServiceImpl {
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
let chat_id = chat_id.to_string();
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
let row = select_answer_where_match_reply_message_id(db, &chat_id, answer_message_id)?
.map(chat_message_from_row)
.ok_or_else(FlowyError::record_not_found)?;
@ -278,8 +278,8 @@ impl ChatCloudService for LocalChatServiceImpl {
chat_id: &Uuid,
) -> Result<ChatSettings, FlowyError> {
let chat_id = chat_id.to_string();
let uid = self.user.user_id()?;
let db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let db = self.logged_user.get_sqlite_db(uid)?;
let row = read_chat(db, &chat_id)?;
let rag_ids = deserialize_rag_ids(&row.rag_ids);
let metadata = deserialize_chat_metadata::<Value>(&row.metadata);
@ -298,8 +298,8 @@ impl ChatCloudService for LocalChatServiceImpl {
id: &Uuid,
s: UpdateChatParams,
) -> Result<(), FlowyError> {
let uid = self.user.user_id()?;
let mut db = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let mut db = self.logged_user.get_sqlite_db(uid)?;
let changeset = ChatTableChangeset {
chat_id: id.to_string(),
name: s.name,

View file

@ -1,5 +1,6 @@
#![allow(unused_variables)]
use crate::af_cloud::define::LoggedUser;
use client_api::entity::workspace_dto::PublishInfoView;
use client_api::entity::PublishInfo;
use collab_entity::CollabType;
@ -10,9 +11,13 @@ use flowy_folder_pub::cloud::{
};
use flowy_folder_pub::entities::PublishPayload;
use lib_infra::async_trait::async_trait;
use std::sync::Arc;
use uuid::Uuid;
pub(crate) struct LocalServerFolderCloudServiceImpl;
pub(crate) struct LocalServerFolderCloudServiceImpl {
#[allow(dead_code)]
pub logged_user: Arc<dyn LoggedUser>,
}
#[async_trait]
impl FolderCloudService for LocalServerFolderCloudServiceImpl {

View file

@ -1,32 +1,38 @@
#![allow(unused_variables)]
use crate::af_cloud::define::LoggedUser;
use crate::local_server::uid::UserIDGenerator;
use client_api::entity::GotrueTokenResponse;
use collab::core::origin::CollabOrigin;
use collab::preclude::Collab;
use collab_entity::CollabObject;
use collab_user::core::UserAwareness;
use lazy_static::lazy_static;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::af_cloud::define::LoggedUser;
use crate::local_server::uid::UserIDGenerator;
use flowy_ai_pub::cloud::billing_dto::WorkspaceUsageAndLimit;
use flowy_ai_pub::cloud::{AFWorkspaceSettings, AFWorkspaceSettingsChange};
use flowy_error::FlowyError;
use flowy_user_pub::cloud::{UserCloudService, UserCollabParams};
use flowy_user_pub::entities::*;
use flowy_user_pub::sql::{select_all_user_workspace, select_user_profile, select_user_workspace};
use flowy_user_pub::sql::{
select_all_user_workspace, select_user_profile, select_user_workspace, select_workspace_member,
select_workspace_setting, update_user_profile, update_workspace_setting, upsert_workspace_member,
upsert_workspace_setting, UserTableChangeset, WorkspaceMemberTable, WorkspaceSettingsChangeset,
WorkspaceSettingsTable,
};
use flowy_user_pub::DEFAULT_USER_NAME;
use lazy_static::lazy_static;
use lib_infra::async_trait::async_trait;
use lib_infra::box_any::BoxAny;
use lib_infra::util::timestamp;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
lazy_static! {
static ref ID_GEN: Mutex<UserIDGenerator> = Mutex::new(UserIDGenerator::new(1));
}
pub(crate) struct LocalServerUserServiceImpl {
pub user: Arc<dyn LoggedUser>,
pub logged_user: Arc<dyn LoggedUser>,
}
#[async_trait]
@ -121,26 +127,30 @@ impl UserCloudService for LocalServerUserServiceImpl {
Err(FlowyError::internal().with_context("Can't oauth url when using offline mode"))
}
async fn update_user(&self, _params: UpdateUserProfileParams) -> Result<(), FlowyError> {
async fn update_user(&self, params: UpdateUserProfileParams) -> Result<(), FlowyError> {
let uid = self.logged_user.user_id()?;
let mut conn = self.logged_user.get_sqlite_db(uid)?;
let changeset = UserTableChangeset::new(params);
update_user_profile(&mut conn, changeset)?;
Ok(())
}
async fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError> {
let conn = self.user.get_sqlite_db(uid)?;
let profile = select_user_profile(uid, conn)?;
let mut conn = self.logged_user.get_sqlite_db(uid)?;
let profile = select_user_profile(uid, &mut conn)?;
Ok(profile)
}
async fn open_workspace(&self, workspace_id: &Uuid) -> Result<UserWorkspace, FlowyError> {
let uid = self.user.user_id()?;
let mut conn = self.user.get_sqlite_db(uid)?;
let uid = self.logged_user.user_id()?;
let mut conn = self.logged_user.get_sqlite_db(uid)?;
let workspace = select_user_workspace(&workspace_id.to_string(), &mut conn)?;
Ok(UserWorkspace::from(workspace))
}
async fn get_all_workspace(&self, uid: i64) -> Result<Vec<UserWorkspace>, FlowyError> {
let conn = self.user.get_sqlite_db(uid)?;
let conn = self.logged_user.get_sqlite_db(uid)?;
let workspaces = select_all_user_workspace(uid, conn)?;
Ok(workspaces)
}
@ -198,4 +208,117 @@ impl UserCloudService for LocalServerUserServiceImpl {
) -> Result<(), FlowyError> {
Ok(())
}
async fn get_workspace_member_info(
&self,
workspace_id: &Uuid,
uid: i64,
) -> Result<WorkspaceMember, FlowyError> {
// For local server, only current user is the member
let conn = self.logged_user.get_sqlite_db(uid)?;
let result = select_workspace_member(conn, &workspace_id.to_string(), uid);
match result {
Ok(row) => Ok(WorkspaceMember::from(row)),
Err(err) => {
if err.is_record_not_found() {
let mut conn = self.logged_user.get_sqlite_db(uid)?;
let profile = select_user_profile(uid, &mut conn)?;
let row = WorkspaceMemberTable {
email: profile.email.to_string(),
role: 0,
name: profile.name.to_string(),
avatar_url: Some(profile.icon_url),
uid,
workspace_id: workspace_id.to_string(),
updated_at: Default::default(),
};
let member = WorkspaceMember::from(row.clone());
upsert_workspace_member(&mut conn, row)?;
Ok(member)
} else {
Err(err)
}
},
}
}
async fn get_workspace_usage(
&self,
workspace_id: &Uuid,
) -> Result<WorkspaceUsageAndLimit, FlowyError> {
Ok(WorkspaceUsageAndLimit {
member_count: 1,
member_count_limit: 1,
storage_bytes: i64::MAX,
storage_bytes_limit: i64::MAX,
storage_bytes_unlimited: true,
single_upload_limit: i64::MAX,
single_upload_unlimited: true,
ai_responses_count: i64::MAX,
ai_responses_count_limit: i64::MAX,
ai_image_responses_count: i64::MAX,
ai_image_responses_count_limit: 0,
local_ai: true,
ai_responses_unlimited: true,
})
}
async fn get_workspace_setting(
&self,
workspace_id: &Uuid,
) -> Result<AFWorkspaceSettings, FlowyError> {
let uid = self.logged_user.user_id()?;
let mut conn = self.logged_user.get_sqlite_db(uid)?;
// By default, workspace setting is existed in local server
let result = select_workspace_setting(&mut conn, &workspace_id.to_string());
match result {
Ok(row) => Ok(AFWorkspaceSettings {
disable_search_indexing: row.disable_search_indexing,
ai_model: row.ai_model,
}),
Err(err) => {
if err.is_record_not_found() {
let row = WorkspaceSettingsTable {
id: workspace_id.to_string(),
disable_search_indexing: false,
ai_model: "".to_string(),
};
let setting = AFWorkspaceSettings {
disable_search_indexing: row.disable_search_indexing,
ai_model: row.ai_model.clone(),
};
upsert_workspace_setting(&mut conn, row)?;
Ok(setting)
} else {
Err(err)
}
},
}
}
async fn update_workspace_setting(
&self,
workspace_id: &Uuid,
workspace_settings: AFWorkspaceSettingsChange,
) -> Result<AFWorkspaceSettings, FlowyError> {
let uid = self.logged_user.user_id()?;
let mut conn = self.logged_user.get_sqlite_db(uid)?;
let changeset = WorkspaceSettingsChangeset {
id: workspace_id.to_string(),
disable_search_indexing: workspace_settings.disable_search_indexing,
ai_model: workspace_settings.ai_model,
};
update_workspace_setting(&mut conn, changeset)?;
let row = select_workspace_setting(&mut conn, &workspace_id.to_string())?;
Ok(AFWorkspaceSettings {
disable_search_indexing: row.disable_search_indexing,
ai_model: row.ai_model,
})
}
}

View file

@ -17,15 +17,15 @@ use flowy_user_pub::cloud::UserCloudService;
use tokio::sync::mpsc;
pub struct LocalServer {
user: Arc<dyn LoggedUser>,
logged_user: Arc<dyn LoggedUser>,
local_ai: Arc<LocalAIController>,
stop_tx: Option<mpsc::Sender<()>>,
}
impl LocalServer {
pub fn new(user: Arc<dyn LoggedUser>, local_ai: Arc<LocalAIController>) -> Self {
pub fn new(logged_user: Arc<dyn LoggedUser>, local_ai: Arc<LocalAIController>) -> Self {
Self {
user,
logged_user,
local_ai,
stop_tx: Default::default(),
}
@ -42,12 +42,14 @@ impl LocalServer {
impl AppFlowyServer for LocalServer {
fn user_service(&self) -> Arc<dyn UserCloudService> {
Arc::new(LocalServerUserServiceImpl {
user: self.user.clone(),
logged_user: self.logged_user.clone(),
})
}
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
Arc::new(LocalServerFolderCloudServiceImpl)
Arc::new(LocalServerFolderCloudServiceImpl {
logged_user: self.logged_user.clone(),
})
}
fn database_service(&self) -> Arc<dyn DatabaseCloudService> {
@ -64,7 +66,7 @@ impl AppFlowyServer for LocalServer {
fn chat_service(&self) -> Arc<dyn ChatCloudService> {
Arc::new(LocalChatServiceImpl {
user: self.user.clone(),
logged_user: self.logged_user.clone(),
local_ai: self.local_ai.clone(),
})
}

View file

@ -1,10 +1,10 @@
use client_api::ClientConfiguration;
use collab_plugins::CollabKVDB;
use flowy_error::{FlowyError, FlowyResult};
use semver::Version;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use flowy_error::{FlowyError, FlowyResult};
use std::sync::{Arc, Weak};
use uuid::Uuid;
use crate::setup_log;
@ -61,6 +61,10 @@ impl LoggedUser for FakeServerUserImpl {
todo!()
}
fn get_collab_db(&self, _uid: i64) -> Result<Weak<CollabKVDB>, FlowyError> {
todo!()
}
fn application_root_dir(&self) -> Result<PathBuf, FlowyError> {
todo!()
}

View file

@ -285,10 +285,7 @@ pub trait UserCloudService: Send + Sync + 'static {
&self,
workspace_id: &Uuid,
uid: i64,
) -> Result<WorkspaceMember, FlowyError> {
Err(FlowyError::not_support())
}
) -> Result<WorkspaceMember, FlowyError>;
/// Get all subscriptions for all workspaces for a user (email)
async fn get_workspace_subscriptions(
&self,
@ -323,9 +320,7 @@ pub trait UserCloudService: Send + Sync + 'static {
async fn get_workspace_usage(
&self,
workspace_id: &Uuid,
) -> Result<WorkspaceUsageAndLimit, FlowyError> {
Err(FlowyError::not_support())
}
) -> Result<WorkspaceUsageAndLimit, FlowyError>;
async fn get_billing_portal_url(&self) -> Result<String, FlowyError> {
Err(FlowyError::not_support())
@ -337,27 +332,23 @@ pub trait UserCloudService: Send + Sync + 'static {
plan: SubscriptionPlan,
recurring_interval: RecurringInterval,
) -> Result<(), FlowyError> {
Err(FlowyError::not_support())
Ok(())
}
async fn get_subscription_plan_details(&self) -> Result<Vec<SubscriptionPlanDetail>, FlowyError> {
Err(FlowyError::not_support())
Ok(vec![])
}
async fn get_workspace_setting(
&self,
workspace_id: &Uuid,
) -> Result<AFWorkspaceSettings, FlowyError> {
Err(FlowyError::not_support())
}
) -> Result<AFWorkspaceSettings, FlowyError>;
async fn update_workspace_setting(
&self,
workspace_id: &Uuid,
workspace_settings: AFWorkspaceSettingsChange,
) -> Result<AFWorkspaceSettings, FlowyError> {
Err(FlowyError::not_support())
}
) -> Result<AFWorkspaceSettings, FlowyError>;
}
pub type UserUpdateReceiver = tokio::sync::mpsc::Receiver<UserUpdate>;

View file

@ -1,10 +1,11 @@
use crate::entities::{Role, WorkspaceMember};
use diesel::{insert_into, RunQueryDsl};
use flowy_error::FlowyResult;
use flowy_sqlite::schema::workspace_members_table;
use flowy_sqlite::schema::workspace_members_table::dsl;
use flowy_sqlite::{prelude::*, DBConnection, ExpressionMethods};
#[derive(Queryable, Insertable, AsChangeset, Debug)]
#[derive(Queryable, Insertable, AsChangeset, Debug, Clone)]
#[diesel(table_name = workspace_members_table)]
#[diesel(primary_key(email, workspace_id))]
pub struct WorkspaceMemberTable {
@ -17,8 +18,19 @@ pub struct WorkspaceMemberTable {
pub updated_at: chrono::NaiveDateTime,
}
impl From<WorkspaceMemberTable> for WorkspaceMember {
fn from(value: WorkspaceMemberTable) -> Self {
Self {
email: value.email,
role: Role::from(value.role),
name: value.name,
avatar_url: value.avatar_url,
}
}
}
pub fn upsert_workspace_member<T: Into<WorkspaceMemberTable>>(
mut conn: DBConnection,
conn: &mut SqliteConnection,
member: T,
) -> FlowyResult<()> {
let member = member.into();
@ -31,7 +43,7 @@ pub fn upsert_workspace_member<T: Into<WorkspaceMemberTable>>(
))
.do_update()
.set(&member)
.execute(&mut conn)?;
.execute(conn)?;
Ok(())
}

View file

@ -92,10 +92,24 @@ impl From<UserUpdate> for UserTableChangeset {
}
}
pub fn select_user_profile(uid: i64, mut conn: DBConnection) -> Result<UserProfile, FlowyError> {
pub fn update_user_profile(
conn: &mut SqliteConnection,
changeset: UserTableChangeset,
) -> Result<(), FlowyError> {
let user_id = changeset.id.clone();
update(user_table::dsl::user_table.filter(user_table::id.eq(&user_id)))
.set(changeset)
.execute(conn)?;
Ok(())
}
pub fn select_user_profile(
uid: i64,
conn: &mut SqliteConnection,
) -> Result<UserProfile, FlowyError> {
let user: UserProfile = user_table::dsl::user_table
.filter(user_table::id.eq(&uid.to_string()))
.first::<UserTable>(&mut *conn)
.first::<UserTable>(conn)
.map_err(|err| {
FlowyError::record_not_found().with_context(format!(
"Can't find the user profile for user id: {}, error: {:?}",

View file

@ -45,7 +45,7 @@ pub fn update_workspace_setting(
/// Upserts a workspace setting into the database.
pub fn upsert_workspace_setting(
conn: &mut DBConnection,
conn: &mut SqliteConnection,
settings: WorkspaceSettingsTable,
) -> Result<(), FlowyError> {
diesel::insert_into(dsl::workspace_setting_table)
@ -62,11 +62,11 @@ pub fn upsert_workspace_setting(
/// Selects a workspace setting by id from the database.
pub fn select_workspace_setting(
conn: &mut DBConnection,
id: &str,
conn: &mut SqliteConnection,
workspace_id: &str,
) -> Result<WorkspaceSettingsTable, FlowyError> {
let setting = dsl::workspace_setting_table
.filter(workspace_setting_table::id.eq(id))
.filter(workspace_setting_table::id.eq(workspace_id))
.first::<WorkspaceSettingsTable>(conn)?;
Ok(setting)
}

View file

@ -103,7 +103,7 @@ pub(crate) fn prepare_import(
);
let imported_user = select_user_profile(
imported_session.user_id,
imported_sqlite_db.get_connection()?,
&mut *imported_sqlite_db.get_connection()?,
)?;
run_collab_data_migration(

View file

@ -126,8 +126,8 @@ impl UserDB {
pool: &Arc<ConnectionPool>,
uid: i64,
) -> Result<UserProfile, FlowyError> {
let conn = pool.get()?;
let profile = select_user_profile(uid, conn)?;
let mut conn = pool.get()?;
let profile = select_user_profile(uid, &mut conn)?;
Ok(profile)
}

View file

@ -1,7 +1,7 @@
use client_api::entity::GotrueTokenResponse;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::CollabKVDB;
use flowy_error::{internal_error, FlowyResult};
use flowy_error::FlowyResult;
use arc_swap::ArcSwapOption;
use collab::lock::RwLock;
@ -506,8 +506,12 @@ impl UserManager {
self.db_connection(session.user_id)?,
changeset,
)?;
self
.cloud_service
.get_user_service()?
.update_user(params)
.await?;
self.update_user(params).await?;
Ok(())
}
@ -539,7 +543,8 @@ impl UserManager {
/// Fetches the user profile for the given user ID.
pub async fn get_user_profile_from_disk(&self, uid: i64) -> Result<UserProfile, FlowyError> {
select_user_profile(uid, self.db_connection(uid)?)
let mut conn = self.db_connection(uid)?;
select_user_profile(uid, &mut conn)
}
#[tracing::instrument(level = "info", skip_all, err)]
@ -625,14 +630,6 @@ impl UserManager {
Ok(None)
}
async fn update_user(&self, params: UpdateUserProfileParams) -> Result<(), FlowyError> {
let server = self.cloud_service.get_user_service()?;
tokio::spawn(async move { server.update_user(params).await })
.await
.map_err(internal_error)??;
Ok(())
}
async fn save_user(&self, uid: i64, user: UserTable) -> Result<(), FlowyError> {
let conn = self.db_connection(uid)?;
upsert_user(user, conn)?;
@ -816,7 +813,7 @@ pub fn upsert_user_profile_change(
"Update user profile with changeset: {:?}",
changeset
);
diesel_update_table!(user_table, changeset, &mut *conn);
update_user_profile(&mut conn, changeset)?;
let user: UserProfile = user_table::dsl::user_table
.filter(user_table::id.eq(&uid.to_string()))
.first::<UserTable>(&mut *conn)?

View file

@ -668,8 +668,8 @@ impl UserManager {
updated_at: Utc::now().naive_utc(),
};
let db = self.authenticate_user.get_sqlite_connection(uid)?;
upsert_workspace_member(db, record)?;
let mut db = self.authenticate_user.get_sqlite_connection(uid)?;
upsert_workspace_member(&mut db, record)?;
Ok(member)
}