tests: AppFlowy Cloud integration test (#4015)

* chore: save cloud ofnig

* chore: fix .a link warnings

* chore: add cloud test runner

* refactor: test folder

* ci: add test

* ci: add test

* ci: fix

* ci: fix
This commit is contained in:
Nathan.fooo
2023-11-27 18:54:31 -08:00
committed by GitHub
parent 9d61ca0278
commit 3e17613f54
80 changed files with 1021 additions and 446 deletions

View File

@ -119,7 +119,8 @@ jobs:
run: flutter analyze . run: flutter analyze .
- name: Compress appflowy_flutter - name: Compress appflowy_flutter
run: tar -czf appflowy_flutter.tar.gz frontend/appflowy_flutter run: |
tar -czf appflowy_flutter.tar.gz frontend/appflowy_flutter
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
@ -297,17 +298,9 @@ jobs:
with: with:
name: ${{ github.run_id }}-${{ matrix.os }} name: ${{ github.run_id }}-${{ matrix.os }}
- name: Uncompress appflowy_flutter - name: Uncompressed appflowy_flutter
run: tar -xf appflowy_flutter.tar.gz run: tar -xf appflowy_flutter.tar.gz
# - name: Create .env.cloud.test file in flowy-server
# working-directory: frontend/appflowy_flutter
# run: |
# touch .env.cloud.test
# echo SUPABASE_URL=${{ secrets.SUPABASE_URL }} >> .env.cloud.test
# echo SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} >> .env.cloud.test
# echo APPFLOWY_CLOUD_URL=${{ secrets.APPFLOWY_CLOUD_URL }} >> .env.cloud.test
- name: Run flutter pub get - name: Run flutter pub get
working-directory: frontend working-directory: frontend
run: cargo make pub_get run: cargo make pub_get
@ -327,19 +320,101 @@ jobs:
fi fi
shell: bash shell: bash
- name: Upload coverage to Codecov cloud_integration_test:
uses: Wandalen/wretry.action@v1.0.36 needs: [prepare]
# if: github.event_name != 'pull_request'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout appflowy cloud code
uses: actions/checkout@v3
with: with:
action: codecov/codecov-action@v3 repository: AppFlowy-IO/AppFlowy-Cloud
with: | path: AppFlowy-Cloud
name: appflowy depth: 1
flags: appflowy_flutter_integrateion_test
fail_ci_if_error: true
verbose: true - name: Prepare appflowy cloud env
os: ${{ matrix.os }} working-directory: AppFlowy-Cloud
token: ${{ secrets.CODECOV_TOKEN }} run: |
attempt_limit: 20 # log level
attempt_delay: 10000 cp dev.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
run: |
docker compose up -d
- name: Checkout source code
uses: actions/checkout@v2
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- uses: taiki-e/install-action@v2
with:
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
- name: Install prerequisites
working-directory: frontend
run: |
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
shell: bash
- name: Enable Flutter Desktop
run: |
flutter config --enable-linux-desktop
shell: bash
- uses: actions/download-artifact@v3
with:
name: ${{ github.run_id }}-${{ matrix.os }}
- name: Uncompressed appflowy_flutter
run: |
tar -xf appflowy_flutter.tar.gz
ls -al
- name: Enable localhost env
working-directory: frontend/appflowy_flutter
run: |
echo 'APPFLOWY_CLOUD_URL=http://localhost' >> .env
dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
- name: Run flutter pub get
working-directory: frontend
run: cargo make pub_get
- name: Run Flutter integration tests
working-directory: frontend/appflowy_flutter
run: |
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
sudo apt-get install network-manager
flutter test integration_test/cloud_runner.dart -d Linux --coverage
shell: bash
build: build:
needs: [prepare] needs: [prepare]
@ -411,7 +486,7 @@ jobs:
with: with:
name: ${{ github.run_id }}-${{ matrix.os }} name: ${{ github.run_id }}-${{ matrix.os }}
- name: Uncompress appflowy_flutter - name: Uncompressed appflowy_flutter
run: tar -xf appflowy_flutter.tar.gz run: tar -xf appflowy_flutter.tar.gz
- name: Build flutter product - name: Build flutter product

View File

@ -28,6 +28,7 @@ LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.3.8" CURRENT_APP_VERSION = "0.3.8"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite" FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy" PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
# If you update the macOS's CRATE_TYPE, don't forget to update the # If you update the macOS's CRATE_TYPE, don't forget to update the
# appflowy_backend.podspec # appflowy_backend.podspec

View File

@ -0,0 +1 @@
APPFLOWY_CLOUD_URL=

View File

@ -4,6 +4,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/settings_appear
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/util.dart'; import 'util/util.dart';
void main() { void main() {

View File

@ -0,0 +1,123 @@
// ignore_for_file: unused_import
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
import 'package:integration_test/integration_test.dart';
import '../util/mock/mock_file_picker.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('appflowy cloud auth', () {
testWidgets('sign in', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloud,
);
await tester.tapGoogleLoginInButton();
tester.expectToSeeHomePage();
});
// testWidgets('sign out', (tester) async {
// await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloud,
// );
// await tester.tapGoogleLoginInButton();
// // Open the setting page and sign out
// await tester.openSettings();
// await tester.openSettingsPage(SettingsPage.user);
// await tester.tapButton(find.byType(SettingLogoutButton));
// tester.expectToSeeText(LocaleKeys.button_ok.tr());
// await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
// // Go to the sign in page again
// await tester.pumpAndSettle(const Duration(seconds: 1));
// tester.expectToSeeGoogleLoginButton();
// });
// testWidgets('sign in as annoymous', (tester) async {
// await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloud,
// );
// await tester.tapSignInAsGuest();
// // should not see the sync setting page when sign in as annoymous
// await tester.openSettings();
// await tester.openSettingsPage(SettingsPage.user);
// tester.expectToSeeGoogleLoginButton();
// });
// testWidgets('enable sync', (tester) async {
// await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloud,
// );
// await tester.tapGoogleLoginInButton();
// // Open the setting page and sign out
// await tester.openSettings();
// await tester.openSettingsPage(SettingsPage.cloud);
// // the switch should be on by default
// tester.assertAppFlowyCloudEnableSyncSwitchValue(true);
// await tester.toggleEnableSync(AppFlowyCloudEnableSync);
// // the switch should be off
// tester.assertAppFlowyCloudEnableSyncSwitchValue(false);
// // the switch should be on after toggling
// await tester.toggleEnableSync(AppFlowyCloudEnableSync);
// tester.assertAppFlowyCloudEnableSyncSwitchValue(true);
// });
// testWidgets('custom folder sign in', (tester) async {
// const userA = 'UserA';
// final initialPath = p.join(userA, appFlowyDataFolder);
// final context = await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloud,
// pathExtension: initialPath,
// );
// // remove the last extension
// final rootPath = context.applicationDataDirectory.replaceFirst(
// initialPath,
// '',
// );
// await tester.tapGoogleLoginInButton();
// // Open the setting page and sign out
// await tester.openSettings();
// await tester.openSettingsPage(SettingsPage.user);
// await tester.enterUserName(userA);
// await tester.openSettingsPage(SettingsPage.files);
// await tester.pumpAndSettle();
// // mock the file_picker result
// await mockGetDirectoryPath(
// p.join(rootPath, "random_folder"),
// );
// // after selecting the folder, an annoymous user should be signed in
// await tester.tapCustomLocationButton();
// tester.expectToSeeHomePage();
// await tester.pumpAndSettle();
// // Login as userA in custom folder
// await tester.openSettings();
// await tester.openSettingsPage(SettingsPage.user);
// await tester.tapGoogleLoginInButton();
// await tester.pumpAndSettle(const Duration(seconds: 1));
// tester.expectToSeeHomePage();
// // UserA should be displayed
// tester.expectToSeeUserName(userA);
// });
});
}

View File

@ -1,6 +1,7 @@
import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -10,15 +11,15 @@ import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('auth', () { group('supabase auth', () {
testWidgets('sign in with supabase', (tester) async { testWidgets('sign in with supabase', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase); await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton(); await tester.tapGoogleLoginInButton();
tester.expectToSeeHomePage(); tester.expectToSeeHomePage();
}); });
testWidgets('sign out with supabase', (tester) async { testWidgets('sign out with supabase', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase); await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton(); await tester.tapGoogleLoginInButton();
// Open the setting page and sign out // Open the setting page and sign out
@ -35,7 +36,7 @@ void main() {
}); });
testWidgets('sign in as annoymous', (tester) async { testWidgets('sign in as annoymous', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase); await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapSignInAsGuest(); await tester.tapSignInAsGuest();
// should not see the sync setting page when sign in as annoymous // should not see the sync setting page when sign in as annoymous
@ -65,7 +66,7 @@ void main() {
// }); // });
testWidgets('enable sync', (tester) async { testWidgets('enable sync', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase); await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton(); await tester.tapGoogleLoginInButton();
// Open the setting page and sign out // Open the setting page and sign out
@ -73,15 +74,15 @@ void main() {
await tester.openSettingsPage(SettingsPage.cloud); await tester.openSettingsPage(SettingsPage.cloud);
// the switch should be on by default // the switch should be on by default
tester.assertEnableSyncSwitchValue(true); tester.assertSupabaseEnableSyncSwitchValue(true);
await tester.toggleEnableSync(); await tester.toggleEnableSync(SupabaseEnableSync);
// the switch should be off // the switch should be off
tester.assertEnableSyncSwitchValue(false); tester.assertSupabaseEnableSyncSwitchValue(false);
// the switch should be on after toggling // the switch should be on after toggling
await tester.toggleEnableSync(); await tester.toggleEnableSync(SupabaseEnableSync);
tester.assertEnableSyncSwitchValue(true); tester.assertSupabaseEnableSyncSwitchValue(true);
}); });
}); });
} }

View File

@ -0,0 +1,13 @@
import 'package:integration_test/integration_test.dart';
import 'auth/appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
import 'empty_test.dart' as first_test;
Future<void> main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// This test must be run first, otherwise the CI will fail.
first_test.main();
appflowy_cloud_auth_test.main();
}

View File

