chore: create user workspace for anon user

This commit is contained in:
Nathan 2025-04-20 14:33:05 +08:00
parent 833a2bf5d6
commit 747a63d452
18 changed files with 126 additions and 47 deletions

View File

@ -194,7 +194,7 @@ class _MobileWorkspace extends StatelessWidget {
context.read<UserWorkspaceBloc>().add( context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.openWorkspace( UserWorkspaceEvent.openWorkspace(
workspace.workspaceId, workspace.workspaceId,
workspace.authType, workspace.workspaceAuthType,
), ),
); );
}, },

View File

@ -54,7 +54,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
Log.info('init open workspace: ${currentWorkspace.workspaceId}'); Log.info('init open workspace: ${currentWorkspace.workspaceId}');
await _userService.openWorkspace( await _userService.openWorkspace(
currentWorkspace.workspaceId, currentWorkspace.workspaceId,
currentWorkspace.authType, currentWorkspace.workspaceAuthType,
); );
} }
@ -92,7 +92,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
add( add(
OpenWorkspace( OpenWorkspace(
currentWorkspace.workspaceId, currentWorkspace.workspaceId,
currentWorkspace.authType, currentWorkspace.workspaceAuthType,
), ),
); );
} }
@ -132,7 +132,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
add( add(
OpenWorkspace( OpenWorkspace(
s.workspaceId, s.workspaceId,
s.authType, s.workspaceAuthType,
), ),
); );
}) })
@ -190,7 +190,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
add( add(
OpenWorkspace( OpenWorkspace(
workspaces.first.workspaceId, workspaces.first.workspaceId,
workspaces.first.authType, workspaces.first.workspaceAuthType,
), ),
); );
} }
@ -203,7 +203,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
add( add(
OpenWorkspace( OpenWorkspace(
workspaces.first.workspaceId, workspaces.first.workspaceId,
workspaces.first.authType, workspaces.first.workspaceAuthType,
), ),
); );
} }
@ -369,7 +369,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
add( add(
OpenWorkspace( OpenWorkspace(
workspaces.first.workspaceId, workspaces.first.workspaceId,
workspaces.first.authType, workspaces.first.workspaceAuthType,
), ),
); );
} }

View File

@ -309,7 +309,7 @@ class _WorkspaceInfo extends StatelessWidget {
context.read<UserWorkspaceBloc>().add( context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.openWorkspace( UserWorkspaceEvent.openWorkspace(
workspace.workspaceId, workspace.workspaceId,
workspace.authType, workspace.workspaceAuthType,
), ),
); );

View File

