diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart index 14c17b40fd..abdeb90e47 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart @@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -52,7 +53,17 @@ class RowActionMenu extends StatelessWidget { child: FlowyButton( text: FlowyText.medium(action.text, overflow: TextOverflow.ellipsis), onTap: () { - action.performAction(context, viewId, rowId); + if (action == RowAction.delete) { + NavigatorOkCancelDialog( + message: LocaleKeys.grid_row_deleteRowPrompt.tr(), + onOkPressed: () { + action.performAction(context, viewId, rowId); + }, + ).show(context); + } else { + action.performAction(context, viewId, rowId); + } + PopoverContainer.of(context).close(); }, leftIcon: icon, diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 315c3a0a31..b8a1ad7c0c 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1087,6 +1087,7 @@ "action": "Action", "add": "Click add to below", "drag": "Drag to move", + "deleteRowPrompt": "Are you sure you want to delete this row? This action cannot be undone", "dragAndClick": "Drag to move, click to open menu", "insertRecordAbove": "Insert record above", "insertRecordBelow": "Insert record below", @@ -1915,4 +1916,4 @@ "title": "Spaces", "defaultSpaceName": "General" } -} +} \ No newline at end of file diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 2bbf911d5c..71fa48ce99 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -1165,7 +1165,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.8.0", + "phf 0.11.2", "smallvec", ] @@ -3790,7 +3790,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros", + "phf_macros 0.8.0", "phf_shared 0.8.0", "proc-macro-hack", ] @@ -3810,6 +3810,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ + "phf_macros 0.11.2", "phf_shared 0.11.2", ] @@ -3877,6 +3878,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.47", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -4080,7 +4094,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", "heck 0.4.1", - "itertools 0.10.5", + "itertools 0.11.0", "log", "multimap", "once_cell", @@ -4101,7 +4115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.47", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 684fed94f0..f561310fec 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -88,6 +88,7 @@ collab-database = { version = "0.2" } collab-plugins = { version = "0.2" } collab-user = { version = "0.2" } yrs = "0.18.8" +validator = { version = "0.16.1", features = ["derive"] } # Please using the following command to update the revision id # Current directory: frontend diff --git a/frontend/rust-lib/flowy-chat/Cargo.toml b/frontend/rust-lib/flowy-chat/Cargo.toml index 0698a593d1..1abbb62a17 100644 --- a/frontend/rust-lib/flowy-chat/Cargo.toml +++ b/frontend/rust-lib/flowy-chat/Cargo.toml @@ -18,7 +18,7 @@ uuid.workspace = true strum_macros = "0.21" protobuf.workspace = true bytes.workspace = true -validator = { version = "0.16.0", features = ["derive"] } +validator = { workspace = true, features = ["derive"] } lib-infra = { workspace = true, features = ["isolate_flutter"] } flowy-chat-pub.workspace = true dashmap = "5.5" diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index 97a11f3064..8a5be01139 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -47,7 +47,7 @@ chrono-tz = "0.8.2" csv = "1.1.6" strum = "0.25" strum_macros = "0.25" -validator = { version = "0.16.0", features = ["derive"] } +validator = { workspace = true, features = ["derive"] } [dev-dependencies] event-integration-test = { path = "../event-integration-test", default-features = false } diff --git a/frontend/rust-lib/flowy-error/Cargo.toml b/frontend/rust-lib/flowy-error/Cargo.toml index e671be811f..ae0d8bf9cd 100644 --- a/frontend/rust-lib/flowy-error/Cargo.toml +++ b/frontend/rust-lib/flowy-error/Cargo.toml @@ -13,7 +13,7 @@ protobuf.workspace = true bytes.workspace = true anyhow.workspace = true thiserror = "1.0" -validator = "0.16.0" +validator.workspace = true tokio = { workspace = true, features = ["sync", "rt"] } fancy-regex = { version = "0.11.0" } diff --git a/frontend/rust-lib/flowy-folder/Cargo.toml b/frontend/rust-lib/flowy-folder/Cargo.toml index c80a0bfdde..b8ed79720f 100644 --- a/frontend/rust-lib/flowy-folder/Cargo.toml +++ b/frontend/rust-lib/flowy-folder/Cargo.toml @@ -35,9 +35,9 @@ strum_macros = "0.21" protobuf.workspace = true uuid.workspace = true tokio-stream = { workspace = true, features = ["sync"] } -serde = { workspace = true, features = ["derive"]} +serde = { workspace = true, features = ["derive"] } serde_json.workspace = true -validator = "0.16.0" +validator.workspace = true async-trait.workspace = true [build-dependencies] diff --git a/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/down.sql b/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/down.sql new file mode 100644 index 0000000000..d9a93fe9a1 --- /dev/null +++ b/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` diff --git a/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/up.sql b/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/up.sql new file mode 100644 index 0000000000..189cebe267 --- /dev/null +++ b/frontend/rust-lib/flowy-sqlite/migrations/2024-06-14-020242_workspace_member/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here +CREATE TABLE workspace_members_table ( + email TEXT KEY NOT NULL, + role INTEGER NOT NULL, + name TEXT NOT NULL, + avatar_url TEXT, + uid BIGINT NOT NULL, + workspace_id TEXT NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (email, workspace_id) +); \ No newline at end of file diff --git a/frontend/rust-lib/flowy-sqlite/src/schema.rs b/frontend/rust-lib/flowy-sqlite/src/schema.rs index b1737d85eb..5dbbc78501 100644 --- a/frontend/rust-lib/flowy-sqlite/src/schema.rs +++ b/frontend/rust-lib/flowy-sqlite/src/schema.rs @@ -67,6 +67,18 @@ diesel::table! { } } +diesel::table! { + workspace_members_table (email, workspace_id) { + email -> Text, + role -> Integer, + name -> Text, + avatar_url -> Nullable, + uid -> BigInt, + workspace_id -> Text, + updated_at -> Timestamp, + } +} + diesel::allow_tables_to_appear_in_same_query!( chat_message_table, chat_table, @@ -74,4 +86,5 @@ diesel::allow_tables_to_appear_in_same_query!( user_data_migration_records, user_table, user_workspace_table, + workspace_members_table, ); diff --git a/frontend/rust-lib/flowy-user-pub/src/entities.rs b/frontend/rust-lib/flowy-user-pub/src/entities.rs index ae4f85d71a..566aabe1c0 100644 --- a/frontend/rust-lib/flowy-user-pub/src/entities.rs +++ b/frontend/rust-lib/flowy-user-pub/src/entities.rs @@ -384,11 +384,33 @@ pub enum UserTokenState { } // Workspace Role -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize_repr, Deserialize_repr)] +#[repr(u8)] pub enum Role { - Owner, - Member, - Guest, + Owner = 0, + Member = 1, + Guest = 2, +} + +impl From for Role { + fn from(value: i32) -> Self { + match value { + 0 => Role::Owner, + 1 => Role::Member, + 2 => Role::Guest, + _ => Role::Guest, + } + } +} + +impl From for i32 { + fn from(value: Role) -> Self { + match value { + Role::Owner => 0, + Role::Member => 1, + Role::Guest => 2, + } + } } pub struct WorkspaceMember { diff --git a/frontend/rust-lib/flowy-user/Cargo.toml b/frontend/rust-lib/flowy-user/Cargo.toml index f206e8d4a8..0d53b9f6b7 100644 --- a/frontend/rust-lib/flowy-user/Cargo.toml +++ b/frontend/rust-lib/flowy-user/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" flowy-derive.workspace = true flowy-sqlite = { workspace = true } flowy-encrypt = { workspace = true } -flowy-error = { workspace = true, features = ["impl_from_dispatch_error", "impl_from_sqlite", "impl_from_collab_folder", "impl_from_collab_persistence"] } +flowy-error = { workspace = true, features = ["impl_from_dispatch_error", "impl_from_sqlite", "impl_from_collab_folder", "impl_from_collab_persistence", "impl_from_collab_document"] } flowy-folder-pub = { workspace = true } lib-infra = { workspace = true } flowy-notification = { workspace = true } @@ -39,7 +39,6 @@ parking_lot.workspace = true strum = "0.25" strum_macros = "0.25.2" tokio = { workspace = true, features = ["rt"] } -validator = "0.16.0" unicode-segmentation = "1.10" fancy-regex = "0.11.0" uuid.workspace = true @@ -47,6 +46,7 @@ chrono = { workspace = true, default-features = false, features = ["clock"] } base64 = "^0.21" tokio-stream = "0.1.14" semver = "1.0.22" +validator = { workspace = true, features = ["derive"] } [dev-dependencies] nanoid = "0.4.0" diff --git a/frontend/rust-lib/flowy-user/src/services/sqlite_sql/member_sql.rs b/frontend/rust-lib/flowy-user/src/services/sqlite_sql/member_sql.rs new file mode 100644 index 0000000000..70351ab105 --- /dev/null +++ b/frontend/rust-lib/flowy-user/src/services/sqlite_sql/member_sql.rs @@ -0,0 +1,52 @@ +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::{query_dsl::*, DBConnection, ExpressionMethods}; + +#[derive(Queryable, Insertable, AsChangeset, Debug)] +#[diesel(table_name = workspace_members_table)] +#[diesel(primary_key(email, workspace_id))] +pub struct WorkspaceMemberTable { + pub email: String, + pub role: i32, + pub name: String, + pub avatar_url: Option, + pub uid: i64, + pub workspace_id: String, + pub updated_at: chrono::NaiveDateTime, +} + +pub fn upsert_workspace_member>( + mut conn: DBConnection, + member: T, +) -> FlowyResult<()> { + let member = member.into(); + + insert_into(workspace_members_table::table) + .values(&member) + .on_conflict(( + workspace_members_table::email, + workspace_members_table::workspace_id, + )) + .do_update() + .set(&member) + .execute(&mut conn)?; + + Ok(()) +} + +pub fn select_workspace_member( + mut conn: DBConnection, + workspace_id: &str, + uid: i64, +) -> FlowyResult { + let member = dsl::workspace_members_table + .filter(workspace_members_table::workspace_id.eq(workspace_id)) + .filter(workspace_members_table::uid.eq(uid)) + .first::(&mut conn)?; + + Ok(member) +} diff --git a/frontend/rust-lib/flowy-user/src/services/sqlite_sql/mod.rs b/frontend/rust-lib/flowy-user/src/services/sqlite_sql/mod.rs index b3ef842a91..93e642f72e 100644 --- a/frontend/rust-lib/flowy-user/src/services/sqlite_sql/mod.rs +++ b/frontend/rust-lib/flowy-user/src/services/sqlite_sql/mod.rs @@ -1,2 +1,3 @@ +pub(crate) mod member_sql; pub(crate) mod user_sql; pub(crate) mod workspace_sql; diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs index a94639e620..cf0de6c36b 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs @@ -1,9 +1,10 @@ +use chrono::{Duration, NaiveDateTime, Utc}; use std::convert::TryFrom; use std::sync::Arc; use collab_entity::{CollabObject, CollabType}; use collab_integrate::CollabKVDB; -use tracing::{error, info, instrument, warn}; +use tracing::{error, info, instrument, trace, warn}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_folder_pub::entities::{AppFlowyData, ImportData}; @@ -23,6 +24,9 @@ use crate::notification::{send_notification, UserNotification}; use crate::services::data_import::{ generate_import_data, upload_collab_objects_data, ImportedFolder, ImportedSource, }; +use crate::services::sqlite_sql::member_sql::{ + select_workspace_member, upsert_workspace_member, WorkspaceMemberTable, +}; use crate::services::sqlite_sql::workspace_sql::{ get_all_user_workspace_op, get_user_workspace_op, insert_new_workspaces_op, UserWorkspaceTable, }; @@ -483,11 +487,54 @@ impl UserManager { #[instrument(level = "debug", skip(self), err)] pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult { let workspace_id = self.get_session()?.user_workspace.id.clone(); + let db = self.authenticate_user.get_sqlite_connection(uid)?; + // Can opt in using memory cache + if let Ok(member_record) = select_workspace_member(db, &workspace_id, uid) { + if is_older_than_n_minutes(member_record.updated_at, 10) { + self + .get_workspace_member_info_from_remote(&workspace_id, uid) + .await?; + } + + return Ok(WorkspaceMember { + email: member_record.email, + role: member_record.role.into(), + name: member_record.name, + avatar_url: member_record.avatar_url, + }); + } + + let member = self + .get_workspace_member_info_from_remote(&workspace_id, uid) + .await?; + + Ok(member) + } + + async fn get_workspace_member_info_from_remote( + &self, + workspace_id: &str, + uid: i64, + ) -> FlowyResult { + trace!("get workspace member info from remote: {}", workspace_id); let member = self .cloud_services .get_user_service()? .get_workspace_member_info(&workspace_id, uid) .await?; + + let record = WorkspaceMemberTable { + email: member.email.clone(), + role: member.role.clone().into(), + name: member.name.clone(), + avatar_url: member.avatar_url.clone(), + uid, + workspace_id: workspace_id.to_string(), + updated_at: Utc::now().naive_utc(), + }; + + let db = self.authenticate_user.get_sqlite_connection(uid)?; + upsert_workspace_member(db, record)?; Ok(member) } } @@ -599,3 +646,11 @@ pub fn delete_user_workspaces(mut conn: DBConnection, workspace_id: &str) -> Flo } Ok(()) } + +fn is_older_than_n_minutes(updated_at: NaiveDateTime, minutes: i64) -> bool { + let current_time: NaiveDateTime = Utc::now().naive_utc(); + match current_time.checked_sub_signed(Duration::minutes(minutes)) { + Some(five_minutes_ago) => updated_at < five_minutes_ago, + None => false, + } +} diff --git a/frontend/rust-lib/lib-dispatch/Cargo.toml b/frontend/rust-lib/lib-dispatch/Cargo.toml index 48a7aba816..f81eb2d084 100644 --- a/frontend/rust-lib/lib-dispatch/Cargo.toml +++ b/frontend/rust-lib/lib-dispatch/Cargo.toml @@ -13,8 +13,7 @@ futures-core = { version = "0.3", default-features = false } futures-channel = "0.3.26" futures.workspace = true futures-util = "0.3.26" -bytes = {version = "1.4", features = ["serde"]} -tokio = { workspace = true, features = ["rt", "sync"] } +bytes = { version = "1.4", features = ["serde"] } nanoid = "0.4.0" dyn-clone = "1.0" @@ -25,16 +24,18 @@ serde_repr = { workspace = true, optional = true } validator = "0.16.1" tracing.workspace = true parking_lot = "0.12" -bincode = { version = "1.3", optional = true} +bincode = { version = "1.3", optional = true } protobuf = { workspace = true, optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] thread-id = "3.3.0" +tokio = { workspace = true, features = ["full", "rt-multi-thread"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["js"]} +getrandom = { version = "0.2", features = ["js"] } wasm-bindgen = { version = "0.2.89" } wasm-bindgen-futures = "0.4" +tokio = { workspace = true, features = ["rt", "sync"] } [dev-dependencies] tokio = { workspace = true, features = ["rt"] } @@ -43,7 +44,7 @@ futures-util = "0.3.26" [features] default = ["use_protobuf"] use_serde = ["bincode", "serde_json", "serde", "serde_repr"] -use_protobuf= ["protobuf"] +use_protobuf = ["protobuf"] local_set = [] diff --git a/frontend/rust-lib/lib-infra/Cargo.toml b/frontend/rust-lib/lib-infra/Cargo.toml index c19b7a50cc..6b902537ee 100644 --- a/frontend/rust-lib/lib-infra/Cargo.toml +++ b/frontend/rust-lib/lib-infra/Cargo.toml @@ -18,7 +18,7 @@ md5 = "0.7.0" anyhow.workspace = true walkdir = "2.4.0" tempfile = "3.8.1" -validator = "0.16.0" +validator = { version = "0.16.1", features = ["derive"] } tracing.workspace = true atomic_refcell = "0.1" allo-isolate = { version = "^0.1", features = ["catch-unwind"], optional = true }