diff --git a/frontend/appflowy_flutter/dev.env b/frontend/appflowy_flutter/dev.env index f51f1cbd9e..2dc13ab045 100644 --- a/frontend/appflowy_flutter/dev.env +++ b/frontend/appflowy_flutter/dev.env @@ -1,15 +1,18 @@ -# Initial Setup +# Copy the 'dev.env' file to '.env': +# Use the command 'cp dev.env .env' to create a copy of 'dev.env' named '.env'. +# After copying, update the '.env' file with the necessary environment parameters. -# 1. Copy the dev.env file to .env: -# cp dev.env .env -# Update the environment parameters as needed. - -# 2. Generate the env.dart from this .env file: -# You can use the "Generate Env File" task in VSCode. -# Alternatively, execute the following commands: +# Generate the 'env.dart' from this '.env' file: +# Option 1: Use the "Generate Env File" task in VSCode. +# Option 2: Execute the commands in the appflowy_flutter directory: # cd appflowy_flutter # dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs +# Note on Configuration Priority: +# If both Supabase config and AppFlowy cloud config are provided in the '.env' file, +# the AppFlowy cloud config will be prioritized and the Supabase config ignored. +# Ensure only one of these configurations is active at any given time. + # Cloud Type Configuration # Use this configuration file to specify the cloud type and its associated settings. The available cloud types are: @@ -25,17 +28,9 @@ SUPABASE_URL= SUPABASE_ANON_KEY= # AppFlowy Cloud Configuration -# If using AppFlowy Cloud (CLOUD_TYPE=2), provide the following details: -# For instance: -# APPFLOWY_CLOUD_BASE_URL=https://xxxxxxxxx -# APPFLOWY_CLOUD_WS_BASE_URL=wss://xxxxxxxxx -# APPFLOWY_CLOUD_GOTRUE_URL=https://xxxxxxxxx +# If using Supabase (CLOUD_TYPE=2), provide the following details: # -# When using localhost for development, you must run AppFlowy Cloud locally -# first. Plese Please follow the instructions below: -# https://github.com/AppFlowy-IO/AppFlowy-Cloud#development -# -# After running AppFlowy Cloud locally, you can use the following settings: +# When using localhost for development. you can use the following settings: # APPFLOWY_CLOUD_BASE_URL=http://localhost:8000 # APPFLOWY_CLOUD_WS_BASE_URL=ws://localhost:8000/ws # APPFLOWY_CLOUD_GOTRUE_URL=http://localhost:9998 diff --git a/frontend/appflowy_flutter/integration_test/auth/auth_test.dart b/frontend/appflowy_flutter/integration_test/auth/auth_test.dart index cb38c6032a..2a3c3aeee6 100644 --- a/frontend/appflowy_flutter/integration_test/auth/auth_test.dart +++ b/frontend/appflowy_flutter/integration_test/auth/auth_test.dart @@ -39,7 +39,7 @@ void main() { // should not see the sync setting page when sign in as annoymous await tester.openSettings(); - await tester.expectNoSettingsPage(SettingsPage.syncSetting); + await tester.expectNoSettingsPage(SettingsPage.cloud); }); testWidgets('enable encryption', (tester) async { @@ -48,7 +48,7 @@ void main() { // Open the setting page and sign out await tester.openSettings(); - await tester.openSettingsPage(SettingsPage.syncSetting); + await tester.openSettingsPage(SettingsPage.cloud); // the switch should be off by default tester.assertEnableEncryptSwitchValue(false); @@ -68,7 +68,7 @@ void main() { // Open the setting page and sign out await tester.openSettings(); - await tester.openSettingsPage(SettingsPage.syncSetting); + await tester.openSettingsPage(SettingsPage.cloud); // the switch should be on by default tester.assertEnableSyncSwitchValue(true); diff --git a/frontend/appflowy_flutter/integration_test/util/auth_operation.dart b/frontend/appflowy_flutter/integration_test/util/auth_operation.dart index ef426ecfdd..1f945dd5d6 100644 --- a/frontend/appflowy_flutter/integration_test/util/auth_operation.dart +++ b/frontend/appflowy_flutter/integration_test/util/auth_operation.dart @@ -1,5 +1,5 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/frontend/appflowy_flutter/lib/core/config/kv.dart b/frontend/appflowy_flutter/lib/core/config/kv.dart index 141041475a..6b3cd813e9 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv.dart @@ -70,9 +70,8 @@ class DartKeyValue implements KeyValueStorage { /// Key-value store /// The data is stored in the local storage of the device. -class RustKeyValue implements KeyValueStorage { - @override - Future set(String key, String value) async { +class RustKeyValue { + static Future set(String key, String value) async { await ConfigEventSetKeyValue( KeyValuePB.create() ..key = key @@ -80,15 +79,13 @@ class RustKeyValue implements KeyValueStorage { ).send(); } - @override - Future> get(String key) async { + static Future> get(String key) async { final payload = KeyPB.create()..key = key; final response = await ConfigEventGetKeyValue(payload).send(); return response.swap().map((r) => r.value); } - @override - Future> getWithFormat( + static Future> getWithFormat( String key, T Function(String value) formatter, ) async { @@ -99,15 +96,9 @@ class RustKeyValue implements KeyValueStorage { ); } - @override - Future remove(String key) async { + static Future remove(String key) async { await ConfigEventRemoveKeyValue( KeyPB.create()..key = key, ).send(); } - - @override - Future clear() async { - // TODO(Lucas): implement clear - } } diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart index 72b0988d5b..fef160c98c 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart @@ -6,13 +6,6 @@ class KVKeys { /// The key for the path location of the local data for the whole app. static const String pathLocation = '$prefix.path_location'; - /// The key for the last time login type. - /// - /// The value is one of the following: - /// - local - /// - supabase - static const String loginType = '$prefix.login_type'; - /// The key for saving the window size /// /// The value is a json string with the following format: diff --git a/frontend/appflowy_flutter/lib/env/backend_env.dart b/frontend/appflowy_flutter/lib/env/backend_env.dart index b9355e66ea..35ad0c87ae 100644 --- a/frontend/appflowy_flutter/lib/env/backend_env.dart +++ b/frontend/appflowy_flutter/lib/env/backend_env.dart @@ -5,10 +5,12 @@ part 'backend_env.g.dart'; @JsonSerializable() class AppFlowyEnv { + final int cloud_type; final SupabaseConfiguration supabase_config; final AppFlowyCloudConfiguration appflowy_cloud_config; AppFlowyEnv({ + required this.cloud_type, required this.supabase_config, required this.appflowy_cloud_config, }); @@ -22,12 +24,10 @@ class AppFlowyEnv { @JsonSerializable() class SupabaseConfiguration { /// Indicates whether the sync feature is enabled. - final bool enable_sync; final String url; final String anon_key; SupabaseConfiguration({ - this.enable_sync = true, required this.url, required this.anon_key, }); diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart index c27ad3455e..0849363daf 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart @@ -34,22 +34,38 @@ class InitRustSDKTask extends LaunchTask { } AppFlowyEnv getAppFlowyEnv() { - final supabaseConfig = SupabaseConfiguration( - enable_sync: true, - url: Env.supabaseUrl, - anon_key: Env.supabaseAnonKey, - ); + if (isCloudEnabled) { + final supabaseConfig = SupabaseConfiguration( + url: Env.supabaseUrl, + anon_key: Env.supabaseAnonKey, + ); - final appflowyCloudConfig = AppFlowyCloudConfiguration( - base_url: Env.afCloudBaseUrl, - ws_base_url: Env.afCloudWSBaseUrl, - gotrue_url: Env.afCloudGoTrueUrl, - ); + final appflowyCloudConfig = AppFlowyCloudConfiguration( + base_url: Env.afCloudBaseUrl, + ws_base_url: Env.afCloudWSBaseUrl, + gotrue_url: Env.afCloudGoTrueUrl, + ); - return AppFlowyEnv( - supabase_config: supabaseConfig, - appflowy_cloud_config: appflowyCloudConfig, - ); + return AppFlowyEnv( + cloud_type: Env.cloudType, + supabase_config: supabaseConfig, + appflowy_cloud_config: appflowyCloudConfig, + ); + } else { + // Use the default configuration if the cloud feature is disabled + final supabaseConfig = SupabaseConfiguration(url: '', anon_key: ''); + final appflowyCloudConfig = AppFlowyCloudConfiguration( + base_url: '', + ws_base_url: '', + gotrue_url: '', + ); + + return AppFlowyEnv( + cloud_type: 0, + supabase_config: supabaseConfig, + appflowy_cloud_config: appflowyCloudConfig, + ); + } } /// The default directory to store the user data. The directory can be diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart index b132fe9d22..c78afbc91b 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart @@ -1,5 +1,3 @@ -import 'package:appflowy/core/config/kv.dart'; -import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/historical_user_bloc.dart'; @@ -41,7 +39,6 @@ class SignInAnonymousButton extends StatelessWidget { : LocaleKeys.signIn_continueAnonymousUser.tr(); final onTap = state.historicalUsers.isEmpty ? () { - getIt().set(KVKeys.loginType, 'local'); context .read() .add(const SignInEvent.signedInAsGuest()); diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart index 8be184ed7b..f63b738ca6 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart @@ -1,8 +1,5 @@ -import 'package:appflowy/core/config/kv.dart'; -import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; import 'package:appflowy/user/presentation/presentation.dart'; import 'package:appflowy/util/platform_extension.dart'; @@ -165,19 +162,16 @@ class _ThirdPartySignInButton extends StatelessWidget { } void _signInWithGoogle(BuildContext context) { - getIt().set(KVKeys.loginType, 'supabase'); context.read().add( const SignInEvent.signedInWithOAuth('google'), ); } void _signInWithGithub(BuildContext context) { - getIt().set(KVKeys.loginType, 'supabase'); context.read().add(const SignInEvent.signedInWithOAuth('github')); } void _signInWithDiscord(BuildContext context) { - getIt().set(KVKeys.loginType, 'supabase'); context .read() .add(const SignInEvent.signedInWithOAuth('discord')); diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/cloud_setting_listener.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/cloud_setting_listener.dart index 034c18a2cc..be02c26501 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/cloud_setting_listener.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/cloud_setting_listener.dart @@ -12,7 +12,7 @@ import '../../../core/notification/user_notification.dart'; class UserCloudConfigListener { final String userId; StreamSubscription? _subscription; - void Function(Either)? _onSettingChanged; + void Function(Either)? _onSettingChanged; UserNotificationParser? _userParser; UserCloudConfigListener({ @@ -20,7 +20,7 @@ class UserCloudConfigListener { }); void start({ - void Function(Either)? onSettingChanged, + void Function(Either)? onSettingChanged, }) { _onSettingChanged = onSettingChanged; _userParser = UserNotificationParser( @@ -45,8 +45,8 @@ class UserCloudConfigListener { switch (ty) { case UserNotification.DidUpdateCloudConfig: result.fold( - (payload) => _onSettingChanged - ?.call(left(UserCloudConfigPB.fromBuffer(payload))), + (payload) => + _onSettingChanged?.call(left(CloudSettingPB.fromBuffer(payload))), (error) => _onSettingChanged?.call(right(error)), ); break; diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart index 0a3604dcf6..4a064c665a 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart @@ -15,7 +15,7 @@ class CloudSettingBloc extends Bloc { CloudSettingBloc({ required String userId, - required UserCloudConfigPB config, + required CloudSettingPB config, }) : _listener = UserCloudConfigListener(userId: userId), super(CloudSettingState.initial(config)) { on((event, emit) async { @@ -38,7 +38,7 @@ class CloudSettingBloc extends Bloc { final update = UpdateCloudConfigPB.create()..enableSync = enable; updateCloudConfig(update); }, - didReceiveConfig: (UserCloudConfigPB config) { + didReceiveConfig: (CloudSettingPB config) { emit( state.copyWith( config: config, @@ -64,7 +64,7 @@ class CloudSettingBloc extends Bloc { class CloudSettingEvent with _$CloudSettingEvent { const factory CloudSettingEvent.initial() = _Initial; const factory CloudSettingEvent.didReceiveConfig( - UserCloudConfigPB config, + CloudSettingPB config, ) = _DidSyncSupabaseConfig; const factory CloudSettingEvent.enableSync(bool enable) = _EnableSync; const factory CloudSettingEvent.enableEncrypt(bool enable) = _EnableEncrypt; @@ -73,13 +73,12 @@ class CloudSettingEvent with _$CloudSettingEvent { @freezed class CloudSettingState with _$CloudSettingState { const factory CloudSettingState({ - required UserCloudConfigPB config, + required CloudSettingPB config, required Either successOrFailure, required LoadingState loadingState, }) = _CloudSettingState; - factory CloudSettingState.initial(UserCloudConfigPB config) => - CloudSettingState( + factory CloudSettingState.initial(CloudSettingPB config) => CloudSettingState( config: config, successOrFailure: left(unit), loadingState: LoadingState.finish(left(unit)), diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart index 2097361465..91a4fbcec4 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart @@ -14,7 +14,7 @@ enum SettingsPage { files, user, notifications, - syncSetting, + cloud, shortcuts, } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart index e676e88a76..696bb9c348 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart @@ -1,5 +1,7 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/settings/application_data_storage.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -32,7 +34,13 @@ class SettingsLocationCubit extends Cubit { } Future _init() async { - final path = await getIt().getPath(); - emit(SettingsLocationState.didReceivedPath(path)); + // The backend might change the real path that storge the data. So it needs + // to get the path from the backend instead of the KeyValueStorage + await UserEventGetUserSetting().send().then((result) { + result.fold( + (l) => emit(SettingsLocationState.didReceivedPath(l.userFolder)), + (r) => Log.error(r), + ); + }); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart index 7fec6a28b3..f4321cecb2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,7 +1,7 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart'; @@ -104,8 +104,8 @@ class SettingsDialog extends StatelessWidget { ); case SettingsPage.notifications: return const SettingsNotificationsView(); - case SettingsPage.syncSetting: - return SyncSettingView(userId: user.id.toString()); + case SettingsPage.cloud: + return SettingCloudView(userId: user.id.toString()); case SettingsPage.shortcuts: return const SettingsCustomizeShortcutsWrapper(); default: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/sync_setting_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud_view.dart similarity index 79% rename from frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/sync_setting_view.dart rename to frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud_view.dart index f362a8e515..45b693df84 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/sync_setting_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud_view.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/setting_supabase_bloc.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; @@ -14,13 +15,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class SyncSettingView extends StatelessWidget { +class SettingCloudView extends StatelessWidget { final String userId; - const SyncSettingView({required this.userId, super.key}); + const SettingCloudView({required this.userId, super.key}); @override Widget build(BuildContext context) { - return FutureBuilder>( + return FutureBuilder>( future: UserEventGetCloudConfig().send(), builder: (context, snapshot) { if (snapshot.data != null && @@ -34,10 +35,15 @@ class SyncSettingView extends StatelessWidget { )..add(const CloudSettingEvent.initial()), child: BlocBuilder( builder: (context, state) { - return const Column( + return Column( children: [ - EnableSync(), - EnableEncrypt(), + const EnableSync(), + // Currently the appflowy cloud is not support end-to-end encryption. + if (!isAppFlowyCloudEnabled) const EnableEncrypt(), + if (isAppFlowyCloudEnabled) + AppFlowyCloudInformationWidget( + url: state.config.serverUrl, + ), ], ); }, @@ -58,6 +64,34 @@ class SyncSettingView extends StatelessWidget { } } +class AppFlowyCloudInformationWidget extends StatelessWidget { + final String url; + + const AppFlowyCloudInformationWidget({required this.url, super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Expanded( + // Wrap the Opacity widget with Expanded + child: Opacity( + opacity: 0.6, + child: FlowyText( + "${LocaleKeys.settings_menu_cloudURL.tr()}: $url", + maxLines: null, // Allow the text to wrap + ), + ), + ), + ], + ), + ], + ); + } +} + class EnableEncrypt extends StatelessWidget { const EnableEncrypt({super.key}); @@ -121,7 +155,6 @@ class EnableEncrypt extends StatelessWidget { await Clipboard.setData( ClipboardData(text: state.config.encryptSecret), ); - // TODO(Lucas): bring the toast to the top of the dialog. showMessageToast(LocaleKeys.message_copy_success.tr()); }, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart index 5200b70623..5537c6b518 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart @@ -48,45 +48,10 @@ class SettingsFileLocationCustomizerState mainAxisSize: MainAxisSize.min, children: [ // display file paths. - Flexible( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText.medium( - LocaleKeys.settings_files_defaultLocation.tr(), - fontSize: 13, - overflow: TextOverflow.visible, - ).padding(horizontal: 5), - const VSpace(5), - _CopyableText( - usingPath: path, - ), - ], - ), - ), + _path(path), // display the icons - Flexible( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Flexible( - child: _ChangeStoragePathButton( - usingPath: path, - ), - ), - const HSpace(10), - _OpenStorageButton( - usingPath: path, - ), - _RecoverDefaultStorageButton( - usingPath: path, - ), - ], - ), - ), + _buttons(path), ], ); }, @@ -95,6 +60,55 @@ class SettingsFileLocationCustomizerState ), ); } + + Widget _path(String path) { + return Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText.medium( + LocaleKeys.settings_files_defaultLocation.tr(), + fontSize: 13, + overflow: TextOverflow.visible, + ).padding(horizontal: 5), + const VSpace(5), + _CopyableText( + usingPath: path, + ), + ], + ), + ); + } + + Widget _buttons(String path) { + final List children = []; + children.addAll([ + Flexible( + child: _ChangeStoragePathButton( + usingPath: path, + ), + ), + const HSpace(10), + ]); + + children.add( + _OpenStorageButton( + usingPath: path, + ), + ); + + children.add( + _RecoverDefaultStorageButton( + usingPath: path, + ), + ); + + return Flexible( + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: children), + ); + } } class _CopyableText extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart index b53b8b0fea..e0780b9f46 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart @@ -67,9 +67,9 @@ class SettingsMenu extends StatelessWidget { if (showSyncSetting) ...[ const SizedBox(height: 10), SettingsMenuElement( - page: SettingsPage.syncSetting, + page: SettingsPage.cloud, selectedPage: currentPage, - label: LocaleKeys.settings_menu_syncSetting.tr(), + label: LocaleKeys.settings_menu_cloudSetting.tr(), icon: Icons.sync, changeSelectedPage: changeSelectedPage, ), diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 40bdee0518..972d958e50 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -138,7 +138,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "reqwest", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -1449,7 +1449,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -2808,7 +2808,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "futures-util", @@ -2824,7 +2824,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -3251,7 +3251,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "reqwest", @@ -4994,7 +4994,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "bincode", @@ -5739,7 +5739,7 @@ dependencies = [ [[package]] name = "shared_entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index 079e2cc4db..5e8bca66fd 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -56,7 +56,7 @@ custom-protocol = ["tauri/custom-protocol"] # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "fe977fc8285addd5386e940738cdffbbda9eb44e" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b578c83cc912255e48dea9e33a203a069ce7d0c5" } # Please use the following script to update collab. # Working directory: frontend # diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs index 3043d0c785..e9e528c496 100644 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ b/frontend/appflowy_tauri/src-tauri/src/init.rs @@ -1,4 +1,5 @@ -use flowy_core::{AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME}; +use flowy_core::config::AppFlowyCoreConfig; +use flowy_core::{AppFlowyCore, DEFAULT_NAME}; pub fn init_flowy_core() -> AppFlowyCore { let config_json = include_str!("../tauri.conf.json"); diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index cd789f5fda..c2907a82de 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -262,6 +262,8 @@ "logoutPrompt": "Are you sure to logout?", "selfEncryptionLogoutPrompt": "Are you sure you want to log out? Please ensure you have copied the encryption secret", "syncSetting": "Sync Setting", + "cloudSetting": "Cloud Setting", + "cloudURL": "Server URL", "enableSync": "Enable sync", "enableEncrypt": "Encrypt data", "enableEncryptPrompt": "Activate encryption to secure your data with this secret. Store it safely; once enabled, it can't be turned off. If lost, your data becomes irretrievable. Click to copy", diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 7770891ec3..8b4d85577c 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -124,7 +124,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "reqwest", @@ -666,7 +666,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -1150,7 +1150,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.2", + "phf 0.8.0", "smallvec", ] @@ -1250,6 +1250,7 @@ dependencies = [ "protobuf", "serde", "serde_json", + "serde_repr", "tokio", "tracing", ] @@ -1276,7 +1277,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -2467,7 +2468,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "futures-util", @@ -2483,7 +2484,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", @@ -2844,7 +2845,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "reqwest", @@ -3657,7 +3658,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", + "phf_macros", "phf_shared 0.8.0", "proc-macro-hack", ] @@ -3677,7 +3678,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ - "phf_macros 0.11.2", "phf_shared 0.11.2", ] @@ -3745,19 +3745,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.31", -] - [[package]] name = "phf_shared" version = "0.8.0" @@ -3961,7 +3948,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" dependencies = [ "bytes", "heck 0.4.1", - "itertools 0.11.0", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -3982,7 +3969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.31", @@ -4321,7 +4308,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "bincode", @@ -4965,7 +4952,7 @@ dependencies = [ [[package]] name = "shared_entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" dependencies = [ "anyhow", "app-error", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 5de9fe026d..301baa6438 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -99,7 +99,7 @@ incremental = false # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "fe977fc8285addd5386e940738cdffbbda9eb44e" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b578c83cc912255e48dea9e33a203a069ce7d0c5" } # Please use the following script to update collab. # Working directory: frontend # diff --git a/frontend/rust-lib/dart-ffi/Cargo.toml b/frontend/rust-lib/dart-ffi/Cargo.toml index a719c3e54b..a758787bce 100644 --- a/frontend/rust-lib/dart-ffi/Cargo.toml +++ b/frontend/rust-lib/dart-ffi/Cargo.toml @@ -17,6 +17,7 @@ byteorder = { version = "1.4.3" } protobuf.workspace = true tokio = { workspace = true, features = ["full", "rt-multi-thread", "tracing"] } serde.workspace = true +serde_repr.workspace = true serde_json.workspace = true bytes.workspace = true crossbeam-utils = "0.8.15" diff --git a/frontend/rust-lib/dart-ffi/src/env_serde.rs b/frontend/rust-lib/dart-ffi/src/env_serde.rs index 2767193231..f9a7092664 100644 --- a/frontend/rust-lib/dart-ffi/src/env_serde.rs +++ b/frontend/rust-lib/dart-ffi/src/env_serde.rs @@ -1,21 +1,63 @@ use serde::Deserialize; +use serde_repr::Deserialize_repr; use flowy_server_config::af_cloud_config::AFCloudConfiguration; use flowy_server_config::supabase_config::SupabaseConfiguration; #[derive(Deserialize, Debug)] pub struct AppFlowyEnv { + cloud_type: CloudType, supabase_config: SupabaseConfiguration, appflowy_cloud_config: AFCloudConfiguration, } +const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE"; + +#[derive(Deserialize_repr, Debug, Clone)] +#[repr(u8)] +pub enum CloudType { + Local = 0, + Supabase = 1, + AppFlowyCloud = 2, +} + +impl CloudType { + fn write_env(&self) { + let s = self.clone() as u8; + std::env::set_var(CLOUT_TYPE_STR, s.to_string()); + } + + #[allow(dead_code)] + fn from_str(s: &str) -> Self { + match s { + "0" => CloudType::Local, + "1" => CloudType::Supabase, + "2" => CloudType::AppFlowyCloud, + _ => CloudType::Local, + } + } + + #[allow(dead_code)] + pub fn from_env() -> Self { + let cloud_type_str = std::env::var(CLOUT_TYPE_STR).unwrap_or_default(); + CloudType::from_str(&cloud_type_str) + } +} + impl AppFlowyEnv { /// Parse the environment variable from the frontend application. The frontend will /// pass the environment variable as a json string after launching. pub fn write_env_from(env_str: &str) { if let Ok(env) = serde_json::from_str::(env_str) { - env.supabase_config.write_env(); - env.appflowy_cloud_config.write_env(); + let _ = env.cloud_type.write_env(); + let is_valid = env.appflowy_cloud_config.write_env().is_ok(); + // Note on Configuration Priority: + // If both Supabase config and AppFlowy cloud config are provided in the '.env' file, + // the AppFlowy cloud config will be prioritized and the Supabase config ignored. + // Ensure only one of these configurations is active at any given time. + if !is_valid { + let _ = env.supabase_config.write_env(); + } } } } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index 3c488c6476..a3fbedefec 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -7,6 +7,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use tracing::{error, trace}; +use flowy_core::config::AppFlowyCoreConfig; use flowy_core::*; use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; use lib_dispatch::prelude::ToBytes; diff --git a/frontend/rust-lib/event-integration/src/lib.rs b/frontend/rust-lib/event-integration/src/lib.rs index 1a9d9cb7a1..a40f30486f 100644 --- a/frontend/rust-lib/event-integration/src/lib.rs +++ b/frontend/rust-lib/event-integration/src/lib.rs @@ -5,7 +5,8 @@ use std::sync::Arc; use nanoid::nanoid; use parking_lot::RwLock; -use flowy_core::{AppFlowyCore, AppFlowyCoreConfig}; +use flowy_core::config::AppFlowyCoreConfig; +use flowy_core::AppFlowyCore; use flowy_notification::register_notification_sender; use flowy_user::entities::AuthTypePB; diff --git a/frontend/rust-lib/event-integration/src/user_event.rs b/frontend/rust-lib/event-integration/src/user_event.rs index 8a180c76e3..f8ba67c8f3 100644 --- a/frontend/rust-lib/event-integration/src/user_event.rs +++ b/frontend/rust-lib/event-integration/src/user_event.rs @@ -13,8 +13,8 @@ use flowy_notification::entities::SubscribeObject; use flowy_notification::NotificationSender; use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID}; use flowy_user::entities::{ - AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB, UpdateCloudConfigPB, - UpdateUserProfilePayloadPB, UserCloudConfigPB, UserProfilePB, + AuthTypePB, CloudSettingPB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB, + UpdateCloudConfigPB, UpdateUserProfilePayloadPB, UserProfilePB, }; use flowy_user::errors::{FlowyError, FlowyResult}; use flowy_user::event_map::UserEvent::*; @@ -29,7 +29,7 @@ impl EventIntegrationTest { .event(GetCloudConfig) .async_send() .await - .parse::(); + .parse::(); let update = UpdateCloudConfigPB { enable_sync: None, enable_encrypt: Some(true), diff --git a/frontend/rust-lib/flowy-core/src/config.rs b/frontend/rust-lib/flowy-core/src/config.rs new file mode 100644 index 0000000000..d25d98e901 --- /dev/null +++ b/frontend/rust-lib/flowy-core/src/config.rs @@ -0,0 +1,86 @@ +use std::fmt; +use std::path::Path; + +use base64::Engine; +use tracing::{error, info}; + +use flowy_server_config::af_cloud_config::AFCloudConfiguration; +use flowy_server_config::supabase_config::SupabaseConfiguration; +use flowy_user::manager::URL_SAFE_ENGINE; + +use crate::integrate::log::create_log_filter; +use crate::integrate::util::copy_dir_recursive; + +#[derive(Clone)] +pub struct AppFlowyCoreConfig { + /// Different `AppFlowyCoreConfig` instance should have different name + pub(crate) name: String, + /// Panics if the `root` path is not existing + pub storage_path: String, + pub(crate) log_filter: String, + cloud_config: Option, +} + +impl fmt::Debug for AppFlowyCoreConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("AppFlowy Configuration"); + debug.field("storage_path", &self.storage_path); + if let Some(config) = &self.cloud_config { + debug.field("base_url", &config.base_url); + debug.field("ws_url", &config.ws_base_url); + } + debug.finish() + } +} + +fn migrate_local_version_data_folder(root: &str, url: &str) -> String { + // Isolate the user data folder by using the base url of AppFlowy cloud. This is to avoid + // the user data folder being shared by different AppFlowy cloud. + let server_base64 = URL_SAFE_ENGINE.encode(&url); + let storage_path = format!("{}_{}", root, server_base64); + + // Copy the user data folder from the root path to the isolated path + // The root path without any suffix is the created by the local version AppFlowy + if !Path::new(&storage_path).exists() && Path::new(root).exists() { + info!("Copy dir from {} to {}", root, storage_path); + let src = Path::new(root); + match copy_dir_recursive(&src, Path::new(&storage_path)) { + Ok(_) => storage_path, + Err(err) => { + // when the copy dir failed, use the root path as the storage path + error!("Copy dir failed: {}", err); + root.to_string() + }, + } + } else { + storage_path + } +} + +impl AppFlowyCoreConfig { + pub fn new(root: &str, name: String) -> Self { + let cloud_config = AFCloudConfiguration::from_env().ok(); + let storage_path = match &cloud_config { + None => { + let supabase_config = SupabaseConfiguration::from_env().ok(); + match &supabase_config { + None => root.to_string(), + Some(config) => migrate_local_version_data_folder(root, &config.url), + } + }, + Some(config) => migrate_local_version_data_folder(root, &config.base_url), + }; + + AppFlowyCoreConfig { + name, + storage_path, + log_filter: create_log_filter("info".to_owned(), vec![]), + cloud_config, + } + } + + pub fn log_filter(mut self, level: &str, with_crates: Vec) -> Self { + self.log_filter = create_log_filter(level.to_owned(), with_crates); + self + } +} diff --git a/frontend/rust-lib/flowy-core/src/integrate/server.rs b/frontend/rust-lib/flowy-core/src/integrate/server.rs index 75acca4a18..e1f78e8c34 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/server.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/server.rs @@ -124,13 +124,7 @@ impl ServerProvider { Ok::, FlowyError>(server) }, ServerType::Supabase => { - let config = match SupabaseConfiguration::from_env() { - Ok(config) => config, - Err(e) => { - *self.enable_sync.write() = false; - return Err(e); - }, - }; + let config = SupabaseConfiguration::from_env()?; let uid = self.uid.clone(); tracing::trace!("🔑Supabase config: {:?}", config); let encryption = Arc::downgrade(&*self.encryption.read()); diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index 2d2acd4575..f5c3dd512a 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -21,6 +21,8 @@ use flowy_error::FlowyError; use flowy_folder_deps::cloud::{ FolderCloudService, FolderData, FolderSnapshot, Workspace, WorkspaceRecord, }; +use flowy_server_config::af_cloud_config::AFCloudConfiguration; +use flowy_server_config::supabase_config::SupabaseConfiguration; use flowy_storage::{FileStorageService, StorageObject}; use flowy_user::event_map::UserCloudServiceProvider; use flowy_user_deps::cloud::UserCloudService; @@ -68,13 +70,10 @@ impl UserCloudServiceProvider for ServerProvider { } fn set_enable_sync(&self, uid: i64, enable_sync: bool) { - match self.get_server(&self.get_server_type()) { - Ok(server) => { - server.set_enable_sync(uid, enable_sync); - *self.enable_sync.write() = enable_sync; - *self.uid.write() = Some(uid); - }, - Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e), + if let Ok(server) = self.get_server(&self.get_server_type()) { + server.set_enable_sync(uid, enable_sync); + *self.enable_sync.write() = enable_sync; + *self.uid.write() = Some(uid); } } @@ -136,8 +135,16 @@ impl UserCloudServiceProvider for ServerProvider { Ok(user_service) } - fn service_name(&self) -> String { - self.get_server_type().to_string() + fn service_url(&self) -> String { + match self.get_server_type() { + ServerType::Local => "".to_string(), + ServerType::AFCloud => AFCloudConfiguration::from_env() + .map(|config| config.base_url) + .unwrap_or_default(), + ServerType::Supabase => SupabaseConfiguration::from_env() + .map(|config| config.url) + .unwrap_or_default(), + } } } diff --git a/frontend/rust-lib/flowy-core/src/integrate/user.rs b/frontend/rust-lib/flowy-core/src/integrate/user.rs index 6375aac0f0..17dd4cb667 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/user.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/user.rs @@ -47,7 +47,7 @@ impl UserStatusCallback for UserStatusCallbackImpl { self .server_provider .set_enable_sync(user_id, cloud_config.enable_sync); - if cloud_config.enable_encrypt() { + if cloud_config.enable_encrypt { self .server_provider .set_encrypt_secret(cloud_config.encrypt_secret.clone()); diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index 0a422a1a6d..9a4ff813dc 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -1,11 +1,9 @@ #![allow(unused_doc_comments)] -use std::path::Path; +use std::sync::Arc; use std::sync::Weak; use std::time::Duration; -use std::{fmt, sync::Arc}; -use base64::Engine; use tokio::sync::RwLock; use tracing::{debug, error, event, info, instrument}; @@ -13,24 +11,24 @@ use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabSource}; use flowy_database2::DatabaseManager; use flowy_document2::manager::DocumentManager; use flowy_folder2::manager::FolderManager; -use flowy_server_config::af_cloud_config::AFCloudConfiguration; use flowy_sqlite::kv::StorePreferences; use flowy_storage::FileStorageService; use flowy_task::{TaskDispatcher, TaskRunner}; use flowy_user::event_map::UserCloudServiceProvider; -use flowy_user::manager::{UserManager, UserSessionConfig, URL_SAFE_ENGINE}; +use flowy_user::manager::{UserManager, UserSessionConfig}; use lib_dispatch::prelude::*; use lib_dispatch::runtime::AFPluginRuntime; use module::make_plugins; pub use module::*; +use crate::config::AppFlowyCoreConfig; use crate::deps_resolve::*; use crate::integrate::collab_interact::CollabInteractImpl; -use crate::integrate::log::{create_log_filter, init_log}; +use crate::integrate::log::init_log; use crate::integrate::server::{current_server_type, ServerProvider, ServerType}; use crate::integrate::user::UserStatusCallbackImpl; -use crate::integrate::util::copy_dir_recursive; +pub mod config; mod deps_resolve; mod integrate; pub mod module; @@ -39,72 +37,6 @@ pub mod module; /// Don't change this. pub const DEFAULT_NAME: &str = "appflowy"; -#[derive(Clone)] -pub struct AppFlowyCoreConfig { - /// Different `AppFlowyCoreConfig` instance should have different name - name: String, - /// Panics if the `root` path is not existing - pub storage_path: String, - log_filter: String, - cloud_config: Option, -} - -impl fmt::Debug for AppFlowyCoreConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut debug = f.debug_struct("AppFlowy Configuration"); - debug.field("storage_path", &self.storage_path); - if let Some(config) = &self.cloud_config { - debug.field("base_url", &config.base_url); - debug.field("ws_url", &config.ws_base_url); - } - debug.finish() - } -} - -impl AppFlowyCoreConfig { - pub fn new(root: &str, name: String) -> Self { - let cloud_config = AFCloudConfiguration::from_env().ok(); - let storage_path = match &cloud_config { - None => root.to_string(), - Some(config) => { - // Isolate the user data folder by the base url of AppFlowy cloud. This is to avoid - // the user data folder being shared by different AppFlowy cloud. - let server_base64 = URL_SAFE_ENGINE.encode(&config.base_url); - let storage_path = format!("{}_{}", root, server_base64); - - // Copy the user data folder from the root path to the isolated path - // The root path only exists when using the local version of appflowy - if !Path::new(&storage_path).exists() && Path::new(root).exists() { - info!("Copy dir from {} to {}", root, storage_path); - let src = Path::new(root); - match copy_dir_recursive(&src, Path::new(&storage_path)) { - Ok(_) => storage_path, - Err(err) => { - // when the copy dir failed, use the root path as the storage path - error!("Copy dir failed: {}", err); - root.to_string() - }, - } - } else { - storage_path - } - }, - }; - - AppFlowyCoreConfig { - name, - storage_path, - log_filter: create_log_filter("info".to_owned(), vec![]), - cloud_config: AFCloudConfiguration::from_env().ok(), - } - } - - pub fn log_filter(mut self, level: &str, with_crates: Vec) -> Self { - self.log_filter = create_log_filter(level.to_owned(), with_crates); - self - } -} - #[derive(Clone)] pub struct AppFlowyCore { #[allow(dead_code)] diff --git a/frontend/rust-lib/flowy-folder2/src/manager.rs b/frontend/rust-lib/flowy-folder2/src/manager.rs index ea4c9e7a5f..287323373a 100644 --- a/frontend/rust-lib/flowy-folder2/src/manager.rs +++ b/frontend/rust-lib/flowy-folder2/src/manager.rs @@ -159,7 +159,7 @@ impl FolderManager { } => { let is_exist = is_exist_in_local_disk(&self.user, &workspace_id).unwrap_or(false); if is_exist { - event!(Level::INFO, "Restore folder from local disk"); + event!(Level::INFO, "Init folder from local disk"); let collab = self .collab_for_folder(uid, &workspace_id, collab_db, vec![]) .await?; diff --git a/frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs b/frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs index 581976db99..fb735a9131 100644 --- a/frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs +++ b/frontend/rust-lib/flowy-server-config/src/af_cloud_config.rs @@ -2,11 +2,11 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use flowy_error::{ErrorCode, FlowyError}; +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -pub const APPFLOWY_CLOUD_BASE_URL: &str = "APPFLOWY_CLOUD_BASE_URL"; -pub const APPFLOWY_CLOUD_WS_BASE_URL: &str = "APPFLOWY_CLOUD_WS_BASE_URL"; -pub const APPFLOWY_CLOUD_GOTRUE_URL: &str = "APPFLOWY_CLOUD_GOTRUE_URL"; +pub const APPFLOWY_CLOUD_BASE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL"; +pub const APPFLOWY_CLOUD_WS_BASE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL"; +pub const APPFLOWY_CLOUD_GOTRUE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL"; #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct AFCloudConfiguration { @@ -60,10 +60,25 @@ impl AFCloudConfiguration { }) } + pub fn validate(&self) -> Result<(), FlowyError> { + if self.base_url.is_empty() || self.ws_base_url.is_empty() || self.gotrue_url.is_empty() { + return Err(FlowyError::new( + ErrorCode::InvalidAuthConfig, + format!( + "Invalid APPFLOWY_CLOUD_BASE_URL: {}, APPFLOWY_CLOUD_WS_BASE_URL: {}, APPFLOWY_CLOUD_GOTRUE_URL: {}", + self.base_url, self.ws_base_url, self.gotrue_url, + )), + ); + } + Ok(()) + } + /// Write the configuration to the environment variables. - pub fn write_env(&self) { + pub fn write_env(&self) -> FlowyResult<()> { + self.validate()?; std::env::set_var(APPFLOWY_CLOUD_BASE_URL, &self.base_url); std::env::set_var(APPFLOWY_CLOUD_WS_BASE_URL, &self.ws_base_url); std::env::set_var(APPFLOWY_CLOUD_GOTRUE_URL, &self.gotrue_url); + Ok(()) } } diff --git a/frontend/rust-lib/flowy-server-config/src/supabase_config.rs b/frontend/rust-lib/flowy-server-config/src/supabase_config.rs index 3e11eb3e7a..6e2a08170b 100644 --- a/frontend/rust-lib/flowy-server-config/src/supabase_config.rs +++ b/frontend/rust-lib/flowy-server-config/src/supabase_config.rs @@ -1,15 +1,9 @@ use serde::{Deserialize, Serialize}; -use flowy_error::{ErrorCode, FlowyError}; +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -pub const ENABLE_SUPABASE_SYNC: &str = "ENABLE_SUPABASE_SYNC"; -pub const SUPABASE_URL: &str = "SUPABASE_URL"; -pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY"; - -pub const SUPABASE_DB: &str = "SUPABASE_DB"; -pub const SUPABASE_DB_USER: &str = "SUPABASE_DB_USER"; -pub const SUPABASE_DB_PASSWORD: &str = "SUPABASE_DB_PASSWORD"; -pub const SUPABASE_DB_PORT: &str = "SUPABASE_DB_PORT"; +pub const SUPABASE_URL: &str = "APPFLOWY_CLOUD_ENV_SUPABASE_URL"; +pub const SUPABASE_ANON_KEY: &str = "APPFLOWY_CLOUD_ENV_SUPABASE_ANON_KEY"; /// The configuration for the postgres database. It supports deserializing from the json string that /// passed from the frontend application. [AppFlowyEnv::parser] @@ -39,9 +33,21 @@ impl SupabaseConfiguration { Ok(Self { url, anon_key }) } + pub fn validate(&self) -> Result<(), FlowyError> { + if self.url.is_empty() || self.anon_key.is_empty() { + return Err(FlowyError::new( + ErrorCode::InvalidAuthConfig, + "Missing SUPABASE_URL or SUPABASE_ANON_KEY", + )); + } + Ok(()) + } + /// Write the configuration to the environment variables. - pub fn write_env(&self) { + pub fn write_env(&self) -> FlowyResult<()> { + self.validate()?; std::env::set_var(SUPABASE_URL, &self.url); std::env::set_var(SUPABASE_ANON_KEY, &self.anon_key); + Ok(()) } } diff --git a/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs b/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs index 423ad5db92..bcbd2e2e16 100644 --- a/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs +++ b/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs @@ -16,7 +16,6 @@ const DB_NAME: &str = "cache.db"; pub struct StorePreferences { database: Option, } - impl StorePreferences { #[tracing::instrument(level = "trace", err)] pub fn new(root: &str) -> Result { @@ -86,7 +85,6 @@ impl StorePreferences { .and_then(|v| serde_json::from_str(&v).ok()) } - #[allow(dead_code)] pub fn remove(&self, key: &str) { if let Some(conn) = self .database diff --git a/frontend/rust-lib/flowy-user-deps/src/cloud.rs b/frontend/rust-lib/flowy-user-deps/src/cloud.rs index bfa800acdd..2910ce6c8f 100644 --- a/frontend/rust-lib/flowy-user-deps/src/cloud.rs +++ b/frontend/rust-lib/flowy-user-deps/src/cloud.rs @@ -20,7 +20,7 @@ use crate::entities::{ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserCloudConfig { pub enable_sync: bool, - enable_encrypt: bool, + pub enable_encrypt: bool, // The secret used to encrypt the user's data pub encrypt_secret: String, } @@ -34,10 +34,6 @@ impl UserCloudConfig { } } - pub fn enable_encrypt(&self) -> bool { - self.enable_encrypt - } - pub fn with_enable_encrypt(mut self, enable_encrypt: bool) -> Self { self.enable_encrypt = enable_encrypt; // When the enable_encrypt is true, the encrypt_secret should not be empty diff --git a/frontend/rust-lib/flowy-user/src/entities/auth.rs b/frontend/rust-lib/flowy-user/src/entities/auth.rs index 311eb9747f..c80dad8c62 100644 --- a/frontend/rust-lib/flowy-user/src/entities/auth.rs +++ b/frontend/rust-lib/flowy-user/src/entities/auth.rs @@ -176,8 +176,8 @@ pub struct OauthProviderDataPB { #[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)] pub enum AuthTypePB { Local = 0, - AFCloud = 1, - Supabase = 2, + Supabase = 1, + AFCloud = 2, } impl Default for AuthTypePB { diff --git a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs index 3e4b59cde3..810bcba00b 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_user_deps::cloud::UserCloudConfig; use crate::entities::EncryptionTypePB; @@ -137,15 +136,18 @@ impl std::default::Default for AppearanceSettingsPB { } #[derive(Default, ProtoBuf)] -pub struct UserCloudConfigPB { +pub struct CloudSettingPB { #[pb(index = 1)] - enable_sync: bool, + pub(crate) enable_sync: bool, #[pb(index = 2)] - enable_encrypt: bool, + pub(crate) enable_encrypt: bool, #[pb(index = 3)] pub encrypt_secret: String, + + #[pb(index = 4)] + pub server_url: String, } #[derive(Default, ProtoBuf)] @@ -178,16 +180,6 @@ pub struct UserEncryptionConfigurationPB { pub require_secret: bool, } -impl From for UserCloudConfigPB { - fn from(value: UserCloudConfig) -> Self { - Self { - enable_sync: value.enable_sync, - enable_encrypt: value.enable_encrypt(), - encrypt_secret: value.encrypt_secret, - } - } -} - #[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)] pub enum NetworkTypePB { #[default] diff --git a/frontend/rust-lib/flowy-user/src/event_handler.rs b/frontend/rust-lib/flowy-user/src/event_handler.rs index c9bc5fb10d..5611f8b2b7 100644 --- a/frontend/rust-lib/flowy-user/src/event_handler.rs +++ b/frontend/rust-lib/flowy-user/src/event_handler.rs @@ -385,7 +385,6 @@ pub async fn set_cloud_config_handler( manager .set_encrypt_secret(session.user_id, encrypt_secret, encryption_type.clone()) .await?; - save_cloud_config(session.user_id, &store_preferences, config.clone())?; let params = UpdateUserProfileParams::new(session.user_id).with_encryption_type(encryption_type); @@ -393,28 +392,40 @@ pub async fn set_cloud_config_handler( } } - let config_pb = UserCloudConfigPB::from(config); + save_cloud_config(session.user_id, &store_preferences, config.clone())?; + + let payload = CloudSettingPB { + enable_sync: config.enable_sync, + enable_encrypt: config.enable_encrypt, + encrypt_secret: config.encrypt_secret, + server_url: manager.cloud_services.service_url(), + }; + send_notification( &session.user_id.to_string(), UserNotification::DidUpdateCloudConfig, ) - .payload(config_pb) + .payload(payload) .send(); Ok(()) } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "info", skip_all, err)] pub async fn get_cloud_config_handler( manager: AFPluginState>, store_preferences: AFPluginState>, -) -> DataResult { +) -> DataResult { let manager = upgrade_manager(manager)?; let session = manager.get_session()?; - let store_preferences = upgrade_store_preferences(store_preferences)?; // Generate the default config if the config is not exist let config = get_or_create_cloud_config(session.user_id, &store_preferences); - data_result_ok(config.into()) + data_result_ok(CloudSettingPB { + enable_sync: config.enable_sync, + enable_encrypt: config.enable_encrypt, + encrypt_secret: config.encrypt_secret, + server_url: manager.cloud_services.service_url(), + }) } #[tracing::instrument(level = "debug", skip(manager), err)] diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 88a8decd29..261f3ea55b 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -119,7 +119,7 @@ pub enum UserEvent { #[event(input = "UpdateCloudConfigPB")] SetCloudConfig = 13, - #[event(output = "UserCloudConfigPB")] + #[event(output = "CloudSettingPB")] GetCloudConfig = 14, #[event(input = "UserSecretPB")] @@ -248,7 +248,7 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static { fn get_authenticator(&self) -> Authenticator; fn set_device_id(&self, device_id: &str); fn get_user_service(&self) -> Result, FlowyError>; - fn service_name(&self) -> String; + fn service_url(&self) -> String; } impl UserCloudServiceProvider for Arc @@ -283,8 +283,8 @@ where (**self).get_user_service() } - fn service_name(&self) -> String { - (**self).service_name() + fn service_url(&self) -> String { + (**self).service_url() } }