@ -90,7 +90,10 @@ async fn af_cloud_create_workspace_test() {
{ {
// after opening new workspace // after opening new workspace
test test
.open_workspace(&created_workspace.workspace_id, created_workspace.auth_type) .open_workspace(
&created_workspace.workspace_id,
created_workspace.workspace_auth_type,
)
.await; .await;
let folder_ws = test.folder_read_current_workspace().await; let folder_ws = test.folder_read_current_workspace().await;
assert_eq!(folder_ws.id, created_workspace.workspace_id); assert_eq!(folder_ws.id, created_workspace.workspace_id);
@ -124,7 +127,10 @@ async fn af_cloud_open_workspace_test() {
.create_workspace("second workspace", AuthType::AppFlowyCloud) .create_workspace("second workspace", AuthType::AppFlowyCloud)
.await; .await;
test test
.open_workspace(&user_workspace.workspace_id, user_workspace.auth_type) .open_workspace(
&user_workspace.workspace_id,
user_workspace.workspace_auth_type,
)
.await; .await;
let second_workspace = test.get_current_workspace().await; let second_workspace = test.get_current_workspace().await;
let second_workspace = test.get_user_workspace(&second_workspace.id).await; let second_workspace = test.get_user_workspace(&second_workspace.id).await;
@ -144,7 +150,7 @@ async fn af_cloud_open_workspace_test() {
test test
.open_workspace( .open_workspace(
&first_workspace.workspace_id, &first_workspace.workspace_id,
first_workspace.auth_type.clone(), first_workspace.workspace_auth_type.clone(),
) )
.await; .await;
sleep(Duration::from_millis(300)).await; sleep(Duration::from_millis(300)).await;
@ -155,7 +161,7 @@ async fn af_cloud_open_workspace_test() {
test test
.open_workspace( .open_workspace(
&second_workspace.workspace_id, &second_workspace.workspace_id,
second_workspace.auth_type.clone(), second_workspace.workspace_auth_type.clone(),
) )
.await; .await;
sleep(Duration::from_millis(200)).await; sleep(Duration::from_millis(200)).await;
@ -168,7 +174,7 @@ async fn af_cloud_open_workspace_test() {
test test
.open_workspace( .open_workspace(
&first_workspace.workspace_id, &first_workspace.workspace_id,
first_workspace.auth_type.clone(), first_workspace.workspace_auth_type.clone(),
) )
.await; .await;
let views_1 = test.get_all_workspace_views().await; let views_1 = test.get_all_workspace_views().await;
@ -180,7 +186,7 @@ async fn af_cloud_open_workspace_test() {
test test
.open_workspace( .open_workspace(
&second_workspace.workspace_id, &second_workspace.workspace_id,
second_workspace.auth_type.clone(), second_workspace.workspace_auth_type.clone(),
) )
.await; .await;
let views_2 = test.get_all_workspace_views().await; let views_2 = test.get_all_workspace_views().await;
@ -239,7 +245,10 @@ async fn af_cloud_different_open_same_workspace_test() {
let index = i % 2; let index = i % 2;
let iter_workspace_id = &all_workspaces[index].workspace_id; let iter_workspace_id = &all_workspaces[index].workspace_id;
client client
.open_workspace(iter_workspace_id, all_workspaces[index].auth_type.clone()) .open_workspace(
iter_workspace_id,
all_workspaces[index].workspace_auth_type.clone(),
)
.await; .await;
if iter_workspace_id == &cloned_shared_workspace_id { if iter_workspace_id == &cloned_shared_workspace_id {
let views = client.get_all_workspace_views().await; let views = client.get_all_workspace_views().await;

View File

@ -133,9 +133,9 @@ impl UserCloudService for LocalServerUserServiceImpl {
async fn open_workspace(&self, workspace_id: &Uuid) -> Result<UserWorkspace, FlowyError> { async fn open_workspace(&self, workspace_id: &Uuid) -> Result<UserWorkspace, FlowyError> {
let uid = self.user.user_id()?; let uid = self.user.user_id()?;
let conn = self.user.get_sqlite_db(uid)?; let mut conn = self.user.get_sqlite_db(uid)?;
let workspace = select_user_workspace(&workspace_id.to_string(), conn)?; let workspace = select_user_workspace(&workspace_id.to_string(), &mut conn)?;
Ok(UserWorkspace::from(workspace)) Ok(UserWorkspace::from(workspace))
} }

View File

@ -1,10 +1,10 @@
-- Your SQL goes here -- Your SQL goes here
ALTER TABLE user_workspace_table ALTER TABLE user_workspace_table
ADD COLUMN auth_type INTEGER NOT NULL DEFAULT 1; ADD COLUMN workspace_type INTEGER NOT NULL DEFAULT 1;
-- 2. Backfill from user_table.auth_type -- 2. Backfill from user_table.auth_type
UPDATE user_workspace_table UPDATE user_workspace_table
SET auth_type = (SELECT ut.auth_type SET workspace_type = (SELECT ut.auth_type
FROM user_table ut FROM user_table ut
WHERE ut.id = CAST(user_workspace_table.uid AS TEXT)) WHERE ut.id = CAST(user_workspace_table.uid AS TEXT))
WHERE EXISTS (SELECT 1 WHERE EXISTS (SELECT 1

View File

@ -107,7 +107,7 @@ diesel::table! {
icon -> Text, icon -> Text,
member_count -> BigInt, member_count -> BigInt,
role -> Nullable<Integer>, role -> Nullable<Integer>,
auth_type -> Integer, workspace_type -> Integer,
} }
} }
@ -132,16 +132,16 @@ diesel::table! {
} }
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
af_collab_metadata, af_collab_metadata,
chat_local_setting_table, chat_local_setting_table,
chat_message_table, chat_message_table,
chat_table, chat_table,
collab_snapshot, collab_snapshot,
upload_file_part, upload_file_part,
upload_file_table, upload_file_table,
user_data_migration_records, user_data_migration_records,
user_table, user_table,
user_workspace_table, user_workspace_table,
workspace_members_table, workspace_members_table,
workspace_setting_table, workspace_setting_table,
); );

View File

@ -17,7 +17,7 @@ pub struct UserWorkspaceTable {
pub icon: String, pub icon: String,
pub member_count: i64, pub member_count: i64,
pub role: Option<i32>, pub role: Option<i32>,
pub auth_type: i32, pub workspace_type: i32,
} }
#[derive(AsChangeset, Identifiable, Default, Debug)] #[derive(AsChangeset, Identifiable, Default, Debug)]
@ -50,18 +50,18 @@ impl UserWorkspaceTable {
icon: workspace.icon.clone(), icon: workspace.icon.clone(),
member_count: workspace.member_count, member_count: workspace.member_count,
role: workspace.role.clone().map(|v| v as i32), role: workspace.role.clone().map(|v| v as i32),
auth_type: auth_type as i32, workspace_type: auth_type as i32,
}) })
} }
} }
pub fn select_user_workspace( pub fn select_user_workspace(
workspace_id: &str, workspace_id: &str,
mut conn: DBConnection, conn: &mut SqliteConnection,
) -> FlowyResult<UserWorkspaceTable> { ) -> FlowyResult<UserWorkspaceTable> {
let row = user_workspace_table::dsl::user_workspace_table let row = user_workspace_table::dsl::user_workspace_table
.filter(user_workspace_table::id.eq(workspace_id)) .filter(user_workspace_table::id.eq(workspace_id))
.first::<UserWorkspaceTable>(&mut *conn)?; .first::<UserWorkspaceTable>(conn)?;
Ok(row) Ok(row)
} }
@ -106,7 +106,7 @@ pub fn upsert_user_workspace(
user_workspace_table::icon.eq(row.icon), user_workspace_table::icon.eq(row.icon),
user_workspace_table::member_count.eq(row.member_count), user_workspace_table::member_count.eq(row.member_count),
user_workspace_table::role.eq(row.role), user_workspace_table::role.eq(row.role),
user_workspace_table::auth_type.eq(row.auth_type), user_workspace_table::workspace_type.eq(row.workspace_type),
)) ))
.execute(conn)?; .execute(conn)?;
@ -153,7 +153,7 @@ pub fn delete_user_all_workspace(
let n = diesel::delete( let n = diesel::delete(
user_workspace_table::dsl::user_workspace_table user_workspace_table::dsl::user_workspace_table
.filter(user_workspace_table::uid.eq(uid)) .filter(user_workspace_table::uid.eq(uid))
.filter(user_workspace_table::auth_type.eq(auth_type as i32)), .filter(user_workspace_table::workspace_type.eq(auth_type as i32)),
) )
.execute(conn)?; .execute(conn)?;
info!( info!(

View File

@ -186,7 +186,7 @@ pub struct UserWorkspacePB {
pub role: Option<AFRolePB>, pub role: Option<AFRolePB>,
#[pb(index = 7)] #[pb(index = 7)]
pub auth_type: AuthTypePB, pub workspace_auth_type: AuthTypePB,
} }
impl From<(AuthType, UserWorkspace)> for UserWorkspacePB { impl From<(AuthType, UserWorkspace)> for UserWorkspacePB {
@ -198,7 +198,7 @@ impl From<(AuthType, UserWorkspace)> for UserWorkspacePB {
icon: value.1.icon, icon: value.1.icon,
member_count: value.1.member_count, member_count: value.1.member_count,
role: value.1.role.map(AFRolePB::from), role: value.1.role.map(AFRolePB::from),
auth_type: AuthTypePB::from(value.0), workspace_auth_type: AuthTypePB::from(value.0),
} }
} }
} }
@ -212,7 +212,7 @@ impl From<UserWorkspaceTable> for UserWorkspacePB {
icon: value.icon, icon: value.icon,
member_count: value.member_count, member_count: value.member_count,
role: value.role.map(AFRolePB::from), role: value.role.map(AFRolePB::from),
auth_type: AuthTypePB::from(value.auth_type), workspace_auth_type: AuthTypePB::from(value.workspace_type),
} }
} }
} }