@ -3,8 +3,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -4,8 +4,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -4,8 +4,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -4,7 +4,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -7,9 +7,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/emoji.dart'; import '../util/emoji.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -2,8 +2,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -3,8 +3,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -2,7 +2,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -2,7 +2,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -3,8 +3,8 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import '../util/database_test_op.dart';
import 'util/util.dart'; import '../util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -2,17 +2,18 @@ import 'package:integration_test/integration_test.dart';
import 'appearance_settings_test.dart' as appearance_test_runner; import 'appearance_settings_test.dart' as appearance_test_runner;
import 'board/board_test_runner.dart' as board_test_runner; import 'board/board_test_runner.dart' as board_test_runner;
import 'database_calendar_test.dart' as database_calendar_test; import 'database/database_calendar_test.dart' as database_calendar_test;
import 'database_cell_test.dart' as database_cell_test; import 'database/database_cell_test.dart' as database_cell_test;
import 'database_field_settings_test.dart' as database_field_settings_test; import 'database/database_field_settings_test.dart'
import 'database_field_test.dart' as database_field_test; as database_field_settings_test;
import 'database_filter_test.dart' as database_filter_test; import 'database/database_field_test.dart' as database_field_test;
import 'database_row_page_test.dart' as database_row_page_test; import 'database/database_filter_test.dart' as database_filter_test;
import 'database_row_test.dart' as database_row_test; import 'database/database_row_page_test.dart' as database_row_page_test;
import 'database_setting_test.dart' as database_setting_test; import 'database/database_row_test.dart' as database_row_test;
import 'database_share_test.dart' as database_share_test; import 'database/database_setting_test.dart' as database_setting_test;
import 'database_sort_test.dart' as database_sort_test; import 'database/database_share_test.dart' as database_share_test;
import 'database_view_test.dart' as database_view_test; import 'database/database_sort_test.dart' as database_sort_test;
import 'database/database_view_test.dart' as database_view_test;
import 'document/document_test_runner.dart' as document_test_runner; import 'document/document_test_runner.dart' as document_test_runner;
import 'empty_test.dart' as first_test; import 'empty_test.dart' as first_test;
import 'hotkeys_test.dart' as hotkeys_test; import 'hotkeys_test.dart' as hotkeys_test;
@ -75,8 +76,6 @@ Future<void> main() async {
// User settings // User settings
settings_test_runner.main(); settings_test_runner.main();
// supabase_auth_test_runner.main();
// board_test.main(); // board_test.main();
// empty_document_test.main(); // empty_document_test.main();
// smart_menu_test.main(); // smart_menu_test.main();

View File

@ -1,4 +1,5 @@
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -34,7 +35,7 @@ extension AppFlowyAuthTest on WidgetTester {
); );
} }
void assertEnableSyncSwitchValue(bool value) { void assertSupabaseEnableSyncSwitchValue(bool value) {
assertSwitchValue( assertSwitchValue(
find.descendant( find.descendant(
of: find.byType(SupabaseEnableSync), of: find.byType(SupabaseEnableSync),
@ -44,6 +45,16 @@ extension AppFlowyAuthTest on WidgetTester {
); );
} }
void assertAppFlowyCloudEnableSyncSwitchValue(bool value) {
assertSwitchValue(
find.descendant(
of: find.byType(AppFlowyCloudEnableSync),
matching: find.byWidgetPredicate((widget) => widget is Switch),
),
value,
);
}
Future<void> toggleEnableEncrypt() async { Future<void> toggleEnableEncrypt() async {
final finder = find.descendant( final finder = find.descendant(
of: find.byType(EnableEncrypt), of: find.byType(EnableEncrypt),
@ -53,9 +64,9 @@ extension AppFlowyAuthTest on WidgetTester {
await tapButton(finder); await tapButton(finder);
} }
Future<void> toggleEnableSync() async { Future<void> toggleEnableSync(Type syncButton) async {
final finder = find.descendant( final finder = find.descendant(
of: find.byType(SupabaseEnableSync), of: find.byType(syncButton),
matching: find.byWidgetPredicate((widget) => widget is Switch), matching: find.byWidgetPredicate((widget) => widget is Switch),
); );

View File

@ -31,7 +31,7 @@ extension AppFlowyTestBase on WidgetTester {
// use to append after the application data directory // use to append after the application data directory
String? pathExtension, String? pathExtension,
Size windowsSize = const Size(1600, 1200), Size windowsSize = const Size(1600, 1200),
CloudType? cloudType, AuthenticatorType? cloudType,
}) async { }) async {
binding.setSurfaceSize(windowsSize); binding.setSurfaceSize(windowsSize);
@ -45,16 +45,32 @@ extension AppFlowyTestBase on WidgetTester {
await FlowyRunner.run( await FlowyRunner.run(
FlowyApp(), FlowyApp(),
IntegrationMode.integrationTest, IntegrationMode.integrationTest,
didInitGetIt: Future( rustEnvsBuilder: () {
final rustEnvs = <String, String>{};
if (cloudType != null) {
switch (cloudType) {
case AuthenticatorType.local:
break;
case AuthenticatorType.supabase:
break;
case AuthenticatorType.appflowyCloud:
rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
break;
}
}
return rustEnvs;
},
didInitGetItCallback: Future(
() async { () async {
if (cloudType != null) { if (cloudType != null) {
switch (cloudType) { switch (cloudType) {
case CloudType.local: case AuthenticatorType.local:
break; break;
case CloudType.supabase: case AuthenticatorType.supabase:
await useSupabaseCloud(); await useSupabaseCloud();
break; break;
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
await useAppFlowyCloud(); await useAppFlowyCloud();
break; break;
} }
@ -227,7 +243,7 @@ extension AppFlowyFinderTestBase on CommonFinders {
} }
Future<void> useSupabaseCloud() async { Future<void> useSupabaseCloud() async {
await setCloudType(CloudType.supabase); await setAuthenticatorType(AuthenticatorType.supabase);
await setSupbaseServer( await setSupbaseServer(
Some(TestEnv.supabaseUrl), Some(TestEnv.supabaseUrl),
Some(TestEnv.supabaseAnonKey), Some(TestEnv.supabaseAnonKey),
@ -235,6 +251,6 @@ Future<void> useSupabaseCloud() async {
} }
Future<void> useAppFlowyCloud() async { Future<void> useAppFlowyCloud() async {
await setCloudType(CloudType.appflowyCloud); await setAuthenticatorType(AuthenticatorType.appflowyCloud);
await setAppFlowyCloudUrl(Some(TestEnv.afCloudUrl)); await setAppFlowyCloudUrl(Some(TestEnv.afCloudUrl));
} }

View File

@ -5,20 +5,24 @@ part 'backend_env.g.dart';
@JsonSerializable() @JsonSerializable()
class AppFlowyConfiguration { class AppFlowyConfiguration {
final String root;
final String custom_app_path; final String custom_app_path;
final String origin_app_path; final String origin_app_path;
final String device_id; final String device_id;
final int cloud_type; final int authenticator_type;
final SupabaseConfiguration supabase_config; final SupabaseConfiguration supabase_config;
final AppFlowyCloudConfiguration appflowy_cloud_config; final AppFlowyCloudConfiguration appflowy_cloud_config;
final Map<String, String> envs;
AppFlowyConfiguration({ AppFlowyConfiguration({
required this.root,
required this.custom_app_path, required this.custom_app_path,
required this.origin_app_path, required this.origin_app_path,
required this.device_id, required this.device_id,
required this.cloud_type, required this.authenticator_type,
required this.supabase_config, required this.supabase_config,
required this.appflowy_cloud_config, required this.appflowy_cloud_config,
required this.envs,
}); });
factory AppFlowyConfiguration.fromJson(Map<String, dynamic> json) => factory AppFlowyConfiguration.fromJson(Map<String, dynamic> json) =>

View File

@ -1,6 +1,7 @@
import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/env/backend_env.dart'; import 'package:appflowy/env/backend_env.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
@ -9,22 +10,22 @@ import 'package:dartz/dartz.dart';
/// ///
/// This method updates the cloud type setting in the key-value storage /// This method updates the cloud type setting in the key-value storage
/// using the [KeyValueStorage] service. The cloud type is identified /// using the [KeyValueStorage] service. The cloud type is identified
/// by the [CloudType] enum. /// by the [AuthenticatorType] enum.
/// ///
/// [ty] - The type of cloud to be set. It must be one of the values from /// [ty] - The type of cloud to be set. It must be one of the values from
/// [CloudType] enum. The corresponding integer value of the enum is stored: /// [AuthenticatorType] enum. The corresponding integer value of the enum is stored:
/// - `CloudType.local` is stored as "0". /// - `CloudType.local` is stored as "0".
/// - `CloudType.supabase` is stored as "1". /// - `CloudType.supabase` is stored as "1".
/// - `CloudType.appflowyCloud` is stored as "2". /// - `CloudType.appflowyCloud` is stored as "2".
Future<void> setCloudType(CloudType ty) async { Future<void> setAuthenticatorType(AuthenticatorType ty) async {
switch (ty) { switch (ty) {
case CloudType.local: case AuthenticatorType.local:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 0.toString()); getIt<KeyValueStorage>().set(KVKeys.kCloudType, 0.toString());
break; break;
case CloudType.supabase: case AuthenticatorType.supabase:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 1.toString()); getIt<KeyValueStorage>().set(KVKeys.kCloudType, 1.toString());
break; break;
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 2.toString()); getIt<KeyValueStorage>().set(KVKeys.kCloudType, 2.toString());
break; break;
} }
@ -34,25 +35,25 @@ Future<void> setCloudType(CloudType ty) async {
/// ///
/// This method fetches the cloud type setting from the key-value storage /// This method fetches the cloud type setting from the key-value storage
/// using the [KeyValueStorage] service and returns the corresponding /// using the [KeyValueStorage] service and returns the corresponding
/// [CloudType] enum value. /// [AuthenticatorType] enum value.
/// ///
/// Returns: /// Returns:
/// A Future that resolves to a [CloudType] enum value representing the /// A Future that resolves to a [AuthenticatorType] enum value representing the
/// currently set cloud type. The default return value is `CloudType.local` /// currently set cloud type. The default return value is `CloudType.local`
/// if no valid setting is found. /// if no valid setting is found.
/// ///
Future<CloudType> getCloudType() async { Future<AuthenticatorType> getAuthenticatorType() async {
final value = await getIt<KeyValueStorage>().get(KVKeys.kCloudType); final value = await getIt<KeyValueStorage>().get(KVKeys.kCloudType);
return value.fold(() => CloudType.local, (s) { return value.fold(() => AuthenticatorType.local, (s) {
switch (s) { switch (s) {
case "0": case "0":
return CloudType.local; return AuthenticatorType.local;
case "1": case "1":
return CloudType.supabase; return AuthenticatorType.supabase;
case "2": case "2":
return CloudType.appflowyCloud; return AuthenticatorType.appflowyCloud;
default: default:
return CloudType.local; return AuthenticatorType.local;
} }
}); });
} }
@ -70,17 +71,15 @@ Future<CloudType> getCloudType() async {
/// Returns `false` otherwise. /// Returns `false` otherwise.
bool get isAuthEnabled { bool get isAuthEnabled {
// Only enable supabase in release and develop mode. // Only enable supabase in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) { if (integrationMode().isRelease ||
integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
final env = getIt<AppFlowyCloudSharedEnv>(); final env = getIt<AppFlowyCloudSharedEnv>();
if (env.cloudType == CloudType.local) { if (env.authenticatorType == AuthenticatorType.supabase) {
return false;
}
if (env.cloudType == CloudType.supabase) {
return env.supabaseConfig.isValid; return env.supabaseConfig.isValid;
} }
if (env.cloudType == CloudType.appflowyCloud) { if (env.authenticatorType == AuthenticatorType.appflowyCloud) {
return env.appflowyCloudConfig.isValid; return env.appflowyCloudConfig.isValid;
} }
@ -101,8 +100,10 @@ bool get isAuthEnabled {
/// is `CloudType.supabase`. Otherwise, it returns `false`. /// is `CloudType.supabase`. Otherwise, it returns `false`.
bool get isSupabaseEnabled { bool get isSupabaseEnabled {
// Only enable supabase in release and develop mode. // Only enable supabase in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) { if (integrationMode().isRelease ||
return currentCloudType() == CloudType.supabase; integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
return currentCloudType() == AuthenticatorType.supabase;
} else { } else {
return false; return false;
} }
@ -119,26 +120,28 @@ bool get isSupabaseEnabled {
/// cloud type is `CloudType.appflowyCloud`. Otherwise, it returns `false`. /// cloud type is `CloudType.appflowyCloud`. Otherwise, it returns `false`.
bool get isAppFlowyCloudEnabled { bool get isAppFlowyCloudEnabled {
// Only enable appflowy cloud in release and develop mode. // Only enable appflowy cloud in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) { if (integrationMode().isRelease ||
return currentCloudType() == CloudType.appflowyCloud; integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
return currentCloudType() == AuthenticatorType.appflowyCloud;
} else { } else {
return false; return false;
} }
} }
enum CloudType { enum AuthenticatorType {
local, local,
supabase, supabase,
appflowyCloud; appflowyCloud;
bool get isEnabled => this != CloudType.local; bool get isEnabled => this != AuthenticatorType.local;
int get value { int get value {
switch (this) { switch (this) {
case CloudType.local: case AuthenticatorType.local:
return 0; return 0;
case CloudType.supabase: case AuthenticatorType.supabase:
return 1; return 1;
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
return 2; return 2;
} }
} }
@ -146,19 +149,19 @@ enum CloudType {
static fromValue(int value) { static fromValue(int value) {
switch (value) { switch (value) {
case 0: case 0:
return CloudType.local; return AuthenticatorType.local;
case 1: case 1:
return CloudType.supabase; return AuthenticatorType.supabase;
case 2: case 2:
return CloudType.appflowyCloud; return AuthenticatorType.appflowyCloud;
default: default:
return CloudType.local; return AuthenticatorType.local;
} }
} }
} }
CloudType currentCloudType() { AuthenticatorType currentCloudType() {
return getIt<AppFlowyCloudSharedEnv>().cloudType; return getIt<AppFlowyCloudSharedEnv>().authenticatorType;
} }
Future<void> setAppFlowyCloudUrl(Option<String> url) async { Future<void> setAppFlowyCloudUrl(Option<String> url) async {
@ -170,22 +173,52 @@ Future<void> setAppFlowyCloudUrl(Option<String> url) async {
/// Use getIt<AppFlowyCloudSharedEnv>() to get the shared environment. /// Use getIt<AppFlowyCloudSharedEnv>() to get the shared environment.
class AppFlowyCloudSharedEnv { class AppFlowyCloudSharedEnv {
final CloudType cloudType; final AuthenticatorType _authenticatorType;
final AppFlowyCloudConfiguration appflowyCloudConfig; final AppFlowyCloudConfiguration appflowyCloudConfig;
final SupabaseConfiguration supabaseConfig; final SupabaseConfiguration supabaseConfig;
AppFlowyCloudSharedEnv({ AppFlowyCloudSharedEnv({
required this.cloudType, required AuthenticatorType authenticatorType,
required this.appflowyCloudConfig, required this.appflowyCloudConfig,
required this.supabaseConfig, required this.supabaseConfig,
}); }) : _authenticatorType = authenticatorType;
AuthenticatorType get authenticatorType => _authenticatorType;
static Future<AppFlowyCloudSharedEnv> fromEnv() async {
if (Env.enableCustomCloud) {
// Use the custom cloud configuration.
final cloudType = await getAuthenticatorType();
final appflowyCloudConfig = await getAppFlowyCloudConfig();
final supabaseCloudConfig = await getSupabaseCloudConfig();
return AppFlowyCloudSharedEnv(
authenticatorType: cloudType,
appflowyCloudConfig: appflowyCloudConfig,
supabaseConfig: supabaseCloudConfig,
);
} else {
final appflowyCloudConfig = AppFlowyCloudConfiguration(
base_url: Env.afCloudUrl,
ws_base_url: await _getAppFlowyCloudWSUrl(Env.afCloudUrl),
gotrue_url: await _getAppFlowyCloudGotrueUrl(Env.afCloudUrl),
);
return AppFlowyCloudSharedEnv(
authenticatorType: AuthenticatorType.fromValue(Env.authenticatorType),
appflowyCloudConfig: appflowyCloudConfig,
supabaseConfig: SupabaseConfiguration.defaultConfig(),
);
}
}
} }
Future<AppFlowyCloudConfiguration> getAppFlowyCloudConfig() async { Future<AppFlowyCloudConfiguration> getAppFlowyCloudConfig() async {
final baseURL = await getAppFlowyCloudUrl();
return AppFlowyCloudConfiguration( return AppFlowyCloudConfiguration(
base_url: await getAppFlowyCloudUrl(), base_url: baseURL,
ws_base_url: await _getAppFlowyCloudWSUrl(), ws_base_url: await _getAppFlowyCloudWSUrl(baseURL),
gotrue_url: await _getAppFlowyCloudGotrueUrl(), gotrue_url: await _getAppFlowyCloudGotrueUrl(baseURL),
); );
} }
@ -198,10 +231,9 @@ Future<String> getAppFlowyCloudUrl() async {
); );
} }
Future<String> _getAppFlowyCloudWSUrl() async { Future<String> _getAppFlowyCloudWSUrl(String baseURL) async {
try { try {
final serverUrl = await getAppFlowyCloudUrl(); final uri = Uri.parse(baseURL);
final uri = Uri.parse(serverUrl);
// Construct the WebSocket URL directly from the parsed URI. // Construct the WebSocket URL directly from the parsed URI.
final wsScheme = uri.isScheme('HTTPS') ? 'wss' : 'ws'; final wsScheme = uri.isScheme('HTTPS') ? 'wss' : 'ws';
@ -214,9 +246,8 @@ Future<String> _getAppFlowyCloudWSUrl() async {
} }
} }
Future<String> _getAppFlowyCloudGotrueUrl() async { Future<String> _getAppFlowyCloudGotrueUrl(String baseURL) async {
final serverUrl = await getAppFlowyCloudUrl(); return "$baseURL/gotrue";
return "$serverUrl/gotrue";
} }
Future<void> setSupbaseServer( Future<void> setSupbaseServer(

View File

@ -0,0 +1,28 @@
// lib/env/env.dart
import 'package:appflowy/env/cloud_env.dart';
import 'package:envied/envied.dart';
part 'env.g.dart';
@Envied(path: '.env')
abstract class Env {
static bool get enableCustomCloud {
return Env.authenticatorType == AuthenticatorType.appflowyCloud.value &&
_Env.afCloudUrl.isEmpty;
}
@EnviedField(
obfuscate: false,
varName: 'AUTHENTICATOR_TYPE',
defaultValue: 2,
)
static const int authenticatorType = _Env.authenticatorType;
/// AppFlowy Cloud Configuration
@EnviedField(
obfuscate: false,
varName: 'APPFLOWY_CLOUD_URL',
defaultValue: '',
)
static const String afCloudUrl = _Env.afCloudUrl;
}

View File

@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_
import 'package:appflowy/plugins/trash/application/prelude.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart'; import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/supabase_auth_service.dart'; import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
import 'package:appflowy/user/application/auth/supabase_mock_auth_service.dart'; import 'package:appflowy/user/application/auth/supabase_mock_auth_service.dart';
@ -57,16 +58,8 @@ class DependencyResolver {
} }
Future<void> _resolveCloudDeps(GetIt getIt) async { Future<void> _resolveCloudDeps(GetIt getIt) async {
final cloudType = await getCloudType(); final env = await AppFlowyCloudSharedEnv.fromEnv();
final appflowyCloudConfig = await getAppFlowyCloudConfig(); getIt.registerFactory<AppFlowyCloudSharedEnv>(() => env);
final supabaseCloudConfig = await getSupabaseCloudConfig();
getIt.registerFactory<AppFlowyCloudSharedEnv>(() {
return AppFlowyCloudSharedEnv(
cloudType: cloudType,
appflowyCloudConfig: appflowyCloudConfig,
supabaseConfig: supabaseCloudConfig,
);
});
} }
void _resolveCommonService( void _resolveCommonService(
@ -130,22 +123,27 @@ void _resolveCommonService(
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) { void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
switch (currentCloudType()) { switch (currentCloudType()) {
case CloudType.local: case AuthenticatorType.local:
getIt.registerFactory<AuthService>( getIt.registerFactory<AuthService>(
() => BackendAuthService( () => BackendAuthService(
AuthTypePB.Local, AuthTypePB.Local,
), ),
); );
break; break;
case CloudType.supabase: case AuthenticatorType.supabase:
if (mode.isIntegrationTest) { if (mode.isIntegrationTest) {
getIt.registerFactory<AuthService>(() => MockAuthService()); getIt.registerFactory<AuthService>(() => SupabaseMockAuthService());
} else { } else {
getIt.registerFactory<AuthService>(() => SupabaseAuthService()); getIt.registerFactory<AuthService>(() => SupabaseAuthService());
} }
break; break;
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
getIt.registerFactory<AuthService>(() => AFCloudAuthService()); if (mode.isIntegrationTest) {
getIt
.registerFactory<AuthService>(() => AppFlowyCloudMockAuthService());
} else {
getIt.registerFactory<AuthService>(() => AppFlowyCloudAuthService());
}
break; break;
} }

View File

@ -7,7 +7,7 @@ class FlowyApp implements EntryPoint {
@override @override
Widget create(LaunchConfiguration config) { Widget create(LaunchConfiguration config) {
return SplashScreen( return SplashScreen(
autoRegister: config.autoRegistrationSupported, isAnon: config.isAnon,
); );
} }
} }

View File

@ -1,8 +1,11 @@
class LaunchConfiguration { class LaunchConfiguration {
const LaunchConfiguration({ const LaunchConfiguration({
this.autoRegistrationSupported = false, this.isAnon = false,
required this.rustEnvs,
}); });
// APP will automatically register after launching. // APP will automatically register after launching.
final bool autoRegistrationSupported; final bool isAnon;
//
final Map<String, String> rustEnvs;
} }

View File

@ -36,21 +36,35 @@ Future<void> runAppFlowy() async {
} }
class FlowyRunner { class FlowyRunner {
static var currentMode = integrationMode();
static Future<FlowyRunnerContext> run( static Future<FlowyRunnerContext> run(
EntryPoint f, EntryPoint f,
IntegrationMode mode, { IntegrationMode mode, {
Future? didInitGetIt, // This callback is triggered after the initialization of 'getIt',
LaunchConfiguration config = const LaunchConfiguration( // which is used for dependency injection throughout the app.
autoRegistrationSupported: false, // If your functionality depends on 'getIt', ensure to register
), // your callback here to execute any necessary actions post-initialization.
Future? didInitGetItCallback,
// Passing the envs to the backend
Map<String, String> Function()? rustEnvsBuilder,
// Indicate whether the app is running in anonymous mode.
// Note: when the app is running in anonymous mode, the user no need to
// sign in, and the app will only save the data in the local storage.
bool isAnon = false,
}) async { }) async {
currentMode = mode;
// Clear all the states in case of rebuilding. // Clear all the states in case of rebuilding.
await getIt.reset(); await getIt.reset();
final config = LaunchConfiguration(
isAnon: isAnon,
rustEnvs: rustEnvsBuilder?.call() ?? {},
);
// Specify the env // Specify the env
await initGetIt(getIt, mode, f, config); await initGetIt(getIt, mode, f, config);
await didInitGetItCallback;
await didInitGetIt;
final applicationDataDirectory = final applicationDataDirectory =
await getIt<ApplicationDataStorage>().getPath().then( await getIt<ApplicationDataStorage>().getPath().then(

View File

@ -23,15 +23,18 @@ class InitRustSDKTask extends LaunchTask {
@override @override
Future<void> initialize(LaunchContext context) async { Future<void> initialize(LaunchContext context) async {
final root = await getApplicationSupportDirectory();
final applicationPath = await appFlowyApplicationDataDirectory(); final applicationPath = await appFlowyApplicationDataDirectory();
final dir = customApplicationPath ?? applicationPath; final dir = customApplicationPath ?? applicationPath;
final deviceId = await getDeviceId(); final deviceId = await getDeviceId();
// Pass the environment variables to the Rust SDK // Pass the environment variables to the Rust SDK
final env = _getAppFlowyConfiguration( final env = _makeAppFlowyConfiguration(
root.path,
dir.path, dir.path,
applicationPath.path, applicationPath.path,
deviceId, deviceId,
rustEnvs: context.config.rustEnvs,
); );
await context.getIt<FlowySDK>().init(jsonEncode(env.toJson())); await context.getIt<FlowySDK>().init(jsonEncode(env.toJson()));
} }
@ -40,19 +43,23 @@ class InitRustSDKTask extends LaunchTask {
Future<void> dispose() async {} Future<void> dispose() async {}
} }
AppFlowyConfiguration _getAppFlowyConfiguration( AppFlowyConfiguration _makeAppFlowyConfiguration(
String root,
String customAppPath, String customAppPath,
String originAppPath, String originAppPath,
String deviceId, String deviceId, {
) { required Map<String, String> rustEnvs,
}) {
final env = getIt<AppFlowyCloudSharedEnv>(); final env = getIt<AppFlowyCloudSharedEnv>();
return AppFlowyConfiguration( return AppFlowyConfiguration(
root: root,
custom_app_path: customAppPath, custom_app_path: customAppPath,
origin_app_path: originAppPath, origin_app_path: originAppPath,
device_id: deviceId, device_id: deviceId,
cloud_type: env.cloudType.value, authenticator_type: env.authenticatorType.value,
supabase_config: env.supabaseConfig, supabase_config: env.supabaseConfig,
appflowy_cloud_config: env.appflowyCloudConfig, appflowy_cloud_config: env.appflowyCloudConfig,
envs: rustEnvs,
); );
} }

View File

@ -14,11 +14,11 @@ import 'package:url_launcher/url_launcher.dart';
import 'auth_error.dart'; import 'auth_error.dart';
import 'device_id.dart'; import 'device_id.dart';
class AFCloudAuthService implements AuthService { class AppFlowyCloudAuthService implements AuthService {
final _appLinks = AppLinks(); final _appLinks = AppLinks();
StreamSubscription<Uri?>? _deeplinkSubscription; StreamSubscription<Uri?>? _deeplinkSubscription;
AFCloudAuthService(); AppFlowyCloudAuthService();
final BackendAuthService _backendAuthService = BackendAuthService( final BackendAuthService _backendAuthService = BackendAuthService(
AuthTypePB.AFCloud, AuthTypePB.AFCloud,
@ -35,7 +35,7 @@ class AFCloudAuthService implements AuthService {
} }
@override @override
Future<Either<FlowyError, UserProfilePB>> signIn({ Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email, required String email,
required String password, required String password,
Map<String, String> params = const {}, Map<String, String> params = const {},

View File

@ -0,0 +1,100 @@
import 'dart:async';
import 'package:appflowy/user/application/auth/backend_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/device_id.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_infra/uuid.dart';
/// Only used for testing.
class AppFlowyCloudMockAuthService implements AuthService {
// Use same email for all tests.
static String currentUserEmail = "";
AppFlowyCloudMockAuthService() {
if (currentUserEmail.isEmpty) {
currentUserEmail = "${uuid()}@appflowy.io";
}
}
final BackendAuthService _appFlowyAuthService =
BackendAuthService(AuthTypePB.Supabase);
@override
Future<Either<FlowyError, UserProfilePB>> signUp({
required String name,
required String email,
required String password,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email,
required String password,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
required String platform,
Map<String, String> params = const {},
}) async {
final payload = SignInUrlPayloadPB.create()
..authType = AuthTypePB.AFCloud
// don't use nanoid here, the gotrue server will transform the email
..email = currentUserEmail;
final deviceId = await getDeviceId();
final getSignInURLResult = await UserEventGenerateSignInURL(payload).send();
return getSignInURLResult.fold(
(urlPB) async {
final payload = OauthSignInPB(
authType: AuthTypePB.AFCloud,
map: {
AuthServiceMapKeys.signInURL: urlPB.signInUrl,
AuthServiceMapKeys.deviceId: deviceId,
},
);
return await UserEventOauthSignIn(payload)
.send()
.then((value) => value.swap());
},
(r) => left(r),
);
}
@override
Future<void> signOut() async {
await _appFlowyAuthService.signOut();
}
@override
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
Map<String, String> params = const {},
}) async {
return _appFlowyAuthService.signUpAsGuest();
}
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> params = const {},
}) async {
throw UnimplementedError();
}
@override
Future<Either<FlowyError, UserProfilePB>> getUser() async {
return UserBackendService.getCurrentUserProfile();
}
}

View File

@ -25,7 +25,7 @@ abstract class AuthService {
/// ///
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError]. /// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
Future<Either<FlowyError, UserProfilePB>> signIn({ Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email, required String email,
required String password, required String password,
Map<String, String> params, Map<String, String> params,

View File

@ -19,7 +19,7 @@ class BackendAuthService implements AuthService {
BackendAuthService(this.authType); BackendAuthService(this.authType);
@override @override
Future<Either<FlowyError, UserProfilePB>> signIn({ Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email, required String email,
required String password, required String password,
Map<String, String> params = const {}, Map<String, String> params = const {},
@ -29,7 +29,7 @@ class BackendAuthService implements AuthService {
..password = password ..password = password
..authType = authType ..authType = authType
..deviceId = await getDeviceId(); ..deviceId = await getDeviceId();
final response = UserEventSignIn(request).send(); final response = UserEventSignInWithEmailPassword(request).send();
return response.then((value) => value.swap()); return response.then((value) => value.swap());
} }

View File

@ -54,7 +54,7 @@ class SupabaseAuthService implements AuthService {
} }
@override @override
Future<Either<FlowyError, UserProfilePB>> signIn({ Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email, required String email,
required String password, required String password,
Map<String, String> params = const {}, Map<String, String> params = const {},
@ -68,7 +68,7 @@ class SupabaseAuthService implements AuthService {
if (uuid == null) { if (uuid == null) {
return Left(AuthError.supabaseSignInError); return Left(AuthError.supabaseSignInError);
} }
return _backendAuthService.signIn( return _backendAuthService.signInWithEmailPassword(
email: email, email: email,
password: password, password: password,
params: { params: {

View File

@ -13,8 +13,8 @@ import 'package:supabase_flutter/supabase_flutter.dart';
import 'auth_error.dart'; import 'auth_error.dart';
/// Only used for testing. /// Only used for testing.
class MockAuthService implements AuthService { class SupabaseMockAuthService implements AuthService {
MockAuthService(); SupabaseMockAuthService();
static OauthSignInPB? signInPayload; static OauthSignInPB? signInPayload;
SupabaseClient get _client => Supabase.instance.client; SupabaseClient get _client => Supabase.instance.client;
@ -34,7 +34,7 @@ class MockAuthService implements AuthService {
} }
@override @override
Future<Either<FlowyError, UserProfilePB>> signIn({ Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email, required String email,
required String password, required String password,
Map<String, String> params = const {}, Map<String, String> params = const {},

View File

@ -59,7 +59,7 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
SignInState state, SignInState state,
Emitter<SignInState> emit, Emitter<SignInState> emit,
) async { ) async {
final result = await authService.signIn( final result = await authService.signInWithEmailPassword(
email: state.email ?? '', email: state.email ?? '',
password: state.password ?? '', password: state.password ?? '',
); );

View File

@ -20,10 +20,15 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
error.msg, error.msg,
); );
break; break;
case ErrorCode.HttpError:
showSnapBar(
context,
error.toString(),
);
default: default:
showSnapBar( showSnapBar(
context, context,
error.msg, error.toString(),
onClosed: () { onClosed: () {
getIt<AuthService>().signOut(); getIt<AuthService>().signOut();
runAppFlowy(); runAppFlowy();

View File

@ -2,7 +2,6 @@ import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/entry_point.dart'; import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/launch_configuration.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/historical_user_bloc.dart'; import 'package:appflowy/user/application/historical_user_bloc.dart';
@ -66,6 +65,13 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
} }
}, },
), ),
// if (Env.enableCustomCloud) ...[
// const VSpace(10),
// const SizedBox(
// width: 340,
// child: _SetupYourServer(),
// ),
// ],
const VSpace(32), const VSpace(32),
SizedBox( SizedBox(
width: size.width * 0.7, width: size.width * 0.7,
@ -98,9 +104,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
await FlowyRunner.run( await FlowyRunner.run(
FlowyApp(), FlowyApp(),
integrationMode(), integrationMode(),
config: const LaunchConfiguration( isAnon: true,
autoRegistrationSupported: true,
),
); );
} }
} }
@ -279,10 +283,33 @@ class GoButton extends StatelessWidget {
? LocaleKeys.letsGoButtonText.tr() ? LocaleKeys.letsGoButtonText.tr()
: LocaleKeys.signIn_continueAnonymousUser.tr(); : LocaleKeys.signIn_continueAnonymousUser.tr();
final textWidget = FlowyText.medium( final textWidget = Row(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: FlowyText.medium(
text, text,
textAlign: TextAlign.center, textAlign: TextAlign.center,
fontSize: 14, fontSize: 14,
),
),
// Tooltip(
// message: LocaleKeys.settings_menu_configServerGuide.tr(),
// child: Container(
// width: 30.0,
// decoration: const BoxDecoration(
// shape: BoxShape.circle,
// ),
// child: Center(
// child: Icon(
// Icons.help,
// color: Colors.white,
// weight: 2,
// ),
// ),
// ),
// ),
],
); );
return SizedBox( return SizedBox(

View File

@ -18,16 +18,14 @@ class SplashScreen extends StatelessWidget {
/// Root Page of the app. /// Root Page of the app.
const SplashScreen({ const SplashScreen({
super.key, super.key,
required this.autoRegister, required this.isAnon,
}); });
final bool autoRegister; final bool isAnon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!autoRegister) { if (isAnon) {
return _buildChild(context);
} else {
return FutureBuilder<void>( return FutureBuilder<void>(
future: _registerIfNeeded(), future: _registerIfNeeded(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -37,6 +35,8 @@ class SplashScreen extends StatelessWidget {
return _buildChild(context); return _buildChild(context);
}, },
); );
} else {
return _buildChild(context);
} }
} }

View File

@ -5,13 +5,13 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'cloud_setting_bloc.freezed.dart'; part 'cloud_setting_bloc.freezed.dart';
class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> { class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
CloudSettingBloc(CloudType cloudType) CloudSettingBloc(AuthenticatorType cloudType)
: super(CloudSettingState.initial(cloudType)) { : super(CloudSettingState.initial(cloudType)) {
on<CloudSettingEvent>((event, emit) async { on<CloudSettingEvent>((event, emit) async {
await event.when( await event.when(
initial: () async {}, initial: () async {},
updateCloudType: (CloudType newCloudType) async { updateCloudType: (AuthenticatorType newCloudType) async {
await setCloudType(newCloudType); await setAuthenticatorType(newCloudType);
emit(state.copyWith(cloudType: newCloudType)); emit(state.copyWith(cloudType: newCloudType));
}, },
); );
@ -22,17 +22,19 @@ class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
@freezed @freezed
class CloudSettingEvent with _$CloudSettingEvent { class CloudSettingEvent with _$CloudSettingEvent {
const factory CloudSettingEvent.initial() = _Initial; const factory CloudSettingEvent.initial() = _Initial;
const factory CloudSettingEvent.updateCloudType(CloudType newCloudType) = const factory CloudSettingEvent.updateCloudType(
_UpdateCloudType; AuthenticatorType newCloudType,
) = _UpdateCloudType;
} }
@freezed @freezed
class CloudSettingState with _$CloudSettingState { class CloudSettingState with _$CloudSettingState {
const factory CloudSettingState({ const factory CloudSettingState({
required CloudType cloudType, required AuthenticatorType cloudType,
}) = _CloudSettingState; }) = _CloudSettingState;
factory CloudSettingState.initial(CloudType cloudType) => CloudSettingState( factory CloudSettingState.initial(AuthenticatorType cloudType) =>
CloudSettingState(
cloudType: cloudType, cloudType: cloudType,
); );
} }

View File

@ -1,3 +1,4 @@
import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/appflowy_cloud_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart'; import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart';
@ -50,7 +51,16 @@ class SettingAppFlowyCloudView extends StatelessWidget {
children: [ children: [
const AppFlowyCloudEnableSync(), const AppFlowyCloudEnableSync(),
const VSpace(40), const VSpace(40),
if (Env.enableCustomCloud)
AppFlowyCloudURLs(didUpdateUrls: () => didResetServerUrl()), AppFlowyCloudURLs(didUpdateUrls: () => didResetServerUrl()),
if (!Env.enableCustomCloud)
Row(
children: [
FlowyText(LocaleKeys.settings_menu_cloudServerType.tr()),
const Spacer(),
const FlowyText(Env.afCloudUrl),
],
),
], ],
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:appflowy/env/cloud_env.dart'; 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/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart';
@ -19,8 +20,9 @@ class SettingCloud extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: getCloudType(), future: getAuthenticatorType(),
builder: (BuildContext context, AsyncSnapshot<CloudType> snapshot) { builder:
(BuildContext context, AsyncSnapshot<AuthenticatorType> snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final cloudType = snapshot.data!; final cloudType = snapshot.data!;
return BlocProvider( return BlocProvider(
@ -29,6 +31,7 @@ class SettingCloud extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return Column( return Column(
children: [ children: [
if (Env.enableCustomCloud)
Row( Row(
children: [ children: [
Expanded( Expanded(
@ -37,8 +40,8 @@ class SettingCloud extends StatelessWidget {
), ),
), ),
Tooltip( Tooltip(
message: message: LocaleKeys.settings_menu_cloudServerTypeTip
LocaleKeys.settings_menu_cloudServerTypeTip.tr(), .tr(),
child: CloudTypeSwitcher( child: CloudTypeSwitcher(
cloudType: state.cloudType, cloudType: state.cloudType,
onSelected: (newCloudType) { onSelected: (newCloudType) {
@ -67,15 +70,15 @@ class SettingCloud extends StatelessWidget {
); );
} }
Widget _viewFromCloudType(CloudType cloudType) { Widget _viewFromCloudType(AuthenticatorType cloudType) {
switch (cloudType) { switch (cloudType) {
case CloudType.local: case AuthenticatorType.local:
return SettingLocalCloud(didResetServerUrl: didResetServerUrl); return SettingLocalCloud(didResetServerUrl: didResetServerUrl);
case CloudType.supabase: case AuthenticatorType.supabase:
return SettingSupabaseCloudView( return SettingSupabaseCloudView(
didResetServerUrl: didResetServerUrl, didResetServerUrl: didResetServerUrl,
); );
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
return SettingAppFlowyCloudView( return SettingAppFlowyCloudView(
didResetServerUrl: didResetServerUrl, didResetServerUrl: didResetServerUrl,
); );
@ -84,8 +87,8 @@ class SettingCloud extends StatelessWidget {
} }
class CloudTypeSwitcher extends StatelessWidget { class CloudTypeSwitcher extends StatelessWidget {
final CloudType cloudType; final AuthenticatorType cloudType;
final Function(CloudType) onSelected; final Function(AuthenticatorType) onSelected;
const CloudTypeSwitcher({ const CloudTypeSwitcher({
required this.cloudType, required this.cloudType,
required this.onSelected, required this.onSelected,
@ -108,12 +111,12 @@ class CloudTypeSwitcher extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return CloudTypeItem( return CloudTypeItem(
cloudType: CloudType.values[index], cloudType: AuthenticatorType.values[index],
currentCloudtype: cloudType, currentCloudtype: cloudType,
onSelected: onSelected, onSelected: onSelected,
); );
}, },
itemCount: CloudType.values.length, itemCount: AuthenticatorType.values.length,
); );
}, },
); );
@ -121,9 +124,9 @@ class CloudTypeSwitcher extends StatelessWidget {
} }
class CloudTypeItem extends StatelessWidget { class CloudTypeItem extends StatelessWidget {
final CloudType cloudType; final AuthenticatorType cloudType;
final CloudType currentCloudtype; final AuthenticatorType currentCloudtype;
final Function(CloudType) onSelected; final Function(AuthenticatorType) onSelected;
const CloudTypeItem({ const CloudTypeItem({
required this.cloudType, required this.cloudType,
@ -154,13 +157,13 @@ class CloudTypeItem extends StatelessWidget {
} }
} }
String titleFromCloudType(CloudType cloudType) { String titleFromCloudType(AuthenticatorType cloudType) {
switch (cloudType) { switch (cloudType) {
case CloudType.local: case AuthenticatorType.local:
return LocaleKeys.settings_menu_cloudLocal.tr(); return LocaleKeys.settings_menu_cloudLocal.tr();
case CloudType.supabase: case AuthenticatorType.supabase:
return LocaleKeys.settings_menu_cloudSupabase.tr(); return LocaleKeys.settings_menu_cloudSupabase.tr();
case CloudType.appflowyCloud: case AuthenticatorType.appflowyCloud:
return LocaleKeys.settings_menu_cloudAppFlowy.tr(); return LocaleKeys.settings_menu_cloudAppFlowy.tr();
} }
} }

View File

@ -16,7 +16,6 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../../generated/locale_keys.g.dart'; import '../../../../generated/locale_keys.g.dart';
import '../../../../startup/launch_configuration.dart';
import '../../../../startup/startup.dart'; import '../../../../startup/startup.dart';
import '../../../../startup/tasks/prelude.dart'; import '../../../../startup/tasks/prelude.dart';
@ -210,10 +209,8 @@ class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
await context.read<SettingsLocationCubit>().setCustomPath(path); await context.read<SettingsLocationCubit>().setCustomPath(path);
await FlowyRunner.run( await FlowyRunner.run(
FlowyApp(), FlowyApp(),
integrationMode(), FlowyRunner.currentMode,
config: const LaunchConfiguration( isAnon: true,
autoRegistrationSupported: true,
),
); );
if (mounted) { if (mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -288,10 +285,8 @@ class _RecoverDefaultStorageButtonState
.resetDataStoragePathToApplicationDefault(); .resetDataStoragePathToApplicationDefault();
await FlowyRunner.run( await FlowyRunner.run(
FlowyApp(), FlowyApp(),
integrationMode(), FlowyRunner.currentMode,
config: const LaunchConfiguration( isAnon: true,
autoRegistrationSupported: true,
),
); );
if (mounted) { if (mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -1,4 +1,4 @@
platform :osx, '10.11' platform :osx, '10.13'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -17,7 +17,7 @@ A new flutter plugin project.
s.public_header_files = 'Classes/**/*.h' s.public_header_files = 'Classes/**/*.h'
s.dependency 'FlutterMacOS' s.dependency 'FlutterMacOS'
s.platform = :osx, '10.11' s.platform = :osx, '10.13'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0' s.swift_version = '5.0'
s.static_framework = true s.static_framework = true

View File

@ -1,4 +1,4 @@
platform :osx, '10.11' platform :osx, '10.13'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -15,7 +15,8 @@ void showSnapBar(BuildContext context, String title, {VoidCallback? onClosed}) {
}, },
child: FlowyText.medium( child: FlowyText.medium(
title, title,
fontSize: 16, fontSize: 12,
maxLines: 3,
), ),
), ),
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,

View File

@ -16,7 +16,7 @@ A new flutter plugin project.
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.dependency 'FlutterMacOS' s.dependency 'FlutterMacOS'
s.platform = :osx, '10.11' s.platform = :osx, '10.13'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0' s.swift_version = '5.0'
end end

View File

@ -271,12 +271,12 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.1" version: "5.0.2"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: "direct main" dependency: transitive
description: description:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
@ -1463,10 +1463,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.2.2"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -1495,10 +1495,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:

View File

@ -74,8 +74,7 @@ dependencies:
package_info_plus: ^4.0.1 package_info_plus: ^4.0.1
url_launcher: ^6.1.11 url_launcher: ^6.1.11
clipboard: ^0.1.3 clipboard: ^0.1.3
connectivity_plus: ^5.0.1 connectivity_plus: ^5.0.2
connectivity_plus_platform_interface: ^1.2.4
easy_localization: ^3.0.2 easy_localization: ^3.0.2
textfield_tags: ^2.0.2 textfield_tags: ^2.0.2
device_info_plus: ^9.0.1 device_info_plus: ^9.0.1
@ -91,7 +90,7 @@ dependencies:
charcode: ^1.3.1 charcode: ^1.3.1
collection: ^1.17.1 collection: ^1.17.1
bloc: ^8.1.2 bloc: ^8.1.2
shared_preferences: ^2.1.1 shared_preferences: ^2.2.2
google_fonts: ^4.0.5 google_fonts: ^4.0.5
percent_indicator: ^4.2.3 percent_indicator: ^4.2.3
calendar_view: calendar_view:

View File

@ -138,7 +138,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -785,7 +785,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -1326,7 +1326,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa 1.0.6", "itoa 1.0.6",
"phf 0.11.2", "phf 0.8.0",
"smallvec", "smallvec",
] ]
@ -1472,7 +1472,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -2830,7 +2830,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2846,7 +2846,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3267,7 +3267,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -4363,7 +4363,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -4455,19 +4454,6 @@ dependencies = [
"syn 1.0.109", "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.29",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -4709,7 +4695,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.4.1", "heck 0.4.1",
"itertools 0.11.0", "itertools 0.10.5",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -4730,7 +4716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.11.0", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.29", "syn 2.0.29",
@ -5025,7 +5011,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -5778,7 +5764,7 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",

View File

@ -57,7 +57,7 @@ custom-protocol = ["tauri/custom-protocol"]
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b578c83cc912255e48dea9e33a203a069ce7d0c5" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5090711272dbc503912544375307365c174bb804" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -5,7 +5,7 @@ import {
UserEventGetUserProfile, UserEventGetUserProfile,
UserEventGetUserSetting, UserEventGetUserSetting,
UserEventSetAppearanceSetting, UserEventSetAppearanceSetting,
UserEventSignIn, UserEventSignInWithEmailPassword,
UserEventSignOut, UserEventSignOut,
UserEventSignUp, UserEventSignUp,
UserEventUpdateUserProfile, UserEventUpdateUserProfile,
@ -99,7 +99,7 @@ export class AuthBackendService {
signIn = (params: { email: string; password: string }) => { signIn = (params: { email: string; password: string }) => {
const payload = SignInPayloadPB.fromObject({ email: params.email, password: params.password }); const payload = SignInPayloadPB.fromObject({ email: params.email, password: params.password });
return UserEventSignIn(payload); return UserEventSignInWithEmailPassword(payload);
}; };
signUp = (params: { name: string; email: string; password: string }) => { signUp = (params: { name: string; email: string; password: string }) => {

View File

@ -290,6 +290,8 @@
"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", "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",
"inputEncryptPrompt": "Please enter your encryption secret for", "inputEncryptPrompt": "Please enter your encryption secret for",
"clickToCopySecret": "Click to copy secret", "clickToCopySecret": "Click to copy secret",
"configServerSetting": "Configurate your server settings",
"configServerGuide": "After selecting `Quick Start`, navigate to `Settings` and then \"Cloud Setting\" to configure your self-hosted server.",
"inputTextFieldHint": "Your secret", "inputTextFieldHint": "Your secret",
"historicalUserList": "User login history", "historicalUserList": "User login history",
"historicalUserListTooltip": "This list displays your anonymous accounts. You can click on an account to view its details. Anonymous accounts are created by clicking the 'Get Started' button", "historicalUserListTooltip": "This list displays your anonymous accounts. You can click on an account to view its details. Anonymous accounts are created by clicking the 'Get Started' button",

View File

@ -1,6 +1,3 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]
[target.x86_64-apple-darwin] [target.x86_64-apple-darwin]
rustflags = ["-C", "target-cpu=native", "-C", "link-arg=-mmacosx-version-min=11.0"] rustflags = ["-C", "target-cpu=native", "-C", "link-arg=-mmacosx-version-min=11.0"]

View File

@ -124,7 +124,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -666,7 +666,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -1150,7 +1150,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf 0.11.2", "phf 0.8.0",
"smallvec", "smallvec",
] ]
@ -1251,6 +1251,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_yaml",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -1277,7 +1278,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -2470,7 +2471,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2486,7 +2487,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -2847,7 +2848,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -3663,7 +3664,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros 0.8.0", "phf_macros",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack", "proc-macro-hack",
] ]
@ -3683,7 +3684,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -3751,19 +3751,6 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -3967,7 +3954,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.4.1", "heck 0.4.1",
"itertools 0.11.0", "itertools 0.10.5",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -3988,7 +3975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.11.0", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.31",
@ -4327,7 +4314,7 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -4868,9 +4855,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.188" version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -4888,9 +4875,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.188" version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4931,6 +4918,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
dependencies = [
"indexmap 2.0.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "servo_arc" name = "servo_arc"
version = "0.3.0" version = "0.3.0"
@ -4980,7 +4980,7 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5090711272dbc503912544375307365c174bb804#5090711272dbc503912544375307365c174bb804"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -5950,6 +5950,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"

View File

@ -99,7 +99,7 @@ incremental = false
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b578c83cc912255e48dea9e33a203a069ce7d0c5" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5090711272dbc503912544375307365c174bb804" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #

View File

@ -34,6 +34,7 @@ flowy-server = { workspace = true }
flowy-server-config = { workspace = true} flowy-server-config = { workspace = true}
collab-integrate = { workspace = true } collab-integrate = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-derive = { path = "../../../shared-lib/flowy-derive" }
serde_yaml = "0.9.27"
[features] [features]
default = ["dart", "rev-sqlite"] default = ["dart", "rev-sqlite"]

View File

@ -0,0 +1,54 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use serde::{Deserialize, Serialize};
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct AppFlowyYamlConfiguration {
cloud_config: Vec<AFCloudConfiguration>,
}
pub fn save_appflowy_cloud_config(
root: impl AsRef<Path>,
new_config: &AFCloudConfiguration,
) -> Result<(), Box<dyn std::error::Error>> {
let file_path = root.as_ref().join("appflowy.yaml");
let mut config = read_yaml_file(&file_path).unwrap_or_default();
if !config
.cloud_config
.iter()
.any(|c| c.base_url == new_config.base_url)
{
config.cloud_config.push(new_config.clone());
write_yaml_file(&file_path, &config)?;
}
Ok(())
}
fn read_yaml_file(
file_path: impl AsRef<Path>,
) -> Result<AppFlowyYamlConfiguration, Box<dyn std::error::Error>> {
let mut file = File::open(file_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: AppFlowyYamlConfiguration = serde_yaml::from_str(&contents)?;
Ok(config)
}
fn write_yaml_file(
file_path: impl AsRef<Path>,
config: &AppFlowyYamlConfiguration,
) -> Result<(), Box<dyn std::error::Error>> {
let yaml_string = serde_yaml::to_string(config)?;
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file_path)?;
file.write_all(yaml_string.as_bytes())?;
Ok(())
}

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use flowy_server_config::af_cloud_config::AFCloudConfiguration; use flowy_server_config::af_cloud_config::AFCloudConfiguration;
@ -6,13 +8,17 @@ use flowy_server_config::AuthenticatorType;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct AppFlowyDartConfiguration { pub struct AppFlowyDartConfiguration {
/// The root path of the application
pub root: String,
/// This path will be used to store the user data /// This path will be used to store the user data
pub custom_app_path: String, pub custom_app_path: String,
pub origin_app_path: String, pub origin_app_path: String,
pub device_id: String, pub device_id: String,
pub cloud_type: AuthenticatorType, pub authenticator_type: AuthenticatorType,
pub(crate) supabase_config: SupabaseConfiguration, pub(crate) supabase_config: SupabaseConfiguration,
pub(crate) appflowy_cloud_config: AFCloudConfiguration, pub(crate) appflowy_cloud_config: AFCloudConfiguration,
#[serde(default)]
pub(crate) envs: HashMap<String, String>,
} }
impl AppFlowyDartConfiguration { impl AppFlowyDartConfiguration {
@ -20,12 +26,13 @@ impl AppFlowyDartConfiguration {
serde_json::from_str::<AppFlowyDartConfiguration>(s).unwrap() serde_json::from_str::<AppFlowyDartConfiguration>(s).unwrap()
} }
/// Parse the environment variable from the frontend application. The frontend will pub fn write_env(&self) {
/// pass the environment variable as a json string after launching. self.authenticator_type.write_env();
pub fn write_env_from(env_str: &str) { self.appflowy_cloud_config.write_env();
let configuration = Self::from_str(env_str); self.supabase_config.write_env();
configuration.cloud_type.write_env();
configuration.appflowy_cloud_config.write_env(); for (k, v) in self.envs.iter() {
configuration.supabase_config.write_env(); std::env::set_var(k, v);
}
} }
} }

View File

@ -10,9 +10,11 @@ use tracing::{error, trace};
use flowy_core::config::AppFlowyCoreConfig; use flowy_core::config::AppFlowyCoreConfig;
use flowy_core::*; use flowy_core::*;
use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
use flowy_server_config::AuthenticatorType;
use lib_dispatch::prelude::ToBytes; use lib_dispatch::prelude::ToBytes;
use lib_dispatch::prelude::*; use lib_dispatch::prelude::*;
use crate::appflowy_yaml::save_appflowy_cloud_config;
use crate::env_serde::AppFlowyDartConfiguration; use crate::env_serde::AppFlowyDartConfiguration;
use crate::notification::DartNotificationSender; use crate::notification::DartNotificationSender;
use crate::{ use crate::{
@ -20,6 +22,7 @@ use crate::{
model::{FFIRequest, FFIResponse}, model::{FFIRequest, FFIResponse},
}; };
mod appflowy_yaml;
mod c; mod c;
mod env_serde; mod env_serde;
mod model; mod model;
@ -53,10 +56,11 @@ pub extern "C" fn init_sdk(data: *mut c_char) -> i64 {
let c_str = unsafe { CStr::from_ptr(data) }; let c_str = unsafe { CStr::from_ptr(data) };
let serde_str = c_str.to_str().unwrap(); let serde_str = c_str.to_str().unwrap();
let configuration = AppFlowyDartConfiguration::from_str(serde_str); let configuration = AppFlowyDartConfiguration::from_str(serde_str);
configuration.write_env();
configuration.cloud_type.write_env(); if configuration.authenticator_type == AuthenticatorType::AppFlowyCloud {
configuration.appflowy_cloud_config.write_env(); let _ = save_appflowy_cloud_config(&configuration.root, &configuration.appflowy_cloud_config);
configuration.supabase_config.write_env(); }
let log_crates = vec!["flowy-ffi".to_string()]; let log_crates = vec!["flowy-ffi".to_string()];
let config = AppFlowyCoreConfig::new( let config = AppFlowyCoreConfig::new(
@ -170,8 +174,6 @@ pub extern "C" fn backend_log(level: i64, data: *const c_char) {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_env(data: *const c_char) { pub extern "C" fn set_env(_data: *const c_char) {
let c_str = unsafe { CStr::from_ptr(data) }; // Deprecated
let serde_str = c_str.to_str().unwrap();
AppFlowyDartConfiguration::write_env_from(serde_str);
} }

View File

@ -134,7 +134,7 @@ impl EventIntegrationTest {
auth_type: AuthTypePB::AFCloud, auth_type: AuthTypePB::AFCloud,
}; };
let sign_in_url = EventBuilder::new(self.clone()) let sign_in_url = EventBuilder::new(self.clone())
.event(GetSignInURL) .event(GenerateSignInURL)
.payload(payload) .payload(payload)
.async_send() .async_send()
.await .await

View File

@ -69,7 +69,7 @@ async fn sign_in_with_invalid_email() {
assert_eq!( assert_eq!(
EventBuilder::new(sdk) EventBuilder::new(sdk)
.event(SignIn) .event(SignInWithEmailPassword)
.payload(request) .payload(request)
.async_send() .async_send()
.await .await
@ -95,7 +95,7 @@ async fn sign_in_with_invalid_password() {
}; };
assert!(EventBuilder::new(sdk) assert!(EventBuilder::new(sdk)
.event(SignIn) .event(SignInWithEmailPassword)
.payload(request) .payload(request)
.async_send() .async_send()
.await .await

View File

@ -77,6 +77,12 @@ impl UserCloudServiceProvider for ServerProvider {
} }
} }
fn set_network_reachable(&self, reachable: bool) {
if let Ok(server) = self.get_server(&self.get_server_type()) {
server.set_network_reachable(reachable);
}
}
fn set_encrypt_secret(&self, secret: String) { fn set_encrypt_secret(&self, secret: String) {
tracing::info!("🔑Set encrypt secret"); tracing::info!("🔑Set encrypt secret");
self.encryption.write().set_secret(secret); self.encryption.write().set_secret(secret);

View File

@ -19,6 +19,7 @@ impl From<AppResponseError> for FlowyError {
AppErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig, AppErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig,
AppErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized, AppErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized,
AppErrorCode::NotEnoughPermissions => ErrorCode::NotEnoughPermissions, AppErrorCode::NotEnoughPermissions => ErrorCode::NotEnoughPermissions,
AppErrorCode::NetworkError => ErrorCode::HttpError,
_ => ErrorCode::Internal, _ => ErrorCode::Internal,
}; };

View File

@ -5,7 +5,7 @@ pub mod supabase_config;
pub const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE"; pub const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE";
#[derive(Deserialize_repr, Debug, Clone)] #[derive(Deserialize_repr, Debug, Clone, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum AuthenticatorType { pub enum AuthenticatorType {
Local = 0, Local = 0,

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use client_api::entity::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset}; use client_api::entity::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
use client_api::entity::{AFRole, AFWorkspace, InsertCollabParams, OAuthProvider}; use client_api::entity::{AFRole, AFWorkspace, AuthProvider, InsertCollabParams};
use collab_entity::CollabObject; use collab_entity::CollabObject;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -38,7 +38,7 @@ impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
where where
T: AFServer, T: AFServer,
{ {
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let params = oauth_params_from_box_any(params)?; let params = oauth_params_from_box_any(params)?;
@ -48,7 +48,7 @@ where
} }
// Zack: Not sure if this is needed anymore since sign_up handles both cases // Zack: Not sure if this is needed anymore since sign_up handles both cases
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let client = try_get_client?; let client = try_get_client?;
@ -58,12 +58,12 @@ where
}) })
} }
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> { fn sign_out(&self, _token: Option<String>) -> FutureResult<(), FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { Ok(try_get_client?.sign_out().await?) }) FutureResult::new(async move { Ok(try_get_client?.sign_out().await?) })
} }
fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, Error> { fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, FlowyError> {
let email = email.to_string(); let email = email.to_string();
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
@ -82,8 +82,7 @@ where
client_api::Client::new(client.base_url(), client.ws_addr(), client.gotrue_url()); client_api::Client::new(client.base_url(), client.ws_addr(), client.gotrue_url());
admin_client admin_client
.sign_in_password(&admin_email, &admin_password) .sign_in_password(&admin_email, &admin_password)
.await .await?;
.unwrap();
let action_link = admin_client.generate_sign_in_action_link(&email).await?; let action_link = admin_client.generate_sign_in_action_link(&email).await?;
let sign_in_url = client.extract_sign_in_url(&action_link).await?; let sign_in_url = client.extract_sign_in_url(&action_link).await?;
@ -91,8 +90,8 @@ where
}) })
} }
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, Error> { fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, FlowyError> {
let provider = OAuthProvider::from(provider); let provider = AuthProvider::from(provider);
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let provider = provider.ok_or(anyhow!("invalid provider"))?; let provider = provider.ok_or(anyhow!("invalid provider"))?;
@ -107,7 +106,7 @@ where
&self, &self,
_credential: UserCredentials, _credential: UserCredentials,
params: UpdateUserProfileParams, params: UpdateUserProfileParams,
) -> FutureResult<(), Error> { ) -> FutureResult<(), FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let client = try_get_client?; let client = try_get_client?;
@ -142,11 +141,11 @@ where
}) })
} }
fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let workspaces = try_get_client?.get_workspaces().await?; let workspaces = try_get_client?.get_workspaces().await?;
Ok(to_user_workspaces(workspaces.0)?) to_user_workspaces(workspaces.0)
}) })
} }

View File

@ -37,6 +37,7 @@ pub struct AppFlowyCloudServer {
pub(crate) config: AFCloudConfiguration, pub(crate) config: AFCloudConfiguration,
pub(crate) client: Arc<AFCloudClient>, pub(crate) client: Arc<AFCloudClient>,
enable_sync: Arc<AtomicBool>, enable_sync: Arc<AtomicBool>,
network_reachable: Arc<AtomicBool>,
#[allow(dead_code)] #[allow(dead_code)]
device_id: String, device_id: String,
ws_client: Arc<WSClient>, ws_client: Arc<WSClient>,
@ -47,6 +48,7 @@ impl AppFlowyCloudServer {
let api_client = AFCloudClient::new(&config.base_url, &config.ws_base_url, &config.gotrue_url); let api_client = AFCloudClient::new(&config.base_url, &config.ws_base_url, &config.gotrue_url);
let token_state_rx = api_client.subscribe_token_state(); let token_state_rx = api_client.subscribe_token_state();
let enable_sync = Arc::new(AtomicBool::new(enable_sync)); let enable_sync = Arc::new(AtomicBool::new(enable_sync));
let network_reachable = Arc::new(AtomicBool::new(true));
let ws_client = WSClient::new(WSClientConfig::default(), api_client.clone()); let ws_client = WSClient::new(WSClientConfig::default(), api_client.clone());
let ws_client = Arc::new(ws_client); let ws_client = Arc::new(ws_client);
@ -63,6 +65,7 @@ impl AppFlowyCloudServer {
config, config,
client: api_client, client: api_client,
enable_sync, enable_sync,
network_reachable,
device_id, device_id,
ws_client, ws_client,
} }
@ -117,8 +120,14 @@ impl AppFlowyServer for AppFlowyCloudServer {
self.enable_sync.store(enable, Ordering::SeqCst); self.enable_sync.store(enable, Ordering::SeqCst);
} }
fn set_network_reachable(&self, reachable: bool) {
self.network_reachable.store(reachable, Ordering::SeqCst);
}
fn user_service(&self) -> Arc<dyn UserCloudService> { fn user_service(&self) -> Arc<dyn UserCloudService> {
let server = AFServerImpl(self.get_client()); let server = AFServerImpl {
client: self.get_client(),
};
let mut user_change = self.ws_client.subscribe_user_changed(); let mut user_change = self.ws_client.subscribe_user_changed();
let (tx, rx) = tokio::sync::mpsc::channel(1); let (tx, rx) = tokio::sync::mpsc::channel(1);
tokio::spawn(async move { tokio::spawn(async move {
@ -139,17 +148,23 @@ impl AppFlowyServer for AppFlowyCloudServer {
} }
fn folder_service(&self) -> Arc<dyn FolderCloudService> { fn folder_service(&self) -> Arc<dyn FolderCloudService> {
let server = AFServerImpl(self.get_client()); let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudFolderCloudServiceImpl(server)) Arc::new(AFCloudFolderCloudServiceImpl(server))
} }
fn database_service(&self) -> Arc<dyn DatabaseCloudService> { fn database_service(&self) -> Arc<dyn DatabaseCloudService> {
let server = AFServerImpl(self.get_client()); let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudDatabaseCloudServiceImpl(server)) Arc::new(AFCloudDatabaseCloudServiceImpl(server))
} }
fn document_service(&self) -> Arc<dyn DocumentCloudService> { fn document_service(&self) -> Arc<dyn DocumentCloudService> {
let server = AFServerImpl(self.get_client()); let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudDocumentCloudServiceImpl(server)) Arc::new(AFCloudDocumentCloudServiceImpl(server))
} }
@ -184,7 +199,9 @@ impl AppFlowyServer for AppFlowyCloudServer {
} }
fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> { fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
let client = AFServerImpl(self.get_client()); let client = AFServerImpl {
client: self.get_client(),
};
Some(Arc::new(AFCloudFileStorageServiceImpl::new(client))) Some(Arc::new(AFCloudFileStorageServiceImpl::new(client)))
} }
} }
@ -274,15 +291,17 @@ pub trait AFServer: Send + Sync + 'static {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct AFServerImpl(pub Option<Arc<AFCloudClient>>); pub struct AFServerImpl {
client: Option<Arc<AFCloudClient>>,
}
impl AFServer for AFServerImpl { impl AFServer for AFServerImpl {
fn get_client(&self) -> Option<Arc<AFCloudClient>> { fn get_client(&self) -> Option<Arc<AFCloudClient>> {
self.0.clone() self.client.clone()
} }
fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error> { fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error> {
match self.0.clone() { match self.client.clone() {
None => Err( None => Err(
FlowyError::new( FlowyError::new(
ErrorCode::DataSyncRequired, ErrorCode::DataSyncRequired,

View File

@ -26,7 +26,7 @@ pub(crate) struct LocalServerUserAuthServiceImpl {
} }
impl UserCloudService for LocalServerUserAuthServiceImpl { impl UserCloudService for LocalServerUserAuthServiceImpl {
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
FutureResult::new(async move { FutureResult::new(async move {
let params = params.unbox_or_error::<SignUpParams>()?; let params = params.unbox_or_error::<SignUpParams>()?;
let uid = ID_GEN.lock().next_id(); let uid = ID_GEN.lock().next_id();
@ -52,7 +52,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
}) })
} }
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
let db = self.db.clone(); let db = self.db.clone();
FutureResult::new(async move { FutureResult::new(async move {
let params: SignInParams = params.unbox_or_error::<SignInParams>()?; let params: SignInParams = params.unbox_or_error::<SignInParams>()?;
@ -76,27 +76,29 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
}) })
} }
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> { fn sign_out(&self, _token: Option<String>) -> FutureResult<(), FlowyError> {
FutureResult::new(async { Ok(()) }) FutureResult::new(async { Ok(()) })
} }
fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, Error> { fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async { FutureResult::new(async {
Err(anyhow::anyhow!( Err(
"Can't generate callback url when using offline mode" FlowyError::internal().with_context("Can't generate callback url when using offline mode"),
)) )
}) })
} }
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, Error> { fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async { Err(anyhow::anyhow!("Can't oauth url when using offline mode")) }) FutureResult::new(async {
Err(FlowyError::internal().with_context("Can't oauth url when using offline mode"))
})
} }
fn update_user( fn update_user(
&self, &self,
_credential: UserCredentials, _credential: UserCredentials,
_params: UpdateUserProfileParams, _params: UpdateUserProfileParams,
) -> FutureResult<(), Error> { ) -> FutureResult<(), FlowyError> {
FutureResult::new(async { Ok(()) }) FutureResult::new(async { Ok(()) })
} }
@ -120,7 +122,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
}) })
} }
fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, FlowyError> {
FutureResult::new(async { Ok(vec![]) }) FutureResult::new(async { Ok(vec![]) })
} }

View File

@ -52,6 +52,12 @@ pub trait AppFlowyServer: Send + Sync + 'static {
/// * `_enable` - A boolean to toggle the server synchronization. /// * `_enable` - A boolean to toggle the server synchronization.
fn set_enable_sync(&self, _uid: i64, _enable: bool) {} fn set_enable_sync(&self, _uid: i64, _enable: bool) {}
/// Sets the network reachability status.
///
/// # Arguments
/// * `reachable`: A boolean indicating whether the network is reachable.
fn set_network_reachable(&self, _reachable: bool) {}
/// Provides access to cloud-based user management functionalities. This includes operations /// Provides access to cloud-based user management functionalities. This includes operations
/// such as user registration, authentication, profile management, and handling of user workspaces. /// such as user registration, authentication, profile management, and handling of user workspaces.
/// The interface also offers methods for managing collaborative objects, subscribing to user updates, /// The interface also offers methods for managing collaborative objects, subscribing to user updates,

View File

@ -64,7 +64,7 @@ impl<T> UserCloudService for SupabaseUserServiceImpl<T>
where where
T: SupabaseServerService, T: SupabaseServerService,
{ {
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;
@ -129,7 +129,7 @@ where
}) })
} }
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error> { fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;
@ -159,23 +159,19 @@ where
}) })
} }
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> { fn sign_out(&self, _token: Option<String>) -> FutureResult<(), FlowyError> {
FutureResult::new(async { Ok(()) }) FutureResult::new(async { Ok(()) })
} }
fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, Error> { fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async { FutureResult::new(async {
Err(anyhow::anyhow!( Err(FlowyError::internal().with_context("Can't generate callback url when using supabase"))
"Can't generate callback url when using supabase"
))
}) })
} }
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, Error> { fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async { FutureResult::new(async {
Err(anyhow::anyhow!( Err(FlowyError::internal().with_context("Can't generate oauth url when using supabase"))
"Can't generate oauth url when using supabase"
))
}) })
} }
@ -183,7 +179,7 @@ where
&self, &self,
_credential: UserCredentials, _credential: UserCredentials,
params: UpdateUserProfileParams, params: UpdateUserProfileParams,
) -> FutureResult<(), Error> { ) -> FutureResult<(), FlowyError> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;
@ -226,7 +222,7 @@ where
}) })
} }
fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, FlowyError> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;

View File

@ -59,32 +59,32 @@ pub trait UserCloudService: Send + Sync + 'static {
/// Sign up a new account. /// Sign up a new account.
/// The type of the params is defined the this trait's implementation. /// The type of the params is defined the this trait's implementation.
/// Use the `unbox_or_error` of the [BoxAny] to get the params. /// Use the `unbox_or_error` of the [BoxAny] to get the params.
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error>; fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError>;
/// Sign in an account /// Sign in an account
/// The type of the params is defined the this trait's implementation. /// The type of the params is defined the this trait's implementation.
fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, Error>; fn sign_in(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError>;
/// Sign out an account /// Sign out an account
fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error>; fn sign_out(&self, token: Option<String>) -> FutureResult<(), FlowyError>;
/// Generate a sign in url for the user with the given email /// Generate a sign in url for the user with the given email
/// Currently, only use the admin client for testing /// Currently, only use the admin client for testing
fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, Error>; fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, FlowyError>;
/// When the user opens the OAuth URL, it redirects to the corresponding provider's OAuth web page. /// When the user opens the OAuth URL, it redirects to the corresponding provider's OAuth web page.
/// After the user is authenticated, the browser will open a deep link to the AppFlowy app (iOS, macOS, etc.), /// After the user is authenticated, the browser will open a deep link to the AppFlowy app (iOS, macOS, etc.),
/// which will call [Client::sign_in_with_url] to sign in. /// which will call [Client::sign_in_with_url] to sign in.
/// ///
/// For example, the OAuth URL on Google looks like `https://appflowy.io/authorize?provider=google`. /// For example, the OAuth URL on Google looks like `https://appflowy.io/authorize?provider=google`.
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, Error>; fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, FlowyError>;
/// Using the user's token to update the user information /// Using the user's token to update the user information
fn update_user( fn update_user(
&self, &self,
credential: UserCredentials, credential: UserCredentials,
params: UpdateUserProfileParams, params: UpdateUserProfileParams,
) -> FutureResult<(), Error>; ) -> FutureResult<(), FlowyError>;
/// Get the user information using the user's token or uid /// Get the user information using the user's token or uid
/// return None if the user is not found /// return None if the user is not found
@ -93,7 +93,7 @@ pub trait UserCloudService: Send + Sync + 'static {
fn open_workspace(&self, workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError>; fn open_workspace(&self, workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError>;
/// Return the all the workspaces of the user /// Return the all the workspaces of the user
fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>; fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, FlowyError>;
fn add_workspace_member( fn add_workspace_member(
&self, &self,

View File

@ -180,7 +180,7 @@ impl EncryptionType {
} }
} }
pub fn is_need_encrypt_secret(&self) -> bool { pub fn require_encrypt_secret(&self) -> bool {
match self { match self {
EncryptionType::NoEncryption => false, EncryptionType::NoEncryption => false,
EncryptionType::SelfEncryption(sign) => !sign.is_empty(), EncryptionType::SelfEncryption(sign) => !sign.is_empty(),

View File

@ -35,7 +35,7 @@ fn upgrade_store_preferences(
} }
#[tracing::instrument(level = "debug", name = "sign_in", skip(data, manager), fields(email = %data.email), err)] #[tracing::instrument(level = "debug", name = "sign_in", skip(data, manager), fields(email = %data.email), err)]
pub async fn sign_in( pub async fn sign_in_with_email_password_handler(
data: AFPluginData<SignInPayloadPB>, data: AFPluginData<SignInPayloadPB>,
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserProfilePB, FlowyError> { ) -> DataResult<UserProfilePB, FlowyError> {
@ -43,10 +43,7 @@ pub async fn sign_in(
let params: SignInParams = data.into_inner().try_into()?; let params: SignInParams = data.into_inner().try_into()?;
let auth_type = params.auth_type.clone(); let auth_type = params.auth_type.clone();
let user_profile: UserProfilePB = manager let user_profile: UserProfilePB = manager.sign_in(params, auth_type).await?.into();
.sign_in(BoxAny::new(params), auth_type)
.await?
.into();
data_result_ok(user_profile) data_result_ok(user_profile)
} }
@ -264,7 +261,7 @@ pub async fn oauth_handler(
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub async fn get_sign_in_url_handler( pub async fn gen_sign_in_url_handler(
data: AFPluginData<SignInUrlPayloadPB>, data: AFPluginData<SignInUrlPayloadPB>,
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<SignInUrlPB, FlowyError> { ) -> DataResult<SignInUrlPB, FlowyError> {
@ -274,8 +271,7 @@ pub async fn get_sign_in_url_handler(
let sign_in_url = manager let sign_in_url = manager
.generate_sign_in_url_with_email(&auth_type, &params.email) .generate_sign_in_url_with_email(&auth_type, &params.email)
.await?; .await?;
let resp = SignInUrlPB { sign_in_url }; data_result_ok(SignInUrlPB { sign_in_url })
data_result_ok(resp)
} }
#[tracing::instrument(level = "debug", skip_all, err)] #[tracing::instrument(level = "debug", skip_all, err)]
@ -459,6 +455,7 @@ pub async fn update_network_state_handler(
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
let reachable = data.into_inner().ty.is_reachable(); let reachable = data.into_inner().ty.is_reachable();
manager.cloud_services.set_network_reachable(reachable);
manager manager
.user_status_callback .user_status_callback
.read() .read()

View File

@ -25,7 +25,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.name("Flowy-User") .name("Flowy-User")
.state(user_session) .state(user_session)
.state(store_preferences) .state(store_preferences)
.event(UserEvent::SignIn, sign_in) .event(UserEvent::SignInWithEmailPassword, sign_in_with_email_password_handler)
.event(UserEvent::SignUp, sign_up) .event(UserEvent::SignUp, sign_up)
.event(UserEvent::InitUser, init_user_handler) .event(UserEvent::InitUser, init_user_handler)
.event(UserEvent::GetUserProfile, get_user_profile_handler) .event(UserEvent::GetUserProfile, get_user_profile_handler)
@ -39,7 +39,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::SetEncryptionSecret, set_encrypt_secret_handler) .event(UserEvent::SetEncryptionSecret, set_encrypt_secret_handler)
.event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler) .event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler)
.event(UserEvent::OauthSignIn, oauth_handler) .event(UserEvent::OauthSignIn, oauth_handler)
.event(UserEvent::GetSignInURL, get_sign_in_url_handler) .event(UserEvent::GenerateSignInURL, gen_sign_in_url_handler)
.event(UserEvent::GetOauthURLWithProvider, sign_in_with_provider_handler) .event(UserEvent::GetOauthURLWithProvider, sign_in_with_provider_handler)
.event(UserEvent::GetAllWorkspace, get_all_workspace_handler) .event(UserEvent::GetAllWorkspace, get_all_workspace_handler)
.event(UserEvent::OpenWorkspace, open_workspace_handler) .event(UserEvent::OpenWorkspace, open_workspace_handler)
@ -69,7 +69,7 @@ pub enum UserEvent {
/// Only use when the [Authenticator] is Local or SelfHosted /// Only use when the [Authenticator] is Local or SelfHosted
/// Logging into an account using a register email and password /// Logging into an account using a register email and password
#[event(input = "SignInPayloadPB", output = "UserProfilePB")] #[event(input = "SignInPayloadPB", output = "UserProfilePB")]
SignIn = 0, SignInWithEmailPassword = 0,
/// Only use when the [Authenticator] is Local or SelfHosted /// Only use when the [Authenticator] is Local or SelfHosted
/// Creating a new account /// Creating a new account
@ -111,7 +111,7 @@ pub enum UserEvent {
/// Get the OAuth callback url /// Get the OAuth callback url
/// Only use when the [Authenticator] is AFCloud /// Only use when the [Authenticator] is AFCloud
#[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")] #[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")]
GetSignInURL = 11, GenerateSignInURL = 11,
#[event(input = "OauthProviderPB", output = "OauthProviderDataPB")] #[event(input = "OauthProviderPB", output = "OauthProviderDataPB")]
GetOauthURLWithProvider = 12, GetOauthURLWithProvider = 12,
@ -234,55 +234,73 @@ pub trait UserStatusCallback: Send + Sync + 'static {
fn did_update_network(&self, _reachable: bool) {} fn did_update_network(&self, _reachable: bool) {}
} }
/// The user cloud service provider. /// `UserCloudServiceProvider` defines a set of methods for managing user cloud services,
/// The provider can be supabase, firebase, aws, or any other cloud service. /// including token management, synchronization settings, network reachability, and authentication.
///
/// This trait is intended for implementation by providers that offer cloud-based services for users.
/// It includes methods for handling authentication tokens, enabling/disabling synchronization,
/// setting network reachability, managing encryption secrets, and accessing user-specific cloud services.
pub trait UserCloudServiceProvider: Send + Sync + 'static { pub trait UserCloudServiceProvider: Send + Sync + 'static {
/// Sets the authentication token for the cloud service.
///
/// # Arguments
/// * `token`: A string slice representing the authentication token.
///
/// # Returns
/// A `Result` which is `Ok` if the token is successfully set, or a `FlowyError` otherwise.
fn set_token(&self, token: &str) -> Result<(), FlowyError>; fn set_token(&self, token: &str) -> Result<(), FlowyError>;
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
None
}
/// Subscribes to the state of the authentication token.
///
/// # Returns
/// An `Option` containing a `WatchStream<UserTokenState>` if available, or `None` otherwise.
/// The stream allows the caller to watch for changes in the token state.
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>>;
/// Sets the synchronization state for a user.
///
/// # Arguments
/// * `uid`: An i64 representing the user ID.
/// * `enable_sync`: A boolean indicating whether synchronization should be enabled or disabled.
fn set_enable_sync(&self, uid: i64, enable_sync: bool); fn set_enable_sync(&self, uid: i64, enable_sync: bool);
/// Sets the network reachability status.
///
/// # Arguments
/// * `reachable`: A boolean indicating whether the network is reachable.
fn set_network_reachable(&self, reachable: bool);
/// Sets the encryption secret for secure communication.
///
/// # Arguments
/// * `secret`: A `String` representing the encryption secret.
fn set_encrypt_secret(&self, secret: String); fn set_encrypt_secret(&self, secret: String);
/// Sets the authenticator used for authentication processes.
///
/// # Arguments
/// * `authenticator`: An `Authenticator` object.
fn set_authenticator(&self, authenticator: Authenticator); fn set_authenticator(&self, authenticator: Authenticator);
/// Retrieves the current authenticator.
///
/// # Returns
/// The current `Authenticator` object.
fn get_authenticator(&self) -> Authenticator; fn get_authenticator(&self) -> Authenticator;
/// Retrieves the user-specific cloud service.
///
/// # Returns
/// A `Result` containing an `Arc<dyn UserCloudService>` if successful, or a `FlowyError` otherwise.
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError>; fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError>;
/// Retrieves the service URL.
///
/// # Returns
/// A `String` representing the service URL.
fn service_url(&self) -> String; fn service_url(&self) -> String;
} }
impl<T> UserCloudServiceProvider for Arc<T>
where
T: UserCloudServiceProvider,
{
fn set_token(&self, token: &str) -> Result<(), FlowyError> {
(**self).set_token(token)
}
fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
(**self).set_enable_sync(uid, enable_sync)
}
fn set_encrypt_secret(&self, secret: String) {
(**self).set_encrypt_secret(secret)
}
fn set_authenticator(&self, authenticator: Authenticator) {
(**self).set_authenticator(authenticator)
}
fn get_authenticator(&self) -> Authenticator {
(**self).get_authenticator()
}
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
(**self).get_user_service()
}
fn service_url(&self) -> String {
(**self).service_url()
}
}
/// Acts as a placeholder [UserStatusCallback] for the user session, but does not perform any function /// Acts as a placeholder [UserStatusCallback] for the user session, but does not perform any function
pub(crate) struct DefaultUserStatusCallback; pub(crate) struct DefaultUserStatusCallback;
impl UserStatusCallback for DefaultUserStatusCallback { impl UserStatusCallback for DefaultUserStatusCallback {

View File

@ -291,14 +291,14 @@ impl UserManager {
#[tracing::instrument(level = "debug", skip(self, params))] #[tracing::instrument(level = "debug", skip(self, params))]
pub async fn sign_in( pub async fn sign_in(
&self, &self,
params: BoxAny, params: SignInParams,
authenticator: Authenticator, authenticator: Authenticator,
) -> Result<UserProfile, FlowyError> { ) -> Result<UserProfile, FlowyError> {
self.update_authenticator(&authenticator).await; self.update_authenticator(&authenticator).await;
let response: AuthResponse = self let response: AuthResponse = self
.cloud_services .cloud_services
.get_user_service()? .get_user_service()?
.sign_in(params) .sign_in(BoxAny::new(params))
.await?; .await?;
let session = Session::from(&response); let session = Session::from(&response);
self.prepare_user(&session).await; self.prepare_user(&session).await;
@ -362,7 +362,7 @@ impl UserManager {
let auth_service = self.cloud_services.get_user_service()?; let auth_service = self.cloud_services.get_user_service()?;
let response: AuthResponse = auth_service.sign_up(params).await?; let response: AuthResponse = auth_service.sign_up(params).await?;
let user_profile = UserProfile::from((&response, &authenticator)); let user_profile = UserProfile::from((&response, &authenticator));
if user_profile.encryption_type.is_need_encrypt_secret() { if user_profile.encryption_type.require_encrypt_secret() {
self self
.resumable_sign_up .resumable_sign_up
.lock() .lock()

View File

@ -51,9 +51,7 @@ private = true
script = [ script = [
""" """
cd rust-lib/ cd rust-lib/
rustup show RUSTFLAGS="--cfg tokio_unstable" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
echo cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cd ../ cd ../
""", """,
] ]
@ -64,8 +62,6 @@ private = true
script = [ script = [
""" """
cd rust-lib/ cd rust-lib/
rustup show
echo RUSTFLAGS="-C target-cpu=native -C link-arg=-mmacosx-version-min=11.0" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
RUSTFLAGS="--cfg tokio_unstable" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}" RUSTFLAGS="--cfg tokio_unstable" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cd ../ cd ../
""", """,
@ -106,6 +102,16 @@ script = [
] ]
script_runner = "@shell" script_runner = "@shell"
[tasks.sdk-release-build.mac]
script = [
"""
cd rust-lib/
cargo build --profile ${CARGO_PROFILE} --${BUILD_FLAG} --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cd ../
""",
]
script_runner = "@shell"
# #
[tasks.post-desktop] [tasks.post-desktop]
mac_alias = "post-desktop-macos" mac_alias = "post-desktop-macos"

View File

@ -33,7 +33,7 @@ dependencies = ["inner_build_test_backend"]
description = "Run flutter unit tests" description = "Run flutter unit tests"
script = ''' script = '''
cd appflowy_flutter cd appflowy_flutter
flutter test --dart-define=RUST_LOG=${RUST_LOG} -j, --concurrency=1 --coverage flutter test -j, --concurrency=1 --coverage
''' '''
[tasks.dart_unit_test_no_build] [tasks.dart_unit_test_no_build]
@ -57,7 +57,7 @@ dependencies = ["copy-from-build-to-sandbox-folder"]
description = "Run flutter unit tests" description = "Run flutter unit tests"
script = ''' script = '''
cd appflowy_flutter cd appflowy_flutter
flutter test --dart-define=RUST_LOG=${RUST_LOG} -j, --concurrency=1 --coverage flutter test -j, --concurrency=1 --coverage
''' '''
script_runner = "@shell" script_runner = "@shell"
@ -259,6 +259,7 @@ run_task = { name = [
[tasks.build_test_backend] [tasks.build_test_backend]
env = { RUST_LOG = "trace" }
script = ''' script = '''
cargo make --profile test-macos-$(uname -m) inner_build_test_backend cargo make --profile test-macos-$(uname -m) inner_build_test_backend
''' '''
@ -292,6 +293,7 @@ windows_alias = "compile_test_backend_windows"
linux_alias = "compile_test_backend_default" linux_alias = "compile_test_backend_default"
[tasks.compile_test_backend_default] [tasks.compile_test_backend_default]
env = { RUST_LOG = "trace" }
private = true private = true
script = [ script = [
""" """