瀏覽代碼

add create app test & create default workspace when user login at first time

appflowy 3 年之前
父節點
當前提交
55fad84590
共有 25 個文件被更改,包括 266 次插入113 次删除
  1. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  2. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  3. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  4. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  5. 1 1
      rust-lib/flowy-dispatch/src/dispatch.rs
  6. 0 1
      rust-lib/flowy-dispatch/src/request/payload.rs
  7. 37 2
      rust-lib/flowy-sdk/src/flowy_server.rs
  8. 0 1
      rust-lib/flowy-sdk/src/lib.rs
  9. 1 3
      rust-lib/flowy-sdk/src/module.rs
  10. 1 1
      rust-lib/flowy-sqlite/src/pool.rs
  11. 8 7
      rust-lib/flowy-test/src/builder.rs
  12. 32 1
      rust-lib/flowy-user/src/errors.rs
  13. 2 2
      rust-lib/flowy-user/src/handlers/auth_handler.rs
  14. 44 38
      rust-lib/flowy-user/src/protobuf/model/errors.rs
  15. 1 0
      rust-lib/flowy-user/src/protobuf/proto/errors.proto
  16. 12 1
      rust-lib/flowy-user/src/services/user_session/user_server.rs
  17. 32 5
      rust-lib/flowy-user/src/services/user_session/user_session.rs
  18. 0 3
      rust-lib/flowy-user/tests/event/sign_up_test.rs
  19. 3 4
      rust-lib/flowy-user/tests/event/user_status_test.rs
  20. 4 0
      rust-lib/flowy-workspace/src/event.rs
  21. 1 0
      rust-lib/flowy-workspace/src/module.rs
  22. 14 8
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  23. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  24. 33 15
      rust-lib/flowy-workspace/tests/event/app_test.rs
  25. 31 18
      rust-lib/flowy-workspace/tests/event/workspace_test.rs

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart

