Procházet zdrojové kódy

create default workspace

appflowy před 3 roky
rodič
revize
dd2cec28e4
36 změnil soubory, kde provedl 775 přidání a 328 odebrání
  1. 13 13
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  2. 5 5
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  3. 3 0
      backend/src/application.rs
  4. 13 31
      backend/src/entities/workspace.rs
  5. 3 1
      backend/src/sqlx_ext/utils.rs
  6. 25 23
      backend/src/user_service/auth.rs
  7. 25 47
      backend/src/workspace_service/app/app.rs
  8. 103 0
      backend/src/workspace_service/app/builder.rs
  9. 3 0
      backend/src/workspace_service/app/mod.rs
  10. 1 0
      backend/src/workspace_service/mod.rs
  11. 3 0
      backend/src/workspace_service/user_default/mod.rs
  12. 73 0
      backend/src/workspace_service/user_default/user_default.rs
  13. 92 0
      backend/src/workspace_service/view/builder.rs
  14. 2 0
      backend/src/workspace_service/view/mod.rs
  15. 20 42
      backend/src/workspace_service/view/view.rs
  16. 82 0
      backend/src/workspace_service/workspace/builder.rs
  17. 2 0
      backend/src/workspace_service/workspace/mod.rs
  18. 10 2
      backend/src/workspace_service/workspace/router.rs
  19. 53 36
      backend/src/workspace_service/workspace/workspace.rs
  20. 6 0
      backend/tests/api/helper.rs
  21. 48 61
      backend/tests/api/workspace.rs
  22. 1 1
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  23. 4 0
      rust-lib/flowy-net/src/request/request.rs
  24. 6 0
      rust-lib/flowy-net/src/response/response.rs
  25. 19 0
      rust-lib/flowy-workspace/src/entities/app/app_query.rs
  26. 24 0
      rust-lib/flowy-workspace/src/entities/app/app_update.rs
  27. 19 0
      rust-lib/flowy-workspace/src/entities/view/view_query.rs
  28. 23 0
      rust-lib/flowy-workspace/src/entities/view/view_update.rs
  29. 2 2
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  30. 14 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs
  31. 2 2
      rust-lib/flowy-workspace/src/handlers/workspace_handler.rs
  32. 56 56
      rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs
  33. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto
  34. 10 1
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  35. 7 2
      rust-lib/flowy-workspace/tests/event/workspace_test.rs
  36. 2 2
      scripts/flowy-tool/src/main.rs

+ 13 - 13
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart

@@ -238,14 +238,14 @@ class Workspace extends $pb.GeneratedMessage {
   $0.RepeatedApp ensureApps() => $_ensure(3);
 }
 
-class Workspaces extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Workspaces', createEmptyInstance: create)
+class RepeatedWorkspace extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedWorkspace', createEmptyInstance: create)
     ..pc<Workspace>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Workspace.create)
     ..hasRequiredFields = false
   ;
 