View File

@ -0,0 +1,58 @@
use diesel::SqliteConnection;
use semver::Version;
use std::sync::Arc;
use tracing::{info, instrument};
use collab_integrate::CollabKVDB;
use flowy_error::FlowyResult;
use flowy_user_pub::entities::AuthType;
use crate::migrations::migration::UserDataMigration;
use flowy_user_pub::session::Session;
use flowy_user_pub::sql::{select_user_workspace, upsert_user_workspace};
pub struct AnonUserWorkspaceTableMigration;
impl UserDataMigration for AnonUserWorkspaceTableMigration {
fn name(&self) -> &str {
"anon_user_workspace_table_migration"
}
fn run_when(
&self,
first_installed_version: &Option<Version>,
_current_version: &Version,
) -> bool {
match first_installed_version {
None => true,
Some(version) => version <= &Version::new(0, 8, 10),
}
}
#[instrument(name = "AnonUserWorkspaceTableMigration", skip_all, err)]
fn run(
&self,
session: &Session,
_collab_db: &Arc<CollabKVDB>,
auth_type: &AuthType,
db: &mut SqliteConnection,
) -> FlowyResult<()> {
// For historical reason, anon user doesn't have a workspace in user_workspace_table.
// So we need to create a new entry for the anon user in the user_workspace_table.
if matches!(auth_type, AuthType::Local) {
let user_workspace = &session.user_workspace;
let result = select_user_workspace(&user_workspace.id, db);
if let Err(e) = result {
if e.is_record_not_found() {
info!(
"Anon user workspace not found in the database, creating a new entry for user_id: {}",
session.user_id
);
upsert_user_workspace(session.user_id, *auth_type, user_workspace.clone(), db)?;
}
}
}
Ok(())
}
}