@@ -24,6 +24,7 @@ class UserErrorCode extends $pb.ProtobufEnum {
   static const UserErrorCode UserNameInvalid = UserErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameInvalid');
   static const UserErrorCode UserWorkspaceInvalid = UserErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
   static const UserErrorCode UserIdInvalid = UserErrorCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
+  static const UserErrorCode CreateDefaultWorkspaceFailed = UserErrorCode._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
 
   static const $core.List<UserErrorCode> values = <UserErrorCode> [
     Unknown,
@@ -40,6 +41,7 @@ class UserErrorCode extends $pb.ProtobufEnum {
     UserNameInvalid,
     UserWorkspaceInvalid,
     UserIdInvalid,
+    CreateDefaultWorkspaceFailed,
   ];
 
   static final $core.Map<$core.int, UserErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 2 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart

@@ -26,11 +26,12 @@ const UserErrorCode$json = const {
     const {'1': 'UserNameInvalid', '2': 22},
     const {'1': 'UserWorkspaceInvalid', '2': 23},
     const {'1': 'UserIdInvalid', '2': 24},
+    const {'1': 'CreateDefaultWorkspaceFailed', '2': 25},
   ],
 };
 
 /// Descriptor for `UserErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIaChZVc2VyRGF0YWJhc2VJbml0RmFpbGVkEAESGwoXVXNlckRhdGFiYXNlV3JpdGVMb2NrZWQQAhIaChZVc2VyRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXVXNlckRhdGFiYXNlRGlkTm90TWF0Y2gQBBIdChlVc2VyRGF0YWJhc2VJbnRlcm5hbEVycm9yEAUSEwoPVXNlck5vdExvZ2luWWV0EAoSFwoTUmVhZEN1cnJlbnRJZEZhaWxlZBALEhgKFFdyaXRlQ3VycmVudElkRmFpbGVkEAwSEAoMRW1haWxJbnZhbGlkEBQSEwoPUGFzc3dvcmRJbnZhbGlkEBUSEwoPVXNlck5hbWVJbnZhbGlkEBYSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQFxIRCg1Vc2VySWRJbnZhbGlkEBg=');
+final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIaChZVc2VyRGF0YWJhc2VJbml0RmFpbGVkEAESGwoXVXNlckRhdGFiYXNlV3JpdGVMb2NrZWQQAhIaChZVc2VyRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXVXNlckRhdGFiYXNlRGlkTm90TWF0Y2gQBBIdChlVc2VyRGF0YWJhc2VJbnRlcm5hbEVycm9yEAUSEwoPVXNlck5vdExvZ2luWWV0EAoSFwoTUmVhZEN1cnJlbnRJZEZhaWxlZBALEhgKFFdyaXRlQ3VycmVudElkRmFpbGVkEAwSEAoMRW1haWxJbnZhbGlkEBQSEwoPUGFzc3dvcmRJbnZhbGlkEBUSEwoPVXNlck5hbWVJbnZhbGlkEBYSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQFxIRCg1Vc2VySWRJbnZhbGlkEBgSIAocQ3JlYXRlRGVmYXVsdFdvcmtzcGFjZUZhaWxlZBAZ');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -12,10 +12,12 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
   static const WorkspaceEvent GetWorkspaceDetail = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspaceDetail');
+  static const WorkspaceEvent CreateApp = WorkspaceEvent._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateApp');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
     CreateWorkspace,
     GetWorkspaceDetail,
+    CreateApp,
   ];
 
   static final $core.Map<$core.int, WorkspaceEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 2 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -14,8 +14,9 @@ const WorkspaceEvent$json = const {
   '2': const [
     const {'1': 'CreateWorkspace', '2': 0},
     const {'1': 'GetWorkspaceDetail', '2': 1},
+    const {'1': 'CreateApp', '2': 101},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIWChJHZXRXb3Jrc3BhY2VEZXRhaWwQAQ==');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIWChJHZXRXb3Jrc3BhY2VEZXRhaWwQARINCglDcmVhdGVBcHAQZQ==');

+ 1 - 1
rust-lib/flowy-dispatch/src/dispatch.rs

@@ -1,7 +1,7 @@
 use crate::{
     errors::{DispatchError, Error, InternalError},
     module::{as_module_map, Module, ModuleMap, ModuleRequest},
-    response::{EventResponse, Responder},
+    response::EventResponse,
     service::{Service, ServiceFactory},
     util::tokio_default_runtime,
 };

+ 0 - 1
rust-lib/flowy-dispatch/src/request/payload.rs

@@ -1,4 +1,3 @@
-use crate::byte_trait::ToBytes;
 use bytes::Bytes;
 use std::{fmt, fmt::Formatter};
 

+ 37 - 2
rust-lib/flowy-sdk/src/flowy_server.rs

@@ -1,9 +1,15 @@
+use flowy_dispatch::prelude::{DispatchFuture, EventDispatch, ModuleRequest, ToBytes};
 use flowy_user::{
     entities::{SignInParams, SignUpParams, UserDetail},
     errors::{ErrorBuilder, UserError, UserErrorCode},
     prelude::UserServer,
     sql_tables::UserTable,
 };
+use flowy_workspace::{
+    entities::workspace::{CreateWorkspaceRequest, WorkspaceDetail},
+    errors::WorkspaceError,
+    event::WorkspaceEvent::CreateWorkspace,
+};
 
 pub type ArcFlowyServer = std::sync::Arc<dyn FlowyServer>;
 
@@ -34,11 +40,40 @@ impl UserServer for FlowyServerMocker {
         ))
     }
 