-  Workspaces._() : super();
-  factory Workspaces({
+  RepeatedWorkspace._() : super();
+  factory RepeatedWorkspace({
     $core.Iterable<Workspace>? items,
   }) {
     final _result = create();
@@ -254,26 +254,26 @@ class Workspaces extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory Workspaces.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Workspaces.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory RepeatedWorkspace.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedWorkspace.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  Workspaces clone() => Workspaces()..mergeFromMessage(this);
+  RepeatedWorkspace clone() => RepeatedWorkspace()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  Workspaces copyWith(void Function(Workspaces) updates) => super.copyWith((message) => updates(message as Workspaces)) as Workspaces; // ignore: deprecated_member_use
+  RepeatedWorkspace copyWith(void Function(RepeatedWorkspace) updates) => super.copyWith((message) => updates(message as RepeatedWorkspace)) as RepeatedWorkspace; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static Workspaces create() => Workspaces._();
-  Workspaces createEmptyInstance() => create();
-  static $pb.PbList<Workspaces> createRepeated() => $pb.PbList<Workspaces>();
+  static RepeatedWorkspace create() => RepeatedWorkspace._();
+  RepeatedWorkspace createEmptyInstance() => create();
+  static $pb.PbList<RepeatedWorkspace> createRepeated() => $pb.PbList<RepeatedWorkspace>();
   @$core.pragma('dart2js:noInline')
-  static Workspaces getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Workspaces>(create);
-  static Workspaces? _defaultInstance;
+  static RepeatedWorkspace getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedWorkspace>(create);
+  static RepeatedWorkspace? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.List<Workspace> get items => $_getList(0);

+ 5 - 5
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart

@@ -44,13 +44,13 @@ const Workspace$json = const {
 
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');
-@$core.Deprecated('Use workspacesDescriptor instead')
-const Workspaces$json = const {
-  '1': 'Workspaces',
+@$core.Deprecated('Use repeatedWorkspaceDescriptor instead')
+const RepeatedWorkspace$json = const {
+  '1': 'RepeatedWorkspace',
   '2': const [
     const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Workspace', '10': 'items'},
   ],
 };
 
-/// Descriptor for `Workspaces`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List workspacesDescriptor = $convert.base64Decode('CgpXb3Jrc3BhY2VzEiAKBWl0ZW1zGAEgAygLMgouV29ya3NwYWNlUgVpdGVtcw==');
+/// Descriptor for `RepeatedWorkspace`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedWorkspaceDescriptor = $convert.base64Decode('ChFSZXBlYXRlZFdvcmtzcGFjZRIgCgVpdGVtcxgBIAMoCzIKLldvcmtzcGFjZVIFaXRlbXM=');

+ 3 - 0
backend/src/application.rs

@@ -78,6 +78,9 @@ fn user_scope() -> Scope {
             .route(web::get().to(workspace::read_handler))
             .route(web::patch().to(workspace::update_handler))
         )
+        .service(web::resource("/workspace_list/{user_id}")
+            .route(web::get().to(workspace::workspace_list))
+        )
         .service(web::resource("/app")
             .route(web::post().to(app::create_handler))
             .route(web::delete().to(app::delete_handler))

+ 13 - 31
backend/src/entities/workspace.rs

@@ -1,8 +1,4 @@
 use chrono::Utc;
-use flowy_workspace::entities::{
-    app::App,
-    view::{RepeatedView, View, ViewType},
-};
 
 #[derive(Debug, Clone, sqlx::FromRow)]
 pub struct WorkspaceTable {
@@ -28,19 +24,6 @@ pub struct AppTable {
     pub(crate) is_trash: bool,
 }
 
-impl std::convert::Into<App> for AppTable {
-    fn into(self) -> App {
-        App {
-            id: self.id.to_string(),
-            workspace_id: self.workspace_id,
-            name: self.name,
-            desc: self.description,
-            belongings: RepeatedView::default(),
-            version: 0,
-        }
-    }
-}
-
 #[derive(Debug, Clone, sqlx::FromRow)]
 pub struct ViewTable {
     pub(crate) id: uuid::Uuid,
@@ -53,17 +36,16 @@ pub struct ViewTable {
     pub(crate) view_type: i32,
     pub(crate) is_trash: bool,
 }
-
-impl std::convert::Into<View> for ViewTable {
-    fn into(self) -> View {
-        View {
-            id: self.id.to_string(),
-            belong_to_id: self.belong_to_id,
-            name: self.name,
-            desc: self.description,
-            view_type: ViewType::from(self.view_type),
-            version: 0,
-            belongings: RepeatedView::default(),
-        }
-    }
-}
+// impl std::convert::Into<View> for ViewTable {
+//     fn into(self) -> View {
+//         View {
+//             id: self.id.to_string(),
+//             belong_to_id: self.belong_to_id,
+//             name: self.name,
+//             desc: self.description,
+//             view_type: ViewType::from(self.view_type),
+//             version: 0,
+//             belongings: RepeatedView::default(),
+//         }
+//     }
+// }

+ 3 - 1
backend/src/sqlx_ext/utils.rs

@@ -1,5 +1,7 @@
 use flowy_net::errors::{ErrorCode, ServerError};
-use sqlx::Error;
+use sqlx::{Error, Postgres, Transaction};
+
+pub type DBTransaction<'a> = Transaction<'a, Postgres>;
 
 pub fn map_sqlx_error(error: sqlx::Error) -> ServerError {
     match error {

+ 25 - 23
backend/src/user_service/auth.rs

@@ -1,6 +1,8 @@
 use crate::{
     entities::{token::Token, user::UserTable},
+    sqlx_ext::DBTransaction,
     user_service::{hash_password, verify_password},
+    workspace_service::user_default::create_default_workspace,
 };
 use actix_identity::Identity;
 use anyhow::Context;
@@ -10,9 +12,8 @@ use flowy_net::{
     response::FlowyResponse,
 };
 use flowy_user::{
-    entities::{parser::UserName, SignInResponse, SignUpResponse},
-    prelude::parser::{UserEmail, UserPassword},
-    protobuf::{SignInParams, SignUpParams},
+    entities::parser::{UserEmail, UserName, UserPassword},
+    protobuf::{SignInParams, SignInResponse, SignUpParams, SignUpResponse},
 };
 use sqlx::{PgPool, Postgres, Transaction};
 
@@ -40,14 +41,14 @@ pub async fn sign_in(
     match verify_password(&password.0, &user.password) {
         Ok(true) => {
             let token = Token::create_token(&user)?;
-            let data = SignInResponse {
-                uid: user.id.to_string(),
-                name: user.name,
-                email: user.email,
-                token: token.into(),
-            };
-            id.remember(data.token.clone());
-            FlowyResponse::success().data(data)
+            let mut response_data = SignInResponse::default();
+            response_data.set_uid(user.id.to_string());
+            response_data.set_name(user.name);
+            response_data.set_email(user.email);
+            response_data.set_token(token.into());
+
+            id.remember(response_data.token.clone());
+            FlowyResponse::success().pb(response_data)
         },
         _ => Err(ServerError::password_not_match()),
     }
@@ -70,7 +71,7 @@ pub async fn register_user(
         .context("Failed to acquire a Postgres connection to register user")?;
 
     let _ = is_email_exist(&mut transaction, email.as_ref()).await?;
-    let data = insert_user(
+    let response_data = insert_new_user(
         &mut transaction,
         name.as_ref(),
         email.as_ref(),
@@ -79,16 +80,18 @@ pub async fn register_user(
     .await
     .context("Failed to insert user")?;
 
+    let _ = create_default_workspace(&mut transaction, response_data.get_uid()).await?;
+
     transaction
         .commit()
         .await
         .context("Failed to commit SQL transaction to register user.")?;
 
-    FlowyResponse::success().data(data)
+    FlowyResponse::success().pb(response_data)
 }
 
 async fn is_email_exist(
-    transaction: &mut Transaction<'_, Postgres>,
+    transaction: &mut DBTransaction<'_>,
     email: &str,
 ) -> Result<(), ServerError> {
     let result = sqlx::query(r#"SELECT email FROM user_table WHERE email = $1"#)
@@ -107,7 +110,7 @@ async fn is_email_exist(
 }
 
 async fn read_user(
-    transaction: &mut Transaction<'_, Postgres>,
+    transaction: &mut DBTransaction<'_>,
     email: &str,
 ) -> Result<UserTable, ServerError> {
     let user = sqlx::query_as::<Postgres, UserTable>("SELECT * FROM user_table WHERE email = $1")
@@ -119,8 +122,8 @@ async fn read_user(
     Ok(user)
 }
 
-async fn insert_user(
-    transaction: &mut Transaction<'_, Postgres>,
+async fn insert_new_user(
+    transaction: &mut DBTransaction<'_>,
     name: &str,
     email: &str,
     password: &str,
@@ -142,11 +145,10 @@ async fn insert_user(
     .await
     .map_err(|e| ServerError::internal().context(e))?;
 
-    let data = SignUpResponse {
-        uid: uuid.to_string(),
-        name: name.to_string(),
-        email: email.to_string(),
-    };
+    let mut response = SignUpResponse::default();
+    response.set_uid(uuid.to_string());
+    response.set_name(name.to_string());
+    response.set_email(email.to_string());
 
-    Ok(data)
+    Ok(response)
 }

+ 25 - 47
backend/src/workspace_service/app/app.rs

@@ -2,8 +2,11 @@ use flowy_net::{errors::ServerError, response::FlowyResponse};
 
 use crate::{
     entities::workspace::AppTable,
-    sqlx_ext::{map_sqlx_error, SqlBuilder},
-    workspace_service::view::read_views_belong_to_id,
+    sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
+    workspace_service::{
+        app::{check_app_id, make_app_from_table, Builder},
+        view::read_views_belong_to_id,
+    },
 };
 use anyhow::Context;
 use chrono::Utc;
@@ -13,13 +16,11 @@ use flowy_workspace::{
     entities::{
         app::{
             parser::{AppDesc, AppId, AppName},
-            App,
             RepeatedApp,
         },
-        view::RepeatedView,
         workspace::parser::WorkspaceId,
     },
-    protobuf::{CreateAppParams, QueryAppParams, UpdateAppParams},
+    protobuf::{App, CreateAppParams, QueryAppParams, RepeatedView, UpdateAppParams},
 };
 use protobuf::Message;
 use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
@@ -27,31 +28,21 @@ use uuid::Uuid;
 
 pub(crate) async fn create_app(
     pool: &PgPool,
-    params: CreateAppParams,
+    mut params: CreateAppParams,
 ) -> Result<FlowyResponse, ServerError> {
-    let color_bytes = params.get_color_style().write_to_bytes()?;
-    let name = AppName::parse(params.name).map_err(invalid_params)?;
-    let workspace_id = WorkspaceId::parse(params.workspace_id).map_err(invalid_params)?;
-    let user_id = UserId::parse(params.user_id).map_err(invalid_params)?;
-    let desc = AppDesc::parse(params.desc).map_err(invalid_params)?;
-
+    let name = AppName::parse(params.take_name()).map_err(invalid_params)?;
+    let workspace_id = WorkspaceId::parse(params.take_workspace_id()).map_err(invalid_params)?;
+    let user_id = UserId::parse(params.take_user_id()).map_err(invalid_params)?;
+    let desc = AppDesc::parse(params.take_desc()).map_err(invalid_params)?;
     let mut transaction = pool
         .begin()
         .await
         .context("Failed to acquire a Postgres connection to create app")?;
 
-    let uuid = uuid::Uuid::new_v4();
-    let time = Utc::now();
-
-    let (sql, args) = SqlBuilder::create("app_table")
-        .add_arg("id", uuid)
-        .add_arg("workspace_id", workspace_id.as_ref())
-        .add_arg("name", name.as_ref())
-        .add_arg("description", desc.as_ref())
-        .add_arg("color_style", color_bytes)
-        .add_arg("modified_time", &time)
-        .add_arg("create_time", &time)
-        .add_arg("user_id", user_id.as_ref())
+    let (sql, args, app) = Builder::new(user_id.as_ref(), workspace_id.as_ref())
+        .name(name.as_ref())
+        .desc(desc.as_ref())
+        .color_style(params.take_color_style())
         .build()?;
 
     let _ = sqlx::query_with(&sql, args)
@@ -64,16 +55,7 @@ pub(crate) async fn create_app(
         .await
         .context("Failed to commit SQL transaction to create app.")?;
 
-    let app = App {
-        id: uuid.to_string(),
-        workspace_id: workspace_id.as_ref().to_owned(),
-        name: name.as_ref().to_string(),
-        desc: desc.as_ref().to_string(),
-        belongings: RepeatedView::default(),
-        version: 0,
-    };
-
-    FlowyResponse::success().data(app)
+    FlowyResponse::success().pb(app)
 }
 
 pub(crate) async fn read_app(
@@ -99,7 +81,11 @@ pub(crate) async fn read_app(
 
     let mut views = RepeatedView::default();
     if params.read_belongings {
-        views.items = read_views_belong_to_id(&mut transaction, &table.id.to_string()).await?;
+        views.set_items(
+            read_views_belong_to_id(&mut transaction, &table.id.to_string())
+                .await?
+                .into(),
+        );
     }
 
     transaction
@@ -107,10 +93,8 @@ pub(crate) async fn read_app(
         .await
         .context("Failed to commit SQL transaction to read app.")?;
 
-    let mut app: App = table.into();
-    app.belongings = views;
-
-    FlowyResponse::success().data(app)
+    let app = make_app_from_table(table, views);
+    FlowyResponse::success().pb(app)
 }
 
 pub(crate) async fn update_app(
@@ -207,7 +191,7 @@ pub(crate) async fn delete_app(pool: &PgPool, app_id: &str) -> Result<FlowyRespo
 
 // transaction must be commit from caller
 pub(crate) async fn read_apps_belong_to_workspace<'c>(
-    transaction: &mut Transaction<'c, Postgres>,
+    transaction: &mut DBTransaction<'_>,
     workspace_id: &str,
 ) -> Result<Vec<App>, ServerError> {
     let workspace_id = WorkspaceId::parse(workspace_id.to_owned()).map_err(invalid_params)?;
@@ -223,14 +207,8 @@ pub(crate) async fn read_apps_belong_to_workspace<'c>(
 
     let apps = tables
         .into_iter()
-        .map(|table| table.into())
+        .map(|table| make_app_from_table(table, RepeatedView::default()))
         .collect::<Vec<App>>();
 
     Ok(apps)
 }
-
-fn check_app_id(id: String) -> Result<Uuid, ServerError> {
-    let app_id = AppId::parse(id).map_err(invalid_params)?;
-    let app_id = Uuid::parse_str(app_id.as_ref())?;
-    Ok(app_id)
-}

+ 103 - 0
backend/src/workspace_service/app/builder.rs

@@ -0,0 +1,103 @@
+use crate::{entities::workspace::AppTable, sqlx_ext::SqlBuilder};
+use chrono::Utc;
+use flowy_net::errors::{invalid_params, ServerError};
+use flowy_workspace::{
+    entities::app::parser::AppId,
+    protobuf::{App, ColorStyle, RepeatedView},
+};
+use protobuf::Message;
+use sqlx::postgres::PgArguments;
+use uuid::Uuid;
+
+pub struct Builder {
+    table: AppTable,
+}
+
+impl Builder {
+    pub fn new(user_id: &str, workspace_id: &str) -> Self {
+        let uuid = uuid::Uuid::new_v4();
+        let time = Utc::now();
+
+        let table = AppTable {
+            id: uuid,
+            workspace_id: workspace_id.to_string(),
+            name: "".to_string(),
+            description: "".to_string(),
+            color_style: default_color_style(),
+            last_view_id: "".to_string(),
+            modified_time: time,
+            create_time: time,
+            user_id: user_id.to_string(),
+            is_trash: false,
+        };
+
+        Self { table }
+    }
+
+    pub fn name(mut self, name: &str) -> Self {
+        self.table.name = name.to_string();
+        self
+    }
+
+    pub fn last_view_id(mut self, view_id: &str) -> Self {
+        self.table.last_view_id = view_id.to_string();
+        self
+    }
+
+    pub fn desc(mut self, desc: &str) -> Self {
+        self.table.description = desc.to_owned();
+        self
+    }
+
+    pub fn color_style(mut self, color_style: ColorStyle) -> Self {
+        self.table.color_style = color_style
+            .write_to_bytes()
+            .unwrap_or(default_color_style());
+        self
+    }
+
+    pub fn build(self) -> Result<(String, PgArguments, App), ServerError> {
+        let app = make_app_from_table(self.table.clone(), RepeatedView::default());
+
+        let (sql, args) = SqlBuilder::create("app_table")
+            .add_arg("id", self.table.id)
+            .add_arg("workspace_id", self.table.workspace_id)
+            .add_arg("name", self.table.name)
+            .add_arg("description", self.table.description)
+            .add_arg("color_style", self.table.color_style)
+            .add_arg("modified_time", self.table.modified_time)
+            .add_arg("create_time", self.table.create_time)
+            .add_arg("user_id", self.table.user_id)
+            .build()?;
+
+        Ok((sql, args, app))
+    }
+}
+
+fn default_color_style() -> Vec<u8> {
+    let mut style = ColorStyle::default();
+    match style.write_to_bytes() {
+        Ok(bytes) => bytes,
+        Err(e) => {
+            log::error!("Serialize color style failed: {:?}", e);
+            vec![]
+        },
+    }
+}
+
+pub(crate) fn make_app_from_table(table: AppTable, views: RepeatedView) -> App {
+    let mut app = App::default();
+    app.set_id(table.id.to_string());
+    app.set_workspace_id(table.workspace_id.to_string());
+    app.set_name(table.name.clone());
+    app.set_desc(table.description.clone());
+    app.set_belongings(views);
+
+    app
+}
+
+pub(crate) fn check_app_id(id: String) -> Result<Uuid, ServerError> {
+    let app_id = AppId::parse(id).map_err(invalid_params)?;
+    let app_id = Uuid::parse_str(app_id.as_ref())?;
+    Ok(app_id)
+}

+ 3 - 0
backend/src/workspace_service/app/mod.rs

@@ -1,2 +1,5 @@
 pub mod app;
 pub mod router;
+
+mod builder;
+pub use builder::*;

+ 1 - 0
backend/src/workspace_service/mod.rs

@@ -1,3 +1,4 @@
 pub mod app;
+pub mod user_default;
 pub mod view;
 pub mod workspace;

+ 3 - 0
backend/src/workspace_service/user_default/mod.rs

@@ -0,0 +1,3 @@
+mod user_default;
+
+pub use user_default::*;

+ 73 - 0
backend/src/workspace_service/user_default/user_default.rs

@@ -0,0 +1,73 @@
+use crate::{
+    sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
+    workspace_service::{
+        app::Builder as AppBuilder,
+        view::Builder as ViewBuilder,
+        workspace::Builder as WorkspaceBuilder,
+    },
+};
+use chrono::Utc;
+use flowy_net::errors::ServerError;
+use flowy_workspace::protobuf::{App, View, ViewType, Workspace};
+use sqlx::{Postgres, Transaction};
+
+pub async fn create_default_workspace(
+    transaction: &mut DBTransaction<'_>,
+    user_id: &str,
+) -> Result<Workspace, ServerError> {
+    let workspace = create_workspace(transaction, user_id).await?;
+    let app = create_app(transaction, user_id, &workspace).await?;
+    let _ = create_view(transaction, &app).await?;
+
+    Ok(workspace)
+}
+
+async fn create_workspace(
+    transaction: &mut DBTransaction<'_>,
+    user_id: &str,
+) -> Result<Workspace, ServerError> {
+    let (sql, args, workspace) = WorkspaceBuilder::new(user_id.as_ref())
+        .name("DefaultWorkspace")
+        .desc("Workspace created by AppFlowy")
+        .build()?;
+
+    let _ = sqlx::query_with(&sql, args)
+        .execute(transaction)
+        .await
+        .map_err(map_sqlx_error)?;
+
+    Ok(workspace)
+}
+
+async fn create_app(
+    transaction: &mut DBTransaction<'_>,
+    user_id: &str,
+    workspace: &Workspace,
+) -> Result<App, ServerError> {
+    let (sql, args, app) = AppBuilder::new(user_id, &workspace.id)
+        .name("DefaultApp")
+        .desc("App created by AppFlowy")
+        .build()?;
+
+    let _ = sqlx::query_with(&sql, args)
+        .execute(transaction)
+        .await
+        .map_err(map_sqlx_error)?;
+
+    Ok(app)
+}
+
+async fn create_view(transaction: &mut DBTransaction<'_>, app: &App) -> Result<View, ServerError> {
+    let (sql, args, view) = ViewBuilder::new(&app.id)
+        .name("DefaultView")
+        .desc("View created by AppFlowy")
+        .thumbnail("https://view.png")
+        .view_type(ViewType::Doc)
+        .build()?;
+
+    let _ = sqlx::query_with(&sql, args)
+        .execute(transaction)
+        .await
+        .map_err(map_sqlx_error)?;
+    Ok(view)
+}

+ 92 - 0
backend/src/workspace_service/view/builder.rs

@@ -0,0 +1,92 @@
+use crate::{entities::workspace::ViewTable, sqlx_ext::SqlBuilder};
+use chrono::Utc;
+use flowy_net::errors::{invalid_params, ServerError};
+use flowy_workspace::{
+    entities::view::parser::ViewId,
+    protobuf::{RepeatedView, View, ViewType},
+};
+use protobuf::ProtobufEnum;
+use sqlx::postgres::PgArguments;
+use uuid::Uuid;
+
+pub struct Builder {
+    table: ViewTable,
+}
+
+impl Builder {
+    pub fn new(belong_to_id: &str) -> Self {
+        let uuid = uuid::Uuid::new_v4();
+        let time = Utc::now();
+
+        let table = ViewTable {
+            id: uuid,
+            belong_to_id: belong_to_id.to_string(),
+            name: "".to_string(),
+            description: "".to_string(),
+            modified_time: time,
+            create_time: time,
+            thumbnail: "".to_string(),
+            view_type: ViewType::Doc.value(),
+            is_trash: false,
+        };
+
+        Self { table }
+    }
+
+    pub fn name(mut self, name: &str) -> Self {
+        self.table.name = name.to_string();
+        self
+    }
+
+    pub fn desc(mut self, desc: &str) -> Self {
+        self.table.description = desc.to_owned();
+        self
+    }
+
+    pub fn thumbnail(mut self, thumbnail: &str) -> Self {
+        self.table.thumbnail = thumbnail.to_owned();
+        self
+    }
+
+    pub fn view_type(mut self, view_type: ViewType) -> Self {
+        self.table.view_type = view_type.value();
+        self
+    }
+
+    pub fn build(self) -> Result<(String, PgArguments, View), ServerError> {
+        let view = make_view_from_table(self.table.clone(), RepeatedView::default());
+
+        let (sql, args) = SqlBuilder::create("view_table")
+            .add_arg("id", self.table.id)
+            .add_arg("belong_to_id", self.table.belong_to_id)
+            .add_arg("name", self.table.name)
+            .add_arg("description", self.table.description)
+            .add_arg("modified_time", self.table.modified_time)
+            .add_arg("create_time", self.table.create_time)
+            .add_arg("thumbnail", self.table.thumbnail)
+            .add_arg("view_type", self.table.view_type)
+            .build()?;
+
+        Ok((sql, args, view))
+    }
+}
+
+pub(crate) fn make_view_from_table(table: ViewTable, views: RepeatedView) -> View {
+    let view_type = ViewType::from_i32(table.view_type).unwrap_or(ViewType::Doc);
+
+    let mut view = View::default();
+    view.set_id(table.id.to_string());
+    view.set_belong_to_id(table.belong_to_id);
+    view.set_name(table.name);
+    view.set_desc(table.description);
+    view.set_view_type(view_type);
+    view.set_belongings(views);
+
+    view
+}
+
+pub(crate) fn check_view_id(id: String) -> Result<Uuid, ServerError> {
+    let view_id = ViewId::parse(id).map_err(invalid_params)?;
+    let view_id = Uuid::parse_str(view_id.as_ref())?;
+    Ok(view_id)
+}

+ 2 - 0
backend/src/workspace_service/view/mod.rs

@@ -1,4 +1,6 @@
+mod builder;
 pub mod router;
 mod view;
 
+pub use builder::*;
 pub use view::*;

+ 20 - 42
backend/src/workspace_service/view/view.rs

@@ -1,6 +1,7 @@
 use crate::{
     entities::workspace::ViewTable,
-    sqlx_ext::{map_sqlx_error, SqlBuilder},
+    sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
+    workspace_service::view::{check_view_id, make_view_from_table, Builder},
 };
 use anyhow::Context;
 use chrono::Utc;
@@ -11,13 +12,9 @@ use flowy_net::{
 use flowy_workspace::{
     entities::{
         app::parser::AppId,
-        view::{
-            parser::{ViewDesc, ViewId, ViewName, ViewThumbnail},
-            RepeatedView,
-            View,
-        },
+        view::parser::{ViewDesc, ViewId, ViewName, ViewThumbnail},
     },
-    protobuf::{CreateViewParams, QueryViewParams, UpdateViewParams},
+    protobuf::{CreateViewParams, QueryViewParams, RepeatedView, UpdateViewParams, View},
 };
 use protobuf::ProtobufEnum;
 use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
@@ -37,18 +34,11 @@ pub(crate) async fn create_view(
         .await
         .context("Failed to acquire a Postgres connection to create view")?;
 
-    let uuid = uuid::Uuid::new_v4();
-    let time = Utc::now();
-
-    let (sql, args) = SqlBuilder::create("view_table")
-        .add_arg("id", uuid)
-        .add_arg("belong_to_id", belong_to_id.as_ref())
-        .add_arg("name", name.as_ref())
-        .add_arg("description", desc.as_ref())
-        .add_arg("modified_time", &time)
-        .add_arg("create_time", &time)
-        .add_arg("thumbnail", thumbnail.as_ref())
-        .add_arg("view_type", params.view_type.value())
+    let (sql, args, view) = Builder::new(belong_to_id.as_ref())
+        .name(name.as_ref())
+        .desc(desc.as_ref())
+        .thumbnail(thumbnail.as_ref())
+        .view_type(params.view_type)
         .build()?;
 
     let _ = sqlx::query_with(&sql, args)
@@ -61,17 +51,7 @@ pub(crate) async fn create_view(
         .await
         .context("Failed to commit SQL transaction to create view.")?;
 
-    let view = View {
-        id: uuid.to_string(),
-        belong_to_id: belong_to_id.as_ref().to_owned(),
-        name: name.as_ref().to_owned(),
-        desc: desc.as_ref().to_owned(),
-        view_type: params.view_type.value().into(),
-        version: 0,
-        belongings: RepeatedView::default(),
-    };
-
-    FlowyResponse::success().data(view)
+    FlowyResponse::success().pb(view)
 }
 
 pub(crate) async fn read_view(
@@ -96,7 +76,11 @@ pub(crate) async fn read_view(
 
     let mut views = RepeatedView::default();
     if params.read_belongings {
-        views.items = read_views_belong_to_id(&mut transaction, &table.id.to_string()).await?;
+        views.set_items(
+            read_views_belong_to_id(&mut transaction, &table.id.to_string())
+                .await?
+                .into(),
+        )
     }
 
     transaction
@@ -104,10 +88,9 @@ pub(crate) async fn read_view(
         .await
         .context("Failed to commit SQL transaction to read view.")?;
 
-    let mut view: View = table.into();
-    view.belongings = views;
+    let view = make_view_from_table(table, views);
 
-    FlowyResponse::success().data(view)
+    FlowyResponse::success().pb(view)
 }
 
 pub(crate) async fn update_view(
@@ -199,13 +182,14 @@ pub(crate) async fn delete_view(
 
 // transaction must be commit from caller
 pub(crate) async fn read_views_belong_to_id<'c>(
-    transaction: &mut Transaction<'c, Postgres>,
+    transaction: &mut DBTransaction<'_>,
     id: &str,
 ) -> Result<Vec<View>, ServerError> {
     // TODO: add index for app_table
     let (sql, args) = SqlBuilder::select("view_table")
         .add_field("*")
         .and_where_eq("belong_to_id", id)
+        .and_where_eq("is_trash", false)
         .build()?;
 
     let tables = sqlx::query_as_with::<Postgres, ViewTable, PgArguments>(&sql, args)
@@ -215,14 +199,8 @@ pub(crate) async fn read_views_belong_to_id<'c>(
 
     let views = tables
         .into_iter()
-        .map(|table| table.into())
+        .map(|table| make_view_from_table(table, RepeatedView::default()))
         .collect::<Vec<View>>();
 
     Ok(views)
 }
-
-fn check_view_id(id: String) -> Result<Uuid, ServerError> {
-    let view_id = ViewId::parse(id).map_err(invalid_params)?;
-    let view_id = Uuid::parse_str(view_id.as_ref())?;
-    Ok(view_id)
-}

+ 82 - 0
backend/src/workspace_service/workspace/builder.rs

@@ -0,0 +1,82 @@
+use crate::{entities::workspace::WorkspaceTable, sqlx_ext::SqlBuilder};
+use chrono::Utc;
+use flowy_net::errors::{invalid_params, ServerError};
+use flowy_workspace::{
+    entities::workspace::parser::WorkspaceId,
+    protobuf::{RepeatedApp, Workspace},
+};
+use sqlx::postgres::PgArguments;
+use uuid::Uuid;
+
+pub struct Builder {
+    table: WorkspaceTable,
+}
+
+impl Builder {
+    pub fn new(user_id: &str) -> Self {
+        let uuid = uuid::Uuid::new_v4();
+        let time = Utc::now();
+
+        let table = WorkspaceTable {
+            id: uuid,
+            name: "".to_string(),
+            description: "".to_string(),
+            modified_time: time,
+            create_time: time,
+            user_id: user_id.to_string(),
+        };
+        Self { table }
+    }
+
+    pub fn name(mut self, name: &str) -> Self {
+        self.table.name = name.to_string();
+        self
+    }
+
+    pub fn desc(mut self, desc: &str) -> Self {
+        self.table.description = desc.to_owned();
+        self
+    }
+
+    pub fn build(self) -> Result<(String, PgArguments, Workspace), ServerError> {
+        let workspace = make_workspace_from_table(self.table.clone(), None);
+
+        // TODO: use macro to fetch each field from struct
+        let (sql, args) = SqlBuilder::create("workspace_table")
+            .add_arg("id", self.table.id)
+            .add_arg("name", self.table.name)
+            .add_arg("description", self.table.description)
+            .add_arg("modified_time", self.table.modified_time)
+            .add_arg("create_time", self.table.create_time)
+            .add_arg("user_id", self.table.user_id)
+            .build()?;
+
+        Ok((sql, args, workspace))
+    }
+}
+
+pub(crate) fn make_workspace_from_table(
+    table: WorkspaceTable,
+    apps: Option<RepeatedApp>,
+) -> Workspace {
+    let mut workspace = Workspace {
+        id: table.id.to_string(),
+        name: table.name,
+        desc: table.description,
+        apps: Default::default(),
+        unknown_fields: Default::default(),
+        cached_size: Default::default(),
+    };
+
+    if let Some(apps) = apps {
+        workspace.set_apps(apps);
+    }
+
+    workspace
+}
+
+pub(crate) fn check_workspace_id(id: String) -> Result<Uuid, ServerError> {
+    let workspace_id = WorkspaceId::parse(id).map_err(invalid_params)?;
+    let workspace_id = Uuid::parse_str(workspace_id.as_ref())?;
+    Ok(workspace_id)
+}

+ 2 - 0
backend/src/workspace_service/workspace/mod.rs

@@ -1,4 +1,6 @@
+pub mod builder;
 pub mod router;
 mod workspace;
 
+pub use builder::*;
 pub use workspace::*;

+ 10 - 2
backend/src/workspace_service/workspace/router.rs

@@ -4,12 +4,13 @@ use crate::{
         create_workspace,
         delete_workspace,
         read_workspace,
+        read_workspace_list,
         update_workspace,
     },
 };
 use actix_identity::Identity;
 use actix_web::{
-    web::{Data, Payload},
+    web::{Data, Path, Payload},
     HttpResponse,
 };
 use flowy_net::errors::ServerError;
@@ -50,10 +51,17 @@ pub async fn delete_handler(
 
 pub async fn update_handler(
     payload: Payload,
-    _id: Identity,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
     let params: UpdateWorkspaceParams = parse_from_payload(payload).await?;
     let resp = update_workspace(pool.get_ref(), params).await?;
     Ok(resp.into())
 }
+
+pub async fn workspace_list(
+    user_id: Path<String>,
+    pool: Data<PgPool>,
+) -> Result<HttpResponse, ServerError> {
+    let resp = read_workspace_list(pool.get_ref(), &user_id).await?;
+    Ok(resp.into())
+}

+ 53 - 36
backend/src/workspace_service/workspace/workspace.rs

@@ -1,3 +1,4 @@
+use super::builder::Builder;
 use crate::{
     entities::workspace::WorkspaceTable,
     sqlx_ext::*,
@@ -10,19 +11,18 @@ use flowy_net::{
     response::FlowyResponse,
 };
 use flowy_user::entities::parser::UserId;
+
+use crate::workspace_service::workspace::{check_workspace_id, make_workspace_from_table};
 use flowy_workspace::{
-    entities::{
-        app::RepeatedApp,
-        workspace::{
-            parser::{WorkspaceDesc, WorkspaceId, WorkspaceName},
-            Workspace,
-        },
-    },
+    entities::workspace::parser::{WorkspaceDesc, WorkspaceId, WorkspaceName},
     protobuf::{
         CreateWorkspaceParams,
         DeleteWorkspaceParams,
         QueryWorkspaceParams,
+        RepeatedApp,
+        RepeatedWorkspace,
         UpdateWorkspaceParams,
+        Workspace,
     },
 };
 use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
@@ -41,17 +41,9 @@ pub(crate) async fn create_workspace(
         .await
         .context("Failed to acquire a Postgres connection to create workspace")?;
 
-    let uuid = uuid::Uuid::new_v4();
-    let time = Utc::now();
-
-    // TODO: use macro to fetch each field from struct
-    let (sql, args) = SqlBuilder::create("workspace_table")
-        .add_arg("id", uuid)
-        .add_arg("name", name.as_ref())
-        .add_arg("description", desc.as_ref())
-        .add_arg("modified_time", &time)
-        .add_arg("create_time", &time)
-        .add_arg("user_id", user_id.as_ref())
+    let (sql, args, workspace) = Builder::new(user_id.as_ref())
+        .name(name.as_ref())
+        .desc(desc.as_ref())
         .build()?;
 
     let _ = sqlx::query_with(&sql, args)
@@ -64,14 +56,7 @@ pub(crate) async fn create_workspace(
         .await
         .context("Failed to commit SQL transaction to create workspace.")?;
 
-    let workspace = Workspace {
-        id: uuid.to_string(),
-        name: name.as_ref().to_owned(),
-        desc: desc.as_ref().to_owned(),
-        apps: RepeatedApp::default(),
-    };
-
-    FlowyResponse::success().data(workspace)
+    FlowyResponse::success().pb(workspace)
 }
 
 pub(crate) async fn read_workspace(
@@ -94,9 +79,13 @@ pub(crate) async fn read_workspace(
         .await
         .map_err(map_sqlx_error)?;
 
-    let mut apps = RepeatedApp { items: vec![] };
+    let mut repeated_app = RepeatedApp::default();
     if params.read_apps {
-        apps.items = read_apps_belong_to_workspace(&mut transaction, &table.id.to_string()).await?;
+        repeated_app.set_items(
+            read_apps_belong_to_workspace(&mut transaction, &table.id.to_string())
+                .await?
+                .into(),
+        );
     }
 
     transaction
@@ -104,10 +93,8 @@ pub(crate) async fn read_workspace(
         .await
         .context("Failed to commit SQL transaction to read workspace.")?;
 
-    let mut workspace = Workspace::new(table.id.to_string(), table.name, table.description);
-    workspace.apps = apps;
-
-    FlowyResponse::success().data(workspace)
+    let workspace = make_workspace_from_table(table, Some(repeated_app));
+    FlowyResponse::success().pb(workspace)
 }
 
 pub(crate) async fn update_workspace(
@@ -186,8 +173,38 @@ pub(crate) async fn delete_workspace(
     Ok(FlowyResponse::success())
 }
 
-fn check_workspace_id(id: String) -> Result<Uuid, ServerError> {
-    let workspace_id = WorkspaceId::parse(id).map_err(invalid_params)?;
-    let workspace_id = Uuid::parse_str(workspace_id.as_ref())?;
-    Ok(workspace_id)
+pub async fn read_workspace_list(
+    pool: &PgPool,
+    user_id: &str,
+) -> Result<FlowyResponse, ServerError> {
+    let mut transaction = pool
+        .begin()
+        .await
+        .context("Failed to acquire a Postgres connection to delete workspace")?;
+
+    let (sql, args) = SqlBuilder::select("workspace_table")
+        .add_field("*")
+        .and_where_eq("user_id", user_id)
+        .build()?;
+
+    let tables = sqlx::query_as_with::<Postgres, WorkspaceTable, PgArguments>(&sql, args)
+        .fetch_all(&mut transaction)
+        .await
+        .map_err(map_sqlx_error)?;
+
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to delete workspace.")?;
+
+    let mut workspace = RepeatedWorkspace::default();
+    workspace.set_items(
+        tables
+            .into_iter()
+            .map(|table| make_workspace_from_table(table, None))
+            .collect::<Vec<Workspace>>()
+            .into(),
+    );
+
+    FlowyResponse::success().pb(workspace)
 }

+ 6 - 0
backend/tests/api/helper.rs

@@ -83,6 +83,12 @@ impl TestApp {
         view
     }
 
+    pub async fn read_workspace_list(&self, user_id: &str) -> RepeatedWorkspace {
+        let url = format!("{}/api/workspace_list/{}", self.address, user_id);
+        let workspaces = read_workspace_list_request(&url).await.unwrap();
+        workspaces
+    }
+
     pub async fn update_view(&self, params: UpdateViewParams) {
         let url = format!("{}/api/view", self.address);
         update_view_request(params, &url).await.unwrap();

+ 48 - 61
backend/tests/api/workspace.rs

@@ -22,12 +22,7 @@ async fn workspace_create() {
 async fn workspace_read() {
     let app = spawn_app().await;
     let (workspace_1, _) = create_test_workspace(&app).await;
-
-    let read_params = QueryWorkspaceParams {
-        workspace_id: workspace_1.id.clone(),
-        read_apps: false,
-    };
-
+    let read_params = QueryWorkspaceParams::new(&workspace_1.id);
     log::info!("{:?}", app.read_workspace(read_params).await.unwrap());
 }
 
@@ -39,11 +34,7 @@ async fn workspace_read_with_belongs() {
     let _ = create_test_app(&application, &workspace.id, &user_id).await;
     let _ = create_test_app(&application, &workspace.id, &user_id).await;
 
-    let read_params = QueryWorkspaceParams {
-        workspace_id: workspace.id.clone(),
-        read_apps: true,
-    };
-
+    let read_params = QueryWorkspaceParams::new(&workspace.id).read_apps();
     let workspace = application.read_workspace(read_params).await.unwrap();
     assert_eq!(workspace.apps.len(), 3);
 }
@@ -108,13 +99,7 @@ async fn app_read() {
     let application = spawn_app().await;
     let (workspace, user_id) = create_test_workspace(&application).await;
     let app = create_test_app(&application, &workspace.id, &user_id).await;
-
-    let read_params = QueryAppParams {
-        app_id: app.id,
-        read_belongings: false,
-        is_trash: false,
-    };
-
+    let read_params = QueryAppParams::new(&app.id);
     log::info!("{:?}", application.read_app(read_params).await.unwrap());
 }
 
@@ -127,38 +112,38 @@ async fn app_read_with_belongs() {
     let _ = create_test_view(&application, &app.id).await;
     let _ = create_test_view(&application, &app.id).await;
 
-    let read_params = QueryAppParams {
-        app_id: app.id,
-        read_belongings: true,
-        is_trash: false,
-    };
-
+    let read_params = QueryAppParams::new(&app.id).read_belongings();
     let app = application.read_app(read_params).await.unwrap();
     assert_eq!(app.belongings.len(), 2);
 }
 
+#[actix_rt::test]
+async fn app_read_with_belongs_in_trash() {
+    let application = spawn_app().await;
+    let (workspace, user_id) = create_test_workspace(&application).await;
+    let app = create_test_app(&application, &workspace.id, &user_id).await;
+
+    let _ = create_test_view(&application, &app.id).await;
+    let view = create_test_view(&application, &app.id).await;
+
+    let update_params = UpdateViewParams::new(&view.id).trash();
+    application.update_view(update_params).await;
+
+    let read_params = QueryAppParams::new(&app.id).read_belongings();
+    let app = application.read_app(read_params).await.unwrap();
+    assert_eq!(app.belongings.len(), 1);
+}
+
 #[actix_rt::test]
 async fn app_update() {
     let application = spawn_app().await;
     let (workspace, user_id) = create_test_workspace(&application).await;
     let app = create_test_app(&application, &workspace.id, &user_id).await;
 
-    let update_params = UpdateAppParams {
-        app_id: app.id.clone(),
-        workspace_id: None,
-        name: Some("flowy".to_owned()),
-        desc: None,
-        color_style: None,
-        is_trash: None,
-    };
+    let update_params = UpdateAppParams::new(&app.id).name("flowy");
     application.update_app(update_params).await;
 
-    let read_params = QueryAppParams {
-        app_id: app.id,
-        read_belongings: false,
-        is_trash: false,
-    };
-
+    let read_params = QueryAppParams::new(&app.id);
     let app = application.read_app(read_params).await.unwrap();
     log::info!("{:?}", app);
 }
@@ -174,12 +159,7 @@ async fn app_delete() {
     };
     application.delete_app(delete_params).await;
 
-    let read_params = QueryAppParams {
-        app_id: app.id,
-        read_belongings: false,
-        is_trash: false,
-    };
-
+    let read_params = QueryAppParams::new(&app.id);
     assert_eq!(application.read_app(read_params).await.is_none(), true);
 }
 
@@ -214,21 +194,13 @@ async fn view_update() {
     let view = create_test_view(&application, &app.id).await;
 
     // update
-    let update_params = UpdateViewParams {
-        view_id: view.id.clone(),
-        name: Some("new view name".to_string()),
-        desc: None,
-        thumbnail: None,
-        is_trash: Some(true),
-    };
+    let update_params = UpdateViewParams::new(&view.id)
+        .trash()
+        .name("new view name");
     application.update_view(update_params).await;
 
     // read
-    let read_params = QueryViewParams {
-        view_id: view.id.clone(),
-        is_trash: true,
-        read_belongings: false,
-    };
+    let read_params = QueryViewParams::new(&view.id).trash();
     let view = application.read_view(read_params).await;
     log::info!("{:?}", view);
 }
@@ -247,11 +219,7 @@ async fn view_delete() {
     application.delete_view(delete_params).await;
 
     // read
-    let read_params = QueryViewParams {
-        view_id: view.id.clone(),
-        is_trash: true,
-        read_belongings: false,
-    };
+    let read_params = QueryViewParams::new(&view.id).trash();
     assert_eq!(application.read_view(read_params).await.is_none(), true);
 }
 
@@ -266,3 +234,22 @@ async fn create_test_view(application: &TestApp, app_id: &str) -> View {
     let app = application.create_view(params).await;
     app
 }
+
+#[actix_rt::test]
+async fn workspace_list_read() {
+    let application = spawn_app().await;
+    let response = application.register_test_user().await;
+    for i in 0..3 {
+        let params = CreateWorkspaceParams {
+            name: format!("{} workspace", i),
+            desc: format!("This is my {} workspace", i),
+            user_id: response.uid.clone(),
+        };
+        let _ = application.create_workspace(params).await;
+    }
+
+    let workspaces = application.read_workspace_list(&response.uid).await;
+    // 3 + 1 (created by default)
+    assert_eq!(workspaces.len(), 4);
+    log::info!("{:?}", workspaces);
+}

+ 1 - 1
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -35,7 +35,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "CreateWorkspaceRequest"
         | "CreateWorkspaceParams"
         | "Workspace"
-        | "Workspaces"
+        | "RepeatedWorkspace"
         | "QueryWorkspaceRequest"
         | "QueryWorkspaceParams"
         | "CurrentWorkspace"

+ 4 - 0
rust-lib/flowy-net/src/request/request.rs

@@ -55,6 +55,10 @@ impl HttpRequestBuilder {
         T1: TryInto<Bytes, Error = ProtobufError>,
     {
         let body: Bytes = body.try_into()?;
+        self.bytes(body)
+    }
+
+    pub fn bytes(mut self, body: Bytes) -> Result<Self, ServerError> {
         self.body = Some(body);
         Ok(self)
     }

+ 6 - 0
rust-lib/flowy-net/src/response/response.rs

@@ -24,6 +24,12 @@ impl FlowyResponse {
         self.data = bytes;
         Ok(self)
     }
+
+    pub fn pb<T: ::protobuf::Message>(mut self, data: T) -> Result<Self, ServerError> {
+        let bytes: Bytes = Bytes::from(data.write_to_bytes()?);
+        self.data = bytes;
+        Ok(self)
+    }
 }
 
 impl std::convert::From<protobuf::ProtobufError> for ServerError {

+ 19 - 0
rust-lib/flowy-workspace/src/entities/app/app_query.rs

@@ -46,6 +46,25 @@ pub struct QueryAppParams {
     pub is_trash: bool,
 }
 
+impl QueryAppParams {
+    pub fn new(app_id: &str) -> Self {
+        Self {
+            app_id: app_id.to_string(),
+            ..Default::default()
+        }
+    }
+
+    pub fn read_belongings(mut self) -> Self {
+        self.read_belongings = true;
+        self
+    }
+
+    pub fn trash(mut self) -> Self {
+        self.is_trash = true;
+        self
+    }
+}
+
 impl TryInto<QueryAppParams> for QueryAppRequest {
     type Error = WorkspaceError;
 

+ 24 - 0
rust-lib/flowy-workspace/src/entities/app/app_update.rs

@@ -53,6 +53,30 @@ pub struct UpdateAppParams {
     pub is_trash: Option<bool>,
 }
 
+impl UpdateAppParams {
+    pub fn new(app_id: &str) -> Self {
+        Self {
+            app_id: app_id.to_string(),
+            ..Default::default()
+        }
+    }
+
+    pub fn name(mut self, name: &str) -> Self {
+        self.name = Some(name.to_string());
+        self
+    }
+
+    pub fn desc(mut self, desc: &str) -> Self {
+        self.desc = Some(desc.to_string());
+        self
+    }
+
+    pub fn trash(mut self) -> Self {
+        self.is_trash = Some(true);
+        self
+    }
+}
+
 impl TryInto<UpdateAppParams> for UpdateAppRequest {
     type Error = WorkspaceError;
 

+ 19 - 0
rust-lib/flowy-workspace/src/entities/view/view_query.rs

@@ -44,6 +44,25 @@ pub struct QueryViewParams {
     pub read_belongings: bool,
 }
 
+impl QueryViewParams {
+    pub fn new(view_id: &str) -> Self {
+        Self {
+            view_id: view_id.to_owned(),
+            ..Default::default()
+        }
+    }
+
+    pub fn trash(mut self) -> Self {
+        self.is_trash = true;
+        self
+    }
+
+    pub fn read_belongings(mut self) -> Self {
+        self.read_belongings = true;
+        self
+    }
+}
+
 impl TryInto<QueryViewParams> for QueryViewRequest {
     type Error = WorkspaceError;
 

+ 23 - 0
rust-lib/flowy-workspace/src/entities/view/view_update.rs

@@ -41,6 +41,29 @@ pub struct UpdateViewParams {
     pub is_trash: Option<bool>,
 }
 
+impl UpdateViewParams {
+    pub fn new(view_id: &str) -> Self {
+        Self {
+            view_id: view_id.to_owned(),
+            ..Default::default()
+        }
+    }
+
+    pub fn trash(mut self) -> Self {
+        self.is_trash = Some(true);
+        self
+    }
+    pub fn name(mut self, name: &str) -> Self {
+        self.name = Some(name.to_owned());
+        self
+    }
+
+    pub fn desc(mut self, desc: &str) -> Self {
+        self.desc = Some(desc.to_owned());
+        self
+    }
+}
+
 impl TryInto<UpdateViewParams> for UpdateViewRequest {
     type Error = WorkspaceError;
 

+ 2 - 2
rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs

@@ -78,9 +78,9 @@ impl Workspace {
 }
 
 #[derive(PartialEq, Debug, Default, ProtoBuf)]
-pub struct Workspaces {
+pub struct RepeatedWorkspace {
     #[pb(index = 1)]
     pub items: Vec<Workspace>,
 }
 
-impl_def_and_def_mut!(Workspaces, Workspace);
+impl_def_and_def_mut!(RepeatedWorkspace, Workspace);

+ 14 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs

@@ -20,6 +20,20 @@ pub struct QueryWorkspaceParams {
     pub read_apps: bool,
 }
 
+impl QueryWorkspaceParams {
+    pub fn new(workspace_id: &str) -> Self {
+        Self {
+            workspace_id: workspace_id.to_owned(),
+            ..Default::default()
+        }
+    }
+
+    pub fn read_apps(mut self) -> Self {
+        self.read_apps = true;
+        self
+    }
+}
+
 impl TryInto<QueryWorkspaceParams> for QueryWorkspaceRequest {
     type Error = WorkspaceError;
 

+ 2 - 2
rust-lib/flowy-workspace/src/handlers/workspace_handler.rs

@@ -44,8 +44,8 @@ pub async fn read_workspace(
 #[tracing::instrument(name = "get_all_workspaces", skip(controller))]
 pub async fn read_all_workspaces(
     controller: Unit<Arc<WorkspaceController>>,
-) -> DataResult<Workspaces, WorkspaceError> {
+) -> DataResult<RepeatedWorkspace, WorkspaceError> {
     let workspaces = controller.read_workspaces_belong_to_user().await?;
 
-    data_result(Workspaces { items: workspaces })
+    data_result(RepeatedWorkspace { items: workspaces })
 }

+ 56 - 56
rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs

@@ -768,7 +768,7 @@ impl ::protobuf::reflect::ProtobufValue for Workspace {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct Workspaces {
+pub struct RepeatedWorkspace {
     // message fields
     pub items: ::protobuf::RepeatedField<Workspace>,
     // special fields
@@ -776,14 +776,14 @@ pub struct Workspaces {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a Workspaces {
-    fn default() -> &'a Workspaces {
-        <Workspaces as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a RepeatedWorkspace {
+    fn default() -> &'a RepeatedWorkspace {
+        <RepeatedWorkspace as ::protobuf::Message>::default_instance()
     }
 }
 
-impl Workspaces {
-    pub fn new() -> Workspaces {
+impl RepeatedWorkspace {
+    pub fn new() -> RepeatedWorkspace {
         ::std::default::Default::default()
     }
 
@@ -813,7 +813,7 @@ impl Workspaces {
     }
 }
 
-impl ::protobuf::Message for Workspaces {
+impl ::protobuf::Message for RepeatedWorkspace {
     fn is_initialized(&self) -> bool {
         for v in &self.items {
             if !v.is_initialized() {
@@ -887,8 +887,8 @@ impl ::protobuf::Message for Workspaces {
         Self::descriptor_static()
     }
 
-    fn new() -> Workspaces {
-        Workspaces::new()
+    fn new() -> RepeatedWorkspace {
+        RepeatedWorkspace::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -897,37 +897,37 @@ impl ::protobuf::Message for Workspaces {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Workspace>>(
                 "items",
-                |m: &Workspaces| { &m.items },
-                |m: &mut Workspaces| { &mut m.items },
+                |m: &RepeatedWorkspace| { &m.items },
+                |m: &mut RepeatedWorkspace| { &mut m.items },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Workspaces>(
-                "Workspaces",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RepeatedWorkspace>(
+                "RepeatedWorkspace",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static Workspaces {
-        static instance: ::protobuf::rt::LazyV2<Workspaces> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(Workspaces::new)
+    fn default_instance() -> &'static RepeatedWorkspace {
+        static instance: ::protobuf::rt::LazyV2<RepeatedWorkspace> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RepeatedWorkspace::new)
     }
 }
 
-impl ::protobuf::Clear for Workspaces {
+impl ::protobuf::Clear for RepeatedWorkspace {
     fn clear(&mut self) {
         self.items.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for Workspaces {
+impl ::std::fmt::Debug for RepeatedWorkspace {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for Workspaces {
+impl ::protobuf::reflect::ProtobufValue for RepeatedWorkspace {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -941,43 +941,43 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \tR\x04desc\x12\x17\n\x07user_id\x18\x03\x20\x01(\tR\x06userId\"e\n\tWor\
     kspace\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\
     \x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\
-    \x12\x20\n\x04apps\x18\x04\x20\x01(\x0b2\x0c.RepeatedAppR\x04apps\".\n\n\
-    Workspaces\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.WorkspaceR\x05item\
-    sJ\xb1\x05\n\x06\x12\x04\0\0\x14\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\t\
-    \n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\x02\x04\0\x12\x04\x03\0\x06\x01\n\n\
-    \n\x03\x04\0\x01\x12\x03\x03\x08\x1e\n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\
-    \x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
-    \x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x04\
-    \x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\
-    \x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x05\
-    \x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x05\x12\x13\n\n\n\x02\x04\
-    \x01\x12\x04\x07\0\x0b\x01\n\n\n\x03\x04\x01\x01\x12\x03\x07\x08\x1d\n\
-    \x0b\n\x04\x04\x01\x02\0\x12\x03\x08\x04\x14\n\x0c\n\x05\x04\x01\x02\0\
-    \x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x08\x0b\x0f\
-    \n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x08\x12\x13\n\x0b\n\x04\x04\x01\
-    \x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\t\x04\
-    \n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\t\x0b\x0f\n\x0c\n\x05\x04\x01\
-    \x02\x01\x03\x12\x03\t\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\n\x04\
-    \x17\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\
-    \x02\x02\x01\x12\x03\n\x0b\x12\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\n\
-    \x15\x16\n\n\n\x02\x04\x02\x12\x04\x0c\0\x11\x01\n\n\n\x03\x04\x02\x01\
-    \x12\x03\x0c\x08\x11\n\x0b\n\x04\x04\x02\x02\0\x12\x03\r\x04\x12\n\x0c\n\
-    \x05\x04\x02\x02\0\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\
-    \x03\r\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\r\x10\x11\n\x0b\n\x04\
-    \x04\x02\x02\x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\
-    \x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0e\x0b\x0f\n\x0c\
-    \n\x05\x04\x02\x02\x01\x03\x12\x03\x0e\x12\x13\n\x0b\n\x04\x04\x02\x02\
-    \x02\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x0f\x04\
-    \n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0f\x0b\x0f\n\x0c\n\x05\x04\
-    \x02\x02\x02\x03\x12\x03\x0f\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\
-    \x10\x04\x19\n\x0c\n\x05\x04\x02\x02\x03\x06\x12\x03\x10\x04\x0f\n\x0c\n\
-    \x05\x04\x02\x02\x03\x01\x12\x03\x10\x10\x14\n\x0c\n\x05\x04\x02\x02\x03\
-    \x03\x12\x03\x10\x17\x18\n\n\n\x02\x04\x03\x12\x04\x12\0\x14\x01\n\n\n\
-    \x03\x04\x03\x01\x12\x03\x12\x08\x12\n\x0b\n\x04\x04\x03\x02\0\x12\x03\
-    \x13\x04!\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x13\x04\x0c\n\x0c\n\x05\
-    \x04\x03\x02\0\x06\x12\x03\x13\r\x16\n\x0c\n\x05\x04\x03\x02\0\x01\x12\
-    \x03\x13\x17\x1c\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x13\x1f\x20b\x06p\
-    roto3\
+    \x12\x20\n\x04apps\x18\x04\x20\x01(\x0b2\x0c.RepeatedAppR\x04apps\"5\n\
+    \x11RepeatedWorkspace\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.Workspa\
+    ceR\x05itemsJ\xb1\x05\n\x06\x12\x04\0\0\x14\x01\n\x08\n\x01\x0c\x12\x03\
+    \0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\x02\x04\0\x12\x04\x03\0\
+    \x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x1e\n\x0b\n\x04\x04\0\x02\0\
+    \x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x04\x04\n\n\x0c\
+    \n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\0\x03\
+    \x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x05\x04\x14\n\x0c\
+    \n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\
+    \x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x05\x12\x13\n\
+    \n\n\x02\x04\x01\x12\x04\x07\0\x0b\x01\n\n\n\x03\x04\x01\x01\x12\x03\x07\
+    \x08\x1d\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\x04\x14\n\x0c\n\x05\x04\
+    \x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\
+    \x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x08\x12\x13\n\x0b\n\
+    \x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\
+    \x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\t\x0b\x0f\n\x0c\
+    \n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\
+    \x12\x03\n\x04\x17\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\n\x04\n\n\x0c\
+    \n\x05\x04\x01\x02\x02\x01\x12\x03\n\x0b\x12\n\x0c\n\x05\x04\x01\x02\x02\
+    \x03\x12\x03\n\x15\x16\n\n\n\x02\x04\x02\x12\x04\x0c\0\x11\x01\n\n\n\x03\
+    \x04\x02\x01\x12\x03\x0c\x08\x11\n\x0b\n\x04\x04\x02\x02\0\x12\x03\r\x04\
+    \x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\
+    \x02\0\x01\x12\x03\r\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\r\x10\
+    \x11\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x04\x02\
+    \x02\x01\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\
+    \x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0e\x12\x13\n\x0b\n\
+    \x04\x04\x02\x02\x02\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\
+    \x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0f\x0b\x0f\n\
+    \x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0f\x12\x13\n\x0b\n\x04\x04\x02\
+    \x02\x03\x12\x03\x10\x04\x19\n\x0c\n\x05\x04\x02\x02\x03\x06\x12\x03\x10\
+    \x04\x0f\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x10\x10\x14\n\x0c\n\x05\
+    \x04\x02\x02\x03\x03\x12\x03\x10\x17\x18\n\n\n\x02\x04\x03\x12\x04\x12\0\
+    \x14\x01\n\n\n\x03\x04\x03\x01\x12\x03\x12\x08\x19\n\x0b\n\x04\x04\x03\
+    \x02\0\x12\x03\x13\x04!\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x13\x04\
+    \x0c\n\x0c\n\x05\x04\x03\x02\0\x06\x12\x03\x13\r\x16\n\x0c\n\x05\x04\x03\
+    \x02\0\x01\x12\x03\x13\x17\x1c\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x13\
+    \x1f\x20b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 1
rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto

@@ -16,6 +16,6 @@ message Workspace {
     string desc = 3;
     RepeatedApp apps = 4;
 }
-message Workspaces {
+message RepeatedWorkspace {
     repeated Workspace items = 1;
 }

+ 10 - 1
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -169,9 +169,18 @@ pub async fn delete_workspace_request(
     params: DeleteWorkspaceParams,
     url: &str,
 ) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::delete(&url.to_owned())
+    let _ = HttpRequestBuilder::delete(url)
         .protobuf(params)?
         .send()
         .await?;
     Ok(())
 }
+
+pub async fn read_workspace_list_request(url: &str) -> Result<RepeatedWorkspace, WorkspaceError> {
+    let workspaces = HttpRequestBuilder::get(url)
+        .send()
+        .await?
+        .response::<RepeatedWorkspace>()
+        .await?;
+    Ok(workspaces)
+}

+ 7 - 2
rust-lib/flowy-workspace/tests/event/workspace_test.rs

@@ -1,6 +1,11 @@
 use crate::helper::*;
 use flowy_workspace::{
-    entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace, Workspaces},
+    entities::workspace::{
+        CreateWorkspaceRequest,
+        QueryWorkspaceRequest,
+        RepeatedWorkspace,
+        Workspace,
+    },
     event::WorkspaceEvent::*,
     prelude::*,
 };
@@ -23,7 +28,7 @@ fn workspace_read_all_success() {
     let workspaces = SingleUserTestBuilder::new()
         .event(ReadAllWorkspace)
         .sync_send()
-        .parse::<Workspaces>();
+        .parse::<RepeatedWorkspace>();
 
     dbg!(&workspaces);
 }

+ 2 - 2
scripts/flowy-tool/src/main.rs

@@ -1,7 +1,7 @@
 mod config;
-
+mod dart_event;
+mod proto;
 mod util;
-
 use clap::{App, Arg};
 
 fn main() {