diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index f3b14d14d4..c974e116a8 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -239,14 +239,10 @@ jobs: working-directory: AppFlowy-Cloud run: | # log level - cp dev.env .env + cp deploy.env .env sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env - sed -i 's/GOTRUE_SMTP_USER=.*/GOTRUE_SMTP_USER=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_USER }}/' .env - sed -i 's/GOTRUE_SMTP_PASS=.*/GOTRUE_SMTP_PASS=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_PASS }}/' .env - sed -i 's/GOTRUE_SMTP_ADMIN_EMAIL=.*/GOTRUE_SMTP_ADMIN_EMAIL=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_ADMIN_EMAIL }}/' .env sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env - cat .env - name: Run Docker-Compose working-directory: AppFlowy-Cloud @@ -254,7 +250,6 @@ jobs: docker compose down -v --remove-orphans docker pull appflowyinc/appflowy_cloud:latest docker compose up -d - docker ps -a - name: Checkout source code uses: actions/checkout@v2 diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 074c67ebad..3416f14607 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -77,22 +77,26 @@ jobs: working-directory: AppFlowy-Cloud run: | # log level - cp dev.env .env + cp deploy.env .env sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env - sed -i 's/GOTRUE_SMTP_USER=.*/GOTRUE_SMTP_USER=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_USER }}/' .env - sed -i 's/GOTRUE_SMTP_PASS=.*/GOTRUE_SMTP_PASS=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_PASS }}/' .env - sed -i 's/GOTRUE_SMTP_ADMIN_EMAIL=.*/GOTRUE_SMTP_ADMIN_EMAIL=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_ADMIN_EMAIL }}/' .env - sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env - name: Run Docker-Compose working-directory: AppFlowy-Cloud run: | + docker pull appflowyinc/appflowy_cloud:latest docker compose up -d - name: Run rust-lib tests working-directory: frontend/rust-lib - run: RUST_LOG=info RUST_BACKTRACE=1 cargo test --no-default-features --features="rev-sqlite" + env: + RUST_LOG: info + RUST_BACKTRACE: 1 + af_cloud_test_base_url: http://localhost + af_cloud_test_ws_url: ws://localhost/ws + af_cloud_test_gotrue_url: http://localhost/gotrue + run: cargo test --no-default-features --features="rev-sqlite" -- --nocapture + - name: rustfmt rust-lib run: cargo fmt --all -- --check diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart index c44db06eb5..e0d70533ff 100644 --- a/frontend/appflowy_flutter/integration_test/util/base.dart +++ b/frontend/appflowy_flutter/integration_test/util/base.dart @@ -59,14 +59,12 @@ extension AppFlowyTestBase on WidgetTester { break; case AuthenticatorType.supabase: break; - case AuthenticatorType.appflowyCloud: - rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com"; - rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password"; - break; case AuthenticatorType.appflowyCloudSelfHost: rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com"; rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password"; break; + default: + throw Exception("not supported"); } } return rustEnvs; @@ -86,20 +84,14 @@ extension AppFlowyTestBase on WidgetTester { () => SupabaseMockAuthService(), ); break; - case AuthenticatorType.appflowyCloud: - await useAppFlowyCloud(); - getIt.unregister(); - getIt.registerFactory( - () => AppFlowyCloudMockAuthService(email: email), - ); - break; case AuthenticatorType.appflowyCloudSelfHost: await useAppFlowyCloud(); getIt.unregister(); getIt.registerFactory( () => AppFlowyCloudMockAuthService(email: email), ); - break; + default: + throw Exception("not supported"); } } }, diff --git a/frontend/appflowy_flutter/lib/env/cloud_env.dart b/frontend/appflowy_flutter/lib/env/cloud_env.dart index 252349c02f..b643bb3455 100644 --- a/frontend/appflowy_flutter/lib/env/cloud_env.dart +++ b/frontend/appflowy_flutter/lib/env/cloud_env.dart @@ -27,11 +27,13 @@ Future setAuthenticatorType(AuthenticatorType ty) async { break; case AuthenticatorType.appflowyCloud: await getIt().set(KVKeys.kCloudType, 2.toString()); - await setAppFlowyCloudUrl(const Some(kAppflowyCloudUrl)); break; case AuthenticatorType.appflowyCloudSelfHost: await getIt().set(KVKeys.kCloudType, 3.toString()); break; + case AuthenticatorType.appflowyCloudDevelop: + await getIt().set(KVKeys.kCloudType, 4.toString()); + break; } } @@ -64,6 +66,8 @@ Future getAuthenticatorType() async { return AuthenticatorType.appflowyCloud; case "3": return AuthenticatorType.appflowyCloudSelfHost; + case "4": + return AuthenticatorType.appflowyCloudDevelop; default: await setAuthenticatorType(AuthenticatorType.appflowyCloud); return AuthenticatorType.appflowyCloud; @@ -87,8 +91,7 @@ bool get isAuthEnabled { return env.supabaseConfig.isValid; } - if (env.authenticatorType == AuthenticatorType.appflowyCloudSelfHost || - env.authenticatorType == AuthenticatorType.appflowyCloud) { + if (env.authenticatorType.isAppFlowyCloudEnabled) { return env.appflowyCloudConfig.isValid; } @@ -117,12 +120,15 @@ enum AuthenticatorType { local, supabase, appflowyCloud, - appflowyCloudSelfHost; + appflowyCloudSelfHost, + // The 'appflowyCloudDevelop' type is used for develop purposes only. + appflowyCloudDevelop; bool get isLocal => this == AuthenticatorType.local; bool get isAppFlowyCloudEnabled => this == AuthenticatorType.appflowyCloudSelfHost || + this == AuthenticatorType.appflowyCloudDevelop || this == AuthenticatorType.appflowyCloud; bool get isSupabaseEnabled => this == AuthenticatorType.supabase; @@ -137,6 +143,8 @@ enum AuthenticatorType { return 2; case AuthenticatorType.appflowyCloudSelfHost: return 3; + case AuthenticatorType.appflowyCloudDevelop: + return 4; } } @@ -150,6 +158,8 @@ enum AuthenticatorType { return AuthenticatorType.appflowyCloud; case 3: return AuthenticatorType.appflowyCloudSelfHost; + case 4: + return AuthenticatorType.appflowyCloudDevelop; default: return AuthenticatorType.local; } @@ -185,26 +195,25 @@ class AppFlowyCloudSharedEnv { // If [Env.enableCustomCloud] is true, then use the custom cloud configuration. if (Env.enableCustomCloud) { // Use the custom cloud configuration. - var cloudType = await getAuthenticatorType(); + var authenticatorType = await getAuthenticatorType(); + + final appflowyCloudConfig = authenticatorType.isLocal + ? AppFlowyCloudConfiguration.defaultConfig() + : await getAppFlowyCloudConfig(authenticatorType); + final supabaseCloudConfig = authenticatorType.isLocal + ? SupabaseConfiguration.defaultConfig() + : await getSupabaseCloudConfig(); // In the backend, the value '2' represents the use of AppFlowy Cloud. However, in the frontend, // we distinguish between [AuthenticatorType.appflowyCloudSelfHost] and [AuthenticatorType.appflowyCloud]. // When the cloud type is [AuthenticatorType.appflowyCloudSelfHost] in the frontend, it should be // converted to [AuthenticatorType.appflowyCloud] to align with the backend representation, // where both types are indicated by the value '2'. - if (cloudType == AuthenticatorType.appflowyCloudSelfHost) { - cloudType = AuthenticatorType.appflowyCloud; + if (authenticatorType.isAppFlowyCloudEnabled) { + authenticatorType = AuthenticatorType.appflowyCloud; } - - final appflowyCloudConfig = cloudType.isLocal - ? AppFlowyCloudConfiguration.defaultConfig() - : await getAppFlowyCloudConfig(); - final supabaseCloudConfig = cloudType.isLocal - ? SupabaseConfiguration.defaultConfig() - : await getSupabaseCloudConfig(); - return AppFlowyCloudSharedEnv( - authenticatorType: cloudType, + authenticatorType: authenticatorType, appflowyCloudConfig: appflowyCloudConfig, supabaseConfig: supabaseCloudConfig, ); @@ -235,13 +244,16 @@ class AppFlowyCloudSharedEnv { Future configurationFromUri( Uri baseUri, String baseUrl, + AuthenticatorType authenticatorType, ) async { -// When the host is set to 'localhost', the application will utilize the local configuration. This setup assumes that 'localhost' does not employ a reverse proxy, therefore default port settings are used. - if (baseUri.host == "localhost") { + // In development mode, the app is configured to access the AppFlowy cloud server directly through specific ports. + // This setup bypasses the need for Nginx, meaning that the AppFlowy cloud should be running without an Nginx server + // in the development environment. + if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) { return AppFlowyCloudConfiguration( base_url: "$baseUrl:8000", ws_base_url: "ws://${baseUri.host}:8000/ws", - gotrue_url: "$baseUrl:9998", + gotrue_url: "$baseUrl:9999", ); } else { return AppFlowyCloudConfiguration( @@ -252,12 +264,14 @@ Future configurationFromUri( } } -Future getAppFlowyCloudConfig() async { +Future getAppFlowyCloudConfig( + AuthenticatorType authenticatorType, +) async { final baseURL = await getAppFlowyCloudUrl(); try { final uri = Uri.parse(baseURL); - return await configurationFromUri(uri, baseURL); + return await configurationFromUri(uri, baseURL, authenticatorType); } catch (e) { Log.error("Failed to parse AppFlowy Cloud URL: $e"); return AppFlowyCloudConfiguration.defaultConfig(); diff --git a/frontend/appflowy_flutter/lib/env/env.dart b/frontend/appflowy_flutter/lib/env/env.dart index 1e94e20ec4..a0786e4d5b 100644 --- a/frontend/appflowy_flutter/lib/env/env.dart +++ b/frontend/appflowy_flutter/lib/env/env.dart @@ -10,7 +10,8 @@ abstract class Env { static bool get enableCustomCloud { return Env.authenticatorType == AuthenticatorType.appflowyCloudSelfHost.value || - Env.authenticatorType == AuthenticatorType.appflowyCloud.value && + Env.authenticatorType == AuthenticatorType.appflowyCloud.value || + Env.authenticatorType == AuthenticatorType.appflowyCloudDevelop.value && _Env.afCloudUrl.isEmpty; } diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index 5d15faae73..9936fdb3cf 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -142,6 +142,7 @@ void _resolveUserDeps(GetIt getIt, IntegrationMode mode) { break; case AuthenticatorType.appflowyCloud: case AuthenticatorType.appflowyCloudSelfHost: + case AuthenticatorType.appflowyCloudDevelop: getIt.registerFactory(() => AppFlowyCloudAuthService()); break; } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart index 52729c0779..0fc308fae3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart @@ -19,8 +19,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; class AppFlowyCloudViewSetting extends StatelessWidget { + final String serverURL; + final AuthenticatorType authenticatorType; final VoidCallback restartAppFlowy; - const AppFlowyCloudViewSetting({required this.restartAppFlowy, super.key}); + const AppFlowyCloudViewSetting({ + required this.restartAppFlowy, + super.key, + this.serverURL = kAppflowyCloudUrl, + this.authenticatorType = AuthenticatorType.appflowyCloud, + }); @override Widget build(BuildContext context) { @@ -58,11 +65,8 @@ class AppFlowyCloudViewSetting extends StatelessWidget { NavigatorAlertDialog( title: LocaleKeys.settings_menu_restartAppTip.tr(), confirm: () async { - await setAppFlowyCloudUrl( - const Some(kAppflowyCloudUrl), - ); - - await setAuthenticatorType(AuthenticatorType.appflowyCloud); + await setAppFlowyCloudUrl(Some(serverURL)); + await setAuthenticatorType(authenticatorType); restartAppFlowy(); }, ).show(context); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart index 831217542b..27d9bd5349 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart @@ -2,6 +2,7 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/env.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/workspace/application/settings/cloud_setting_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_local_cloud.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; @@ -86,6 +87,12 @@ class SettingCloud extends StatelessWidget { return CustomAppFlowyCloudView( restartAppFlowy: didResetServerUrl, ); + case AuthenticatorType.appflowyCloudDevelop: + return AppFlowyCloudViewSetting( + serverURL: "http://localhost", + authenticatorType: AuthenticatorType.appflowyCloudDevelop, + restartAppFlowy: didResetServerUrl, + ); } } } @@ -111,16 +118,26 @@ class CloudTypeSwitcher extends StatelessWidget { onPressed: () {}, ), popupBuilder: (BuildContext context) { + final isDevelopMode = integrationMode().isDevelop; + // Only show the appflowyCloudDevelop in develop mode + final values = AuthenticatorType.values + .where( + (element) => + isDevelopMode || + element != AuthenticatorType.appflowyCloudDevelop, + ) + .toList(); + return ListView.builder( shrinkWrap: true, itemBuilder: (context, index) { return CloudTypeItem( - cloudType: AuthenticatorType.values[index], + cloudType: values[index], currentCloudtype: cloudType, onSelected: onSelected, ); }, - itemCount: AuthenticatorType.values.length, + itemCount: values.length, ); }, ); @@ -171,5 +188,7 @@ String titleFromCloudType(AuthenticatorType cloudType) { return LocaleKeys.settings_menu_cloudAppFlowy.tr(); case AuthenticatorType.appflowyCloudSelfHost: return LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr(); + case AuthenticatorType.appflowyCloudDevelop: + return "AppFlowyCloud Develop"; } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart index e2dc2e582d..e461d5c695 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart @@ -49,13 +49,18 @@ class _ImportAppFlowyDataState extends State { }, child: BlocBuilder( builder: (context, state) { - return const Column( - children: [ - ImportAppFlowyDataButton(), - VSpace(6), - AppFlowyDataImportTip(), - ], - ); + final List children = [ + const ImportAppFlowyDataButton(), + const VSpace(6), + ]; + + if (state.loadingState.isLoading()) { + children.add(const AppFlowyDataImportingTip()); + } else { + children.add(const AppFlowyDataImportTip()); + } + + return Column(children: children); }, ), ), @@ -127,6 +132,7 @@ class _ImportAppFlowyDataButtonState extends State { SizedBox( height: 40, child: FlowyButton( + disable: state.loadingState.isLoading(), text: FlowyText(LocaleKeys.settings_menu_importAppFlowyData.tr()), onTap: () async { @@ -150,3 +156,24 @@ class _ImportAppFlowyDataButtonState extends State { ); } } + +class AppFlowyDataImportingTip extends StatelessWidget { + const AppFlowyDataImportingTip({super.key}); + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: 0.6, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.settings_menu_importingAppFlowyDataTip.tr(), + style: Theme.of(context).textTheme.bodySmall!, + ), + ], + ), + ), + ); + } +} diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 3a1fab8cd8..d484de7dae 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -139,7 +139,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -685,7 +685,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -1261,7 +1261,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -2496,7 +2496,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "futures-util", @@ -2512,7 +2512,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -2931,7 +2931,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "reqwest", @@ -4590,7 +4590,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -4612,7 +4612,7 @@ dependencies = [ [[package]] name = "realtime-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -5260,7 +5260,7 @@ dependencies = [ [[package]] name = "shared_entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -7064,7 +7064,7 @@ dependencies = [ [[package]] name = "workspace-template" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "async-trait", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index c2b9be1ad7..3ed0998b77 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -57,7 +57,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 = "79702b1ced7b4497afef4e7d42e9fed1084e33e5" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5a353856afbcb29ee357c4f2bda183a94d4f811d" } # Please use the following script to update collab. # Working directory: frontend # diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 2c9b3f5180..95f17abd00 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -304,6 +304,7 @@ "openHistoricalUser": "Click to open the anonymous account", "customPathPrompt": "Storing the AppFlowy data folder in a cloud-synced folder such as Google Drive can pose risks. If the database within this folder is accessed or modified from multiple locations at the same time, it may result in synchronization conflicts and potential data corruption", "importAppFlowyData": "Import Data from External AppFlowy Folder", + "importingAppFlowyDataTip": "Data import is in progress. Please do not close the app", "importAppFlowyDataDescription": "Copy data from an external AppFlowy data folder and import it into the current AppFlowy data folder", "importSuccess": "Successfully imported the AppFlowy data folder", "importFailed": "Importing the AppFlowy data folder failed", diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 4fe53ac8eb..e2659d4b9c 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -140,7 +140,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -658,7 +658,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -1194,7 +1194,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "futures-util", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -2705,7 +2705,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "reqwest", @@ -4076,7 +4076,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -4098,7 +4098,7 @@ dependencies = [ [[package]] name = "realtime-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "bincode", @@ -4677,7 +4677,7 @@ dependencies = [ [[package]] name = "shared_entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "app-error", @@ -5996,7 +5996,7 @@ dependencies = [ [[package]] name = "workspace-template" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d" dependencies = [ "anyhow", "async-trait", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 977ba1dae5..f475830aaa 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -105,7 +105,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 = "79702b1ced7b4497afef4e7d42e9fed1084e33e5" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5a353856afbcb29ee357c4f2bda183a94d4f811d" } # Please use the following script to update collab. # Working directory: frontend # diff --git a/frontend/rust-lib/event-integration/src/user_event.rs b/frontend/rust-lib/event-integration/src/user_event.rs index 9b2c3730ab..cfc5f1b1bf 100644 --- a/frontend/rust-lib/event-integration/src/user_event.rs +++ b/frontend/rust-lib/event-integration/src/user_event.rs @@ -326,12 +326,26 @@ pub struct SignUpContext { pub async fn user_localhost_af_cloud() { AuthenticatorType::AppFlowyCloud.write_env(); + let base_url = + std::env::var("af_cloud_test_base_url").unwrap_or("http://localhost:8000".to_string()); + let ws_base_url = + std::env::var("af_cloud_test_ws_url").unwrap_or("ws://localhost:8000/ws".to_string()); + let gotrue_url = + std::env::var("af_cloud_test_gotrue_url").unwrap_or("http://localhost:9999".to_string()); AFCloudConfiguration { - base_url: "http://localhost:8000".to_string(), - ws_base_url: "ws://localhost:8000/ws".to_string(), - gotrue_url: "http://localhost:9998".to_string(), + base_url, + ws_base_url, + gotrue_url, } .write_env(); std::env::set_var("GOTRUE_ADMIN_EMAIL", "admin@example.com"); std::env::set_var("GOTRUE_ADMIN_PASSWORD", "password"); } + +#[allow(dead_code)] +pub async fn user_localhost_af_cloud_with_nginx() { + std::env::set_var("af_cloud_test_base_url", "http://localhost"); + std::env::set_var("af_cloud_test_ws_url", "ws://localhost/ws"); + std::env::set_var("af_cloud_test_gotrue_url", "http://localhost/gotrue"); + user_localhost_af_cloud().await +} diff --git a/frontend/rust-lib/event-integration/tests/user/af_cloud_test/auth_test.rs b/frontend/rust-lib/event-integration/tests/user/af_cloud_test/auth_test.rs index d1d9beac2b..48590532f1 100644 --- a/frontend/rust-lib/event-integration/tests/user/af_cloud_test/auth_test.rs +++ b/frontend/rust-lib/event-integration/tests/user/af_cloud_test/auth_test.rs @@ -6,6 +6,7 @@ use crate::util::generate_test_email; #[tokio::test] async fn af_cloud_sign_up_test() { + // user_localhost_af_cloud_with_nginx().await; user_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; let email = generate_test_email(); diff --git a/frontend/rust-lib/flowy-document/src/manager.rs b/frontend/rust-lib/flowy-document/src/manager.rs index 179617fade..0309fdf15c 100644 --- a/frontend/rust-lib/flowy-document/src/manager.rs +++ b/frontend/rust-lib/flowy-document/src/manager.rs @@ -110,7 +110,8 @@ impl DocumentManager { )) } else { let doc_state = - doc_state_from_document_data(doc_id, data.unwrap_or_else(default_document_data))? + doc_state_from_document_data(doc_id, data.unwrap_or_else(default_document_data)) + .await? .doc_state .to_vec(); let collab = self @@ -290,15 +291,21 @@ impl DocumentManager { } } -fn doc_state_from_document_data( +async fn doc_state_from_document_data( doc_id: &str, data: DocumentData, ) -> Result { - let collab = Arc::new(MutexCollab::from_collab(Collab::new_with_origin( - CollabOrigin::Empty, - doc_id, - vec![], - ))); - let _ = Document::create_with_data(collab.clone(), data).map_err(internal_error)?; - Ok(collab.encode_collab_v1()) + let doc_id = doc_id.to_string(); + // spawn_blocking is used to avoid blocking the tokio thread pool if the document is large. + let encoded_collab = tokio::task::spawn_blocking(move || { + let collab = Arc::new(MutexCollab::from_collab(Collab::new_with_origin( + CollabOrigin::Empty, + doc_id, + vec![], + ))); + let _ = Document::create_with_data(collab.clone(), data).map_err(internal_error)?; + Ok::<_, FlowyError>(collab.encode_collab_v1()) + }) + .await??; + Ok(encoded_collab) } diff --git a/frontend/rust-lib/flowy-user/src/services/data_import/appflowy_data_import.rs b/frontend/rust-lib/flowy-user/src/services/data_import/appflowy_data_import.rs index e84d564df1..6d1389b33e 100644 --- a/frontend/rust-lib/flowy-user/src/services/data_import/appflowy_data_import.rs +++ b/frontend/rust-lib/flowy-user/src/services/data_import/appflowy_data_import.rs @@ -20,7 +20,7 @@ use collab_document::document_data::default_document_collab_data; use collab_entity::CollabType; use collab_folder::{Folder, UserId, View, ViewIdentifier, ViewLayout}; use collab_integrate::{CollabKVAction, CollabKVDB, PersistenceError}; -use flowy_error::{internal_error, FlowyError}; +use flowy_error::FlowyError; use flowy_folder_pub::cloud::gen_view_id; use flowy_folder_pub::entities::{AppFlowyData, ImportData}; use flowy_folder_pub::folder_builder::{ParentChildViews, ViewBuilder}; @@ -31,8 +31,8 @@ use parking_lot::{Mutex, RwLock}; use std::collections::{HashMap, HashSet}; use std::ops::{Deref, DerefMut}; use std::path::Path; -use std::sync::Arc; -use tracing::{debug, error, event, info, instrument}; +use std::sync::{Arc, Weak}; +use tracing::{debug, error, event, info, instrument, warn}; pub(crate) struct ImportContext { pub imported_session: Session, @@ -618,7 +618,7 @@ impl DerefMut for OldToNewIdMap { #[instrument(level = "debug", skip_all)] pub async fn upload_collab_objects_data( uid: i64, - user_collab_db: Arc, + user_collab_db: Weak, workspace_id: &str, user_authenticator: &Authenticator, appflowy_data: AppFlowyData, @@ -637,6 +637,10 @@ pub async fn upload_collab_objects_data( database_object_ids, } => { let object_by_collab_type = tokio::task::spawn_blocking(move || { + let user_collab_db = user_collab_db.upgrade().ok_or_else(|| { + FlowyError::internal().with_context("The collab db has been dropped, indicating that the user has switched to a new account") + })?; + let collab_read = user_collab_db.read_txn(); let mut object_by_collab_type = HashMap::new(); @@ -657,13 +661,10 @@ pub async fn upload_collab_objects_data( CollabType::DatabaseRow, load_and_process_collab_data(uid, &collab_read, &row_object_ids), ); - - object_by_collab_type + Ok::<_, FlowyError>(object_by_collab_type) }) - .await - .map_err(internal_error)?; + .await??; - // Upload let mut size_counter = 0; let mut objects: Vec = vec![]; for (collab_type, encoded_collab_by_oid) in object_by_collab_type { @@ -679,6 +680,9 @@ pub async fn upload_collab_objects_data( } } + // Spawn a new task to upload the collab objects data in the background. If the + // upload fails, we will retry the upload later. + // af_spawn(async move { if !objects.is_empty() { batch_create( uid, @@ -689,6 +693,7 @@ pub async fn upload_collab_objects_data( ) .await; } + // }); }, } @@ -707,20 +712,19 @@ async fn batch_create( .map(|o| o.object_id.clone()) .collect::>() .join(", "); - match user_cloud_service .batch_create_collab_object(workspace_id, objects) .await { Ok(_) => { info!( - "Batch creating collab objects success: {}, payload size: {}", - ids, size_counter + "Batch creating collab objects success, origin payload size: {}", + size_counter ); }, Err(err) => { error!( - "Batch creating collab objects fail:{}, payload size: {}, workspace_id:{}, uid: {}, error: {:?}", + "Batch creating collab objects fail:{}, origin payload size: {}, workspace_id:{}, uid: {}, error: {:?}", ids, size_counter, workspace_id, uid,err ); }, diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs index 126ef82917..60e8c0741a 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use collab_entity::{CollabObject, CollabType}; use collab_integrate::CollabKVDB; -use tracing::{error, instrument}; +use tracing::{error, info, instrument}; use flowy_error::{FlowyError, FlowyResult}; use flowy_folder_pub::entities::{AppFlowyData, ImportData}; @@ -16,6 +16,7 @@ use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB}; use crate::migrations::AnonUser; use crate::notification::{send_notification, UserNotification}; use crate::services::data_import::{upload_collab_objects_data, ImportContext}; +use crate::services::entities::Session; use crate::services::sqlite_sql::workspace_sql::UserWorkspaceTable; use crate::user_manager::UserManager; @@ -33,52 +34,81 @@ impl UserManager { match import_data { ImportData::AppFlowyDataFolder { items } => { for item in items { - match item { - AppFlowyData::Folder { - views, - database_view_ids_by_database_id, - } => { - let (tx, rx) = tokio::sync::oneshot::channel(); - let cloned_workspace_service = self.user_workspace_service.clone(); - tokio::spawn(async move { - let result = async { - cloned_workspace_service - .did_import_database_views(database_view_ids_by_database_id) - .await?; - cloned_workspace_service.did_import_views(views).await?; - Ok::<(), FlowyError>(()) - } - .await; - let _ = tx.send(result); - }) + self.upload_appflowy_data_item(&session, item).await?; + } + }, + } + Ok(()) + } + + async fn upload_appflowy_data_item( + &self, + session: &Session, + item: AppFlowyData, + ) -> Result<(), FlowyError> { + match item { + AppFlowyData::Folder { + views, + database_view_ids_by_database_id, + } => { + // Since `async_trait` does not implement `Sync`, and the handler requires `Sync`, we use a + // channel to synchronize the operation. This approach allows asynchronous trait methods to be compatible + // with synchronous handler requirements." + let (tx, rx) = tokio::sync::oneshot::channel(); + let cloned_workspace_service = self.user_workspace_service.clone(); + tokio::spawn(async move { + let result = async { + cloned_workspace_service + .did_import_database_views(database_view_ids_by_database_id) .await?; - rx.await??; - }, - AppFlowyData::CollabObject { - row_object_ids, - document_object_ids, - database_object_ids, - } => { - let user = self.get_user_profile_from_disk(session.user_id).await?; - let user_collab_db = self - .get_collab_db(session.user_id)? - .upgrade() - .ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?; - upload_collab_objects_data( - session.user_id, - user_collab_db, - &user.workspace_id, - &user.authenticator, - AppFlowyData::CollabObject { - row_object_ids, - document_object_ids, - database_object_ids, - }, - self.cloud_services.get_user_service()?, - ) - .await?; - }, + cloned_workspace_service.did_import_views(views).await?; + Ok::<(), FlowyError>(()) } + .await; + let _ = tx.send(result); + }) + .await?; + rx.await??; + }, + AppFlowyData::CollabObject { + row_object_ids, + document_object_ids, + database_object_ids, + } => { + let user = self.get_user_profile_from_disk(session.user_id).await?; + let user_collab_db = self + .get_collab_db(session.user_id)? + .upgrade() + .ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?; + + let user_id = session.user_id; + let weak_user_collab_db = Arc::downgrade(&user_collab_db); + let weak_user_cloud_service = self.cloud_services.get_user_service()?; + match upload_collab_objects_data( + user_id, + weak_user_collab_db, + &user.workspace_id, + &user.authenticator, + AppFlowyData::CollabObject { + row_object_ids, + document_object_ids, + database_object_ids, + }, + weak_user_cloud_service, + ) + .await + { + Ok(_) => info!( + "Successfully uploaded collab objects data for user:{}", + user_id + ), + Err(err) => { + error!( + "Failed to upload collab objects data: {:?} for user:{}", + err, user_id + ); + // TODO(nathan): retry uploading the collab objects data. + }, } }, } diff --git a/frontend/scripts/docker-buildfiles/Dockerfile b/frontend/scripts/docker-buildfiles/Dockerfile index 3c3592cfc8..3f58943c88 100644 --- a/frontend/scripts/docker-buildfiles/Dockerfile +++ b/frontend/scripts/docker-buildfiles/Dockerfile @@ -32,8 +32,8 @@ RUN yay -S --noconfirm curl base-devel openssl clang cmake ninja pkg-config xdg- RUN xdg-user-dirs-update RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y RUN source ~/.cargo/env && \ - rustup toolchain install 1.70 && \ - rustup default 1.70 + rustup toolchain install 1.75 && \ + rustup default 1.75 # Install Flutter RUN sudo pacman -S --noconfirm git tar gtk3