View File

@ -2,6 +2,7 @@ use std::sync::Arc;
use collab_plugins::local_storage::kv::doc::migrate_old_keys; use collab_plugins::local_storage::kv::doc::migrate_old_keys;
use collab_plugins::local_storage::kv::KVTransactionDB; use collab_plugins::local_storage::kv::KVTransactionDB;
use diesel::SqliteConnection;
use semver::Version; use semver::Version;
use tracing::{instrument, trace}; use tracing::{instrument, trace};
@ -40,6 +41,7 @@ impl UserDataMigration for CollabDocKeyWithWorkspaceIdMigration {
session: &Session, session: &Session,
collab_db: &Arc<CollabKVDB>, collab_db: &Arc<CollabKVDB>,
_authenticator: &AuthType, _authenticator: &AuthType,
_db: &mut SqliteConnection,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
trace!( trace!(
"migrate key with workspace id:{}", "migrate key with workspace id:{}",

View File

@ -6,6 +6,7 @@ use collab_document::document::Document;
use collab_document::document_data::default_document_data; use collab_document::document_data::default_document_data;
use collab_folder::{Folder, View}; use collab_folder::{Folder, View};
use collab_plugins::local_storage::kv::KVTransactionDB; use collab_plugins::local_storage::kv::KVTransactionDB;
use diesel::SqliteConnection;
use semver::Version; use semver::Version;
use tracing::{event, instrument}; use tracing::{event, instrument};
@ -42,6 +43,7 @@ impl UserDataMigration for HistoricalEmptyDocumentMigration {
session: &Session, session: &Session,
collab_db: &Arc<CollabKVDB>, collab_db: &Arc<CollabKVDB>,
authenticator: &AuthType, authenticator: &AuthType,
_db: &mut SqliteConnection,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
// - The `empty document` struct has already undergone refactoring prior to the launch of the AppFlowy cloud version. // - The `empty document` struct has already undergone refactoring prior to the launch of the AppFlowy cloud version.
// - Consequently, if a user is utilizing the AppFlowy cloud version, there is no need to perform any migration for the `empty document` struct. // - Consequently, if a user is utilizing the AppFlowy cloud version, there is no need to perform any migration for the `empty document` struct.

View File

@ -54,7 +54,7 @@ impl UserLocalDataMigration {
pub fn run( pub fn run(
self, self,
migrations: Vec<Box<dyn UserDataMigration>>, migrations: Vec<Box<dyn UserDataMigration>>,
authenticator: &AuthType, auth_type: &AuthType,
app_version: &Version, app_version: &Version,
) -> FlowyResult<Vec<String>> { ) -> FlowyResult<Vec<String>> {
let mut applied_migrations = vec![]; let mut applied_migrations = vec![];
@ -75,7 +75,7 @@ impl UserLocalDataMigration {
let migration_name = migration.name().to_string(); let migration_name = migration.name().to_string();
if !duplicated_names.contains(&migration_name) { if !duplicated_names.contains(&migration_name) {
migration.run(&self.session, &self.collab_db, authenticator)?; migration.run(&self.session, &self.collab_db, auth_type, &mut conn)?;
applied_migrations.push(migration.name().to_string()); applied_migrations.push(migration.name().to_string());
save_migration_record(&mut conn, &migration_name); save_migration_record(&mut conn, &migration_name);
duplicated_names.push(migration_name); duplicated_names.push(migration_name);
@ -99,6 +99,7 @@ pub trait UserDataMigration {
user: &Session, user: &Session,
collab_db: &Arc<CollabKVDB>, collab_db: &Arc<CollabKVDB>,
authenticator: &AuthType, authenticator: &AuthType,
db: &mut SqliteConnection,
) -> FlowyResult<()>; ) -> FlowyResult<()>;
} }

View File

@ -1,6 +1,7 @@
use flowy_user_pub::session::Session; use flowy_user_pub::session::Session;
use std::sync::Arc; use std::sync::Arc;
pub mod anon_user_workspace;
pub mod doc_key_with_workspace; pub mod doc_key_with_workspace;
pub mod document_empty_content; pub mod document_empty_content;
pub mod migration; pub mod migration;

View File

@ -2,6 +2,7 @@ use std::sync::Arc;
use collab_folder::Folder; use collab_folder::Folder;
use collab_plugins::local_storage::kv::{KVTransactionDB, PersistenceError}; use collab_plugins::local_storage::kv::{KVTransactionDB, PersistenceError};
use diesel::SqliteConnection;
use semver::Version; use semver::Version;
use tracing::instrument; use tracing::instrument;
@ -40,6 +41,7 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
session: &Session, session: &Session,
collab_db: &Arc<CollabKVDB>, collab_db: &Arc<CollabKVDB>,
_authenticator: &AuthType, _authenticator: &AuthType,
_db: &mut SqliteConnection,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
collab_db.with_write_txn(|write_txn| { collab_db.with_write_txn(|write_txn| {
if let Ok(collab) = load_collab( if let Ok(collab) = load_collab(

View File

@ -2,6 +2,7 @@ use std::sync::Arc;
use collab_folder::Folder; use collab_folder::Folder;
use collab_plugins::local_storage::kv::{KVTransactionDB, PersistenceError}; use collab_plugins::local_storage::kv::{KVTransactionDB, PersistenceError};
use diesel::SqliteConnection;
use semver::Version; use semver::Version;
use tracing::instrument; use tracing::instrument;
@ -38,6 +39,7 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
session: &Session, session: &Session,
collab_db: &Arc<CollabKVDB>, collab_db: &Arc<CollabKVDB>,
_authenticator: &AuthType, _authenticator: &AuthType,
_db: &mut SqliteConnection,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
collab_db.with_write_txn(|write_txn| { collab_db.with_write_txn(|write_txn| {
if let Ok(collab) = load_collab( if let Ok(collab) = load_collab(

View File

@ -38,6 +38,7 @@ use crate::services::authenticate_user::AuthenticateUser;
use crate::services::cloud_config::get_cloud_config; use crate::services::cloud_config::get_cloud_config;
use crate::services::collab_interact::{DefaultCollabInteract, UserReminder}; use crate::services::collab_interact::{DefaultCollabInteract, UserReminder};
use crate::migrations::anon_user_workspace::AnonUserWorkspaceTableMigration;
use crate::migrations::doc_key_with_workspace::CollabDocKeyWithWorkspaceIdMigration; use crate::migrations::doc_key_with_workspace::CollabDocKeyWithWorkspaceIdMigration;
use crate::{errors::FlowyError, notification::*}; use crate::{errors::FlowyError, notification::*};
use flowy_user_pub::session::Session; use flowy_user_pub::session::Session;
@ -849,6 +850,7 @@ fn collab_migration_list() -> Vec<Box<dyn UserDataMigration>> {
Box::new(FavoriteV1AndWorkspaceArrayMigration), Box::new(FavoriteV1AndWorkspaceArrayMigration),
Box::new(WorkspaceTrashMapToSectionMigration), Box::new(WorkspaceTrashMapToSectionMigration),
Box::new(CollabDocKeyWithWorkspaceIdMigration), Box::new(CollabDocKeyWithWorkspaceIdMigration),
Box::new(AnonUserWorkspaceTableMigration),
] ]
} }

View File

@ -157,8 +157,8 @@ impl UserManager {
self.cloud_service.set_server_auth_type(&auth_type); self.cloud_service.set_server_auth_type(&auth_type);
let uid = self.user_id()?; let uid = self.user_id()?;
let conn = self.db_connection(self.user_id()?)?; let mut conn = self.db_connection(self.user_id()?)?;
let user_workspace = match select_user_workspace(&workspace_id.to_string(), conn) { let user_workspace = match select_user_workspace(&workspace_id.to_string(), &mut conn) {
Err(err) => { Err(err) => {
if err.is_record_not_found() { if err.is_record_not_found() {
sync_workspace( sync_workspace(
@ -401,8 +401,8 @@ impl UserManager {
uid: i64, uid: i64,
workspace_id: &Uuid, workspace_id: &Uuid,
) -> FlowyResult<UserWorkspaceTable> { ) -> FlowyResult<UserWorkspaceTable> {
let conn = self.db_connection(uid)?; let mut conn = self.db_connection(uid)?;
select_user_workspace(workspace_id.to_string().as_str(), conn) select_user_workspace(workspace_id.to_string().as_str(), &mut conn)
} }
pub async fn get_all_user_workspaces( pub async fn get_all_user_workspaces(