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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 1021 additions and 446 deletions

View File

@ -119,7 +119,8 @@ jobs:
run: flutter analyze .
- 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
with:
@ -297,17 +298,9 @@ jobs:
with:
name: ${{ github.run_id }}-${{ matrix.os }}
- name: Uncompress appflowy_flutter
- name: Uncompressed appflowy_flutter
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
working-directory: frontend
run: cargo make pub_get
@ -327,19 +320,101 @@ jobs:
fi
shell: bash
- name: Upload coverage to Codecov
uses: Wandalen/wretry.action@v1.0.36
cloud_integration_test:
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:
action: codecov/codecov-action@v3
with: |
name: appflowy
flags: appflowy_flutter_integrateion_test
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
repository: AppFlowy-IO/AppFlowy-Cloud
path: AppFlowy-Cloud
depth: 1
- name: Prepare appflowy cloud env
working-directory: AppFlowy-Cloud
run: |
# log level
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:
needs: [prepare]
@ -411,7 +486,7 @@ jobs:
with:
name: ${{ github.run_id }}-${{ matrix.os }}
- name: Uncompress appflowy_flutter
- name: Uncompressed appflowy_flutter
run: tar -xf appflowy_flutter.tar.gz
- name: Build flutter product

View File

@ -309,7 +309,7 @@
{
"label": "AF: Generate Env File",
"type": "shell",
"command": "dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs ",
"command": "dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs",
"options": {
"cwd": "${workspaceFolder}/appflowy_flutter"
}

View File

@ -28,6 +28,7 @@ LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.3.8"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
# If you update the macOS's CRATE_TYPE, don't forget to update the
# 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_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
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/generated/locale_keys.g.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:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
@ -10,15 +11,15 @@ import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('auth', () {
group('supabase auth', () {
testWidgets('sign in with supabase', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton();
tester.expectToSeeHomePage();
});
testWidgets('sign out with supabase', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton();
// Open the setting page and sign out
@ -35,7 +36,7 @@ void main() {
});
testWidgets('sign in as annoymous', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapSignInAsGuest();
// should not see the sync setting page when sign in as annoymous
@ -65,7 +66,7 @@ void main() {
// });
testWidgets('enable sync', (tester) async {
await tester.initializeAppFlowy(cloudType: CloudType.supabase);
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
await tester.tapGoogleLoginInButton();
// Open the setting page and sign out
@ -73,15 +74,15 @@ void main() {
await tester.openSettingsPage(SettingsPage.cloud);
// the switch should be on by default
tester.assertEnableSyncSwitchValue(true);
await tester.toggleEnableSync();
tester.assertSupabaseEnableSyncSwitchValue(true);
await tester.toggleEnableSync(SupabaseEnableSync);
// the switch should be off
tester.assertEnableSyncSwitchValue(false);
tester.assertSupabaseEnableSyncSwitchValue(false);
// the switch should be on after toggling
await tester.toggleEnableSync();
tester.assertEnableSyncSwitchValue(true);
await tester.toggleEnableSync(SupabaseEnableSync);
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -4,8 +4,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:intl/intl.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import '../util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

View File

@ -7,9 +7,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/emoji.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/emoji.dart';
import '../util/util.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import '../util/database_test_op.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import '../util/database_test_op.dart';
void main() {
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:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
import '../util/database_test_op.dart';
import '../util/util.dart';
void main() {
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 'board/board_test_runner.dart' as board_test_runner;
import 'database_calendar_test.dart' as database_calendar_test;
import 'database_cell_test.dart' as database_cell_test;
import 'database_field_settings_test.dart' as database_field_settings_test;
import 'database_field_test.dart' as database_field_test;
import 'database_filter_test.dart' as database_filter_test;
import 'database_row_page_test.dart' as database_row_page_test;
import 'database_row_test.dart' as database_row_test;
import 'database_setting_test.dart' as database_setting_test;
import 'database_share_test.dart' as database_share_test;
import 'database_sort_test.dart' as database_sort_test;
import 'database_view_test.dart' as database_view_test;
import 'database/database_calendar_test.dart' as database_calendar_test;
import 'database/database_cell_test.dart' as database_cell_test;
import 'database/database_field_settings_test.dart'
as database_field_settings_test;
import 'database/database_field_test.dart' as database_field_test;
import 'database/database_filter_test.dart' as database_filter_test;
import 'database/database_row_page_test.dart' as database_row_page_test;
import 'database/database_row_test.dart' as database_row_test;
import 'database/database_setting_test.dart' as database_setting_test;
import 'database/database_share_test.dart' as database_share_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 'empty_test.dart' as first_test;
import 'hotkeys_test.dart' as hotkeys_test;
@ -75,8 +76,6 @@ Future<void> main() async {
// User settings
settings_test_runner.main();
// supabase_auth_test_runner.main();
// board_test.main();
// empty_document_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/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
import 'package:flutter/material.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(
find.descendant(
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 {
final finder = find.descendant(
of: find.byType(EnableEncrypt),
@ -53,9 +64,9 @@ extension AppFlowyAuthTest on WidgetTester {
await tapButton(finder);
}
Future<void> toggleEnableSync() async {
Future<void> toggleEnableSync(Type syncButton) async {
final finder = find.descendant(
of: find.byType(SupabaseEnableSync),
of: find.byType(syncButton),
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
String? pathExtension,
Size windowsSize = const Size(1600, 1200),
CloudType? cloudType,
AuthenticatorType? cloudType,
}) async {
binding.setSurfaceSize(windowsSize);
@ -45,16 +45,32 @@ extension AppFlowyTestBase on WidgetTester {
await FlowyRunner.run(
FlowyApp(),
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 {
if (cloudType != null) {
switch (cloudType) {
case CloudType.local:
case AuthenticatorType.local:
break;
case CloudType.supabase:
case AuthenticatorType.supabase:
await useSupabaseCloud();
break;
case CloudType.appflowyCloud:
case AuthenticatorType.appflowyCloud:
await useAppFlowyCloud();
break;
}
@ -227,7 +243,7 @@ extension AppFlowyFinderTestBase on CommonFinders {
}
Future<void> useSupabaseCloud() async {
await setCloudType(CloudType.supabase);
await setAuthenticatorType(AuthenticatorType.supabase);
await setSupbaseServer(
Some(TestEnv.supabaseUrl),
Some(TestEnv.supabaseAnonKey),
@ -235,6 +251,6 @@ Future<void> useSupabaseCloud() async {
}
Future<void> useAppFlowyCloud() async {
await setCloudType(CloudType.appflowyCloud);
await setAuthenticatorType(AuthenticatorType.appflowyCloud);
await setAppFlowyCloudUrl(Some(TestEnv.afCloudUrl));
}

View File

@ -5,20 +5,24 @@ part 'backend_env.g.dart';
@JsonSerializable()
class AppFlowyConfiguration {
final String root;
final String custom_app_path;
final String origin_app_path;
final String device_id;
final int cloud_type;
final int authenticator_type;
final SupabaseConfiguration supabase_config;
final AppFlowyCloudConfiguration appflowy_cloud_config;
final Map<String, String> envs;
AppFlowyConfiguration({
required this.root,
required this.custom_app_path,
required this.origin_app_path,
required this.device_id,
required this.cloud_type,
required this.authenticator_type,
required this.supabase_config,
required this.appflowy_cloud_config,
required this.envs,
});
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_keys.dart';
import 'package:appflowy/env/backend_env.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/log.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
/// 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
/// [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.supabase` is stored as "1".
/// - `CloudType.appflowyCloud` is stored as "2".
Future<void> setCloudType(CloudType ty) async {
Future<void> setAuthenticatorType(AuthenticatorType ty) async {
switch (ty) {
case CloudType.local:
case AuthenticatorType.local:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 0.toString());
break;
case CloudType.supabase:
case AuthenticatorType.supabase:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 1.toString());
break;
case CloudType.appflowyCloud:
case AuthenticatorType.appflowyCloud:
getIt<KeyValueStorage>().set(KVKeys.kCloudType, 2.toString());
break;
}
@ -34,25 +35,25 @@ Future<void> setCloudType(CloudType ty) async {
///
/// This method fetches the cloud type setting from the key-value storage
/// using the [KeyValueStorage] service and returns the corresponding
/// [CloudType] enum value.
/// [AuthenticatorType] enum value.
///
/// 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`
/// if no valid setting is found.
///
Future<CloudType> getCloudType() async {
Future<AuthenticatorType> getAuthenticatorType() async {
final value = await getIt<KeyValueStorage>().get(KVKeys.kCloudType);
return value.fold(() => CloudType.local, (s) {
return value.fold(() => AuthenticatorType.local, (s) {
switch (s) {
case "0":
return CloudType.local;
return AuthenticatorType.local;
case "1":
return CloudType.supabase;
return AuthenticatorType.supabase;
case "2":
return CloudType.appflowyCloud;
return AuthenticatorType.appflowyCloud;
default:
return CloudType.local;
return AuthenticatorType.local;
}
});
}
@ -70,17 +71,15 @@ Future<CloudType> getCloudType() async {
/// Returns `false` otherwise.
bool get isAuthEnabled {
// Only enable supabase in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) {
if (integrationMode().isRelease ||
integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
final env = getIt<AppFlowyCloudSharedEnv>();
if (env.cloudType == CloudType.local) {
return false;
}
if (env.cloudType == CloudType.supabase) {
if (env.authenticatorType == AuthenticatorType.supabase) {
return env.supabaseConfig.isValid;
}
if (env.cloudType == CloudType.appflowyCloud) {
if (env.authenticatorType == AuthenticatorType.appflowyCloud) {
return env.appflowyCloudConfig.isValid;
}
@ -101,8 +100,10 @@ bool get isAuthEnabled {
/// is `CloudType.supabase`. Otherwise, it returns `false`.
bool get isSupabaseEnabled {
// Only enable supabase in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) {
return currentCloudType() == CloudType.supabase;
if (integrationMode().isRelease ||
integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
return currentCloudType() == AuthenticatorType.supabase;
} else {
return false;
}
@ -119,26 +120,28 @@ bool get isSupabaseEnabled {
/// cloud type is `CloudType.appflowyCloud`. Otherwise, it returns `false`.
bool get isAppFlowyCloudEnabled {
// Only enable appflowy cloud in release and develop mode.
if (integrationMode().isRelease || integrationMode().isDevelop) {
return currentCloudType() == CloudType.appflowyCloud;
if (integrationMode().isRelease ||
integrationMode().isDevelop ||
integrationMode().isIntegrationTest) {
return currentCloudType() == AuthenticatorType.appflowyCloud;
} else {
return false;
}
}
enum CloudType {
enum AuthenticatorType {
local,
supabase,
appflowyCloud;
bool get isEnabled => this != CloudType.local;
bool get isEnabled => this != AuthenticatorType.local;
int get value {
switch (this) {
case CloudType.local:
case AuthenticatorType.local:
return 0;
case CloudType.supabase:
case AuthenticatorType.supabase:
return 1;
case CloudType.appflowyCloud:
case AuthenticatorType.appflowyCloud:
return 2;
}
}
@ -146,19 +149,19 @@ enum CloudType {
static fromValue(int value) {
switch (value) {
case 0:
return CloudType.local;
return AuthenticatorType.local;
case 1:
return CloudType.supabase;
return AuthenticatorType.supabase;
case 2:
return CloudType.appflowyCloud;
return AuthenticatorType.appflowyCloud;
default:
return CloudType.local;
return AuthenticatorType.local;
}
}
}
CloudType currentCloudType() {
return getIt<AppFlowyCloudSharedEnv>().cloudType;
AuthenticatorType currentCloudType() {
return getIt<AppFlowyCloudSharedEnv>().authenticatorType;
}
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.
class AppFlowyCloudSharedEnv {
final CloudType cloudType;
final AuthenticatorType _authenticatorType;
final AppFlowyCloudConfiguration appflowyCloudConfig;
final SupabaseConfiguration supabaseConfig;
AppFlowyCloudSharedEnv({
required this.cloudType,
required AuthenticatorType authenticatorType,
required this.appflowyCloudConfig,
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 {
final baseURL = await getAppFlowyCloudUrl();
return AppFlowyCloudConfiguration(
base_url: await getAppFlowyCloudUrl(),
ws_base_url: await _getAppFlowyCloudWSUrl(),
gotrue_url: await _getAppFlowyCloudGotrueUrl(),
base_url: baseURL,
ws_base_url: await _getAppFlowyCloudWSUrl(baseURL),
gotrue_url: await _getAppFlowyCloudGotrueUrl(baseURL),
);
}
@ -198,10 +231,9 @@ Future<String> getAppFlowyCloudUrl() async {
);
}
Future<String> _getAppFlowyCloudWSUrl() async {
Future<String> _getAppFlowyCloudWSUrl(String baseURL) async {
try {
final serverUrl = await getAppFlowyCloudUrl();
final uri = Uri.parse(serverUrl);
final uri = Uri.parse(baseURL);
// Construct the WebSocket URL directly from the parsed URI.
final wsScheme = uri.isScheme('HTTPS') ? 'wss' : 'ws';
@ -214,9 +246,8 @@ Future<String> _getAppFlowyCloudWSUrl() async {
}
}
Future<String> _getAppFlowyCloudGotrueUrl() async {
final serverUrl = await getAppFlowyCloudUrl();
return "$serverUrl/gotrue";
Future<String> _getAppFlowyCloudGotrueUrl(String baseURL) async {
return "$baseURL/gotrue";
}
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/startup/startup.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/supabase_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 {
final cloudType = await getCloudType();
final appflowyCloudConfig = await getAppFlowyCloudConfig();
final supabaseCloudConfig = await getSupabaseCloudConfig();
getIt.registerFactory<AppFlowyCloudSharedEnv>(() {
return AppFlowyCloudSharedEnv(
cloudType: cloudType,
appflowyCloudConfig: appflowyCloudConfig,
supabaseConfig: supabaseCloudConfig,
);
});
final env = await AppFlowyCloudSharedEnv.fromEnv();
getIt.registerFactory<AppFlowyCloudSharedEnv>(() => env);
}
void _resolveCommonService(
@ -130,22 +123,27 @@ void _resolveCommonService(
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
switch (currentCloudType()) {
case CloudType.local:
case AuthenticatorType.local:
getIt.registerFactory<AuthService>(
() => BackendAuthService(
AuthTypePB.Local,
),
);
break;
case CloudType.supabase:
case AuthenticatorType.supabase:
if (mode.isIntegrationTest) {
getIt.registerFactory<AuthService>(() => MockAuthService());
getIt.registerFactory<AuthService>(() => SupabaseMockAuthService());
} else {
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
}
break;
case CloudType.appflowyCloud:
getIt.registerFactory<AuthService>(() => AFCloudAuthService());
case AuthenticatorType.appflowyCloud:
if (mode.isIntegrationTest) {
getIt
.registerFactory<AuthService>(() => AppFlowyCloudMockAuthService());
} else {
getIt.registerFactory<AuthService>(() => AppFlowyCloudAuthService());
}
break;
}

View File

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

View File

@ -1,8 +1,11 @@
class LaunchConfiguration {
const LaunchConfiguration({
this.autoRegistrationSupported = false,
this.isAnon = false,
required this.rustEnvs,
});
// 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 {
static var currentMode = integrationMode();
static Future<FlowyRunnerContext> run(
EntryPoint f,
IntegrationMode mode, {
Future? didInitGetIt,
LaunchConfiguration config = const LaunchConfiguration(
autoRegistrationSupported: false,
),
// This callback is triggered after the initialization of 'getIt',
// which is used for dependency injection throughout the app.
// 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 {
currentMode = mode;
// Clear all the states in case of rebuilding.
await getIt.reset();
final config = LaunchConfiguration(
isAnon: isAnon,
rustEnvs: rustEnvsBuilder?.call() ?? {},
);
// Specify the env
await initGetIt(getIt, mode, f, config);
await didInitGetIt;
await didInitGetItCallback;
final applicationDataDirectory =
await getIt<ApplicationDataStorage>().getPath().then(

View File

@ -23,15 +23,18 @@ class InitRustSDKTask extends LaunchTask {
@override
Future<void> initialize(LaunchContext context) async {
final root = await getApplicationSupportDirectory();
final applicationPath = await appFlowyApplicationDataDirectory();
final dir = customApplicationPath ?? applicationPath;
final deviceId = await getDeviceId();
// Pass the environment variables to the Rust SDK
final env = _getAppFlowyConfiguration(
final env = _makeAppFlowyConfiguration(
root.path,
dir.path,
applicationPath.path,
deviceId,
rustEnvs: context.config.rustEnvs,
);
await context.getIt<FlowySDK>().init(jsonEncode(env.toJson()));
}
@ -40,19 +43,23 @@ class InitRustSDKTask extends LaunchTask {
Future<void> dispose() async {}
}
AppFlowyConfiguration _getAppFlowyConfiguration(
AppFlowyConfiguration _makeAppFlowyConfiguration(
String root,
String customAppPath,
String originAppPath,
String deviceId,
) {
String deviceId, {
required Map<String, String> rustEnvs,
}) {
final env = getIt<AppFlowyCloudSharedEnv>();
return AppFlowyConfiguration(
root: root,
custom_app_path: customAppPath,
origin_app_path: originAppPath,
device_id: deviceId,
cloud_type: env.cloudType.value,
authenticator_type: env.authenticatorType.value,
supabase_config: env.supabaseConfig,
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 'device_id.dart';
class AFCloudAuthService implements AuthService {
class AppFlowyCloudAuthService implements AuthService {
final _appLinks = AppLinks();
StreamSubscription<Uri?>? _deeplinkSubscription;
AFCloudAuthService();
AppFlowyCloudAuthService();
final BackendAuthService _backendAuthService = BackendAuthService(
AuthTypePB.AFCloud,
@ -35,7 +35,7 @@ class AFCloudAuthService implements AuthService {
}
@override
Future<Either<FlowyError, UserProfilePB>> signIn({
Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email,
required String password,
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].
Future<Either<FlowyError, UserProfilePB>> signIn({
Future<Either<FlowyError, UserProfilePB>> signInWithEmailPassword({
required String email,
required String password,
Map<String, String> params,

View File

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

View File

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

View File

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

View File

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

View File

@ -20,10 +20,15 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
error.msg,
);
break;
case ErrorCode.HttpError:
showSnapBar(
context,
error.toString(),
);
default:
showSnapBar(
context,
error.msg,
error.toString(),
onClosed: () {
getIt<AuthService>().signOut();
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/locale_keys.g.dart';
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/launch_configuration.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.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),
SizedBox(
width: size.width * 0.7,
@ -98,9 +104,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
await FlowyRunner.run(
FlowyApp(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),
isAnon: true,
);
}
}
@ -279,10 +283,33 @@ class GoButton extends StatelessWidget {
? LocaleKeys.letsGoButtonText.tr()
: LocaleKeys.signIn_continueAnonymousUser.tr();
final textWidget = FlowyText.medium(
text,
textAlign: TextAlign.center,
fontSize: 14,
final textWidget = Row(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: FlowyText.medium(
text,
textAlign: TextAlign.center,
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(

View File

@ -18,16 +18,14 @@ class SplashScreen extends StatelessWidget {
/// Root Page of the app.
const SplashScreen({
super.key,
required this.autoRegister,
required this.isAnon,
});
final bool autoRegister;
final bool isAnon;
@override
Widget build(BuildContext context) {
if (!autoRegister) {
return _buildChild(context);
} else {
if (isAnon) {
return FutureBuilder<void>(
future: _registerIfNeeded(),
builder: (context, snapshot) {
@ -37,6 +35,8 @@ class SplashScreen extends StatelessWidget {
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';
class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
CloudSettingBloc(CloudType cloudType)
CloudSettingBloc(AuthenticatorType cloudType)
: super(CloudSettingState.initial(cloudType)) {
on<CloudSettingEvent>((event, emit) async {
await event.when(
initial: () async {},
updateCloudType: (CloudType newCloudType) async {
await setCloudType(newCloudType);
updateCloudType: (AuthenticatorType newCloudType) async {
await setAuthenticatorType(newCloudType);
emit(state.copyWith(cloudType: newCloudType));
},
);
@ -22,17 +22,19 @@ class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
@freezed
class CloudSettingEvent with _$CloudSettingEvent {
const factory CloudSettingEvent.initial() = _Initial;
const factory CloudSettingEvent.updateCloudType(CloudType newCloudType) =
_UpdateCloudType;
const factory CloudSettingEvent.updateCloudType(
AuthenticatorType newCloudType,
) = _UpdateCloudType;
}
@freezed
class CloudSettingState with _$CloudSettingState {
const factory CloudSettingState({
required CloudType cloudType,
required AuthenticatorType cloudType,
}) = _CloudSettingState;
factory CloudSettingState.initial(CloudType cloudType) => CloudSettingState(
factory CloudSettingState.initial(AuthenticatorType cloudType) =>
CloudSettingState(
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/workspace/application/settings/appflowy_cloud_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart';
@ -50,7 +51,16 @@ class SettingAppFlowyCloudView extends StatelessWidget {
children: [
const AppFlowyCloudEnableSync(),
const VSpace(40),
AppFlowyCloudURLs(didUpdateUrls: () => didResetServerUrl()),
if (Env.enableCustomCloud)
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/env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart';
@ -19,8 +20,9 @@ class SettingCloud extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getCloudType(),
builder: (BuildContext context, AsyncSnapshot<CloudType> snapshot) {
future: getAuthenticatorType(),
builder:
(BuildContext context, AsyncSnapshot<AuthenticatorType> snapshot) {
if (snapshot.hasData) {
final cloudType = snapshot.data!;
return BlocProvider(
@ -29,29 +31,30 @@ class SettingCloud extends StatelessWidget {
builder: (context, state) {
return Column(
children: [
Row(
children: [
Expanded(
child: FlowyText.medium(
LocaleKeys.settings_menu_cloudServerType.tr(),
if (Env.enableCustomCloud)
Row(
children: [
Expanded(
child: FlowyText.medium(
LocaleKeys.settings_menu_cloudServerType.tr(),
),
),
),
Tooltip(
message:
LocaleKeys.settings_menu_cloudServerTypeTip.tr(),
child: CloudTypeSwitcher(
cloudType: state.cloudType,
onSelected: (newCloudType) {
context.read<CloudSettingBloc>().add(
CloudSettingEvent.updateCloudType(
newCloudType,
),
);
},
Tooltip(
message: LocaleKeys.settings_menu_cloudServerTypeTip
.tr(),
child: CloudTypeSwitcher(
cloudType: state.cloudType,
onSelected: (newCloudType) {
context.read<CloudSettingBloc>().add(
CloudSettingEvent.updateCloudType(
newCloudType,
),
);
},
),
),
),
],
),
],
),
_viewFromCloudType(state.cloudType),
],
);
@ -67,15 +70,15 @@ class SettingCloud extends StatelessWidget {
);
}
Widget _viewFromCloudType(CloudType cloudType) {
Widget _viewFromCloudType(AuthenticatorType cloudType) {
switch (cloudType) {
case CloudType.local:
case AuthenticatorType.local:
return SettingLocalCloud(didResetServerUrl: didResetServerUrl);
case CloudType.supabase:
case AuthenticatorType.supabase:
return SettingSupabaseCloudView(
didResetServerUrl: didResetServerUrl,
);
case CloudType.appflowyCloud:
case AuthenticatorType.appflowyCloud:
return SettingAppFlowyCloudView(
didResetServerUrl: didResetServerUrl,
);
@ -84,8 +87,8 @@ class SettingCloud extends StatelessWidget {
}
class CloudTypeSwitcher extends StatelessWidget {
final CloudType cloudType;
final Function(CloudType) onSelected;
final AuthenticatorType cloudType;
final Function(AuthenticatorType) onSelected;
const CloudTypeSwitcher({
required this.cloudType,
required this.onSelected,
@ -108,12 +111,12 @@ class CloudTypeSwitcher extends StatelessWidget {
shrinkWrap: true,
itemBuilder: (context, index) {
return CloudTypeItem(
cloudType: CloudType.values[index],
cloudType: AuthenticatorType.values[index],
currentCloudtype: cloudType,
onSelected: onSelected,
);
},
itemCount: CloudType.values.length,
itemCount: AuthenticatorType.values.length,
);
},
);
@ -121,9 +124,9 @@ class CloudTypeSwitcher extends StatelessWidget {
}
class CloudTypeItem extends StatelessWidget {
final CloudType cloudType;
final CloudType currentCloudtype;
final Function(CloudType) onSelected;
final AuthenticatorType cloudType;
final AuthenticatorType currentCloudtype;
final Function(AuthenticatorType) onSelected;
const CloudTypeItem({
required this.cloudType,
@ -154,13 +157,13 @@ class CloudTypeItem extends StatelessWidget {
}
}
String titleFromCloudType(CloudType cloudType) {
String titleFromCloudType(AuthenticatorType cloudType) {
switch (cloudType) {
case CloudType.local:
case AuthenticatorType.local:
return LocaleKeys.settings_menu_cloudLocal.tr();
case CloudType.supabase:
case AuthenticatorType.supabase:
return LocaleKeys.settings_menu_cloudSupabase.tr();
case CloudType.appflowyCloud:
case AuthenticatorType.appflowyCloud:
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 '../../../../generated/locale_keys.g.dart';
import '../../../../startup/launch_configuration.dart';
import '../../../../startup/startup.dart';
import '../../../../startup/tasks/prelude.dart';
@ -210,10 +209,8 @@ class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
await context.read<SettingsLocationCubit>().setCustomPath(path);
await FlowyRunner.run(
FlowyApp(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),
FlowyRunner.currentMode,
isAnon: true,
);
if (mounted) {
Navigator.of(context).pop();
@ -288,10 +285,8 @@ class _RecoverDefaultStorageButtonState
.resetDataStoragePathToApplicationDefault();
await FlowyRunner.run(
FlowyApp(),
integrationMode(),
config: const LaunchConfiguration(
autoRegistrationSupported: true,
),
FlowyRunner.currentMode,
isAnon: true,
);
if (mounted) {
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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -17,7 +17,7 @@ A new flutter plugin project.
s.public_header_files = 'Classes/**/*.h'
s.dependency 'FlutterMacOS'
s.platform = :osx, '10.11'
s.platform = :osx, '10.13'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0'
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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import {
UserEventGetUserProfile,
UserEventGetUserSetting,
UserEventSetAppearanceSetting,
UserEventSignIn,
UserEventSignInWithEmailPassword,
UserEventSignOut,
UserEventSignUp,
UserEventUpdateUserProfile,
@ -98,8 +98,8 @@ export class UserBackendService {
export class AuthBackendService {
signIn = (params: { email: string; password: string }) => {
const payload = SignInPayloadPB.fromObject({ email: params.email, password: params.password });
return UserEventSignIn(payload);
return UserEventSignInWithEmailPassword(payload);
};
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",
"inputEncryptPrompt": "Please enter your encryption secret for",
"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",
"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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,7 @@ async fn sign_in_with_invalid_email() {
assert_eq!(
EventBuilder::new(sdk)
.event(SignIn)
.event(SignInWithEmailPassword)
.payload(request)
.async_send()
.await
@ -95,7 +95,7 @@ async fn sign_in_with_invalid_password() {
};
assert!(EventBuilder::new(sdk)
.event(SignIn)
.event(SignInWithEmailPassword)
.payload(request)
.async_send()
.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) {
tracing::info!("🔑Set encrypt secret");
self.encryption.write().set_secret(secret);

View File

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

View File

@ -5,7 +5,7 @@ pub mod supabase_config;
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)]
pub enum AuthenticatorType {
Local = 0,

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Error};
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 parking_lot::RwLock;
@ -38,7 +38,7 @@ impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
where
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();
FutureResult::new(async move {
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
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();
FutureResult::new(async move {
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();
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 try_get_client = self.server.try_get_client();
FutureResult::new(async move {
@ -82,8 +82,7 @@ where
client_api::Client::new(client.base_url(), client.ws_addr(), client.gotrue_url());
admin_client
.sign_in_password(&admin_email, &admin_password)
.await
.unwrap();
.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?;
@ -91,8 +90,8 @@ where
})
}
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, Error> {
let provider = OAuthProvider::from(provider);
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, FlowyError> {
let provider = AuthProvider::from(provider);
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let provider = provider.ok_or(anyhow!("invalid provider"))?;
@ -107,7 +106,7 @@ where
&self,
_credential: UserCredentials,
params: UpdateUserProfileParams,
) -> FutureResult<(), Error> {
) -> FutureResult<(), FlowyError> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
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();
FutureResult::new(async move {
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) client: Arc<AFCloudClient>,
enable_sync: Arc<AtomicBool>,
network_reachable: Arc<AtomicBool>,
#[allow(dead_code)]
device_id: String,
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 token_state_rx = api_client.subscribe_token_state();
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 = Arc::new(ws_client);
@ -63,6 +65,7 @@ impl AppFlowyCloudServer {
config,
client: api_client,
enable_sync,
network_reachable,
device_id,
ws_client,
}
@ -117,8 +120,14 @@ impl AppFlowyServer for AppFlowyCloudServer {
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> {
let server = AFServerImpl(self.get_client());
let server = AFServerImpl {
client: self.get_client(),
};
let mut user_change = self.ws_client.subscribe_user_changed();
let (tx, rx) = tokio::sync::mpsc::channel(1);
tokio::spawn(async move {
@ -139,17 +148,23 @@ impl AppFlowyServer for AppFlowyCloudServer {
}
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
let server = AFServerImpl(self.get_client());
let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudFolderCloudServiceImpl(server))
}
fn database_service(&self) -> Arc<dyn DatabaseCloudService> {
let server = AFServerImpl(self.get_client());
let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudDatabaseCloudServiceImpl(server))
}
fn document_service(&self) -> Arc<dyn DocumentCloudService> {
let server = AFServerImpl(self.get_client());
let server = AFServerImpl {
client: self.get_client(),
};
Arc::new(AFCloudDocumentCloudServiceImpl(server))
}
@ -184,7 +199,9 @@ impl AppFlowyServer for AppFlowyCloudServer {
}
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)))
}
}
@ -274,15 +291,17 @@ pub trait AFServer: Send + Sync + 'static {
}
#[derive(Clone)]
pub struct AFServerImpl(pub Option<Arc<AFCloudClient>>);
pub struct AFServerImpl {
client: Option<Arc<AFCloudClient>>,
}
impl AFServer for AFServerImpl {
fn get_client(&self) -> Option<Arc<AFCloudClient>> {
self.0.clone()
self.client.clone()
}
fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error> {
match self.0.clone() {
match self.client.clone() {
None => Err(
FlowyError::new(
ErrorCode::DataSyncRequired,

View File

@ -26,7 +26,7 @@ pub(crate) struct 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 {
let params = params.unbox_or_error::<SignUpParams>()?;
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();
FutureResult::new(async move {
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(()) })
}
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 {
Err(anyhow::anyhow!(
"Can't generate callback url when using offline mode"
))
Err(
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> {
FutureResult::new(async { Err(anyhow::anyhow!("Can't oauth url when using offline mode")) })
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, FlowyError> {
FutureResult::new(async {
Err(FlowyError::internal().with_context("Can't oauth url when using offline mode"))
})
}
fn update_user(
&self,
_credential: UserCredentials,
_params: UpdateUserProfileParams,
) -> FutureResult<(), Error> {
) -> FutureResult<(), FlowyError> {
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![]) })
}

View File

@ -52,6 +52,12 @@ pub trait AppFlowyServer: Send + Sync + 'static {
/// * `_enable` - A boolean to toggle the server synchronization.
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
/// 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,

View File

@ -64,7 +64,7 @@ impl<T> UserCloudService for SupabaseUserServiceImpl<T>
where
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();
FutureResult::new(async move {
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();
FutureResult::new(async move {
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(()) })
}
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 {
Err(anyhow::anyhow!(
"Can't generate callback url when using supabase"
))
Err(FlowyError::internal().with_context("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 {
Err(anyhow::anyhow!(
"Can't generate oauth url when using supabase"
))
Err(FlowyError::internal().with_context("Can't generate oauth url when using supabase"))
})
}
@ -183,7 +179,7 @@ where
&self,
_credential: UserCredentials,
params: UpdateUserProfileParams,
) -> FutureResult<(), Error> {
) -> FutureResult<(), FlowyError> {
let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move {
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();
FutureResult::new(async move {
let postgrest = try_get_postgrest?;

View File

@ -59,32 +59,32 @@ pub trait UserCloudService: Send + Sync + 'static {
/// Sign up a new account.
/// The type of the params is defined the this trait's implementation.
/// Use the `unbox_or_error` of the [BoxAny] to get the params.
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, Error>;
fn sign_up(&self, params: BoxAny) -> FutureResult<AuthResponse, FlowyError>;
/// Sign in an account
/// 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
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
/// 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.
/// 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.
///
/// 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
fn update_user(
&self,
credential: UserCredentials,
params: UpdateUserProfileParams,
) -> FutureResult<(), Error>;
) -> FutureResult<(), FlowyError>;
/// Get the user information using the user's token or uid
/// 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>;
/// 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(
&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 {
EncryptionType::NoEncryption => false,
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)]
pub async fn sign_in(
pub async fn sign_in_with_email_password_handler(
data: AFPluginData<SignInPayloadPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserProfilePB, FlowyError> {
@ -43,10 +43,7 @@ pub async fn sign_in(
let params: SignInParams = data.into_inner().try_into()?;
let auth_type = params.auth_type.clone();
let user_profile: UserProfilePB = manager
.sign_in(BoxAny::new(params), auth_type)
.await?
.into();
let user_profile: UserProfilePB = manager.sign_in(params, auth_type).await?.into();
data_result_ok(user_profile)
}
@ -264,7 +261,7 @@ pub async fn oauth_handler(
}
#[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>,
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<SignInUrlPB, FlowyError> {
@ -274,8 +271,7 @@ pub async fn get_sign_in_url_handler(
let sign_in_url = manager
.generate_sign_in_url_with_email(&auth_type, &params.email)
.await?;
let resp = SignInUrlPB { sign_in_url };
data_result_ok(resp)
data_result_ok(SignInUrlPB { sign_in_url })
}
#[tracing::instrument(level = "debug", skip_all, err)]
@ -459,6 +455,7 @@ pub async fn update_network_state_handler(
) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?;
let reachable = data.into_inner().ty.is_reachable();
manager.cloud_services.set_network_reachable(reachable);
manager
.user_status_callback
.read()

View File

@ -25,7 +25,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.name("Flowy-User")
.state(user_session)
.state(store_preferences)
.event(UserEvent::SignIn, sign_in)
.event(UserEvent::SignInWithEmailPassword, sign_in_with_email_password_handler)
.event(UserEvent::SignUp, sign_up)
.event(UserEvent::InitUser, init_user_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::CheckEncryptionSign, check_encrypt_secret_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::GetAllWorkspace, get_all_workspace_handler)
.event(UserEvent::OpenWorkspace, open_workspace_handler)
@ -69,7 +69,7 @@ pub enum UserEvent {
/// Only use when the [Authenticator] is Local or SelfHosted
/// Logging into an account using a register email and password
#[event(input = "SignInPayloadPB", output = "UserProfilePB")]
SignIn = 0,
SignInWithEmailPassword = 0,
/// Only use when the [Authenticator] is Local or SelfHosted
/// Creating a new account
@ -111,7 +111,7 @@ pub enum UserEvent {
/// Get the OAuth callback url
/// Only use when the [Authenticator] is AFCloud
#[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")]
GetSignInURL = 11,
GenerateSignInURL = 11,
#[event(input = "OauthProviderPB", output = "OauthProviderDataPB")]
GetOauthURLWithProvider = 12,
@ -234,55 +234,73 @@ pub trait UserStatusCallback: Send + Sync + 'static {
fn did_update_network(&self, _reachable: bool) {}
}
/// The user cloud service provider.
/// The provider can be supabase, firebase, aws, or any other cloud service.
/// `UserCloudServiceProvider` defines a set of methods for managing user cloud services,
/// 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 {
/// 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 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);
/// 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);
/// Sets the authenticator used for authentication processes.
///
/// # Arguments
/// * `authenticator`: An `Authenticator` object.
fn set_authenticator(&self, authenticator: Authenticator);
/// Retrieves the current authenticator.
///
/// # Returns
/// The current `Authenticator` object.
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>;
/// Retrieves the service URL.
///
/// # Returns
/// A `String` representing the service URL.
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
pub(crate) struct DefaultUserStatusCallback;
impl UserStatusCallback for DefaultUserStatusCallback {

View File

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

View File

@ -51,9 +51,7 @@ private = true
script = [
"""
cd rust-lib/
rustup show
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}"
RUSTFLAGS="--cfg tokio_unstable" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cd ../
""",
]
@ -64,8 +62,6 @@ private = true
script = [
"""
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}"
cd ../
""",
@ -106,6 +102,16 @@ script = [
]
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]
mac_alias = "post-desktop-macos"

View File

@ -33,7 +33,7 @@ dependencies = ["inner_build_test_backend"]
description = "Run flutter unit tests"
script = '''
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]
@ -57,7 +57,7 @@ dependencies = ["copy-from-build-to-sandbox-folder"]
description = "Run flutter unit tests"
script = '''
cd appflowy_flutter
flutter test --dart-define=RUST_LOG=${RUST_LOG} -j, --concurrency=1 --coverage
flutter test -j, --concurrency=1 --coverage
'''
script_runner = "@shell"
@ -259,6 +259,7 @@ run_task = { name = [
[tasks.build_test_backend]
env = { RUST_LOG = "trace" }
script = '''
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"
[tasks.compile_test_backend_default]
env = { RUST_LOG = "trace" }
private = true
script = [
"""