-    fn get_user_info(&self, _user_id: &str) -> Result<UserDetail, UserError> {
+    fn sign_out(&self, _user_id: &str) -> Result<(), UserError> {
         Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
     }
 
-    fn sign_out(&self, _user_id: &str) -> Result<(), UserError> {
+    fn get_user_info(&self, _user_id: &str) -> Result<UserDetail, UserError> {
         Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
     }
+
+    fn create_workspace(
+        &self,
+        name: &str,
+        desc: &str,
+        _user_id: &str,
+    ) -> DispatchFuture<Result<(), UserError>> {
+        let payload: Vec<u8> = CreateWorkspaceRequest {
+            name: name.to_string(),
+            desc: desc.to_string(),
+        }
+        .into_bytes()
+        .unwrap();
+
+        let request = ModuleRequest::new(CreateWorkspace).payload(payload);
+        DispatchFuture {
+            fut: Box::pin(async move {
+                let _ = EventDispatch::async_send(request)
+                    .await
+                    .parse::<WorkspaceDetail, WorkspaceError>()
+                    .map_err(|e| {
+                        ErrorBuilder::new(UserErrorCode::CreateDefaultWorkspaceFailed)
+                            .error(e)
+                            .build()
+                    })?;
+                Ok(())
+            }),
+        }
+    }
 }

+ 0 - 1
rust-lib/flowy-sdk/src/lib.rs

@@ -3,7 +3,6 @@ mod flowy_server;
 pub mod module;
 
 pub use crate::flowy_server::{ArcFlowyServer, FlowyServerMocker};
-use deps_resolve::*;
 use flowy_dispatch::prelude::*;
 use module::build_modules;
 pub use module::*;

+ 1 - 3
rust-lib/flowy-sdk/src/module.rs

@@ -1,11 +1,9 @@
 use crate::flowy_server::{ArcFlowyServer, FlowyServerMocker};
-use flowy_database::DBConnection;
-use flowy_dispatch::prelude::{DispatchFuture, Module};
+use flowy_dispatch::prelude::Module;
 use flowy_user::prelude::*;
 use flowy_workspace::prelude::*;
 
 use crate::deps_resolve::WorkspaceUserImpl;
-use flowy_workspace::entities::workspace::UserWorkspace;
 use std::sync::Arc;
 
 pub struct ModuleConfig {

+ 1 - 1
rust-lib/flowy-sqlite/src/pool.rs

@@ -1,4 +1,4 @@
-use crate::{conn_ext::*, errors::*, pragma::*};
+use crate::{errors::*, pragma::*};
 use diesel::{connection::Connection, SqliteConnection};
 use r2d2::{CustomizeConnection, ManageConnection, Pool};
 use scheduled_thread_pool::ScheduledThreadPool;

+ 8 - 7
rust-lib/flowy-test/src/builder.rs

@@ -11,17 +11,19 @@ use crate::{
 };
 use flowy_user::errors::UserError;
 use flowy_workspace::errors::WorkspaceError;
-use std::marker::PhantomData;
+use std::{marker::PhantomData, sync::Once};
+static INIT: Once = Once::new();
 
 pub type WorkspaceTestBuilder = TestBuilder<FixedUserTester<WorkspaceError>>;
 impl WorkspaceTestBuilder {
     pub fn new() -> Self {
-        let builder = Self {
+        let mut builder = Self {
             tester: Box::new(FixedUserTester::<WorkspaceError>::new()),
             user_detail: None,
         };
 
-        builder.login_if_need()
+        INIT.call_once(|| builder.login_if_need());
+        builder
     }
 }
 
@@ -36,7 +38,7 @@ impl UserTestBuilder {
         builder
     }
 
-    pub fn reset(mut self) -> Self { self.logout().login() }
+    pub fn reset(mut self) -> Self { self.login() }
 }
 
 pub struct TestBuilder<T: TesterTrait> {
@@ -54,14 +56,13 @@ where
         self
     }
 
-    pub fn login_if_need(mut self) -> Self {
+    pub fn login_if_need(&mut self) {
         let user_detail = self.tester.login_if_need();
         self.user_detail = Some(user_detail);
-        self
     }
 
     pub fn logout(self) -> Self {
-        self.tester.logout();
+        // self.tester.logout();
         self
     }
 

+ 32 - 1
rust-lib/flowy-user/src/errors.rs

@@ -53,6 +53,8 @@ pub enum UserErrorCode {
     UserWorkspaceInvalid = 23,
     #[display(fmt = "User id is invalid")]
     UserIdInvalid        = 24,
+    #[display(fmt = "Create user default workspace failed")]
+    CreateDefaultWorkspaceFailed = 25,
 }
 
 impl std::default::Default for UserErrorCode {
@@ -66,9 +68,38 @@ impl std::convert::From<flowy_database::result::Error> for UserError {
             .build()
     }
 }
-
+use diesel::result::DatabaseErrorKind;
 impl std::convert::From<flowy_sqlite::Error> for UserError {
     fn from(error: flowy_sqlite::Error) -> Self {
+        // match error.kind() {
+        //     ErrorKind::Msg(_) => {},
+        //     ErrorKind::R2D2(_) => {},
+        //     ErrorKind::Migrations(_) => {},
+        //     ErrorKind::Diesel(diesel_err) => match diesel_err {
+        //         Error::InvalidCString(_) => {},
+        //         Error::DatabaseError(kind, _) => {
+        //             match kind {
+        //                 DatabaseErrorKind::UniqueViolation => {
+        //
+        //                 }
+        //                 _ => {}
+        //             }
+        //
+        //         },
+        //         Error::NotFound => {},
+        //         Error::QueryBuilderError(_) => {},
+        //         Error::DeserializationError(_) => {},
+        //         Error::SerializationError(_) => {},
+        //         Error::RollbackTransaction => {},
+        //         Error::AlreadyInTransaction => {},
+        //         Error::__Nonexhaustive => {},
+        //     },
+        //     ErrorKind::Connection(_) => {},
+        //     ErrorKind::Io(_) => {},
+        //     ErrorKind::UnknownMigrationExists(_) => {},
+        //     ErrorKind::__Nonexhaustive { .. } => {},
+        // }
+
         ErrorBuilder::new(UserErrorCode::UserDatabaseInternalError)
             .error(error)
             .build()

+ 2 - 2
rust-lib/flowy-user/src/handlers/auth_handler.rs

@@ -15,7 +15,7 @@ pub async fn user_sign_in_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
     let params: SignInParams = data.into_inner().try_into()?;
-    let user = session.sign_in(params)?;
+    let user = session.sign_in(params).await?;
     let user_detail = UserDetail::from(user);
     response_ok(user_detail)
 }
@@ -33,7 +33,7 @@ pub async fn user_sign_up_handler(
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
     let params: SignUpParams = data.into_inner().try_into()?;
-    let user = session.sign_up(params)?;
+    let user = session.sign_up(params).await?;
     let user_detail = UserDetail::from(user);
     response_ok(user_detail)
 }

+ 44 - 38
rust-lib/flowy-user/src/protobuf/model/errors.rs

@@ -229,6 +229,7 @@ pub enum UserErrorCode {
     UserNameInvalid = 22,
     UserWorkspaceInvalid = 23,
     UserIdInvalid = 24,
+    CreateDefaultWorkspaceFailed = 25,
 }
 
 impl ::protobuf::ProtobufEnum for UserErrorCode {
@@ -252,6 +253,7 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
             22 => ::std::option::Option::Some(UserErrorCode::UserNameInvalid),
             23 => ::std::option::Option::Some(UserErrorCode::UserWorkspaceInvalid),
             24 => ::std::option::Option::Some(UserErrorCode::UserIdInvalid),
+            25 => ::std::option::Option::Some(UserErrorCode::CreateDefaultWorkspaceFailed),
             _ => ::std::option::Option::None
         }
     }
@@ -272,6 +274,7 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
             UserErrorCode::UserNameInvalid,
             UserErrorCode::UserWorkspaceInvalid,
             UserErrorCode::UserIdInvalid,
+            UserErrorCode::CreateDefaultWorkspaceFailed,
         ];
         values
     }
@@ -302,7 +305,7 @@ impl ::protobuf::reflect::ProtobufValue for UserErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"A\n\tUserError\x12\"\n\x04code\x18\x01\x20\x01(\x0e2\
     \x0e.UserErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\
-    \xde\x02\n\rUserErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDa\
+    \x80\x03\n\rUserErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDa\
     tabaseInitFailed\x10\x01\x12\x1b\n\x17UserDatabaseWriteLocked\x10\x02\
     \x12\x1a\n\x16UserDatabaseReadLocked\x10\x03\x12\x1b\n\x17UserDatabaseDi\
     dNotMatch\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x13\
@@ -310,43 +313,46 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x18\n\x14WriteCurrentIdFailed\x10\x0c\x12\x10\n\x0cEmailInvalid\x10\x14\
     \x12\x13\n\x0fPasswordInvalid\x10\x15\x12\x13\n\x0fUserNameInvalid\x10\
     \x16\x12\x18\n\x14UserWorkspaceInvalid\x10\x17\x12\x11\n\rUserIdInvalid\
-    \x10\x18J\xee\x05\n\x06\x12\x04\0\0\x15\x01\n\x08\n\x01\x0c\x12\x03\0\0\
-    \x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\
-    \x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1b\n\x0c\n\x05\x04\
-    \0\x02\0\x06\x12\x03\x03\x04\x11\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
-    \x12\x16\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x19\x1a\n\x0b\n\x04\x04\
-    \0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\
-    \x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\
-    \0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x15\x01\
-    \n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x12\n\x0b\n\x04\x05\0\x02\0\x12\x03\
-    \x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\
-    \x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
-    \x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x1a\n\x0c\n\
-    \x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\x05\0\x02\x02\x12\
-    \x03\t\x04\x20\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x1b\n\x0c\n\
-    \x05\x05\0\x02\x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\x04\x05\0\x02\x03\x12\
-    \x03\n\x04\x1f\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x1a\n\x0c\n\
-    \x05\x05\0\x02\x03\x02\x12\x03\n\x1d\x1e\n\x0b\n\x04\x05\0\x02\x04\x12\
-    \x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x1b\n\x0c\
-    \n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\x05\0\x02\x05\
-    \x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1d\n\
-    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x20!\n\x0b\n\x04\x05\0\x02\x06\
-    \x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x13\n\x0c\
-    \n\x05\x05\0\x02\x06\x02\x12\x03\r\x16\x18\n\x0b\n\x04\x05\0\x02\x07\x12\
-    \x03\x0e\x04\x1d\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x17\n\x0c\
-    \n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x1a\x1c\n\x0b\n\x04\x05\0\x02\x08\
-    \x12\x03\x0f\x04\x1e\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x18\n\
-    \x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x1b\x1d\n\x0b\n\x04\x05\0\x02\t\
-    \x12\x03\x10\x04\x16\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x13\x15\n\x0b\n\x04\x05\0\x02\n\
-    \x12\x03\x11\x04\x19\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x13\n\
-    \x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x16\x18\n\x0b\n\x04\x05\0\x02\x0b\
-    \x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x13\n\
-    \x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\x05\0\x02\
-    \x0c\x12\x03\x13\x04\x1e\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\
-    \x18\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x1b\x1d\n\x0b\n\x04\x05\0\
-    \x02\r\x12\x03\x14\x04\x17\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\
-    \x11\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x14\x16b\x06proto3\
+    \x10\x18\x12\x20\n\x1cCreateDefaultWorkspaceFailed\x10\x19J\x97\x06\n\
+    \x06\x12\x04\0\0\x16\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\
+    \x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x11\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x1b\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\
+    \x03\x04\x11\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x12\x16\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x19\x1a\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\
+    \x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x16\x01\n\n\n\x03\x05\0\
+    \x01\x12\x03\x06\x05\x12\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
+    \x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x1f\n\
+    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x1a\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x20\
+    \n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\
+    \x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x1f\n\
+    \x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x1a\n\x0c\n\x05\x05\0\x02\x03\
+    \x02\x12\x03\n\x1d\x1e\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x20\n\
+    \x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x1b\n\x0c\n\x05\x05\0\x02\
+    \x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04\"\
+    \n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1d\n\x0c\n\x05\x05\0\x02\
+    \x05\x02\x12\x03\x0c\x20!\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x19\n\
+    \x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x13\n\x0c\n\x05\x05\0\x02\x06\
+    \x02\x12\x03\r\x16\x18\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x17\n\x0c\n\x05\x05\0\x02\
+    \x07\x02\x12\x03\x0e\x1a\x1c\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\
+    \x1e\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x18\n\x0c\n\x05\x05\0\
+    \x02\x08\x02\x12\x03\x0f\x1b\x1d\n\x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\
+    \x16\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x10\n\x0c\n\x05\x05\0\
+    \x02\t\x02\x12\x03\x10\x13\x15\n\x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x13\n\x0c\n\x05\x05\0\
+    \x02\n\x02\x12\x03\x11\x16\x18\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x05\0\
+    \x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\
+    \x04\x1e\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\x18\n\x0c\n\x05\
+    \x05\0\x02\x0c\x02\x12\x03\x13\x1b\x1d\n\x0b\n\x04\x05\0\x02\r\x12\x03\
+    \x14\x04\x17\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\x11\n\x0c\n\x05\
+    \x05\0\x02\r\x02\x12\x03\x14\x14\x16\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\
+    \x15\x04&\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x15\x04\x20\n\x0c\n\x05\
+    \x05\0\x02\x0e\x02\x12\x03\x15#%b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
rust-lib/flowy-user/src/protobuf/proto/errors.proto

@@ -19,4 +19,5 @@ enum UserErrorCode {
     UserNameInvalid = 22;
     UserWorkspaceInvalid = 23;
     UserIdInvalid = 24;
+    CreateDefaultWorkspaceFailed = 25;
 }

+ 12 - 1
rust-lib/flowy-user/src/services/user_session/user_server.rs

@@ -3,10 +3,21 @@ use crate::{
     errors::UserError,
     sql_tables::UserTable,
 };
+use flowy_dispatch::prelude::DispatchFuture;
 
 pub trait UserServer {
     fn sign_up(&self, params: SignUpParams) -> Result<UserTable, UserError>;
+
     fn sign_in(&self, params: SignInParams) -> Result<UserTable, UserError>;
-    fn get_user_info(&self, user_id: &str) -> Result<UserDetail, UserError>;
+
     fn sign_out(&self, user_id: &str) -> Result<(), UserError>;
+
+    fn get_user_info(&self, user_id: &str) -> Result<UserDetail, UserError>;
+
+    fn create_workspace(
+        &self,
+        name: &str,
+        desc: &str,
+        user_id: &str,
+    ) -> DispatchFuture<Result<(), UserError>>;
 }

+ 32 - 5
rust-lib/flowy-user/src/services/user_session/user_session.rs

@@ -18,6 +18,10 @@ use crate::{
 };
 use flowy_dispatch::prelude::{EventDispatch, ModuleRequest, ToBytes};
 
+const DEFAULT_WORKSPACE_NAME: &'static str = "My workspace";
+const DEFAULT_WORKSPACE_DESC: &'static str = "This is your first workspace";
+const DEFAULT_WORKSPACE: &'static str = "Default_Workspace";
+
 pub struct UserSessionConfig {
     root_dir: String,
 }
@@ -56,17 +60,25 @@ impl UserSession {
         self.database.get_connection(&user_id)
     }
 
-    pub fn sign_in(&self, params: SignInParams) -> Result<UserTable, UserError> {
+    pub async fn sign_in(&self, params: SignInParams) -> Result<UserTable, UserError> {
         let user = self.server.sign_in(params)?;
         let _ = self.set_user_id(Some(user.id.clone()))?;
 
-        self.save_user(user)
+        let user_table = self.save_user(user)?;
+        let _ = self
+            .create_default_workspace_if_need(&user_table.id)
+            .await?;
+        Ok(user_table)
     }
 
-    pub fn sign_up(&self, params: SignUpParams) -> Result<UserTable, UserError> {
+    pub async fn sign_up(&self, params: SignUpParams) -> Result<UserTable, UserError> {
         let user = self.server.sign_up(params)?;
         let _ = self.set_user_id(Some(user.id.clone()))?;
-        self.save_user(user)
+        let user_table = self.save_user(user)?;
+        let _ = self
+            .create_default_workspace_if_need(&user_table.id)
+            .await?;
+        Ok(user_table)
     }
 
     pub fn sign_out(&self) -> Result<(), UserError> {
@@ -147,7 +159,7 @@ impl UserSession {
 
         if user_id.is_none() {
             user_id = KVStore::get_str(USER_ID_CACHE_KEY);
-            self.set_user_id(user_id.clone());
+            let _ = self.set_user_id(user_id.clone())?;
         }
 
         match user_id {
@@ -170,6 +182,21 @@ impl UserSession {
             .unwrap()?;
         Ok(())
     }
+
+    async fn create_default_workspace_if_need(&self, user_id: &str) -> Result<(), UserError> {
+        let key = format!("{}{}", user_id, DEFAULT_WORKSPACE);
+        if KVStore::get_bool(&key).unwrap_or(false) {
+            return Ok(());
+        }
+
+        KVStore::set_bool(&key, true);
+        log::debug!("Create user:{} default workspace", user_id);
+        let _ = self
+            .server
+            .create_workspace(DEFAULT_WORKSPACE_NAME, DEFAULT_WORKSPACE_DESC, user_id)
+            .await?;
+        Ok(())
+    }
 }
 
 pub fn current_user_id() -> Result<String, UserError> {

+ 0 - 3
rust-lib/flowy-user/tests/event/sign_up_test.rs

@@ -5,7 +5,6 @@ use serial_test::*;
 #[test]
 #[serial]
 fn sign_up_success() {
-    let _ = UserTestBuilder::new().event(SignOut).sync_send();
     let request = SignUpRequest {
         email: random_valid_email(),
         name: valid_name(),
@@ -17,8 +16,6 @@ fn sign_up_success() {
         .event(SignUp)
         .request(request)
         .sync_send();
-    // .parse::<SignUpResponse>();
-    // dbg!(&response);
 }
 
 #[test]

+ 3 - 4
rust-lib/flowy-user/tests/event/user_status_test.rs

@@ -3,14 +3,13 @@ use flowy_user::{event::UserEvent::*, prelude::*};
 use serial_test::*;
 
 #[test]
-#[should_panic]
 #[serial]
 fn user_status_get_failed_before_login() {
-    let _ = UserTestBuilder::new()
+    let a = UserTestBuilder::new()
         .logout()
         .event(GetStatus)
-        .sync_send()
-        .parse::<UserDetail>();
+        .assert_error()
+        .sync_send();
 }
 
 #[test]

+ 4 - 0
rust-lib/flowy-workspace/src/event.rs

@@ -11,4 +11,8 @@ pub enum WorkspaceEvent {
     #[display(fmt = "Get user's workspace detail")]
     #[event(output = "UserWorkspaceDetail")]
     GetWorkspaceDetail = 1,
+
+    #[display(fmt = "Create app")]
+    #[event(input = "CreateAppRequest", output = "AppDetail")]
+    CreateApp          = 101,
 }

+ 1 - 0
rust-lib/flowy-workspace/src/module.rs

@@ -26,4 +26,5 @@ pub fn create(user: Arc<dyn WorkspaceUser>) -> Module {
         .data(app_controller)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
         .event(WorkspaceEvent::GetWorkspaceDetail, get_workspace_detail)
+        .event(WorkspaceEvent::CreateApp, create_app)
 }

+ 14 - 8
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -27,6 +27,7 @@
 pub enum WorkspaceEvent {
     CreateWorkspace = 0,
     GetWorkspaceDetail = 1,
+    CreateApp = 101,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceEvent {
@@ -38,6 +39,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
         match value {
             0 => ::std::option::Option::Some(WorkspaceEvent::CreateWorkspace),
             1 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspaceDetail),
+            101 => ::std::option::Option::Some(WorkspaceEvent::CreateApp),
             _ => ::std::option::Option::None
         }
     }
@@ -46,6 +48,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
         static values: &'static [WorkspaceEvent] = &[
             WorkspaceEvent::CreateWorkspace,
             WorkspaceEvent::GetWorkspaceDetail,
+            WorkspaceEvent::CreateApp,
         ];
         values
     }
@@ -74,14 +77,17 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*=\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
-    \0\x12\x16\n\x12GetWorkspaceDetail\x10\x01J|\n\x06\x12\x04\0\0\x05\x01\n\
-    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x05\x01\n\n\
-    \n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\
-    \x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\
-    \0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\
-    \x04\x1b\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x16\n\x0c\n\x05\
-    \x05\0\x02\x01\x02\x12\x03\x04\x19\x1ab\x06proto3\
+    \n\x0bevent.proto*L\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
+    \0\x12\x16\n\x12GetWorkspaceDetail\x10\x01\x12\r\n\tCreateApp\x10eJ\xa5\
+    \x01\n\x06\x12\x04\0\0\x06\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
+    \x05\0\x12\x04\x02\0\x06\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\
+    \x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
+    \x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\
+    \x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x1b\n\x0c\n\x05\x05\0\x02\x01\
+    \x01\x12\x03\x04\x04\x16\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x19\
+    \x1a\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x05\0\x02\
+    \x02\x01\x12\x03\x05\x04\r\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x10\
+    \x13b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -3,4 +3,5 @@ syntax = "proto3";
 enum WorkspaceEvent {
     CreateWorkspace = 0;
     GetWorkspaceDetail = 1;
+    CreateApp = 101;
 }

+ 33 - 15
rust-lib/flowy-workspace/tests/event/app_test.rs

@@ -1,15 +1,33 @@
-// #[test]
-// fn app_create_success() {
-//     let request = CreateAppRequest {
-//         workspace_id: "".to_string(),
-//         name: "123".to_owned(),
-//         desc: "".to_owned(),
-//         color_style: Default::default(),
-//     };
-//
-//     let response = WorkspaceEventTester::new(CreateWorkspace)
-//         .request(request)
-//         .sync_send()
-//         .parse::<WorkspaceDetail>();
-//     dbg!(&response);
-// }
+use flowy_test::builder::WorkspaceTestBuilder;
+use flowy_workspace::{
+    entities::{
+        app::{AppDetail, CreateAppRequest},
+        workspace::UserWorkspaceDetail,
+    },
+    event::WorkspaceEvent::{CreateApp, GetWorkspaceDetail},
+};
+
+#[test]
+fn app_create_success() {
+    let user_workspace = WorkspaceTestBuilder::new()
+        .event(GetWorkspaceDetail)
+        .sync_send()
+        .parse::<UserWorkspaceDetail>();
+
+    let request = CreateAppRequest {
+        workspace_id: user_workspace.workspace.id,
+        name: "Github".to_owned(),
+        desc: "AppFlowy Github Project".to_owned(),
+        color_style: Default::default(),
+    };
+
+    let app_detail = WorkspaceTestBuilder::new()
+        .event(CreateApp)
+        .request(request)
+        .sync_send()
+        .parse::<AppDetail>();
+
+    dbg!(&app_detail);
+}
+
+// TODO 1) test update app 2) delete app

+ 31 - 18
rust-lib/flowy-workspace/tests/event/workspace_test.rs

@@ -22,6 +22,16 @@ fn workspace_create_success() {
 
 #[test]
 fn workspace_get_detail_success() {
+    let user_workspace = WorkspaceTestBuilder::new()
+        .event(GetWorkspaceDetail)
+        .sync_send()
+        .parse::<UserWorkspaceDetail>();
+
+    dbg!(&user_workspace);
+}
+
+#[test]
+fn workspace_create_and_then_get_detail_success() {
     let request = CreateWorkspaceRequest {
         name: "Team A".to_owned(),
         desc: "Team A Description".to_owned(),
@@ -61,21 +71,24 @@ fn workspace_create_with_invalid_name_test() {
     }
 }
 
-// #[test]
-// fn workspace_update_with_invalid_name_test() {
-//     for name in invalid_workspace_name_test_case() {
-//         let request = CreateWorkspaceRequest {
-//             name,
-//             desc: "".to_owned(),
-//         };
-//
-//         assert_eq!(
-//             WorkspaceEventTester::new(CreateWorkspace)
-//                 .request(request)
-//                 .sync_send()
-//                 .error()
-//                 .code,
-//             WorkspaceErrorCode::WorkspaceNameInvalid
-//         )
-//     }
-// }
+#[test]
+fn workspace_update_with_invalid_name_test() {
+    for name in invalid_workspace_name_test_case() {
+        let request = CreateWorkspaceRequest {
+            name,
+            desc: "".to_owned(),
+        };
+
+        assert_eq!(
+            WorkspaceTestBuilder::new()
+                .event(CreateWorkspace)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            WorkspaceErrorCode::WorkspaceNameInvalid
+        )
+    }
+}
+
+// TODO 1) delete workspace, but can't delete the last workspace