mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: integrate client-api (#3430)
* chore: update client-api rev * chore: update collab rev id * feat: add sign_in_request and import shared entity * feat: added to userworkspace from af_workspace * chore: add script to update the client-api rev id * chore: update client-api rev * feat: add workspaces api * feat: added check user * chore: config * chore: update client_api version * chore: ws connect * chore: ws connect * chore: update crate versions * chore: rename event * chore: update client-appi * chore: set appflowy cloud env * chore: add env template * chore: update env name * docs: update docs * fix: check_user * feat: impl sign_in_with_url * feat: add file storage placeholders * chore: update client-api * chore: disable test * feat: impl workspace add and remove * chore: sign up test * feat: select cover image on upload (#3488) * fix: close popover after item selection in settings view (#3362) * fix: close popover after item selection in settings view * fix: add missing await before closing popover * fix: find popover container by context instead of passing controllers around * fix: add requested changes * feat: close text direction settings popups after selection * fix: clean up * fix: restore theme value dropdown as StatefulWidget * feat: openai and stabilityai integration (#3439) * chore: create trait * test: add tests * chore: remove log * chore: disable log * chore: checklist ux flow redesign (#3418) * chore: ux flow redesign * chore: remove unused imports * fix: allow creation of tasks of the same name * chore: apply code suggestions from Mathias * fix: add padding below field title text field (#3440) * Fixed Issue no #3426 * Reversed the pubspec.lock mistaken update * FIXED PADDING * Fixed Padding issue on calender field edit popup * chore: rename package name (#3501) * fix: right icon size sam as left one (#3494) * feat: enable removing user icon (#3487) * feat: enable removing user icon * fix: generate to true * fix: review comments * fix: more review comments * fix: integration test and final changes * fix: made cursor grab and background color when hovering on Appearance Options Buttons (#3498) * chore: calendar UI polish (#3484) * chore: update calendar theming * feat: add event popup editor * chore: new event button redesign and add card shadows * chore: unscheduled events button * chore: event title text field * fix: focus node double dispose * chore: show popover when create new event * test: integrate some tests for integration testing purposes * fix: some fixes and more integration tests * chore: add more space between font item and font menu * feat: add reset font button in toolbar * feat: only show text direction toolbar item when RTL is enabled * fix: unable to change RTL of heading block * test: add integration test for ltr/rtl mode * chore: update inlang project settings (#3441) * feat: using script to update the collab source. (#3508) * chore: add script * chore: update script * chore: update bytes version * chore: submit lock file * chore: update test * chore: update test * chore: bump version * chore: update * ci: fix * ci: fix * chore: update commit id * chore: update commit id * chore: update commit id * fix: is cloud enable --------- Co-authored-by: Fu Zi Xiang <speed2exe@live.com.sg> Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Co-authored-by: Vincenzo De Petris <37916223+vincendep@users.noreply.github.com> Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Co-authored-by: Aryan More <61151896+aryan-more@users.noreply.github.com> Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io> Co-authored-by: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Co-authored-by: Nitin-Poojary <70025277+Nitin-Poojary@users.noreply.github.com> Co-authored-by: Jannes Blobel <72493222+jannesblobel@users.noreply.github.com>
This commit is contained in:
parent
01c3fec5aa
commit
7f44b181bd
4
frontend/appflowy_flutter/.gitignore
vendored
4
frontend/appflowy_flutter/.gitignore
vendored
@ -69,8 +69,8 @@ windows/flutter/dart_ffi/
|
||||
**/.sandbox
|
||||
**/.vscode/
|
||||
|
||||
*.env
|
||||
*.env.*
|
||||
.env
|
||||
.env.*
|
||||
|
||||
coverage/
|
||||
|
||||
|
21
frontend/appflowy_flutter/dev.env
Normal file
21
frontend/appflowy_flutter/dev.env
Normal file
@ -0,0 +1,21 @@
|
||||
# Initial Setup
|
||||
# 1. Copy the dev.env file to .env:
|
||||
# cp dev.env .env
|
||||
# 2. Alternatively, you can generate the .env file using the "Generate Env File" task in VSCode.
|
||||
|
||||
# Configuring Cloud Type
|
||||
# This configuration file is used to specify the cloud type and the necessary configurations for each cloud type. The available options are:
|
||||
# Supabase: Set CLOUD_TYPE to 1
|
||||
# AppFlowy Cloud: Set CLOUD_TYPE to 2
|
||||
|
||||
CLOUD_TYPE=1
|
||||
|
||||
# Supabase Configuration
|
||||
# If you're using Supabase (CLOUD_TYPE=1), you need to provide the following configurations:
|
||||
SUPABASE_URL=replace-with-your-supabase-url
|
||||
SUPABASE_ANON_KEY=replace-with-your-supabase-key
|
||||
|
||||
# AppFlowy Cloud Configuration
|
||||
# If you're using AppFlowy Cloud (CLOUD_TYPE=2), you need to provide the following configurations:
|
||||
APPFLOWY_CLOUD_BASE_URL=replace-with-your-appflowy-cloud-url
|
||||
APPFLOWY_CLOUD_BASE_WS_URL=replace-with-your-appflowy-cloud-ws-url
|
@ -73,7 +73,7 @@ void main() {
|
||||
user_icon_test.main();
|
||||
user_language_test.main();
|
||||
|
||||
if (isSupabaseEnabled) {
|
||||
if (isCloudEnabled) {
|
||||
auth_test_runner.main();
|
||||
}
|
||||
|
||||
|
59
frontend/appflowy_flutter/lib/env/env.dart
vendored
59
frontend/appflowy_flutter/lib/env/env.dart
vendored
@ -1,5 +1,6 @@
|
||||
// lib/env/env.dart
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:envied/envied.dart';
|
||||
|
||||
part 'env.g.dart';
|
||||
@ -17,6 +18,29 @@ part 'env.g.dart';
|
||||
///
|
||||
@Envied(path: '.env')
|
||||
abstract class Env {
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'CLOUD_TYPE',
|
||||
defaultValue: '0',
|
||||
)
|
||||
static final int cloudType = _Env.cloudType;
|
||||
|
||||
/// AppFlowy Cloud Configuration
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'APPFLOWY_CLOUD_BASE_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String afCloudBaseUrl = _Env.afCloudBaseUrl;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'APPFLOWY_CLOUD_BASE_WS_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String afCloudBaseWSUrl = _Env.afCloudBaseWSUrl;
|
||||
|
||||
// Supabase Configuration:
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_URL',
|
||||
@ -31,11 +55,42 @@ abstract class Env {
|
||||
static final String supabaseAnonKey = _Env.supabaseAnonKey;
|
||||
}
|
||||
|
||||
bool get isSupabaseEnabled {
|
||||
bool get isCloudEnabled {
|
||||
// Only enable supabase in release and develop mode.
|
||||
if (integrationMode().isRelease || integrationMode().isDevelop) {
|
||||
return Env.supabaseUrl.isNotEmpty && Env.supabaseAnonKey.isNotEmpty;
|
||||
return currentCloudType().isEnabled;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum CloudType {
|
||||
unknown,
|
||||
supabase,
|
||||
appflowyCloud;
|
||||
|
||||
bool get isEnabled => this != CloudType.unknown;
|
||||
}
|
||||
|
||||
CloudType currentCloudType() {
|
||||
final value = Env.cloudType;
|
||||
if (value == 1) {
|
||||
if (Env.supabaseUrl.isEmpty || Env.supabaseAnonKey.isEmpty) {
|
||||
Log.error("Supabase is not configured");
|
||||
return CloudType.unknown;
|
||||
} else {
|
||||
return CloudType.supabase;
|
||||
}
|
||||
}
|
||||
|
||||
if (value == 2) {
|
||||
if (Env.afCloudBaseUrl.isEmpty || Env.afCloudBaseWSUrl.isEmpty) {
|
||||
Log.error("AppFlowy cloud is not configured");
|
||||
return CloudType.unknown;
|
||||
} else {
|
||||
return CloudType.appflowyCloud;
|
||||
}
|
||||
}
|
||||
|
||||
return CloudType.unknown;
|
||||
}
|
||||
|
@ -11,8 +11,9 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/supabase_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
|
||||
import 'package:appflowy/user/application/prelude.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
@ -29,7 +30,7 @@ import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_impl.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
@ -90,14 +91,24 @@ void _resolveCommonService(
|
||||
}
|
||||
|
||||
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
|
||||
if (isSupabaseEnabled) {
|
||||
if (mode.isIntegrationTest) {
|
||||
getIt.registerFactory<AuthService>(() => MockAuthService());
|
||||
} else {
|
||||
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
|
||||
}
|
||||
} else {
|
||||
getIt.registerFactory<AuthService>(() => AppFlowyAuthService());
|
||||
switch (currentCloudType()) {
|
||||
case CloudType.unknown:
|
||||
getIt.registerFactory<AuthService>(
|
||||
() => BackendAuthService(
|
||||
AuthTypePB.Local,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case CloudType.supabase:
|
||||
if (mode.isIntegrationTest) {
|
||||
getIt.registerFactory<AuthService>(() => MockAuthService());
|
||||
} else {
|
||||
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
|
||||
}
|
||||
break;
|
||||
case CloudType.appflowyCloud:
|
||||
getIt.registerFactory<AuthService>(() => AFCloudAuthService());
|
||||
break;
|
||||
}
|
||||
|
||||
getIt.registerFactory<AuthRouter>(() => AuthRouter());
|
||||
|
@ -36,8 +36,14 @@ AppFlowyEnv getAppFlowyEnv() {
|
||||
anon_key: Env.supabaseAnonKey,
|
||||
);
|
||||
|
||||
final appflowyCloudConfig = AppFlowyCloudConfiguration(
|
||||
base_url: Env.afCloudBaseUrl,
|
||||
base_ws_url: Env.afCloudBaseWSUrl,
|
||||
);
|
||||
|
||||
return AppFlowyEnv(
|
||||
supabase_config: supabaseConfig,
|
||||
appflowy_cloud_config: appflowyCloudConfig,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ SupbaseRealtimeService? realtimeService;
|
||||
class InitSupabaseTask extends LaunchTask {
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
if (!isSupabaseEnabled) {
|
||||
if (!isCloudEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/user/application/auth/backend_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
class AFCloudAuthService implements AuthService {
|
||||
AFCloudAuthService();
|
||||
|
||||
final BackendAuthService _backendAuthService = BackendAuthService(
|
||||
AuthTypePB.AFCloud,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
//
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
await _backendAuthService.signOut();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
return _backendAuthService.signUpAsGuest();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
|
||||
required String email,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> getUser() async {
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
@ -21,7 +20,6 @@ abstract class AuthService {
|
||||
///
|
||||
/// - `email`: The email address of the user.
|
||||
/// - `password`: The password of the user.
|
||||
/// - `authType`: The type of authentication (optional).
|
||||
/// - `params`: Additional parameters for authentication (optional).
|
||||
///
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
@ -29,7 +27,6 @@ abstract class AuthService {
|
||||
Future<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> params,
|
||||
});
|
||||
|
||||
@ -38,7 +35,6 @@ abstract class AuthService {
|
||||
/// - `name`: The name of the user.
|
||||
/// - `email`: The email address of the user.
|
||||
/// - `password`: The password of the user.
|
||||
/// - `authType`: The type of authentication (optional).
|
||||
/// - `params`: Additional parameters for registration (optional).
|
||||
///
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
@ -46,31 +42,26 @@ abstract class AuthService {
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> params,
|
||||
});
|
||||
|
||||
/// Registers a new user with an OAuth platform.
|
||||
///
|
||||
/// - `platform`: The OAuth platform name.
|
||||
/// - `authType`: The type of authentication (optional).
|
||||
/// - `params`: Additional parameters for OAuth registration (optional).
|
||||
///
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> params,
|
||||
});
|
||||
|
||||
/// Registers a user as a guest.
|
||||
///
|
||||
/// - `authType`: The type of authentication (optional).
|
||||
/// - `params`: Additional parameters for guest registration (optional).
|
||||
///
|
||||
/// Returns a default [UserProfilePB].
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType,
|
||||
Map<String, String> params,
|
||||
});
|
||||
|
||||
|
@ -13,12 +13,15 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
import '../../../generated/locale_keys.g.dart';
|
||||
import 'device_id.dart';
|
||||
|
||||
class AppFlowyAuthService implements AuthService {
|
||||
class BackendAuthService implements AuthService {
|
||||
final AuthTypePB authType;
|
||||
|
||||
BackendAuthService(this.authType);
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
final request = SignInPayloadPB.create()
|
||||
@ -35,7 +38,6 @@ class AppFlowyAuthService implements AuthService {
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
final request = SignUpPayloadPB.create()
|
||||
@ -52,7 +54,6 @@ class AppFlowyAuthService implements AuthService {
|
||||
|
||||
@override
|
||||
Future<void> signOut({
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
await UserEventSignOut().send();
|
||||
@ -61,7 +62,6 @@ class AppFlowyAuthService implements AuthService {
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> params = const {},
|
||||
}) {
|
||||
const password = "Guest!@123456";
|
@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/backend_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/device_id.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
@ -20,14 +20,15 @@ class SupabaseAuthService implements AuthService {
|
||||
SupabaseClient get _client => Supabase.instance.client;
|
||||
GoTrueClient get _auth => _client.auth;
|
||||
|
||||
final AppFlowyAuthService _appFlowyAuthService = AppFlowyAuthService();
|
||||
final BackendAuthService _backendAuthService = BackendAuthService(
|
||||
AuthTypePB.Supabase,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
// fetch the uuid from supabase.
|
||||
@ -41,11 +42,10 @@ class SupabaseAuthService implements AuthService {
|
||||
}
|
||||
// assign the uuid to our backend service.
|
||||
// and will transfer this logic to backend later.
|
||||
return _appFlowyAuthService.signUp(
|
||||
return _backendAuthService.signUp(
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
authType: authType,
|
||||
params: {
|
||||
AuthServiceMapKeys.uuid: uuid,
|
||||
},
|
||||
@ -56,7 +56,6 @@ class SupabaseAuthService implements AuthService {
|
||||
Future<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
try {
|
||||
@ -68,10 +67,9 @@ class SupabaseAuthService implements AuthService {
|
||||
if (uuid == null) {
|
||||
return Left(AuthError.supabaseSignInError);
|
||||
}
|
||||
return _appFlowyAuthService.signIn(
|
||||
return _backendAuthService.signIn(
|
||||
email: email,
|
||||
password: password,
|
||||
authType: authType,
|
||||
params: {
|
||||
AuthServiceMapKeys.uuid: uuid,
|
||||
},
|
||||
@ -85,7 +83,6 @@ class SupabaseAuthService implements AuthService {
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
// Before signing in, sign out any existing users. Otherwise, the callback will be triggered even if the user doesn't click the 'Sign In' button on the website
|
||||
@ -118,23 +115,18 @@ class SupabaseAuthService implements AuthService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signOut({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
}) async {
|
||||
Future<void> signOut() async {
|
||||
await _auth.signOut();
|
||||
await _appFlowyAuthService.signOut(
|
||||
authType: authType,
|
||||
);
|
||||
await _backendAuthService.signOut();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
// supabase don't support guest login.
|
||||
// so, just forward to our backend.
|
||||
return _appFlowyAuthService.signUpAsGuest();
|
||||
return _backendAuthService.signUpAsGuest();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -177,13 +169,12 @@ class SupabaseAuthService implements AuthService {
|
||||
Future<Either<FlowyError, UserProfilePB>> _setupAuth({
|
||||
required Map<String, String> map,
|
||||
}) async {
|
||||
final payload = ThirdPartyAuthPB(
|
||||
final payload = OAuthPB(
|
||||
authType: AuthTypePB.Supabase,
|
||||
map: map,
|
||||
);
|
||||
return UserEventThirdPartyAuth(payload)
|
||||
.send()
|
||||
.then((value) => value.swap());
|
||||
|
||||
return UserEventOAuth(payload).send().then((value) => value.swap());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/backend_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
@ -20,14 +20,14 @@ class MockAuthService implements AuthService {
|
||||
SupabaseClient get _client => Supabase.instance.client;
|
||||
GoTrueClient get _auth => _client.auth;
|
||||
|
||||
final AppFlowyAuthService _appFlowyAuthService = AppFlowyAuthService();
|
||||
final BackendAuthService _appFlowyAuthService =
|
||||
BackendAuthService(AuthTypePB.Supabase);
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
@ -37,7 +37,6 @@ class MockAuthService implements AuthService {
|
||||
Future<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
@ -46,7 +45,6 @@ class MockAuthService implements AuthService {
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
try {
|
||||
@ -58,7 +56,7 @@ class MockAuthService implements AuthService {
|
||||
final uuid = response.user!.id;
|
||||
final email = response.user!.email!;
|
||||
|
||||
final payload = ThirdPartyAuthPB(
|
||||
final payload = OAuthPB(
|
||||
authType: AuthTypePB.Supabase,
|
||||
map: {
|
||||
AuthServiceMapKeys.uuid: uuid,
|
||||
@ -66,9 +64,8 @@ class MockAuthService implements AuthService {
|
||||
AuthServiceMapKeys.deviceId: 'MockDeviceId'
|
||||
},
|
||||
);
|
||||
return UserEventThirdPartyAuth(payload)
|
||||
.send()
|
||||
.then((value) => value.swap());
|
||||
|
||||
return UserEventOAuth(payload).send().then((value) => value.swap());
|
||||
} on AuthException catch (e) {
|
||||
Log.error(e);
|
||||
return Left(AuthError.supabaseSignInError);
|
||||
@ -76,18 +73,13 @@ class MockAuthService implements AuthService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signOut({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
}) async {
|
||||
Future<void> signOut() async {
|
||||
await _auth.signOut();
|
||||
await _appFlowyAuthService.signOut(
|
||||
authType: authType,
|
||||
);
|
||||
await _appFlowyAuthService.signOut();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
// supabase don't support guest login.
|
@ -1,4 +1,4 @@
|
||||
export 'auth/appflowy_auth_service.dart';
|
||||
export 'auth/backend_auth_service.dart';
|
||||
export './sign_in_bloc.dart';
|
||||
export './sign_up_bloc.dart';
|
||||
export './splash_bloc.dart';
|
||||
|
@ -105,10 +105,10 @@ class SplashScreen extends StatelessWidget {
|
||||
|
||||
void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
|
||||
Log.trace(
|
||||
'_handleUnauthenticated -> Supabase is enabled: $isSupabaseEnabled',
|
||||
'_handleUnauthenticated -> cloud is enabled: $isCloudEnabled',
|
||||
);
|
||||
// replace Splash screen as root page
|
||||
if (isSupabaseEnabled) {
|
||||
if (isCloudEnabled) {
|
||||
context.go(SignInScreen.routeName);
|
||||
} else {
|
||||
// if the env is not configured, we will skip to the 'skip login screen'.
|
||||
|
@ -60,7 +60,7 @@ class SettingsMenu extends StatelessWidget {
|
||||
),
|
||||
|
||||
// Only show supabase setting if supabase is enabled and the current auth type is not local
|
||||
if (isSupabaseEnabled &&
|
||||
if (isCloudEnabled &&
|
||||
context.read<SettingsDialogBloc>().state.userProfile.authType !=
|
||||
AuthTypePB.Local)
|
||||
SettingsMenuElement(
|
||||
|
@ -54,7 +54,7 @@ class SettingsUserView extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildUserIconSetting(context),
|
||||
if (isSupabaseEnabled) ...[
|
||||
if (isCloudEnabled) ...[
|
||||
const VSpace(12),
|
||||
UserEmailInput(user.email)
|
||||
],
|
||||
@ -188,7 +188,7 @@ class SettingsUserView extends StatelessWidget {
|
||||
BuildContext context,
|
||||
SettingsUserState state,
|
||||
) {
|
||||
if (!isSupabaseEnabled) {
|
||||
if (!isCloudEnabled) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,16 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
//
|
||||
// the file `env_serde.g.dart` will be generated in the same directory. Rename
|
||||
// the file to `env_serde.i.dart` because the file is ignored by default.
|
||||
part 'env_serde.i.dart';
|
||||
part 'env_serde.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyEnv {
|
||||
final SupabaseConfiguration supabase_config;
|
||||
final AppFlowyCloudConfiguration appflowy_cloud_config;
|
||||
|
||||
AppFlowyEnv({
|
||||
required this.supabase_config,
|
||||
required this.appflowy_cloud_config,
|
||||
});
|
||||
|
||||
factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
|
||||
@ -39,3 +41,19 @@ class SupabaseConfiguration {
|
||||
|
||||
Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyCloudConfiguration {
|
||||
final String base_url;
|
||||
final String base_ws_url;
|
||||
|
||||
AppFlowyCloudConfiguration({
|
||||
required this.base_url,
|
||||
required this.base_ws_url,
|
||||
});
|
||||
|
||||
factory AppFlowyCloudConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppFlowyCloudConfigurationFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$AppFlowyCloudConfigurationToJson(this);
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'env_serde.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AppFlowyEnv _$AppFlowyEnvFromJson(Map<String, dynamic> json) => AppFlowyEnv(
|
||||
supabase_config: SupabaseConfiguration.fromJson(
|
||||
json['supabase_config'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppFlowyEnvToJson(AppFlowyEnv instance) =>
|
||||
<String, dynamic>{
|
||||
'supabase_config': instance.supabase_config,
|
||||
};
|
||||
|
||||
SupabaseConfiguration _$SupabaseConfigurationFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SupabaseConfiguration(
|
||||
enable_sync: json['enable_sync'] as bool? ?? true,
|
||||
url: json['url'] as String,
|
||||
anon_key: json['anon_key'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SupabaseConfigurationToJson(
|
||||
SupabaseConfiguration instance) =>
|
||||
<String, dynamic>{
|
||||
'enable_sync': instance.enable_sync,
|
||||
'url': instance.url,
|
||||
'anon_key': instance.anon_key,
|
||||
};
|
@ -88,7 +88,6 @@ class FlowyColorScheme {
|
||||
final Color calendarWeekendBGColor;
|
||||
//grid bottom count color
|
||||
final Color gridRowCountColor;
|
||||
|
||||
const FlowyColorScheme({
|
||||
required this.surface,
|
||||
required this.hover,
|
||||
|
602
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
602
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,18 +18,11 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["fs-all", "shell-open"] }
|
||||
tauri-utils = "1.2"
|
||||
bytes = { version = "1.4" }
|
||||
bytes = { version = "1.5" }
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = [
|
||||
"use_serde",
|
||||
] }
|
||||
flowy-core = { path = "../../rust-lib/flowy-core", features = [
|
||||
"rev-sqlite",
|
||||
"ts",
|
||||
] }
|
||||
flowy-notification = { path = "../../rust-lib/flowy-notification", features = [
|
||||
"ts",
|
||||
] }
|
||||
lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
|
||||
flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] }
|
||||
flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
@ -40,24 +33,30 @@ default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
[patch.crates-io]
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
|
||||
|
||||
# ⚠️⚠️⚠️
|
||||
# Please using the following command to update the revision id
|
||||
# Current directory: frontend
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b0c213" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_collab_rev.sh e37ee7
|
||||
# scripts/tool/update_collab_rev.sh new_rev_id
|
||||
#
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
|
||||
|
||||
|
||||
|
||||
|
724
frontend/rust-lib/Cargo.lock
generated
724
frontend/rust-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -77,9 +77,12 @@ lto = false
|
||||
incremental = false
|
||||
|
||||
[patch.crates-io]
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
|
||||
|
||||
# ⚠️⚠️⚠️
|
||||
# Please using the following command to update the revision id
|
||||
# Current directory: frontend
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b0c213" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -89,12 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e37ee7" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
|
@ -201,7 +201,17 @@ impl AppFlowyCollabBuilder {
|
||||
CollabSource::AFCloud => {
|
||||
#[cfg(feature = "appflowy_cloud_integrate")]
|
||||
{
|
||||
//
|
||||
let local_collab = Arc::downgrade(&collab);
|
||||
let plugins = block_on(
|
||||
cloud_storage.get_plugins(CollabPluginContext::AppFlowyCloud {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
}),
|
||||
);
|
||||
for plugin in plugins {
|
||||
collab.lock().add_plugin(plugin);
|
||||
}
|
||||
}
|
||||
},
|
||||
CollabSource::Supabase => {
|
||||
|
@ -1,10 +1,12 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AppFlowyEnv {
|
||||
supabase_config: SupabaseConfiguration,
|
||||
appflowy_cloud_config: AFCloudConfiguration,
|
||||
}
|
||||
|
||||
impl AppFlowyEnv {
|
||||
@ -13,6 +15,7 @@ impl AppFlowyEnv {
|
||||
pub fn parser(env_str: &str) {
|
||||
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
|
||||
env.supabase_config.write_env();
|
||||
env.appflowy_cloud_config.write_env();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +31,13 @@ collab = { version = "0.1.0" }
|
||||
diesel = { version = "1.4.8", features = ["sqlite"] }
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
flowy-storage = { workspace = true }
|
||||
client-api = { version = "0.1.0", features = ["collab-sync"] }
|
||||
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
bytes = "1.5"
|
||||
tokio = { version = "1.26", features = ["full"] }
|
||||
tokio-stream = {version = "0.1.14", features = ["sync"]}
|
||||
console-subscriber = { version = "0.1.8", optional = true }
|
||||
parking_lot = "0.12.1"
|
||||
anyhow = "1.0.75"
|
||||
|
@ -25,7 +25,7 @@ pub(crate) fn create_log_filter(level: String, with_crates: Vec<String>) -> Stri
|
||||
filters.push(format!("collab_persistence={}", level));
|
||||
filters.push(format!("collab_database={}", level));
|
||||
filters.push(format!("collab_plugins={}", level));
|
||||
filters.push(format!("appflowy_integrate={}", level));
|
||||
filters.push(format!("collab_integrate={}", level));
|
||||
filters.push(format!("collab={}", level));
|
||||
filters.push(format!("flowy_user={}", level));
|
||||
filters.push(format!("flowy_document2={}", level));
|
||||
@ -37,7 +37,7 @@ pub(crate) fn create_log_filter(level: String, with_crates: Vec<String>) -> Stri
|
||||
|
||||
filters.push(format!("dart_ffi={}", "info"));
|
||||
filters.push(format!("flowy_sqlite={}", "info"));
|
||||
filters.push(format!("flowy_net={}", level));
|
||||
filters.push(format!("client_api={}", level));
|
||||
#[cfg(feature = "profiling")]
|
||||
filters.push(format!("tokio={}", level));
|
||||
|
||||
|
@ -6,12 +6,12 @@ use parking_lot::RwLock;
|
||||
use serde_repr::*;
|
||||
|
||||
use collab_integrate::YrsDocAction;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_server::af_cloud::configuration::appflowy_cloud_server_configuration;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_server::af_cloud::AFCloudServer;
|
||||
use flowy_server::local_server::{LocalServer, LocalServerDB};
|
||||
use flowy_server::supabase::SupabaseServer;
|
||||
use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
|
||||
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_sqlite::kv::StorePreferences;
|
||||
use flowy_user::services::database::{
|
||||
@ -30,10 +30,10 @@ pub enum ServerType {
|
||||
/// Local server provider.
|
||||
/// Offline mode, no user authentication and the data is stored locally.
|
||||
Local = 0,
|
||||
/// Self-hosted server provider.
|
||||
/// AppFlowy Cloud server provider.
|
||||
/// The [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Cloud) is still a work in
|
||||
/// progress.
|
||||
AppFlowyCloud = 1,
|
||||
AFCloud = 1,
|
||||
/// Supabase server provider.
|
||||
/// It uses supabase postgresql database to store data and user authentication.
|
||||
Supabase = 2,
|
||||
@ -43,7 +43,7 @@ impl Display for ServerType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerType::Local => write!(f, "Local"),
|
||||
ServerType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
|
||||
ServerType::AFCloud => write!(f, "AppFlowyCloud"),
|
||||
ServerType::Supabase => write!(f, "Supabase"),
|
||||
}
|
||||
}
|
||||
@ -111,16 +111,8 @@ impl ServerProvider {
|
||||
let server = Arc::new(LocalServer::new(local_db));
|
||||
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
||||
},
|
||||
ServerType::AppFlowyCloud => {
|
||||
let config = appflowy_cloud_server_configuration().map_err(|e| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
format!(
|
||||
"Missing self host config: {:?}. Error: {:?}",
|
||||
server_type, e
|
||||
),
|
||||
)
|
||||
})?;
|
||||
ServerType::AFCloud => {
|
||||
let config = AFCloudConfiguration::from_env()?;
|
||||
tracing::trace!("🔑AppFlowy cloud config: {:?}", config);
|
||||
let server = Arc::new(AFCloudServer::new(
|
||||
config,
|
||||
@ -163,7 +155,7 @@ impl From<AuthType> for ServerType {
|
||||
fn from(auth_provider: AuthType) -> Self {
|
||||
match auth_provider {
|
||||
AuthType::Local => ServerType::Local,
|
||||
AuthType::SelfHosted => ServerType::AppFlowyCloud,
|
||||
AuthType::AFCloud => ServerType::AFCloud,
|
||||
AuthType::Supabase => ServerType::Supabase,
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use bytes::Bytes;
|
||||
use client_api::collab_sync::{SinkConfig, SyncObject, SyncPlugin};
|
||||
use collab::core::origin::{CollabClient, CollabOrigin};
|
||||
use collab::preclude::CollabPlugin;
|
||||
use collab_define::CollabType;
|
||||
use collab_plugins::sync_plugin::{SyncObject, SyncPlugin};
|
||||
|
||||
use collab_integrate::collab_builder::{CollabPluginContext, CollabSource, CollabStorageProvider};
|
||||
use collab_integrate::postgres::SupabaseDBPlugin;
|
||||
@ -177,14 +177,14 @@ impl DatabaseCloudService for ServerProvider {
|
||||
fn get_collab_update(
|
||||
&self,
|
||||
object_id: &str,
|
||||
object_ty: CollabType,
|
||||
collab_type: CollabType,
|
||||
) -> FutureResult<CollabObjectUpdate, Error> {
|
||||
let server = self.get_server(&self.get_server_type());
|
||||
let database_id = object_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
server?
|
||||
.database_service()
|
||||
.get_collab_update(&database_id, object_ty)
|
||||
.get_collab_update(&database_id, collab_type)
|
||||
.await
|
||||
})
|
||||
}
|
||||
@ -273,19 +273,31 @@ impl CollabStorageProvider for ServerProvider {
|
||||
collab_object,
|
||||
local_collab,
|
||||
} => {
|
||||
if let Ok(server) = self.get_server(&ServerType::AppFlowyCloud) {
|
||||
if let Ok(server) = self.get_server(&ServerType::AFCloud) {
|
||||
match server.collab_ws_channel(&collab_object.object_id).await {
|
||||
Ok(Some(channel)) => {
|
||||
Ok(Some((channel, ws_connect_state))) => {
|
||||
let origin = CollabOrigin::Client(CollabClient::new(
|
||||
collab_object.uid,
|
||||
collab_object.device_id.clone(),
|
||||
));
|
||||
let sync_object = SyncObject::from(collab_object);
|
||||
let (sink, stream) = (channel.sink(), channel.stream());
|
||||
let sync_plugin = SyncPlugin::new(origin, sync_object, local_collab, sink, stream);
|
||||
let sink_config = SinkConfig::new().with_timeout(6);
|
||||
let sync_plugin = SyncPlugin::new(
|
||||
origin,
|
||||
sync_object,
|
||||
local_collab,
|
||||
sink,
|
||||
sink_config,
|
||||
stream,
|
||||
Some(channel),
|
||||
ws_connect_state,
|
||||
);
|
||||
plugins.push(Arc::new(sync_plugin));
|
||||
},
|
||||
Ok(None) => {},
|
||||
Ok(None) => {
|
||||
tracing::error!("🔴Failed to get collab ws channel: channel is none");
|
||||
},
|
||||
Err(err) => tracing::error!("🔴Failed to get collab ws channel: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ impl From<ServerType> for CollabSource {
|
||||
fn from(server_type: ServerType) -> Self {
|
||||
match server_type {
|
||||
ServerType::Local => CollabSource::Local,
|
||||
ServerType::AppFlowyCloud => CollabSource::Local,
|
||||
ServerType::AFCloud => CollabSource::AFCloud,
|
||||
ServerType::Supabase => CollabSource::Supabase,
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ pub trait DatabaseCloudService: Send + Sync {
|
||||
fn get_collab_update(
|
||||
&self,
|
||||
object_id: &str,
|
||||
object_ty: CollabType,
|
||||
collab_type: CollabType,
|
||||
) -> FutureResult<CollabObjectUpdate, Error>;
|
||||
|
||||
fn batch_get_collab_updates(
|
||||
|
@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
serde_repr = "0.1"
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
rust_decimal = "1.28.1"
|
||||
rusty-money = {version = "0.4.1", features = ["iso"]}
|
||||
lazy_static = "1.4.0"
|
||||
@ -39,7 +39,7 @@ anyhow = "1.0"
|
||||
async-stream = "0.3.4"
|
||||
rayon = "1.6.1"
|
||||
nanoid = "0.4.0"
|
||||
async-trait = "0.1"
|
||||
async-trait = "0.1.73"
|
||||
chrono-tz = "0.8.2"
|
||||
csv = "1.1.6"
|
||||
|
||||
|
@ -7,7 +7,6 @@ use collab::core::collab::MutexCollab;
|
||||
use collab_document::{blocks::DocumentData, document::Document};
|
||||
use futures::StreamExt;
|
||||
use parking_lot::Mutex;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
@ -61,7 +60,7 @@ fn subscribe_document_changed(doc_id: &str, document: &MutexDocument) {
|
||||
|
||||
fn subscribe_document_snapshot_state(collab: &Arc<MutexCollab>) {
|
||||
let document_id = collab.lock().object_id.clone();
|
||||
let mut snapshot_state = WatchStream::new(collab.lock().subscribe_snapshot_state());
|
||||
let mut snapshot_state = collab.lock().subscribe_snapshot_state();
|
||||
tokio::spawn(async move {
|
||||
while let Some(snapshot_state) = snapshot_state.next().await {
|
||||
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
|
||||
@ -79,7 +78,7 @@ fn subscribe_document_snapshot_state(collab: &Arc<MutexCollab>) {
|
||||
|
||||
fn subscribe_document_sync_state(collab: &Arc<MutexCollab>) {
|
||||
let document_id = collab.lock().object_id.clone();
|
||||
let mut sync_state_stream = WatchStream::new(collab.lock().subscribe_sync_state());
|
||||
let mut sync_state_stream = collab.lock().subscribe_sync_state();
|
||||
tokio::spawn(async move {
|
||||
while let Some(sync_state) = sync_state_stream.next().await {
|
||||
send_notification(
|
||||
|
@ -250,6 +250,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("Missing payload")]
|
||||
MissingPayload = 82,
|
||||
|
||||
#[error("Permission denied")]
|
||||
NotEnoughPermissions = 83,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -20,6 +20,9 @@ impl From<AppError> for FlowyError {
|
||||
client_api::error::ErrorCode::UrlMissingParameter => ErrorCode::InvalidParams,
|
||||
client_api::error::ErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig,
|
||||
client_api::error::ErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized,
|
||||
client_api::error::ErrorCode::NotEnoughPermissions => ErrorCode::NotEnoughPermissions,
|
||||
client_api::error::ErrorCode::UserNameIsEmpty => ErrorCode::UserNameIsEmpty,
|
||||
_ => ErrorCode::Internal,
|
||||
};
|
||||
|
||||
FlowyError::new(code, error.message)
|
||||
|
@ -24,7 +24,7 @@ lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
tokio = { version = "1.26", features = ["full"] }
|
||||
nanoid = "0.4.0"
|
||||
lazy_static = "1.4.0"
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
strum_macros = "0.21"
|
||||
protobuf = {version = "2.28.0"}
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
|
40
frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs
Normal file
40
frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
|
||||
pub const AF_CLOUD_BASE_URL: &str = "AF_CLOUD_BASE_URL";
|
||||
pub const AF_CLOUD_WS_BASE_URL: &str = "AF_CLOUD_WS_BASE_URL";
|
||||
pub const AF_CLOUD_GOTRUE_URL: &str = "AF_GOTRUE_URL";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct AFCloudConfiguration {
|
||||
pub base_url: String,
|
||||
pub base_ws_url: String,
|
||||
pub gotrue_url: String,
|
||||
}
|
||||
|
||||
impl AFCloudConfiguration {
|
||||
pub fn from_env() -> Result<Self, FlowyError> {
|
||||
let base_url = std::env::var(AF_CLOUD_BASE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_BASE_URL"))?;
|
||||
|
||||
let base_ws_url = std::env::var(AF_CLOUD_WS_BASE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_WS_BASE_URL"))?;
|
||||
|
||||
let gotrue_url = std::env::var(AF_CLOUD_GOTRUE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_GOTRUE_URL"))?;
|
||||
|
||||
Ok(Self {
|
||||
base_url,
|
||||
base_ws_url,
|
||||
gotrue_url,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the configuration to the environment variables.
|
||||
pub fn write_env(&self) {
|
||||
std::env::set_var(AF_CLOUD_BASE_URL, &self.base_url);
|
||||
std::env::set_var(AF_CLOUD_WS_BASE_URL, &self.base_ws_url);
|
||||
std::env::set_var(AF_CLOUD_GOTRUE_URL, &self.gotrue_url);
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod af_cloud_config;
|
||||
pub mod supabase_config;
|
||||
|
@ -19,11 +19,11 @@ thiserror = "1.0"
|
||||
tokio = { version = "1.26", features = ["sync"]}
|
||||
parking_lot = "0.12"
|
||||
lazy_static = "1.4.0"
|
||||
bytes = { version = "1.0.1", features = ["serde"] }
|
||||
bytes = { version = "1.5", features = ["serde"] }
|
||||
tokio-retry = "0.3"
|
||||
anyhow = "1.0"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
collab = { version = "0.1.0" }
|
||||
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
|
||||
collab-document = { version = "0.1.0" }
|
||||
@ -42,7 +42,7 @@ flowy-storage = { workspace = true }
|
||||
mime_guess = "2.0"
|
||||
url = "2.4"
|
||||
tokio-util = "0.7"
|
||||
client-api = { version = "0.1.0" }
|
||||
client-api = { version = "0.1.0", features = ["collab-sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
@ -51,3 +51,4 @@ dotenv = "0.15.0"
|
||||
yrs = "0.16.5"
|
||||
assert-json-diff = "2.0.2"
|
||||
serde_json = "1.0.104"
|
||||
client-api = { version = "0.1.0" }
|
||||
|
@ -1,79 +0,0 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use config::FileFormat;
|
||||
use serde_aux::field_attributes::deserialize_number_from_string;
|
||||
|
||||
pub const HEADER_TOKEN: &str = "token";
|
||||
|
||||
#[derive(serde::Deserialize, Clone, Debug)]
|
||||
pub struct AFCloudConfiguration {
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
pub port: u16,
|
||||
pub host: String,
|
||||
pub http_scheme: String,
|
||||
pub ws_scheme: String,
|
||||
}
|
||||
|
||||
pub fn appflowy_cloud_server_configuration() -> Result<AFCloudConfiguration, config::ConfigError> {
|
||||
let mut settings = config::Config::default();
|
||||
let base = include_str!("./configuration/base.yaml");
|
||||
settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?;
|
||||
|
||||
let environment: Environment = std::env::var("APP_ENVIRONMENT")
|
||||
.unwrap_or_else(|_| "local".to_owned())
|
||||
.try_into()
|
||||
.expect("Failed to parse APP_ENVIRONMENT.");
|
||||
|
||||
let custom = match environment {
|
||||
Environment::Local => include_str!("./configuration/local.yaml"),
|
||||
Environment::Production => include_str!("./configuration/production.yaml"),
|
||||
};
|
||||
|
||||
settings.merge(config::File::from_str(custom, FileFormat::Yaml).required(true))?;
|
||||
settings.try_into()
|
||||
}
|
||||
|
||||
impl AFCloudConfiguration {
|
||||
pub fn reset_host_with_port(&mut self, host: &str, port: u16) {
|
||||
self.host = host.to_owned();
|
||||
self.port = port;
|
||||
}
|
||||
|
||||
pub fn base_url(&self) -> String {
|
||||
format!("{}://{}:{}", self.http_scheme, self.host, self.port)
|
||||
}
|
||||
|
||||
pub fn ws_addr(&self) -> String {
|
||||
format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Environment {
|
||||
Local,
|
||||
Production,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
#[allow(dead_code)]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Environment::Local => "local",
|
||||
Environment::Production => "production",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Environment {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"local" => Ok(Self::Local),
|
||||
"production" => Ok(Self::Production),
|
||||
other => Err(format!(
|
||||
"{} is not a supported environment. Use either `local` or `production`.",
|
||||
other
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
port: 8000
|
||||
host: 0.0.0.0
|
||||
http_scheme: http
|
||||
ws_scheme: ws
|
||||
|
@ -1,3 +0,0 @@
|
||||
host: 127.0.0.1
|
||||
http_scheme: http
|
||||
ws_scheme: ws
|
@ -1,2 +0,0 @@
|
||||
host: 0.0.0.0
|
||||
|
@ -1,4 +1,6 @@
|
||||
use anyhow::Error;
|
||||
use client_api::entity::QueryCollabParams;
|
||||
use client_api::error::ErrorCode::RecordNotFound;
|
||||
use collab_define::CollabType;
|
||||
|
||||
use flowy_database_deps::cloud::{
|
||||
@ -16,10 +18,27 @@ where
|
||||
{
|
||||
fn get_collab_update(
|
||||
&self,
|
||||
_object_id: &str,
|
||||
_object_ty: CollabType,
|
||||
object_id: &str,
|
||||
collab_type: CollabType,
|
||||
) -> FutureResult<CollabObjectUpdate, Error> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
let object_id = object_id.to_string();
|
||||
let try_get_client = self.0.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
object_id,
|
||||
collab_type,
|
||||
};
|
||||
match try_get_client?.get_collab(params).await {
|
||||
Ok(data) => Ok(vec![data]),
|
||||
Err(err) => {
|
||||
if err.code == RecordNotFound {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(Error::new(err))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn batch_get_collab_updates(
|
||||
|
@ -1,4 +1,8 @@
|
||||
use anyhow::Error;
|
||||
use client_api::entity::QueryCollabParams;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_define::CollabType;
|
||||
use collab_document::document::Document;
|
||||
|
||||
use flowy_document_deps::cloud::*;
|
||||
use lib_infra::future::FutureResult;
|
||||
@ -11,8 +15,17 @@ impl<T> DocumentCloudService for AFCloudDocumentCloudServiceImpl<T>
|
||||
where
|
||||
T: AFServer,
|
||||
{
|
||||
fn get_document_updates(&self, _document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
fn get_document_updates(&self, document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
|
||||
let try_get_client = self.0.try_get_client();
|
||||
let document_id = document_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
object_id: document_id.to_string(),
|
||||
collab_type: CollabType::Document,
|
||||
};
|
||||
let data = try_get_client?.get_collab(params).await?;
|
||||
Ok(vec![data])
|
||||
})
|
||||
}
|
||||
|
||||
fn get_document_snapshots(
|
||||
@ -23,7 +36,17 @@ where
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
}
|
||||
|
||||
fn get_document_data(&self, _document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
|
||||
FutureResult::new(async move { Ok(None) })
|
||||
fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
|
||||
let try_get_client = self.0.try_get_client();
|
||||
let document_id = document_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
object_id: document_id.clone(),
|
||||
collab_type: CollabType::Document,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
let document = Document::from_updates(CollabOrigin::Empty, updates, &document_id, vec![])?;
|
||||
Ok(document.get_document_data().ok())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_storage::{FileStorageService, StorageObject};
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::af_cloud::AFServer;
|
||||
|
||||
pub struct AFCloudFileStorageServiceImpl<T> {
|
||||
#[allow(dead_code)]
|
||||
client: T,
|
||||
}
|
||||
|
||||
impl<T> AFCloudFileStorageServiceImpl<T> {
|
||||
pub fn new(client: T) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FileStorageService for AFCloudFileStorageServiceImpl<T>
|
||||
where
|
||||
T: AFServer,
|
||||
{
|
||||
fn create_object(&self, _object: StorageObject) -> FutureResult<String, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
// TODO
|
||||
Ok("".to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_object_by_url(&self, _object_url: String) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
// TODO
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_object_by_url(&self, _object_url: String) -> FutureResult<Bytes, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
// TODO
|
||||
Ok(Bytes::new())
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
use anyhow::Error;
|
||||
use client_api::entity::QueryCollabParams;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_define::CollabType;
|
||||
|
||||
use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
|
||||
use flowy_folder_deps::cloud::{Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace};
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::af_cloud::AFServer;
|
||||
@ -15,8 +18,19 @@ where
|
||||
FutureResult::new(async move { todo!() })
|
||||
}
|
||||
|
||||
fn get_folder_data(&self, _workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
|
||||
FutureResult::new(async move { Ok(None) })
|
||||
fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let try_get_client = self.0.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
object_id: workspace_id.clone(),
|
||||
collab_type: CollabType::Folder,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
let folder =
|
||||
Folder::from_collab_raw_data(CollabOrigin::Empty, updates, &workspace_id, vec![])?;
|
||||
Ok(folder.get_folder_data())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_folder_snapshots(
|
||||
@ -27,15 +41,20 @@ where
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
}
|
||||
|
||||
fn get_folder_updates(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_uid: i64,
|
||||
) -> FutureResult<Vec<Vec<u8>>, Error> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
fn get_folder_updates(&self, workspace_id: &str, _uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let try_get_client = self.0.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let params = QueryCollabParams {
|
||||
object_id: workspace_id,
|
||||
collab_type: CollabType::Folder,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
Ok(updates)
|
||||
})
|
||||
}
|
||||
|
||||
fn service_name(&self) -> String {
|
||||
"SelfHosted".to_string()
|
||||
"AppFlowy Cloud".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
pub(crate) use database::*;
|
||||
pub(crate) use document::*;
|
||||
pub(crate) use file_storage::*;
|
||||
pub(crate) use folder::*;
|
||||
pub(crate) use user::*;
|
||||
|
||||
mod database;
|
||||
mod document;
|
||||
mod file_storage;
|
||||
mod folder;
|
||||
mod user;
|
||||
|
@ -1,15 +1,19 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::{anyhow, Error};
|
||||
use client_api::entity::dto::UserUpdateParams;
|
||||
use client_api::entity::{AFUserProfileView, AFWorkspace, AFWorkspaces, InsertCollabParams};
|
||||
use collab_define::CollabObject;
|
||||
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_user_deps::cloud::UserCloudService;
|
||||
use flowy_user_deps::entities::*;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::af_cloud::{AFCloudClient, AFServer};
|
||||
use crate::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL};
|
||||
|
||||
pub(crate) struct AFCloudUserAuthServiceImpl<T> {
|
||||
server: T,
|
||||
@ -25,67 +29,151 @@ impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
|
||||
where
|
||||
T: AFServer,
|
||||
{
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> {
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let params = params.unbox_or_error::<SignUpParams>()?;
|
||||
let params = oauth_params_from_box_any(params)?;
|
||||
let resp = user_sign_up_request(try_get_client?, params).await?;
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, _params: BoxAny) -> FutureResult<SignInResponse, Error> {
|
||||
todo!()
|
||||
// Zack: Not sure if this is needed anymore since sign_up handles both cases
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let params = oauth_params_from_box_any(params)?;
|
||||
let resp = user_sign_in_with_url(client, params).await?;
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> {
|
||||
todo!()
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move { Ok(try_get_client?.sign_out().await?) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, email: &str) -> FutureResult<String, Error> {
|
||||
let email = email.to_string();
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
// TODO(nathan): replace the admin_email and admin_password with encryption key
|
||||
let admin_email = std::env::var("GOTRUE_ADMIN_EMAIL").unwrap();
|
||||
let admin_password = std::env::var("GOTRUE_ADMIN_PASSWORD").unwrap();
|
||||
let url = try_get_client?
|
||||
.generate_sign_in_callback_url(&admin_email, &admin_password, &email)
|
||||
.await?;
|
||||
Ok(url)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
_params: UpdateUserProfileParams,
|
||||
params: UpdateUserProfileParams,
|
||||
) -> FutureResult<(), Error> {
|
||||
todo!()
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
client
|
||||
.update(UserUpdateParams {
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_user_profile(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
) -> FutureResult<Option<UserProfile>, Error> {
|
||||
todo!()
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let profile = client.profile().await?;
|
||||
let encryption_type = encryption_type_from_profile(&profile);
|
||||
Ok(Some(UserProfile {
|
||||
email: profile.email.unwrap_or("".to_string()),
|
||||
name: profile.name.unwrap_or("".to_string()),
|
||||
token: token_from_client(client).await.unwrap_or("".to_string()),
|
||||
icon_url: "".to_owned(),
|
||||
openai_key: "".to_owned(),
|
||||
workspace_id: match profile.latest_workspace_id {
|
||||
Some(w) => w.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
auth_type: AuthType::AFCloud,
|
||||
encryption_type,
|
||||
uid: profile.uid.ok_or(anyhow!("no uid found"))?,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
fn get_user_workspaces(
|
||||
&self,
|
||||
_uid: i64,
|
||||
) -> FutureResult<std::vec::Vec<flowy_user_deps::entities::UserWorkspace>, Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
todo!()
|
||||
fn get_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let workspaces = try_get_client?.workspaces().await?;
|
||||
Ok(to_user_workspaces(workspaces)?)
|
||||
})
|
||||
}
|
||||
|
||||
fn check_user(&self, _credential: UserCredentials) -> FutureResult<(), Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
FutureResult::new(async { Ok(()) })
|
||||
fn check_user(&self, credential: UserCredentials) -> FutureResult<(), Error> {
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
// from params
|
||||
let token = credential.token.ok_or(anyhow!("expecting token"))?;
|
||||
let uuid = credential.uuid.ok_or(anyhow!("expecting uuid"))?;
|
||||
let uid = credential.uid.ok_or(anyhow!("expecting uid"))?;
|
||||
|
||||
// from cloud
|
||||
let client = try_get_client?;
|
||||
let profile = client.profile().await?;
|
||||
let client_token = client.access_token()?;
|
||||
|
||||
// compare and check
|
||||
if uuid != profile.uuid.ok_or(anyhow!("expecting uuid"))?.to_string() {
|
||||
return Err(anyhow!("uuid mismatch"));
|
||||
}
|
||||
if uid != profile.uid.ok_or(anyhow!("expecting uid"))? {
|
||||
return Err(anyhow!("uid mismatch"));
|
||||
}
|
||||
if token != client_token {
|
||||
return Err(anyhow!("token mismatch"));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn add_workspace_member(
|
||||
&self,
|
||||
_user_email: String,
|
||||
_workspace_id: String,
|
||||
user_email: String,
|
||||
workspace_id: String,
|
||||
) -> FutureResult<(), Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
FutureResult::new(async { Ok(()) })
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
try_get_client?
|
||||
.add_workspace_members(workspace_id.parse()?, vec![user_email])
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_workspace_member(
|
||||
&self,
|
||||
_user_email: String,
|
||||
_workspace_id: String,
|
||||
user_email: String,
|
||||
workspace_id: String,
|
||||
) -> FutureResult<(), Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
FutureResult::new(async { Ok(()) })
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
try_get_client?
|
||||
.remove_workspace_members(workspace_id.parse()?, vec![user_email])
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_user_awareness_updates(&self, _uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
|
||||
@ -100,39 +188,108 @@ where
|
||||
|
||||
fn create_collab_object(
|
||||
&self,
|
||||
_collab_object: &CollabObject,
|
||||
_data: Vec<u8>,
|
||||
collab_object: &CollabObject,
|
||||
data: Vec<u8>,
|
||||
) -> FutureResult<(), Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
FutureResult::new(async { Ok(()) })
|
||||
let try_get_client = self.server.try_get_client();
|
||||
let collab_object = collab_object.clone();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let params = InsertCollabParams::new(
|
||||
collab_object.uid,
|
||||
collab_object.object_id.clone(),
|
||||
collab_object.collab_type.clone(),
|
||||
data,
|
||||
collab_object.workspace_id.clone(),
|
||||
);
|
||||
client.create_collab(params).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_sign_up_request(
|
||||
client: Arc<AFCloudClient>,
|
||||
params: SignUpParams,
|
||||
) -> Result<SignUpResponse, FlowyError> {
|
||||
client
|
||||
.read()
|
||||
.await
|
||||
.sign_up(¶ms.email, ¶ms.password)
|
||||
.await?;
|
||||
todo!()
|
||||
// tracing::info!("User signed up: {:?}", user);
|
||||
// match user.confirmed_at {
|
||||
// Some(_) => {
|
||||
// // User is already confirmed, help her/him to sign in
|
||||
// let token = client.sign_in_password(¶ms.email, ¶ms.password).await?;
|
||||
//
|
||||
// // TODO:
|
||||
// // Query workspace list
|
||||
// // Query user profile
|
||||
//
|
||||
// todo!()
|
||||
// },
|
||||
// None => Err(FlowyError::new(
|
||||
// ErrorCode::AwaitingEmailConfirmation,
|
||||
// "Awaiting email confirmation".to_string(),
|
||||
// )),
|
||||
// }
|
||||
params: AFCloudOAuthParams,
|
||||
) -> Result<AuthResponse, FlowyError> {
|
||||
user_sign_in_with_url(client, params).await
|
||||
}
|
||||
|
||||
pub async fn user_sign_in_with_url(
|
||||
client: Arc<AFCloudClient>,
|
||||
params: AFCloudOAuthParams,
|
||||
) -> Result<AuthResponse, FlowyError> {
|
||||
let is_new_user = client.sign_in_url(¶ms.sign_in_url).await?;
|
||||
let (profile, af_workspaces) = tokio::try_join!(client.profile(), client.workspaces())?;
|
||||
|
||||
let latest_workspace = to_user_workspace(
|
||||
af_workspaces
|
||||
.get_latest(&profile)
|
||||
.or(af_workspaces.first().cloned())
|
||||
.ok_or(anyhow!("no workspace found"))?,
|
||||
)?;
|
||||
|
||||
let user_workspaces = to_user_workspaces(af_workspaces)?;
|
||||
let encryption_type = encryption_type_from_profile(&profile);
|
||||
|
||||
Ok(AuthResponse {
|
||||
user_id: profile.uid.ok_or(anyhow!("no uid found"))?,
|
||||
name: profile.name.ok_or(anyhow!("no name found"))?,
|
||||
latest_workspace,
|
||||
user_workspaces,
|
||||
email: profile.email,
|
||||
token: token_from_client(client.clone()).await,
|
||||
device_id: params.device_id,
|
||||
encryption_type,
|
||||
is_new_user,
|
||||
})
|
||||
}
|
||||
|
||||
async fn token_from_client(client: Arc<AFCloudClient>) -> Option<String> {
|
||||
client.access_token().ok()
|
||||
}
|
||||
|
||||
fn encryption_type_from_profile(profile: &AFUserProfileView) -> EncryptionType {
|
||||
match &profile.encryption_sign {
|
||||
Some(e) => EncryptionType::SelfEncryption(e.to_string()),
|
||||
None => EncryptionType::NoEncryption,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_user_workspace(af_workspace: AFWorkspace) -> Result<UserWorkspace, FlowyError> {
|
||||
Ok(UserWorkspace {
|
||||
id: af_workspace.workspace_id.to_string(),
|
||||
name: af_workspace
|
||||
.workspace_name
|
||||
.ok_or(anyhow!("no workspace_name found"))?,
|
||||
created_at: af_workspace
|
||||
.created_at
|
||||
.ok_or(anyhow!("no created_at found"))?,
|
||||
database_views_aggregate_id: af_workspace
|
||||
.database_storage_id
|
||||
.ok_or(anyhow!("no database_views_aggregate_id found"))?
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn to_user_workspaces(af_workspaces: AFWorkspaces) -> Result<Vec<UserWorkspace>, FlowyError> {
|
||||
let mut result = Vec::with_capacity(af_workspaces.len());
|
||||
for item in af_workspaces.0.into_iter() {
|
||||
let user_workspace = to_user_workspace(item)?;
|
||||
result.push(user_workspace);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn oauth_params_from_box_any(any: BoxAny) -> Result<AFCloudOAuthParams, Error> {
|
||||
let map: HashMap<String, String> = any.unbox_or_error()?;
|
||||
let sign_in_url = map
|
||||
.get(USER_SIGN_IN_URL)
|
||||
.ok_or_else(|| FlowyError::new(ErrorCode::MissingAuthField, "Missing token field"))?
|
||||
.as_str();
|
||||
let device_id = map.get(USER_DEVICE_ID).cloned().unwrap_or_default();
|
||||
Ok(AFCloudOAuthParams {
|
||||
sign_in_url: sign_in_url.to_string(),
|
||||
device_id,
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
pub use server::*;
|
||||
|
||||
pub mod configuration;
|
||||
pub mod impls;
|
||||
mod server;
|
||||
|
@ -3,7 +3,9 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use client_api::notify::{TokenState, TokenStateReceiver};
|
||||
use client_api::ws::{BusinessID, WSClient, WSClientConfig, WebSocketChannel};
|
||||
use client_api::ws::{
|
||||
BusinessID, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel,
|
||||
};
|
||||
use client_api::Client;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@ -11,18 +13,18 @@ use flowy_database_deps::cloud::DatabaseCloudService;
|
||||
use flowy_document_deps::cloud::DocumentCloudService;
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_folder_deps::cloud::FolderCloudService;
|
||||
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
|
||||
use flowy_storage::FileStorageService;
|
||||
use flowy_user_deps::cloud::UserCloudService;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::af_cloud::configuration::AFCloudConfiguration;
|
||||
use crate::af_cloud::impls::{
|
||||
AFCloudDatabaseCloudServiceImpl, AFCloudDocumentCloudServiceImpl, AFCloudFolderCloudServiceImpl,
|
||||
AFCloudUserAuthServiceImpl,
|
||||
AFCloudDatabaseCloudServiceImpl, AFCloudDocumentCloudServiceImpl, AFCloudFileStorageServiceImpl,
|
||||
AFCloudFolderCloudServiceImpl, AFCloudUserAuthServiceImpl,
|
||||
};
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
pub(crate) type AFCloudClient = RwLock<client_api::Client>;
|
||||
pub(crate) type AFCloudClient = client_api::Client;
|
||||
|
||||
pub struct AFCloudServer {
|
||||
#[allow(dead_code)]
|
||||
@ -41,17 +43,22 @@ impl AFCloudServer {
|
||||
device_id: Arc<parking_lot::RwLock<String>>,
|
||||
) -> Self {
|
||||
let http_client = reqwest::Client::new();
|
||||
let api_client = client_api::Client::from(http_client, &config.base_url(), &config.ws_addr());
|
||||
let api_client = client_api::Client::from(
|
||||
http_client,
|
||||
&config.base_url,
|
||||
&config.base_ws_url,
|
||||
&config.gotrue_url,
|
||||
);
|
||||
let token_state_rx = api_client.subscribe_token_state();
|
||||
let enable_sync = AtomicBool::new(enable_sync);
|
||||
|
||||
let ws_client = WSClient::new(WSClientConfig {
|
||||
buffer_capacity: 100,
|
||||
ping_per_secs: 2,
|
||||
ping_per_secs: 8,
|
||||
retry_connect_per_pings: 5,
|
||||
});
|
||||
let ws_client = Arc::new(RwLock::new(ws_client));
|
||||
let api_client = Arc::new(RwLock::new(api_client));
|
||||
let api_client = Arc::new(api_client);
|
||||
|
||||
spawn_ws_conn(&device_id, token_state_rx, &ws_client, &api_client);
|
||||
Self {
|
||||
@ -100,24 +107,24 @@ impl AppFlowyServer for AFCloudServer {
|
||||
fn collab_ws_channel(
|
||||
&self,
|
||||
object_id: &str,
|
||||
) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
|
||||
) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver)>, anyhow::Error> {
|
||||
if self.enable_sync.load(Ordering::SeqCst) {
|
||||
let object_id = object_id.to_string();
|
||||
let weak_ws_client = Arc::downgrade(&self.ws_client);
|
||||
FutureResult::new(async move {
|
||||
match weak_ws_client.upgrade() {
|
||||
None => {
|
||||
tracing::warn!("🟡Collab WS client is dropped");
|
||||
Ok(None)
|
||||
},
|
||||
Some(ws_client) => Ok(
|
||||
ws_client
|
||||
None => Ok(None),
|
||||
Some(ws_client) => {
|
||||
let channel = ws_client
|
||||
.read()
|
||||
.await
|
||||
.subscribe(BusinessID::CollabId, object_id)
|
||||
.await
|
||||
.ok(),
|
||||
),
|
||||
.ok();
|
||||
let connect_state_recv = ws_client.read().await.subscribe_connect_state().await;
|
||||
|
||||
Ok(channel.map(|c| (c, connect_state_recv)))
|
||||
},
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -126,7 +133,8 @@ impl AppFlowyServer for AFCloudServer {
|
||||
}
|
||||
|
||||
fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
|
||||
None
|
||||
let client = AFServerImpl(self.get_client());
|
||||
Some(Arc::new(AFCloudFileStorageServiceImpl::new(client)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,8 +146,34 @@ fn spawn_ws_conn(
|
||||
device_id: &Arc<parking_lot::RwLock<String>>,
|
||||
mut token_state_rx: TokenStateReceiver,
|
||||
ws_client: &Arc<RwLock<WSClient>>,
|
||||
api_client: &Arc<RwLock<Client>>,
|
||||
api_client: &Arc<Client>,
|
||||
) {
|
||||
let weak_device_id = Arc::downgrade(device_id);
|
||||
let weak_ws_client = Arc::downgrade(ws_client);
|
||||
let weak_api_client = Arc::downgrade(api_client);
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Some(ws_client) = weak_ws_client.upgrade() {
|
||||
let mut state_recv = ws_client.read().await.subscribe_connect_state().await;
|
||||
while let Ok(state) = state_recv.recv().await {
|
||||
if !state.is_timeout() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to reconnect if the connection is timed out.
|
||||
if let (Some(api_client), Some(device_id)) =
|
||||
(weak_api_client.upgrade(), weak_device_id.upgrade())
|
||||
{
|
||||
let device_id = device_id.read().clone();
|
||||
if let Ok(ws_addr) = api_client.ws_url(&device_id) {
|
||||
tracing::info!("🟢WebSocket Reconnecting");
|
||||
let _ = ws_client.write().await.connect(ws_addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let weak_device_id = Arc::downgrade(device_id);
|
||||
let weak_ws_client = Arc::downgrade(ws_client);
|
||||
let weak_api_client = Arc::downgrade(api_client);
|
||||
@ -154,15 +188,14 @@ fn spawn_ws_conn(
|
||||
weak_device_id.upgrade(),
|
||||
) {
|
||||
let device_id = device_id.read().clone();
|
||||
if let Ok(ws_addr) = api_client.read().await.ws_url(&device_id) {
|
||||
tracing::info!("🟢Connecting to websocket");
|
||||
if let Ok(ws_addr) = api_client.ws_url(&device_id) {
|
||||
let _ = ws_client.write().await.connect(ws_addr).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
TokenState::Invalid => {
|
||||
if let Some(ws_client) = weak_ws_client.upgrade() {
|
||||
tracing::info!("🟡Disconnecting from websocket");
|
||||
tracing::info!("🟡WebSocket Disconnecting");
|
||||
ws_client.write().await.disconnect().await;
|
||||
}
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ impl DatabaseCloudService for LocalServerDatabaseCloudServiceImpl {
|
||||
fn get_collab_update(
|
||||
&self,
|
||||
_object_id: &str,
|
||||
_object_ty: CollabType,
|
||||
_collab_type: CollabType,
|
||||
) -> FutureResult<CollabObjectUpdate, Error> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ pub(crate) struct LocalServerUserAuthServiceImpl {
|
||||
}
|
||||
|
||||
impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> {
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
FutureResult::new(async move {
|
||||
let params = params.unbox_or_error::<SignUpParams>()?;
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
@ -35,7 +35,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
} else {
|
||||
params.name.clone()
|
||||
};
|
||||
Ok(SignUpResponse {
|
||||
Ok(AuthResponse {
|
||||
user_id: uid,
|
||||
name: user_name,
|
||||
latest_workspace: user_workspace.clone(),
|
||||
@ -49,7 +49,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error> {
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
let db = self.db.clone();
|
||||
FutureResult::new(async move {
|
||||
let params: SignInParams = params.unbox_or_error::<SignInParams>()?;
|
||||
@ -58,11 +58,12 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
let user_workspace = db
|
||||
.get_user_workspace(uid)?
|
||||
.unwrap_or_else(make_user_workspace);
|
||||
Ok(SignInResponse {
|
||||
Ok(AuthResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
latest_workspace: user_workspace.clone(),
|
||||
user_workspaces: vec![user_workspace],
|
||||
is_new_user: false,
|
||||
email: Some(params.email),
|
||||
token: None,
|
||||
device_id: params.device_id,
|
||||
@ -75,6 +76,14 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
FutureResult::new(async {
|
||||
Err(anyhow::anyhow!(
|
||||
"Can't generate callback url when using offline mode"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use client_api::ws::WebSocketChannel;
|
||||
use client_api::ws::{WSConnectStateReceiver, WebSocketChannel};
|
||||
use collab_define::CollabObject;
|
||||
use collab_plugins::cloud_storage::RemoteCollabStorage;
|
||||
use parking_lot::RwLock;
|
||||
@ -94,7 +94,7 @@ pub trait AppFlowyServer: Send + Sync + 'static {
|
||||
fn collab_ws_channel(
|
||||
&self,
|
||||
_object_id: &str,
|
||||
) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
|
||||
) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver)>, anyhow::Error> {
|
||||
FutureResult::new(async { Ok(None) })
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ where
|
||||
fn get_collab_update(
|
||||
&self,
|
||||
object_id: &str,
|
||||
object_ty: CollabType,
|
||||
collab_type: CollabType,
|
||||
) -> FutureResult<CollabObjectUpdate, Error> {
|
||||
let try_get_postgrest = self.server.try_get_weak_postgrest();
|
||||
let object_id = object_id.to_string();
|
||||
@ -38,7 +38,7 @@ where
|
||||
tx.send(
|
||||
async move {
|
||||
let postgrest = try_get_postgrest?;
|
||||
let updates = FetchObjectUpdateAction::new(object_id.to_string(), object_ty, postgrest)
|
||||
let updates = FetchObjectUpdateAction::new(object_id.to_string(), collab_type, postgrest)
|
||||
.run_with_fix_interval(5, 10)
|
||||
.await?;
|
||||
Ok(updates)
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::iter::Take;
|
||||
use std::pin::Pin;
|
||||
@ -63,11 +64,11 @@ impl<T> UserCloudService for SupabaseUserServiceImpl<T>
|
||||
where
|
||||
T: SupabaseServerService,
|
||||
{
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> {
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
let try_get_postgrest = self.server.try_get_postgrest();
|
||||
FutureResult::new(async move {
|
||||
let postgrest = try_get_postgrest?;
|
||||
let params = third_party_params_from_box_any(params)?;
|
||||
let params = oauth_params_from_box_any(params)?;
|
||||
let is_new_user = postgrest
|
||||
.from(USER_TABLE)
|
||||
.select("uid")
|
||||
@ -117,7 +118,7 @@ where
|
||||
user_profile.name
|
||||
};
|
||||
|
||||
Ok(SignUpResponse {
|
||||
Ok(AuthResponse {
|
||||
user_id: user_profile.uid,
|
||||
name: user_name,
|
||||
latest_workspace: latest_workspace.unwrap(),
|
||||
@ -131,11 +132,11 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error> {
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> {
|
||||
let try_get_postgrest = self.server.try_get_postgrest();
|
||||
FutureResult::new(async move {
|
||||
let postgrest = try_get_postgrest?;
|
||||
let params = third_party_params_from_box_any(params)?;
|
||||
let params = oauth_params_from_box_any(params)?;
|
||||
let uuid = params.uuid;
|
||||
let response = get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(uuid))
|
||||
.await?
|
||||
@ -146,11 +147,12 @@ where
|
||||
.find(|user_workspace| user_workspace.id == response.latest_workspace_id)
|
||||
.cloned();
|
||||
|
||||
Ok(SignInResponse {
|
||||
Ok(AuthResponse {
|
||||
user_id: response.uid,
|
||||
name: DEFAULT_USER_NAME(),
|
||||
latest_workspace: latest_workspace.unwrap(),
|
||||
user_workspaces,
|
||||
is_new_user: false,
|
||||
email: None,
|
||||
token: None,
|
||||
device_id: params.device_id,
|
||||
@ -163,6 +165,14 @@ where
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
FutureResult::new(async {
|
||||
Err(anyhow::anyhow!(
|
||||
"Can't generate callback url when using supabase"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
@ -624,3 +634,15 @@ fn empty_workspace_update(collab_object: &CollabObject) -> Vec<u8> {
|
||||
folder.set_current_workspace(&workspace_id);
|
||||
collab.encode_as_update_v1().0
|
||||
}
|
||||
|
||||
fn oauth_params_from_box_any(any: BoxAny) -> Result<SupabaseOAuthParams, Error> {
|
||||
let map: HashMap<String, String> = any.unbox_or_error()?;
|
||||
let uuid = uuid_from_map(&map)?;
|
||||
let email = map.get("email").cloned().unwrap_or_default();
|
||||
let device_id = map.get("device_id").cloned().unwrap_or_default();
|
||||
Ok(SupabaseOAuthParams {
|
||||
uuid,
|
||||
email,
|
||||
device_id,
|
||||
})
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub const AF_COLLAB_SNAPSHOT_CREATED_AT_COLUMN: &str = "created_at";
|
||||
pub const AF_COLLAB_SNAPSHOT_TABLE: &str = "af_collab_snapshot";
|
||||
|
||||
pub const USER_UUID: &str = "uuid";
|
||||
pub const USER_SIGN_IN_URL: &str = "sign_in_url";
|
||||
pub const USER_UID: &str = "uid";
|
||||
pub const OWNER_USER_UID: &str = "owner_uid";
|
||||
pub const USER_EMAIL: &str = "email";
|
||||
|
@ -0,0 +1,2 @@
|
||||
mod user_test;
|
||||
mod util;
|
@ -0,0 +1,21 @@
|
||||
use flowy_server::AppFlowyServer;
|
||||
use flowy_user_deps::entities::AuthResponse;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::af_cloud_test::util::{
|
||||
af_cloud_server, af_cloud_sign_up_param, generate_test_email, get_af_cloud_config,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_up_test() {
|
||||
if let Some(config) = get_af_cloud_config() {
|
||||
let server = af_cloud_server(config.clone());
|
||||
let user_service = server.user_service();
|
||||
let email = generate_test_email();
|
||||
let params = af_cloud_sign_up_param(&email, &config).await;
|
||||
let resp: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
assert_eq!(resp.email.unwrap(), email);
|
||||
assert!(resp.is_new_user);
|
||||
assert_eq!(resp.user_workspaces.len(), 1);
|
||||
}
|
||||
}
|
57
frontend/rust-lib/flowy-server/tests/af_cloud_test/util.rs
Normal file
57
frontend/rust-lib/flowy-server/tests/af_cloud_test/util.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use flowy_server::af_cloud::AFCloudServer;
|
||||
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL};
|
||||
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
|
||||
|
||||
use crate::setup_log;
|
||||
|
||||
pub fn get_af_cloud_config() -> Option<AFCloudConfiguration> {
|
||||
dotenv::from_filename("./.env.ci").ok()?;
|
||||
setup_log();
|
||||
AFCloudConfiguration::from_env().ok()
|
||||
}
|
||||
|
||||
pub fn af_cloud_server(config: AFCloudConfiguration) -> Arc<AFCloudServer> {
|
||||
let fake_device_id = uuid::Uuid::new_v4().to_string();
|
||||
let device_id = Arc::new(RwLock::new(fake_device_id));
|
||||
Arc::new(AFCloudServer::new(config, true, device_id))
|
||||
}
|
||||
|
||||
pub async fn generate_sign_in_url(user_email: &str, config: &AFCloudConfiguration) -> String {
|
||||
let http_client = reqwest::Client::new();
|
||||
let api_client = client_api::Client::from(
|
||||
http_client,
|
||||
&config.base_url,
|
||||
&config.base_ws_url,
|
||||
&config.gotrue_url,
|
||||
);
|
||||
|
||||
let admin_email = std::env::var("GOTRUE_ADMIN_EMAIL").unwrap();
|
||||
let admin_password = std::env::var("GOTRUE_ADMIN_PASSWORD").unwrap();
|
||||
api_client
|
||||
.generate_sign_in_callback_url(&admin_email, &admin_password, user_email)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn af_cloud_sign_up_param(
|
||||
email: &str,
|
||||
config: &AFCloudConfiguration,
|
||||
) -> HashMap<String, String> {
|
||||
let mut params = HashMap::new();
|
||||
params.insert(
|
||||
USER_SIGN_IN_URL.to_string(),
|
||||
generate_sign_in_url(email, config).await,
|
||||
);
|
||||
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
|
||||
params
|
||||
}
|
||||
|
||||
pub fn generate_test_email() -> String {
|
||||
format!("{}@test.com", Uuid::new_v4())
|
||||
}
|
@ -4,6 +4,7 @@ use tracing_subscriber::fmt::Subscriber;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
mod af_cloud_test;
|
||||
mod supabase_test;
|
||||
|
||||
pub fn setup_log() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use collab_define::{CollabObject, CollabType};
|
||||
use uuid::Uuid;
|
||||
|
||||
use flowy_user_deps::entities::SignUpResponse;
|
||||
use flowy_user_deps::entities::AuthResponse;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::supabase_test::util::{
|
||||
@ -18,7 +18,7 @@ async fn supabase_create_database_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
|
||||
let collab_service = collab_service();
|
||||
let database_service = database_service();
|
||||
|
@ -6,7 +6,7 @@ use yrs::types::ToJson;
|
||||
use yrs::updates::decoder::Decode;
|
||||
use yrs::{merge_updates_v1, Array, Doc, Map, MapPrelim, ReadTxn, StateVector, Transact, Update};
|
||||
|
||||
use flowy_user_deps::entities::SignUpResponse;
|
||||
use flowy_user_deps::entities::AuthResponse;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::supabase_test::util::{
|
||||
@ -37,7 +37,7 @@ async fn supabase_get_folder_test() {
|
||||
let collab_service = collab_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
|
||||
let collab_object = CollabObject::new(
|
||||
user.user_id,
|
||||
@ -111,7 +111,7 @@ async fn supabase_duplicate_updates_test() {
|
||||
let collab_service = collab_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
|
||||
let collab_object = CollabObject::new(
|
||||
user.user_id,
|
||||
@ -218,7 +218,7 @@ async fn supabase_diff_state_vector_test() {
|
||||
let collab_service = collab_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
|
||||
let collab_object = CollabObject::new(
|
||||
user.user_id,
|
||||
|
@ -17,7 +17,7 @@ async fn supabase_user_sign_up_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
assert!(!user.latest_workspace.id.is_empty());
|
||||
assert!(!user.user_workspaces.is_empty());
|
||||
assert!(!user.latest_workspace.database_views_aggregate_id.is_empty());
|
||||
@ -31,11 +31,11 @@ async fn supabase_user_sign_up_with_existing_uuid_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let _user: SignUpResponse = user_service
|
||||
let _user: AuthResponse = user_service
|
||||
.sign_up(BoxAny::new(params.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
assert!(!user.latest_workspace.id.is_empty());
|
||||
assert!(!user.latest_workspace.database_views_aggregate_id.is_empty());
|
||||
assert!(!user.user_workspaces.is_empty());
|
||||
@ -49,7 +49,7 @@ async fn supabase_update_user_profile_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service
|
||||
let user: AuthResponse = user_service
|
||||
.sign_up(BoxAny::new(params.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
@ -87,7 +87,7 @@ async fn supabase_get_user_profile_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service
|
||||
let user: AuthResponse = user_service
|
||||
.sign_up(BoxAny::new(params.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
@ -123,7 +123,7 @@ async fn user_encryption_sign_test() {
|
||||
let user_service = user_auth_service();
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let params = third_party_sign_up_param(uuid);
|
||||
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
let user: AuthResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
|
||||
|
||||
// generate encryption sign
|
||||
let secret = generate_encryption_secret();
|
||||
|
@ -37,7 +37,7 @@ pub fn get_supabase_dev_config() -> Option<SupabaseConfiguration> {
|
||||
}
|
||||
|
||||
pub fn collab_service() -> Arc<dyn RemoteCollabStorage> {
|
||||
let (server, encryption_impl) = appflowy_server(None);
|
||||
let (server, encryption_impl) = supabase_server_service(None);
|
||||
Arc::new(SupabaseCollabStorageImpl::new(
|
||||
server,
|
||||
None,
|
||||
@ -46,17 +46,17 @@ pub fn collab_service() -> Arc<dyn RemoteCollabStorage> {
|
||||
}
|
||||
|
||||
pub fn database_service() -> Arc<dyn DatabaseCloudService> {
|
||||
let (server, _encryption_impl) = appflowy_server(None);
|
||||
let (server, _encryption_impl) = supabase_server_service(None);
|
||||
Arc::new(SupabaseDatabaseServiceImpl::new(server))
|
||||
}
|
||||
|
||||
pub fn user_auth_service() -> Arc<dyn UserCloudService> {
|
||||
let (server, _encryption_impl) = appflowy_server(None);
|
||||
let (server, _encryption_impl) = supabase_server_service(None);
|
||||
Arc::new(SupabaseUserServiceImpl::new(server, vec![], None))
|
||||
}
|
||||
|
||||
pub fn folder_service() -> Arc<dyn FolderCloudService> {
|
||||
let (server, _encryption_impl) = appflowy_server(None);
|
||||
let (server, _encryption_impl) = supabase_server_service(None);
|
||||
Arc::new(SupabaseFolderServiceImpl::new(server))
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ pub fn file_storage_service() -> Arc<dyn FileStorageService> {
|
||||
pub fn encryption_folder_service(
|
||||
secret: Option<String>,
|
||||
) -> (Arc<dyn FolderCloudService>, Arc<dyn AppFlowyEncryption>) {
|
||||
let (server, encryption_impl) = appflowy_server(secret);
|
||||
let (server, encryption_impl) = supabase_server_service(secret);
|
||||
let service = Arc::new(SupabaseFolderServiceImpl::new(server));
|
||||
(service, encryption_impl)
|
||||
}
|
||||
@ -86,7 +86,7 @@ pub fn encryption_folder_service(
|
||||
pub fn encryption_collab_service(
|
||||
secret: Option<String>,
|
||||
) -> (Arc<dyn RemoteCollabStorage>, Arc<dyn AppFlowyEncryption>) {
|
||||
let (server, encryption_impl) = appflowy_server(secret);
|
||||
let (server, encryption_impl) = supabase_server_service(secret);
|
||||
let service = Arc::new(SupabaseCollabStorageImpl::new(
|
||||
server,
|
||||
None,
|
||||
@ -120,7 +120,7 @@ pub async fn print_encryption_folder_snapshot(folder_id: &str, encryption_secret
|
||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
||||
}
|
||||
|
||||
pub fn appflowy_server(
|
||||
pub fn supabase_server_service(
|
||||
encryption_secret: Option<String>,
|
||||
) -> (SupabaseServerServiceImpl, Arc<dyn AppFlowyEncryption>) {
|
||||
let config = SupabaseConfiguration::from_env().unwrap();
|
||||
|
@ -35,22 +35,22 @@ nanoid = "0.4.0"
|
||||
tracing = { version = "0.1.27" }
|
||||
parking_lot = "0.12.1"
|
||||
uuid = { version = "1.3.3", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
dotenv = "0.15.0"
|
||||
tempdir = "0.3.7"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
collab = { version = "0.1.0" }
|
||||
collab-document = { version = "0.1.0" }
|
||||
collab-folder = { version = "0.1.0" }
|
||||
collab-database = { version = "0.1.0" }
|
||||
collab-plugins = { version = "0.1.0" }
|
||||
collab-define = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
dotenv = "0.15.0"
|
||||
tempdir = "0.3.7"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
assert-json-diff = "2.0.2"
|
||||
tokio-postgres = { version = "0.7.8" }
|
||||
zip = "0.6.6"
|
||||
|
||||
[features]
|
||||
default = ["cloud_test"]
|
||||
default = ["supabase_cloud_test"]
|
||||
dart = ["flowy-core/dart"]
|
||||
cloud_test = []
|
||||
supabase_cloud_test = []
|
@ -5,6 +5,12 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab::core::collab::MutexCollab;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab::preclude::updates::decoder::Decode;
|
||||
use collab::preclude::{merge_updates_v1, Update};
|
||||
use collab_document::blocks::DocumentData;
|
||||
use collab_document::document::Document;
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::RwLock;
|
||||
use protobuf::ProtobufError;
|
||||
@ -21,14 +27,15 @@ use flowy_folder2::entities::*;
|
||||
use flowy_folder2::event_map::FolderEvent;
|
||||
use flowy_notification::entities::SubscribeObject;
|
||||
use flowy_notification::{register_notification_sender, NotificationSender};
|
||||
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_UUID};
|
||||
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
|
||||
use flowy_user::entities::{
|
||||
AuthTypePB, ThirdPartyAuthPB, UpdateCloudConfigPB, UserCloudConfigPB, UserProfilePB,
|
||||
AuthTypePB, OAuthCallbackRequestPB, OAuthCallbackResponsePB, OAuthPB, UpdateCloudConfigPB,
|
||||
UserCloudConfigPB, UserProfilePB,
|
||||
};
|
||||
use flowy_user::errors::{FlowyError, FlowyResult};
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
use crate::document::document_event::OpenDocumentData;
|
||||
use crate::document::document_event::{DocumentEventTest, OpenDocumentData};
|
||||
use crate::event_builder::EventBuilder;
|
||||
use crate::user_event::{async_sign_up, SignUpContext};
|
||||
|
||||
@ -59,10 +66,52 @@ impl FlowyCoreTest {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn insert_document_text(&self, document_id: &str, text: &str, index: usize) {
|
||||
let document_event = DocumentEventTest::new_with_core(self.clone());
|
||||
document_event
|
||||
.insert_index(document_id, text, index, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
|
||||
let pb = EventBuilder::new(self.clone())
|
||||
.event(DocumentEvent::GetDocumentData)
|
||||
.payload(OpenDocumentPayloadPB {
|
||||
document_id: view_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<DocumentDataPB>();
|
||||
|
||||
DocumentData::from(pb)
|
||||
}
|
||||
|
||||
pub async fn get_document_update(&self, document_id: &str) -> Vec<u8> {
|
||||
let cloud_service = self.document_manager.get_cloud_service().clone();
|
||||
let remote_updates = cloud_service
|
||||
.get_document_updates(document_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if remote_updates.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let updates = remote_updates
|
||||
.iter()
|
||||
.map(|update| update.as_ref())
|
||||
.collect::<Vec<&[u8]>>();
|
||||
|
||||
merge_updates_v1(&updates).unwrap()
|
||||
}
|
||||
|
||||
pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self {
|
||||
let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter(
|
||||
"debug",
|
||||
vec!["flowy_test".to_string(), "lib_dispatch".to_string()],
|
||||
"trace",
|
||||
vec![
|
||||
"flowy_test".to_string(),
|
||||
// "lib_dispatch".to_string()
|
||||
],
|
||||
);
|
||||
|
||||
let inner = std::thread::spawn(|| AppFlowyCore::new(config))
|
||||
@ -120,13 +169,13 @@ impl FlowyCoreTest {
|
||||
|
||||
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
|
||||
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
|
||||
let payload = ThirdPartyAuthPB {
|
||||
let payload = OAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
EventBuilder::new(self.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.event(OAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -148,7 +197,38 @@ impl FlowyCoreTest {
|
||||
self.sign_up_as_guest().await.user_profile
|
||||
}
|
||||
|
||||
pub async fn third_party_sign_up_with_uuid(
|
||||
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
|
||||
let payload = OAuthCallbackRequestPB {
|
||||
email: email.to_string(),
|
||||
auth_type: AuthTypePB::AFCloud,
|
||||
};
|
||||
let sign_in_url = EventBuilder::new(self.clone())
|
||||
.event(OAuthCallbackURL)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.try_parse::<OAuthCallbackResponsePB>()?
|
||||
.sign_in_url;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
|
||||
map.insert(USER_DEVICE_ID.to_string(), uuid::Uuid::new_v4().to_string());
|
||||
let payload = OAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::AFCloud,
|
||||
};
|
||||
|
||||
let user_profile = EventBuilder::new(self.clone())
|
||||
.event(OAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.try_parse::<UserProfilePB>()?;
|
||||
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub async fn supabase_sign_up_with_uuid(
|
||||
&self,
|
||||
uuid: &str,
|
||||
email: Option<String>,
|
||||
@ -160,13 +240,13 @@ impl FlowyCoreTest {
|
||||
USER_EMAIL.to_string(),
|
||||
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
|
||||
);
|
||||
let payload = ThirdPartyAuthPB {
|
||||
let payload = OAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let user_profile = EventBuilder::new(self.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.event(OAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -879,3 +959,14 @@ pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
|
||||
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
|
||||
params
|
||||
}
|
||||
|
||||
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
|
||||
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
|
||||
collab.lock().with_origin_transact_mut(|txn| {
|
||||
let update = Update::decode_v1(collab_update).unwrap();
|
||||
txn.apply_update(update);
|
||||
});
|
||||
let document = Document::open(Arc::new(collab)).unwrap();
|
||||
let actual = document.get_document_data().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod local_test;
|
||||
|
||||
#[cfg(feature = "cloud_test")]
|
||||
#[cfg(feature = "supabase_cloud_test")]
|
||||
mod supabase_test;
|
||||
|
@ -23,20 +23,14 @@ impl FlowySupabaseDatabaseTest {
|
||||
#[allow(dead_code)]
|
||||
pub async fn new_with_user(uuid: String) -> Option<Self> {
|
||||
let inner = FlowySupabaseTest::new()?;
|
||||
inner
|
||||
.third_party_sign_up_with_uuid(&uuid, None)
|
||||
.await
|
||||
.unwrap();
|
||||
inner.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||
Some(Self { uuid, inner })
|
||||
}
|
||||
|
||||
pub async fn new_with_new_user() -> Option<Self> {
|
||||
let inner = FlowySupabaseTest::new()?;
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let _ = inner
|
||||
.third_party_sign_up_with_uuid(&uuid, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = inner.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||
Some(Self { uuid, inner })
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use flowy_document2::entities::DocumentSyncStatePB;
|
||||
use flowy_test::assert_document_data_equal;
|
||||
|
||||
use crate::document::af_cloud_test::util::AFCloudDocumentTest;
|
||||
use crate::util::receive_with_timeout;
|
||||
|
||||
#[tokio::test]
|
||||
async fn af_cloud_edit_document_test() {
|
||||
if let Some(test) = AFCloudDocumentTest::new().await {
|
||||
let document_id = test.create_document().await;
|
||||
let cloned_test = test.clone();
|
||||
let cloned_document_id = document_id.clone();
|
||||
tokio::spawn(async move {
|
||||
cloned_test
|
||||
.insert_document_text(&cloned_document_id, "hello world", 0)
|
||||
.await;
|
||||
});
|
||||
|
||||
// wait all update are send to the remote
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(15))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let document_data = test.get_document_data(&document_id).await;
|
||||
let update = test.get_document_update(&document_id).await;
|
||||
assert!(!update.is_empty());
|
||||
assert_document_data_equal(&update, &document_id, document_data);
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
mod edit_test;
|
||||
mod util;
|
@ -0,0 +1,37 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::util::{generate_test_email, AFCloudTest};
|
||||
|
||||
pub struct AFCloudDocumentTest {
|
||||
inner: AFCloudTest,
|
||||
}
|
||||
|
||||
impl AFCloudDocumentTest {
|
||||
pub async fn new() -> Option<Self> {
|
||||
let inner = AFCloudTest::new()?;
|
||||
let email = generate_test_email();
|
||||
let _ = inner.af_cloud_sign_in_with_email(&email).await.unwrap();
|
||||
Some(Self { inner })
|
||||
}
|
||||
|
||||
pub async fn create_document(&self) -> String {
|
||||
let current_workspace = self.inner.get_current_workspace().await;
|
||||
let view = self
|
||||
.inner
|
||||
.create_document(
|
||||
¤t_workspace.workspace.id,
|
||||
"my document".to_string(),
|
||||
vec![],
|
||||
)
|
||||
.await;
|
||||
view.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AFCloudDocumentTest {
|
||||
type Target = AFCloudTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
mod local_test;
|
||||
|
||||
#[cfg(feature = "cloud_test")]
|
||||
mod af_cloud_test;
|
||||
#[cfg(feature = "supabase_cloud_test")]
|
||||
mod supabase_test;
|
||||
|
@ -0,0 +1,61 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use flowy_document2::entities::DocumentSyncStatePB;
|
||||
use flowy_test::assert_document_data_equal;
|
||||
|
||||
use crate::document::supabase_test::helper::FlowySupabaseDocumentTest;
|
||||
use crate::util::receive_with_timeout;
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_document_edit_sync_test() {
|
||||
if let Some(test) = FlowySupabaseDocumentTest::new().await {
|
||||
let view = test.create_document().await;
|
||||
let document_id = view.id.clone();
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let cloned_document_id = document_id.clone();
|
||||
tokio::spawn(async move {
|
||||
cloned_test
|
||||
.insert_document_text(&cloned_document_id, "hello world", 0)
|
||||
.await;
|
||||
});
|
||||
|
||||
// wait all update are send to the remote
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let document_data = test.get_document_data(&document_id).await;
|
||||
let update = test.get_document_update(&document_id).await;
|
||||
assert_document_data_equal(&update, &document_id, document_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_document_edit_sync_test2() {
|
||||
if let Some(test) = FlowySupabaseDocumentTest::new().await {
|
||||
let view = test.create_document().await;
|
||||
let document_id = view.id.clone();
|
||||
|
||||
for i in 0..10 {
|
||||
test
|
||||
.insert_document_text(&document_id, "hello world", i)
|
||||
.await;
|
||||
}
|
||||
|
||||
// wait all update are send to the remote
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let document_data = test.get_document_data(&document_id).await;
|
||||
let update = test.get_document_update(&document_id).await;
|
||||
assert_document_data_equal(&update, &document_id, document_data);
|
||||
}
|
||||
}
|
@ -1,17 +1,7 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab::core::collab::MutexCollab;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab::preclude::updates::decoder::Decode;
|
||||
use collab::preclude::{merge_updates_v1, Update};
|
||||
use collab_document::blocks::DocumentData;
|
||||
use collab_document::document::Document;
|
||||
|
||||
use flowy_document2::entities::{
|
||||
DocumentDataPB, OpenDocumentPayloadPB, RepeatedDocumentSnapshotPB,
|
||||
};
|
||||
use flowy_document2::event_map::DocumentEvent::{GetDocumentData, GetDocumentSnapshots};
|
||||
use flowy_document2::entities::{OpenDocumentPayloadPB, RepeatedDocumentSnapshotPB};
|
||||
use flowy_document2::event_map::DocumentEvent::GetDocumentSnapshots;
|
||||
use flowy_folder2::entities::ViewPB;
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
|
||||
@ -25,7 +15,7 @@ impl FlowySupabaseDocumentTest {
|
||||
pub async fn new() -> Option<Self> {
|
||||
let inner = FlowySupabaseTest::new()?;
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let _ = inner.third_party_sign_up_with_uuid(&uuid, None).await;
|
||||
let _ = inner.supabase_sign_up_with_uuid(&uuid, None).await;
|
||||
Some(Self { inner })
|
||||
}
|
||||
|
||||
@ -41,6 +31,7 @@ impl FlowySupabaseDocumentTest {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_document_snapshots(&self, view_id: &str) -> RepeatedDocumentSnapshotPB {
|
||||
EventBuilder::new(self.inner.deref().clone())
|
||||
.event(GetDocumentSnapshots)
|
||||
@ -51,38 +42,6 @@ impl FlowySupabaseDocumentTest {
|
||||
.await
|
||||
.parse::<RepeatedDocumentSnapshotPB>()
|
||||
}
|
||||
|
||||
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
|
||||
let pb = EventBuilder::new(self.inner.deref().clone())
|
||||
.event(GetDocumentData)
|
||||
.payload(OpenDocumentPayloadPB {
|
||||
document_id: view_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<DocumentDataPB>();
|
||||
|
||||
DocumentData::from(pb)
|
||||
}
|
||||
|
||||
pub async fn get_collab_update(&self, document_id: &str) -> Vec<u8> {
|
||||
let cloud_service = self.document_manager.get_cloud_service().clone();
|
||||
let remote_updates = cloud_service
|
||||
.get_document_updates(document_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if remote_updates.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let updates = remote_updates
|
||||
.iter()
|
||||
.map(|update| update.as_ref())
|
||||
.collect::<Vec<&[u8]>>();
|
||||
|
||||
merge_updates_v1(&updates).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for FlowySupabaseDocumentTest {
|
||||
@ -92,14 +51,3 @@ impl Deref for FlowySupabaseDocumentTest {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
|
||||
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
|
||||
collab.lock().with_origin_transact_mut(|txn| {
|
||||
let update = Update::decode_v1(collab_update).unwrap();
|
||||
txn.apply_update(update);
|
||||
});
|
||||
let document = Document::open(Arc::new(collab)).unwrap();
|
||||
let actual = document.get_document_data().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
mod edit_test;
|
||||
mod file_test;
|
||||
mod helper;
|
||||
mod test;
|
||||
|
@ -1,86 +0,0 @@
|
||||
use std::ops::Deref;
|
||||
use std::time::Duration;
|
||||
|
||||
use flowy_document2::entities::{DocumentSnapshotStatePB, DocumentSyncStatePB};
|
||||
use flowy_document2::notification::DocumentNotification::DidUpdateDocumentSnapshotState;
|
||||
use flowy_test::document::document_event::DocumentEventTest;
|
||||
|
||||
use crate::document::supabase_test::helper::{
|
||||
assert_document_data_equal, FlowySupabaseDocumentTest,
|
||||
};
|
||||
use crate::util::receive_with_timeout;
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_initial_document_snapshot_test() {
|
||||
if let Some(test) = FlowySupabaseDocumentTest::new().await {
|
||||
let view = test.create_document().await;
|
||||
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe::<DocumentSnapshotStatePB>(&view.id, DidUpdateDocumentSnapshotState);
|
||||
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let snapshots = test.get_document_snapshots(&view.id).await;
|
||||
assert_eq!(snapshots.items.len(), 1);
|
||||
|
||||
let document_data = test.get_document_data(&view.id).await;
|
||||
assert_document_data_equal(&snapshots.items[0].data, &view.id, document_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_document_edit_sync_test() {
|
||||
if let Some(test) = FlowySupabaseDocumentTest::new().await {
|
||||
let view = test.create_document().await;
|
||||
let document_id = view.id.clone();
|
||||
|
||||
let core = test.deref().deref().clone();
|
||||
let document_event = DocumentEventTest::new_with_core(core);
|
||||
document_event
|
||||
.insert_index(&document_id, "hello world", 0, None)
|
||||
.await;
|
||||
|
||||
// wait all update are send to the remote
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let document_data = test.get_document_data(&document_id).await;
|
||||
let update = test.get_collab_update(&document_id).await;
|
||||
assert_document_data_equal(&update, &document_id, document_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_document_edit_sync_test2() {
|
||||
if let Some(test) = FlowySupabaseDocumentTest::new().await {
|
||||
let view = test.create_document().await;
|
||||
let document_id = view.id.clone();
|
||||
let core = test.deref().deref().clone();
|
||||
let document_event = DocumentEventTest::new_with_core(core);
|
||||
|
||||
for i in 0..10 {
|
||||
document_event
|
||||
.insert_index(&document_id, "hello world", i, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
// wait all update are send to the remote
|
||||
let mut rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(&mut rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let document_data = test.get_document_data(&document_id).await;
|
||||
let update = test.get_collab_update(&document_id).await;
|
||||
assert_document_data_equal(&update, &document_id, document_data);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
mod local_test;
|
||||
|
||||
#[cfg(feature = "cloud_test")]
|
||||
#[cfg(feature = "supabase_cloud_test")]
|
||||
mod supabase_test;
|
||||
|
@ -21,7 +21,7 @@ impl FlowySupabaseFolderTest {
|
||||
pub async fn new() -> Option<Self> {
|
||||
let inner = FlowySupabaseTest::new()?;
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let _ = inner.third_party_sign_up_with_uuid(&uuid, None).await;
|
||||
let _ = inner.supabase_sign_up_with_uuid(&uuid, None).await;
|
||||
Some(Self { inner })
|
||||
}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
mod test;
|
@ -0,0 +1,13 @@
|
||||
use flowy_test::FlowyCoreTest;
|
||||
|
||||
use crate::util::{generate_test_email, get_af_cloud_config};
|
||||
|
||||
#[tokio::test]
|
||||
async fn af_cloud_sign_up_test() {
|
||||
if get_af_cloud_config().is_some() {
|
||||
let test = FlowyCoreTest::new();
|
||||
let email = generate_test_email();
|
||||
let user = test.af_cloud_sign_in_with_email(&email).await.unwrap();
|
||||
assert_eq!(user.email, email);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
mod local_test;
|
||||
mod migration_test;
|
||||
|
||||
#[cfg(feature = "cloud_test")]
|
||||
mod af_cloud_test;
|
||||
#[cfg(feature = "supabase_cloud_test")]
|
||||
mod supabase_test;
|
||||
|
@ -14,9 +14,7 @@ use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
|
||||
use flowy_test::document::document_event::DocumentEventTest;
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
use flowy_test::FlowyCoreTest;
|
||||
use flowy_user::entities::{
|
||||
AuthTypePB, ThirdPartyAuthPB, UpdateUserProfilePayloadPB, UserProfilePB,
|
||||
};
|
||||
use flowy_user::entities::{AuthTypePB, OAuthPB, UpdateUserProfilePayloadPB, UserProfilePB};
|
||||
use flowy_user::errors::ErrorCode;
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
@ -32,13 +30,13 @@ async fn third_party_sign_up_test() {
|
||||
USER_EMAIL.to_string(),
|
||||
format!("{}@appflowy.io", nanoid!(6)),
|
||||
);
|
||||
let payload = ThirdPartyAuthPB {
|
||||
let payload = OAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let response = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.event(OAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -74,8 +72,8 @@ async fn third_party_sign_up_with_duplicated_uuid() {
|
||||
map.insert(USER_EMAIL.to_string(), email.clone());
|
||||
|
||||
let response_1 = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.payload(ThirdPartyAuthPB {
|
||||
.event(OAuth)
|
||||
.payload(OAuthPB {
|
||||
map: map.clone(),
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
})
|
||||
@ -85,8 +83,8 @@ async fn third_party_sign_up_with_duplicated_uuid() {
|
||||
dbg!(&response_1);
|
||||
|
||||
let response_2 = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.payload(ThirdPartyAuthPB {
|
||||
.event(OAuth)
|
||||
.payload(OAuthPB {
|
||||
map: map.clone(),
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
})
|
||||
@ -103,11 +101,11 @@ async fn third_party_sign_up_with_duplicated_email() {
|
||||
let test = FlowyCoreTest::new();
|
||||
let email = format!("{}@appflowy.io", nanoid!(6));
|
||||
test
|
||||
.third_party_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let error = test
|
||||
.third_party_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
@ -127,10 +125,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
|
||||
let old_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
test
|
||||
.third_party_sign_up_with_uuid(&uuid, None)
|
||||
.await
|
||||
.unwrap();
|
||||
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||
let new_views = test
|
||||
.folder_manager
|
||||
.get_current_workspace_views()
|
||||
@ -159,7 +154,7 @@ async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
|
||||
let email = format!("{}@appflowy.io", nanoid!(6));
|
||||
// The workspace of the guest will be migrated to the new user with given uuid
|
||||
let _user_profile = test
|
||||
.third_party_sign_up_with_uuid(&uuid, Some(email.clone()))
|
||||
.supabase_sign_up_with_uuid(&uuid, Some(email.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let old_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
||||
@ -185,7 +180,7 @@ async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
|
||||
// upload to cloud user with given uuid. This time the workspace of the guest will not be merged
|
||||
// because the cloud user already has a workspace
|
||||
test
|
||||
.third_party_sign_up_with_uuid(&uuid, Some(email))
|
||||
.supabase_sign_up_with_uuid(&uuid, Some(email))
|
||||
.await
|
||||
.unwrap();
|
||||
let new_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
||||
@ -214,10 +209,7 @@ async fn check_not_exist_user_test() {
|
||||
async fn get_user_profile_test() {
|
||||
if let Some(test) = FlowySupabaseTest::new() {
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
test
|
||||
.third_party_sign_up_with_uuid(&uuid, None)
|
||||
.await
|
||||
.unwrap();
|
||||
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||
|
||||
let result = test.get_user_profile().await;
|
||||
assert!(result.is_ok());
|
||||
@ -228,10 +220,7 @@ async fn get_user_profile_test() {
|
||||
async fn update_user_profile_test() {
|
||||
if let Some(test) = FlowySupabaseTest::new() {
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let profile = test
|
||||
.third_party_sign_up_with_uuid(&uuid, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let profile = test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||
test
|
||||
.update_user_profile(UpdateUserProfilePayloadPB::new(profile.id).name("lucas"))
|
||||
.await;
|
||||
@ -246,11 +235,11 @@ async fn update_user_profile_with_existing_email_test() {
|
||||
if let Some(test) = FlowySupabaseTest::new() {
|
||||
let email = format!("{}@appflowy.io", nanoid!(6));
|
||||
let _ = test
|
||||
.third_party_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
||||
.await;
|
||||
|
||||
let profile = test
|
||||
.third_party_sign_up_with_uuid(
|
||||
.supabase_sign_up_with_uuid(
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
Some(format!("{}@appflowy.io", nanoid!(6))),
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ use flowy_folder2::entities::WorkspaceSettingPB;
|
||||
use flowy_folder2::event_map::FolderEvent::GetCurrentWorkspace;
|
||||
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{AuthTypePB, ThirdPartyAuthPB, UserProfilePB};
|
||||
use flowy_user::entities::{AuthTypePB, OAuthPB, UserProfilePB};
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
use crate::util::*;
|
||||
@ -19,13 +19,13 @@ async fn initial_workspace_test() {
|
||||
USER_EMAIL.to_string(),
|
||||
format!("{}@gmail.com", uuid::Uuid::new_v4()),
|
||||
);
|
||||
let payload = ThirdPartyAuthPB {
|
||||
let payload = OAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let _ = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.event(OAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
|
@ -11,12 +11,14 @@ use collab_plugins::cloud_storage::RemoteCollabStorage;
|
||||
use nanoid::nanoid;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::time::timeout;
|
||||
use uuid::Uuid;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use flowy_database_deps::cloud::DatabaseCloudService;
|
||||
use flowy_folder_deps::cloud::{FolderCloudService, FolderSnapshot};
|
||||
use flowy_server::supabase::api::*;
|
||||
use flowy_server::{AppFlowyEncryption, EncryptionImpl};
|
||||
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
use flowy_test::Cleaner;
|
||||
@ -211,3 +213,35 @@ pub fn unzip_history_user_db(root: &str, folder_name: &str) -> std::io::Result<(
|
||||
PathBuf::from(path),
|
||||
))
|
||||
}
|
||||
|
||||
pub struct AFCloudTest {
|
||||
inner: FlowyCoreTest,
|
||||
}
|
||||
|
||||
impl AFCloudTest {
|
||||
pub fn new() -> Option<Self> {
|
||||
let _ = get_af_cloud_config()?;
|
||||
let test = FlowyCoreTest::new();
|
||||
test.set_auth_type(AuthTypePB::AFCloud);
|
||||
test.server_provider.set_auth_type(AuthType::AFCloud);
|
||||
|
||||
Some(Self { inner: test })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AFCloudTest {
|
||||
type Target = FlowyCoreTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_test_email() -> String {
|
||||
format!("{}@test.com", Uuid::new_v4())
|
||||
}
|
||||
|
||||
pub fn get_af_cloud_config() -> Option<AFCloudConfiguration> {
|
||||
dotenv::from_filename("./.env.ci").ok()?;
|
||||
AFCloudConfiguration::from_env().ok()
|
||||
}
|
||||
|
@ -13,6 +13,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
collab-define = { version = "0.1.0" }
|
||||
serde_json = { version = "1.0"}
|
||||
serde_repr = "0.1"
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock", "serde"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock", "serde"] }
|
||||
anyhow = "1.0.71"
|
||||
tokio = { version = "1.26", features = ["sync"] }
|
||||
|
@ -13,8 +13,7 @@ use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::entities::{
|
||||
SignInResponse, SignUpResponse, ThirdPartyParams, UpdateUserProfileParams, UserCredentials,
|
||||
UserProfile, UserWorkspace,
|
||||
AuthResponse, UpdateUserProfileParams, UserCredentials, UserProfile, UserWorkspace,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -62,15 +61,18 @@ pub trait UserCloudService: Send + Sync + 'static {
|
||||
/// Sign up a new account.
|
||||
/// The type of the params is defined the this trait's implementation.
|
||||
/// Use the `unbox_or_error` of the [BoxAny] to get the params.
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error>;
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error>;
|
||||
|
||||
/// Sign in an account
|
||||
/// The type of the params is defined the this trait's implementation.
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error>;
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error>;
|
||||
|
||||
/// Sign out an account
|
||||
fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error>;
|
||||
|
||||
/// Generate a sign in callback url for the user with the given email
|
||||
fn generate_sign_in_callback_url(&self, email: &str) -> FutureResult<String, Error>;
|
||||
|
||||
/// Using the user's token to update the user information
|
||||
fn update_user(
|
||||
&self,
|
||||
@ -129,18 +131,6 @@ pub struct UserUpdate {
|
||||
pub encryption_sign: String,
|
||||
}
|
||||
|
||||
pub fn third_party_params_from_box_any(any: BoxAny) -> Result<ThirdPartyParams, Error> {
|
||||
let map: HashMap<String, String> = any.unbox_or_error()?;
|
||||
let uuid = uuid_from_map(&map)?;
|
||||
let email = map.get("email").cloned().unwrap_or_default();
|
||||
let device_id = map.get("device_id").cloned().unwrap_or_default();
|
||||
Ok(ThirdPartyParams {
|
||||
uuid,
|
||||
email,
|
||||
device_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn uuid_from_map(map: &HashMap<String, String>) -> Result<Uuid, Error> {
|
||||
let uuid = map
|
||||
.get("uuid")
|
||||
|
@ -81,7 +81,7 @@ pub struct SignUpParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SignUpResponse {
|
||||
pub struct AuthResponse {
|
||||
pub user_id: i64,
|
||||
pub name: String,
|
||||
pub latest_workspace: UserWorkspace,
|
||||
@ -93,7 +93,7 @@ pub struct SignUpResponse {
|
||||
pub encryption_type: EncryptionType,
|
||||
}
|
||||
|
||||
impl UserAuthResponse for SignUpResponse {
|
||||
impl UserAuthResponse for AuthResponse {
|
||||
fn user_id(&self) -> i64 {
|
||||
self.user_id
|
||||
}
|
||||
@ -129,7 +129,7 @@ impl UserAuthResponse for SignUpResponse {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserCredentials {
|
||||
/// Currently, the token is only used when the [AuthType] is SelfHosted
|
||||
/// Currently, the token is only used when the [AuthType] is AFCloud
|
||||
pub token: Option<String>,
|
||||
|
||||
/// The user id
|
||||
@ -326,7 +326,7 @@ pub enum AuthType {
|
||||
Local = 0,
|
||||
/// Currently not supported. It will be supported in the future when the
|
||||
/// [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Server) ready.
|
||||
SelfHosted = 1,
|
||||
AFCloud = 1,
|
||||
/// It uses Supabase as the backend.
|
||||
Supabase = 2,
|
||||
}
|
||||
@ -347,14 +347,19 @@ impl From<i32> for AuthType {
|
||||
fn from(value: i32) -> Self {
|
||||
match value {
|
||||
0 => AuthType::Local,
|
||||
1 => AuthType::SelfHosted,
|
||||
1 => AuthType::AFCloud,
|
||||
2 => AuthType::Supabase,
|
||||
_ => AuthType::Local,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct ThirdPartyParams {
|
||||
pub struct SupabaseOAuthParams {
|
||||
pub uuid: Uuid,
|
||||
pub email: String,
|
||||
pub device_id: String,
|
||||
}
|
||||
|
||||
pub struct AFCloudOAuthParams {
|
||||
pub sign_in_url: String,
|
||||
pub device_id: String,
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ validator = "0.16.0"
|
||||
unicode-segmentation = "1.10"
|
||||
fancy-regex = "0.11.0"
|
||||
uuid = { version = "1.3.3", features = [ "v4"] }
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
base64 = "^0.21"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -79,7 +79,7 @@ impl TryInto<SignUpParams> for SignUpPayloadPB {
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct ThirdPartyAuthPB {
|
||||
pub struct OAuthPB {
|
||||
/// Use this field to store the third party auth information.
|
||||
/// Different auth type has different fields.
|
||||
/// Supabase:
|
||||
@ -92,10 +92,25 @@ pub struct ThirdPartyAuthPB {
|
||||
pub auth_type: AuthTypePB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OAuthCallbackRequestPB {
|
||||
#[pb(index = 1)]
|
||||
pub email: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub auth_type: AuthTypePB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OAuthCallbackResponsePB {
|
||||
#[pb(index = 1)]
|
||||
pub sign_in_url: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
pub enum AuthTypePB {
|
||||
Local = 0,
|
||||
SelfHosted = 1,
|
||||
AFCloud = 1,
|
||||
Supabase = 2,
|
||||
}
|
||||
|
||||
|
@ -218,8 +218,8 @@ pub async fn get_user_setting(
|
||||
/// Only used for third party auth.
|
||||
/// Use [UserEvent::SignIn] or [UserEvent::SignUp] If the [AuthType] is Local or SelfHosted
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub async fn third_party_auth_handler(
|
||||
data: AFPluginData<ThirdPartyAuthPB>,
|
||||
pub async fn oauth_handler(
|
||||
data: AFPluginData<OAuthPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<UserProfilePB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
@ -229,6 +229,21 @@ pub async fn third_party_auth_handler(
|
||||
data_result_ok(user_profile.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub async fn get_oauth_url_handler(
|
||||
data: AFPluginData<OAuthCallbackRequestPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<OAuthCallbackResponsePB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params = data.into_inner();
|
||||
let auth_type: AuthType = params.auth_type.into();
|
||||
let sign_in_url = manager
|
||||
.generate_sign_in_callback_url(&auth_type, ¶ms.email)
|
||||
.await?;
|
||||
let resp = OAuthCallbackResponsePB { sign_in_url };
|
||||
data_result_ok(resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn set_encrypt_secret_handler(
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
|
@ -37,7 +37,8 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
|
||||
.event(UserEvent::GetCloudConfig, get_cloud_config_handler)
|
||||
.event(UserEvent::SetEncryptionSecret, set_encrypt_secret_handler)
|
||||
.event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler)
|
||||
.event(UserEvent::ThirdPartyAuth, third_party_auth_handler)
|
||||
.event(UserEvent::OAuth, oauth_handler)
|
||||
.event(UserEvent::OAuthCallbackURL, get_oauth_url_handler)
|
||||
.event(
|
||||
UserEvent::GetAllUserWorkspaces,
|
||||
get_all_user_workspace_handler,
|
||||
@ -229,8 +230,13 @@ pub enum UserEvent {
|
||||
#[event(output = "UserSettingPB")]
|
||||
GetUserSetting = 9,
|
||||
|
||||
#[event(input = "ThirdPartyAuthPB", output = "UserProfilePB")]
|
||||
ThirdPartyAuth = 10,
|
||||
#[event(input = "OAuthPB", output = "UserProfilePB")]
|
||||
OAuth = 10,
|
||||
|
||||
/// Get the OAuth callback url
|
||||
/// Only use when the [AuthType] is AFCloud
|
||||
#[event(input = "OAuthCallbackRequestPB", output = "OAuthCallbackResponsePB")]
|
||||
OAuthCallbackURL = 11,
|
||||
|
||||
#[event(input = "UpdateCloudConfigPB")]
|
||||
SetCloudConfig = 13,
|
||||
|
@ -195,7 +195,7 @@ impl UserManager {
|
||||
auth_type: AuthType,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
self.update_auth_type(&auth_type).await;
|
||||
let response: SignInResponse = self
|
||||
let response: AuthResponse = self
|
||||
.cloud_services
|
||||
.get_user_service()?
|
||||
.sign_in(params)
|
||||
@ -252,7 +252,7 @@ impl UserManager {
|
||||
|
||||
let migration_user = self.get_migration_user(&auth_type).await;
|
||||
let auth_service = self.cloud_services.get_user_service()?;
|
||||
let response: SignUpResponse = auth_service.sign_up(params).await?;
|
||||
let response: AuthResponse = auth_service.sign_up(params).await?;
|
||||
let user_profile = UserProfile::from((&response, &auth_type));
|
||||
if user_profile.encryption_type.is_need_encrypt_secret() {
|
||||
self
|
||||
@ -300,7 +300,7 @@ impl UserManager {
|
||||
&self,
|
||||
user_profile: &UserProfile,
|
||||
migration_user: Option<MigrationUser>,
|
||||
response: SignUpResponse,
|
||||
response: AuthResponse,
|
||||
auth_type: &AuthType,
|
||||
) -> FlowyResult<()> {
|
||||
let new_session = Session::from(&response);
|
||||
@ -543,6 +543,18 @@ impl UserManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_sign_in_callback_url(
|
||||
&self,
|
||||
auth_type: &AuthType,
|
||||
email: &str,
|
||||
) -> Result<String, FlowyError> {
|
||||
self.update_auth_type(auth_type).await;
|
||||
|
||||
let auth_service = self.cloud_services.get_user_service()?;
|
||||
let url = auth_service.generate_sign_in_callback_url(email).await?;
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
async fn save_auth_data(
|
||||
&self,
|
||||
response: &impl UserAuthResponse,
|
||||
|
@ -7,8 +7,8 @@ use serde::de::{Deserializer, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use flowy_user_deps::entities::{AuthResponse, UserProfile, UserWorkspace};
|
||||
use flowy_user_deps::entities::{AuthType, UserAuthResponse};
|
||||
use flowy_user_deps::entities::{SignUpResponse, UserProfile, UserWorkspace};
|
||||
|
||||
use crate::entities::AuthTypePB;
|
||||
use crate::migrations::MigrationUser;
|
||||
@ -162,7 +162,7 @@ impl From<AuthTypePB> for AuthType {
|
||||
match pb {
|
||||
AuthTypePB::Supabase => AuthType::Supabase,
|
||||
AuthTypePB::Local => AuthType::Local,
|
||||
AuthTypePB::SelfHosted => AuthType::SelfHosted,
|
||||
AuthTypePB::AFCloud => AuthType::AFCloud,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +172,7 @@ impl From<AuthType> for AuthTypePB {
|
||||
match auth_type {
|
||||
AuthType::Supabase => AuthTypePB::Supabase,
|
||||
AuthType::Local => AuthTypePB::Local,
|
||||
AuthType::SelfHosted => AuthTypePB::SelfHosted,
|
||||
AuthType::AFCloud => AuthTypePB::AFCloud,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,7 +206,7 @@ const DEFAULT_AUTH_TYPE: fn() -> AuthType = || AuthType::Local;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ResumableSignUp {
|
||||
pub user_profile: UserProfile,
|
||||
pub response: SignUpResponse,
|
||||
pub response: AuthResponse,
|
||||
pub auth_type: AuthType,
|
||||
pub migration_user: Option<MigrationUser>,
|
||||
}
|
||||
|
30
frontend/scripts/tool/update_client_api_rev.sh
Executable file
30
frontend/scripts/tool/update_client_api_rev.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Ensure a new revision ID is provided
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <new_revision_id>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_REV="$1"
|
||||
echo "New revision: $NEW_REV"
|
||||
directories=("rust-lib" "appflowy_tauri/src-tauri")
|
||||
|
||||
for dir in "${directories[@]}"; do
|
||||
echo "Updating $dir"
|
||||
|
||||
cd "$dir"
|
||||
sed -i.bak "/^client-api[[:alnum:]-]*[[:space:]]*=/s/rev = \"[a-fA-F0-9]\{6,40\}\"/rev = \"$NEW_REV\"/g" Cargo.toml
|
||||
|
||||
# Detect changed crates
|
||||
client_api_crates=($(grep -E '^client-api[a-zA-Z0-9_-]* =' Cargo.toml | awk -F'=' '{print $1}' | tr -d ' '))
|
||||
|
||||
# Update only the changed crates in Cargo.lock
|
||||
for crate in "${client_api_crates[@]}"; do
|
||||
echo "Updating $crate"
|
||||
cargo update -p $crate
|
||||
done
|
||||
|
||||
cd ..
|
||||
done
|
||||
|
@ -20,11 +20,16 @@ for dir in "${directories[@]}"; do
|
||||
collab_crates=($(grep -E '^collab[a-zA-Z0-9_-]* =' Cargo.toml | awk -F'=' '{print $1}' | tr -d ' '))
|
||||
|
||||
# Update only the changed crates in Cargo.lock
|
||||
|
||||
crates_to_update=""
|
||||
for crate in "${collab_crates[@]}"; do
|
||||
echo "Updating $crate"
|
||||
cargo update -p $crate
|
||||
crates_to_update="$crates_to_update -p $crate"
|
||||
done
|
||||
|
||||
# Update all the specified crates at once
|
||||
echo "Updating crates: $crates_to_update"
|
||||
cargo update $crates_to_update
|
||||
|
||||
cd ..
|
||||
done
|
||||
|
||||
|
18
shared-lib/Cargo.lock
generated
18
shared-lib/Cargo.lock
generated
@ -115,9 +115,9 @@ checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
@ -949,22 +949,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
|
||||
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.12"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1011,9 +1011,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.57"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -6,9 +6,9 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
|
||||
bytes = { version = "1.4" }
|
||||
pin-project = "1.0.12"
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
bytes = { version = "1.5" }
|
||||
pin-project = "1.1.3"
|
||||
futures-core = { version = "0.3" }
|
||||
tokio = { version = "1.26", features = ["time", "rt"] }
|
||||
rand = "0.8.5"
|
||||
|
Loading…
Reference in New Issue
Block a user