feat: show server url (#3956)

* chore: data folder for cloud

* chore: display server url

* chore: fix test
This commit is contained in:
Nathan.fooo 2023-11-17 15:38:56 +08:00 committed by GitHub
parent 4a1a143a66
commit 8179419f8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 425 additions and 313 deletions

View File

@ -1,15 +1,18 @@
# Initial Setup
# Copy the 'dev.env' file to '.env':
# Use the command 'cp dev.env .env' to create a copy of 'dev.env' named '.env'.
# After copying, update the '.env' file with the necessary environment parameters.
# 1. Copy the dev.env file to .env:
# cp dev.env .env
# Update the environment parameters as needed.
# 2. Generate the env.dart from this .env file:
# You can use the "Generate Env File" task in VSCode.
# Alternatively, execute the following commands:
# Generate the 'env.dart' from this '.env' file:
# Option 1: Use the "Generate Env File" task in VSCode.
# Option 2: Execute the commands in the appflowy_flutter directory:
# cd appflowy_flutter
# dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
# Note on Configuration Priority:
# If both Supabase config and AppFlowy cloud config are provided in the '.env' file,
# the AppFlowy cloud config will be prioritized and the Supabase config ignored.
# Ensure only one of these configurations is active at any given time.
# Cloud Type Configuration
# Use this configuration file to specify the cloud type and its associated settings. The available cloud types are:
@ -25,17 +28,9 @@ SUPABASE_URL=
SUPABASE_ANON_KEY=
# AppFlowy Cloud Configuration
# If using AppFlowy Cloud (CLOUD_TYPE=2), provide the following details:
# For instance:
# APPFLOWY_CLOUD_BASE_URL=https://xxxxxxxxx
# APPFLOWY_CLOUD_WS_BASE_URL=wss://xxxxxxxxx
# APPFLOWY_CLOUD_GOTRUE_URL=https://xxxxxxxxx
# If using Supabase (CLOUD_TYPE=2), provide the following details:
#
# When using localhost for development, you must run AppFlowy Cloud locally
# first. Plese Please follow the instructions below:
# https://github.com/AppFlowy-IO/AppFlowy-Cloud#development
#
# After running AppFlowy Cloud locally, you can use the following settings:
# When using localhost for development. you can use the following settings:
# APPFLOWY_CLOUD_BASE_URL=http://localhost:8000
# APPFLOWY_CLOUD_WS_BASE_URL=ws://localhost:8000/ws
# APPFLOWY_CLOUD_GOTRUE_URL=http://localhost:9998

View File

@ -39,7 +39,7 @@ void main() {
// should not see the sync setting page when sign in as annoymous
await tester.openSettings();
await tester.expectNoSettingsPage(SettingsPage.syncSetting);
await tester.expectNoSettingsPage(SettingsPage.cloud);
});
testWidgets('enable encryption', (tester) async {
@ -48,7 +48,7 @@ void main() {
// Open the setting page and sign out
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.syncSetting);
await tester.openSettingsPage(SettingsPage.cloud);
// the switch should be off by default
tester.assertEnableEncryptSwitchValue(false);
@ -68,7 +68,7 @@ void main() {
// Open the setting page and sign out
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.syncSetting);
await tester.openSettingsPage(SettingsPage.cloud);
// the switch should be on by default
tester.assertEnableSyncSwitchValue(true);

View File

@ -1,5 +1,5 @@
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

View File

@ -70,9 +70,8 @@ class DartKeyValue implements KeyValueStorage {
/// Key-value store
/// The data is stored in the local storage of the device.
class RustKeyValue implements KeyValueStorage {
@override
Future<void> set(String key, String value) async {
class RustKeyValue {
static Future<void> set(String key, String value) async {
await ConfigEventSetKeyValue(
KeyValuePB.create()
..key = key
@ -80,15 +79,13 @@ class RustKeyValue implements KeyValueStorage {
).send();
}
@override
Future<Either<FlowyError, String>> get(String key) async {
static Future<Either<FlowyError, String>> get(String key) async {
final payload = KeyPB.create()..key = key;
final response = await ConfigEventGetKeyValue(payload).send();
return response.swap().map((r) => r.value);
}
@override
Future<Either<FlowyError, T>> getWithFormat<T>(
static Future<Either<FlowyError, T>> getWithFormat<T>(
String key,
T Function(String value) formatter,
) async {
@ -99,15 +96,9 @@ class RustKeyValue implements KeyValueStorage {
);
}
@override
Future<void> remove(String key) async {
static Future<void> remove(String key) async {
await ConfigEventRemoveKeyValue(
KeyPB.create()..key = key,
).send();
}
@override
Future<void> clear() async {
// TODO(Lucas): implement clear
}
}

View File

@ -6,13 +6,6 @@ class KVKeys {
/// The key for the path location of the local data for the whole app.
static const String pathLocation = '$prefix.path_location';
/// The key for the last time login type.
///
/// The value is one of the following:
/// - local
/// - supabase
static const String loginType = '$prefix.login_type';
/// The key for saving the window size
///
/// The value is a json string with the following format:

View File

@ -5,10 +5,12 @@ part 'backend_env.g.dart';
@JsonSerializable()
class AppFlowyEnv {
final int cloud_type;
final SupabaseConfiguration supabase_config;
final AppFlowyCloudConfiguration appflowy_cloud_config;
AppFlowyEnv({
required this.cloud_type,
required this.supabase_config,
required this.appflowy_cloud_config,
});
@ -22,12 +24,10 @@ class AppFlowyEnv {
@JsonSerializable()
class SupabaseConfiguration {
/// Indicates whether the sync feature is enabled.
final bool enable_sync;
final String url;
final String anon_key;
SupabaseConfiguration({
this.enable_sync = true,
required this.url,
required this.anon_key,
});

View File

@ -34,22 +34,38 @@ class InitRustSDKTask extends LaunchTask {
}
AppFlowyEnv getAppFlowyEnv() {
final supabaseConfig = SupabaseConfiguration(
enable_sync: true,
url: Env.supabaseUrl,
anon_key: Env.supabaseAnonKey,
);
if (isCloudEnabled) {
final supabaseConfig = SupabaseConfiguration(
url: Env.supabaseUrl,
anon_key: Env.supabaseAnonKey,
);
final appflowyCloudConfig = AppFlowyCloudConfiguration(
base_url: Env.afCloudBaseUrl,
ws_base_url: Env.afCloudWSBaseUrl,
gotrue_url: Env.afCloudGoTrueUrl,
);
final appflowyCloudConfig = AppFlowyCloudConfiguration(
base_url: Env.afCloudBaseUrl,
ws_base_url: Env.afCloudWSBaseUrl,
gotrue_url: Env.afCloudGoTrueUrl,
);
return AppFlowyEnv(
supabase_config: supabaseConfig,
appflowy_cloud_config: appflowyCloudConfig,
);
return AppFlowyEnv(
cloud_type: Env.cloudType,
supabase_config: supabaseConfig,
appflowy_cloud_config: appflowyCloudConfig,
);
} else {
// Use the default configuration if the cloud feature is disabled
final supabaseConfig = SupabaseConfiguration(url: '', anon_key: '');
final appflowyCloudConfig = AppFlowyCloudConfiguration(
base_url: '',
ws_base_url: '',
gotrue_url: '',
);
return AppFlowyEnv(
cloud_type: 0,
supabase_config: supabaseConfig,
appflowy_cloud_config: appflowyCloudConfig,
);
}
}
/// The default directory to store the user data. The directory can be

View File

@ -1,5 +1,3 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/historical_user_bloc.dart';
@ -41,7 +39,6 @@ class SignInAnonymousButton extends StatelessWidget {
: LocaleKeys.signIn_continueAnonymousUser.tr();
final onTap = state.historicalUsers.isEmpty
? () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInAsGuest());

View File

@ -1,8 +1,5 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/util/platform_extension.dart';
@ -165,19 +162,16 @@ class _ThirdPartySignInButton extends StatelessWidget {
}
void _signInWithGoogle(BuildContext context) {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
}
void _signInWithGithub(BuildContext context) {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context.read<SignInBloc>().add(const SignInEvent.signedInWithOAuth('github'));
}
void _signInWithDiscord(BuildContext context) {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('discord'));

View File

@ -12,7 +12,7 @@ import '../../../core/notification/user_notification.dart';
class UserCloudConfigListener {
final String userId;
StreamSubscription<SubscribeObject>? _subscription;
void Function(Either<UserCloudConfigPB, FlowyError>)? _onSettingChanged;
void Function(Either<CloudSettingPB, FlowyError>)? _onSettingChanged;
UserNotificationParser? _userParser;
UserCloudConfigListener({
@ -20,7 +20,7 @@ class UserCloudConfigListener {
});
void start({
void Function(Either<UserCloudConfigPB, FlowyError>)? onSettingChanged,
void Function(Either<CloudSettingPB, FlowyError>)? onSettingChanged,
}) {
_onSettingChanged = onSettingChanged;
_userParser = UserNotificationParser(
@ -45,8 +45,8 @@ class UserCloudConfigListener {
switch (ty) {
case UserNotification.DidUpdateCloudConfig:
result.fold(
(payload) => _onSettingChanged
?.call(left(UserCloudConfigPB.fromBuffer(payload))),
(payload) =>
_onSettingChanged?.call(left(CloudSettingPB.fromBuffer(payload))),
(error) => _onSettingChanged?.call(right(error)),
);
break;

View File

@ -15,7 +15,7 @@ class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
CloudSettingBloc({
required String userId,
required UserCloudConfigPB config,
required CloudSettingPB config,
}) : _listener = UserCloudConfigListener(userId: userId),
super(CloudSettingState.initial(config)) {
on<CloudSettingEvent>((event, emit) async {
@ -38,7 +38,7 @@ class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
final update = UpdateCloudConfigPB.create()..enableSync = enable;
updateCloudConfig(update);
},
didReceiveConfig: (UserCloudConfigPB config) {
didReceiveConfig: (CloudSettingPB config) {
emit(
state.copyWith(
config: config,
@ -64,7 +64,7 @@ class CloudSettingBloc extends Bloc<CloudSettingEvent, CloudSettingState> {
class CloudSettingEvent with _$CloudSettingEvent {
const factory CloudSettingEvent.initial() = _Initial;
const factory CloudSettingEvent.didReceiveConfig(
UserCloudConfigPB config,
CloudSettingPB config,
) = _DidSyncSupabaseConfig;
const factory CloudSettingEvent.enableSync(bool enable) = _EnableSync;
const factory CloudSettingEvent.enableEncrypt(bool enable) = _EnableEncrypt;
@ -73,13 +73,12 @@ class CloudSettingEvent with _$CloudSettingEvent {
@freezed
class CloudSettingState with _$CloudSettingState {
const factory CloudSettingState({
required UserCloudConfigPB config,
required CloudSettingPB config,
required Either<Unit, String> successOrFailure,
required LoadingState loadingState,
}) = _CloudSettingState;
factory CloudSettingState.initial(UserCloudConfigPB config) =>
CloudSettingState(
factory CloudSettingState.initial(CloudSettingPB config) => CloudSettingState(
config: config,
successOrFailure: left(unit),
loadingState: LoadingState.finish(left(unit)),

View File

@ -14,7 +14,7 @@ enum SettingsPage {
files,
user,
notifications,
syncSetting,
cloud,
shortcuts,
}

View File

@ -1,5 +1,7 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -32,7 +34,13 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
}
Future<void> _init() async {
final path = await getIt<ApplicationDataStorage>().getPath();
emit(SettingsLocationState.didReceivedPath(path));
// The backend might change the real path that storge the data. So it needs
// to get the path from the backend instead of the KeyValueStorage
await UserEventGetUserSetting().send().then((result) {
result.fold(
(l) => emit(SettingsLocationState.didReceivedPath(l.userFolder)),
(r) => Log.error(r),
);
});
}
}

View File

@ -1,7 +1,7 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
@ -104,8 +104,8 @@ class SettingsDialog extends StatelessWidget {
);
case SettingsPage.notifications:
return const SettingsNotificationsView();
case SettingsPage.syncSetting:
return SyncSettingView(userId: user.id.toString());
case SettingsPage.cloud:
return SettingCloudView(userId: user.id.toString());
case SettingsPage.shortcuts:
return const SettingsCustomizeShortcutsWrapper();
default:

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/setting_supabase_bloc.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
@ -14,13 +15,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SyncSettingView extends StatelessWidget {
class SettingCloudView extends StatelessWidget {
final String userId;
const SyncSettingView({required this.userId, super.key});
const SettingCloudView({required this.userId, super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder<Either<UserCloudConfigPB, FlowyError>>(
return FutureBuilder<Either<CloudSettingPB, FlowyError>>(
future: UserEventGetCloudConfig().send(),
builder: (context, snapshot) {
if (snapshot.data != null &&
@ -34,10 +35,15 @@ class SyncSettingView extends StatelessWidget {
)..add(const CloudSettingEvent.initial()),
child: BlocBuilder<CloudSettingBloc, CloudSettingState>(
builder: (context, state) {
return const Column(
return Column(
children: [
EnableSync(),
EnableEncrypt(),
const EnableSync(),
// Currently the appflowy cloud is not support end-to-end encryption.
if (!isAppFlowyCloudEnabled) const EnableEncrypt(),
if (isAppFlowyCloudEnabled)
AppFlowyCloudInformationWidget(
url: state.config.serverUrl,
),
],
);
},
@ -58,6 +64,34 @@ class SyncSettingView extends StatelessWidget {
}
}
class AppFlowyCloudInformationWidget extends StatelessWidget {
final String url;
const AppFlowyCloudInformationWidget({required this.url, super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Expanded(
// Wrap the Opacity widget with Expanded
child: Opacity(
opacity: 0.6,
child: FlowyText(
"${LocaleKeys.settings_menu_cloudURL.tr()}: $url",
maxLines: null, // Allow the text to wrap
),
),
),
],
),
],
);
}
}
class EnableEncrypt extends StatelessWidget {
const EnableEncrypt({super.key});
@ -121,7 +155,6 @@ class EnableEncrypt extends StatelessWidget {
await Clipboard.setData(
ClipboardData(text: state.config.encryptSecret),
);
// TODO(Lucas): bring the toast to the top of the dialog.
showMessageToast(LocaleKeys.message_copy_success.tr());
},
),

View File

@ -48,45 +48,10 @@ class SettingsFileLocationCustomizerState
mainAxisSize: MainAxisSize.min,
children: [
// display file paths.
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.medium(
LocaleKeys.settings_files_defaultLocation.tr(),
fontSize: 13,
overflow: TextOverflow.visible,
).padding(horizontal: 5),
const VSpace(5),
_CopyableText(
usingPath: path,
),
],
),
),
_path(path),
// display the icons
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: _ChangeStoragePathButton(
usingPath: path,
),
),
const HSpace(10),
_OpenStorageButton(
usingPath: path,
),
_RecoverDefaultStorageButton(
usingPath: path,
),
],
),
),
_buttons(path),
],
);
},
@ -95,6 +60,55 @@ class SettingsFileLocationCustomizerState
),
);
}
Widget _path(String path) {
return Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.medium(
LocaleKeys.settings_files_defaultLocation.tr(),
fontSize: 13,
overflow: TextOverflow.visible,
).padding(horizontal: 5),
const VSpace(5),
_CopyableText(
usingPath: path,
),
],
),
);
}
Widget _buttons(String path) {
final List<Widget> children = [];
children.addAll([
Flexible(
child: _ChangeStoragePathButton(
usingPath: path,
),
),
const HSpace(10),
]);
children.add(
_OpenStorageButton(
usingPath: path,
),
);
children.add(
_RecoverDefaultStorageButton(
usingPath: path,
),
);
return Flexible(
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: children),
);
}
}
class _CopyableText extends StatelessWidget {

View File

@ -67,9 +67,9 @@ class SettingsMenu extends StatelessWidget {
if (showSyncSetting) ...[
const SizedBox(height: 10),
SettingsMenuElement(
page: SettingsPage.syncSetting,
page: SettingsPage.cloud,
selectedPage: currentPage,
label: LocaleKeys.settings_menu_syncSetting.tr(),
label: LocaleKeys.settings_menu_cloudSetting.tr(),
icon: Icons.sync,
changeSelectedPage: changeSelectedPage,
),

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=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"reqwest",
@ -768,7 +768,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -1449,7 +1449,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -2808,7 +2808,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"futures-util",
@ -2824,7 +2824,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -3251,7 +3251,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"reqwest",
@ -4994,7 +4994,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"bincode",
@ -5739,7 +5739,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",

View File

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

View File

@ -1,4 +1,5 @@
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
use flowy_core::config::AppFlowyCoreConfig;
use flowy_core::{AppFlowyCore, DEFAULT_NAME};
pub fn init_flowy_core() -> AppFlowyCore {
let config_json = include_str!("../tauri.conf.json");

View File

@ -262,6 +262,8 @@
"logoutPrompt": "Are you sure to logout?",
"selfEncryptionLogoutPrompt": "Are you sure you want to log out? Please ensure you have copied the encryption secret",
"syncSetting": "Sync Setting",
"cloudSetting": "Cloud Setting",
"cloudURL": "Server URL",
"enableSync": "Enable sync",
"enableEncrypt": "Encrypt data",
"enableEncryptPrompt": "Activate encryption to secure your data with this secret. Store it safely; once enabled, it can't be turned off. If lost, your data becomes irretrievable. Click to copy",

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=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"reqwest",
@ -666,7 +666,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -1150,7 +1150,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.11.2",
"phf 0.8.0",
"smallvec",
]
@ -1250,6 +1250,7 @@ dependencies = [
"protobuf",
"serde",
"serde_json",
"serde_repr",
"tokio",
"tracing",
]
@ -1276,7 +1277,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -2467,7 +2468,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"futures-util",
@ -2483,7 +2484,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",
@ -2844,7 +2845,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"reqwest",
@ -3657,7 +3658,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros 0.8.0",
"phf_macros",
"phf_shared 0.8.0",
"proc-macro-hack",
]
@ -3677,7 +3678,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2",
]
@ -3745,19 +3745,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -3961,7 +3948,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [
"bytes",
"heck 0.4.1",
"itertools 0.11.0",
"itertools 0.10.5",
"log",
"multimap",
"once_cell",
@ -3982,7 +3969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [
"anyhow",
"itertools 0.11.0",
"itertools 0.10.5",
"proc-macro2",
"quote",
"syn 2.0.31",
@ -4321,7 +4308,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"bincode",
@ -4965,7 +4952,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=fe977fc8285addd5386e940738cdffbbda9eb44e#fe977fc8285addd5386e940738cdffbbda9eb44e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b578c83cc912255e48dea9e33a203a069ce7d0c5#b578c83cc912255e48dea9e33a203a069ce7d0c5"
dependencies = [
"anyhow",
"app-error",

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 = "fe977fc8285addd5386e940738cdffbbda9eb44e" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b578c83cc912255e48dea9e33a203a069ce7d0c5" }
# Please use the following script to update collab.
# Working directory: frontend
#

View File

@ -17,6 +17,7 @@ byteorder = { version = "1.4.3" }
protobuf.workspace = true
tokio = { workspace = true, features = ["full", "rt-multi-thread", "tracing"] }
serde.workspace = true
serde_repr.workspace = true
serde_json.workspace = true
bytes.workspace = true
crossbeam-utils = "0.8.15"

View File

@ -1,21 +1,63 @@
use serde::Deserialize;
use serde_repr::Deserialize_repr;
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
use flowy_server_config::supabase_config::SupabaseConfiguration;
#[derive(Deserialize, Debug)]
pub struct AppFlowyEnv {
cloud_type: CloudType,
supabase_config: SupabaseConfiguration,
appflowy_cloud_config: AFCloudConfiguration,
}
const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE";
#[derive(Deserialize_repr, Debug, Clone)]
#[repr(u8)]
pub enum CloudType {
Local = 0,
Supabase = 1,
AppFlowyCloud = 2,
}
impl CloudType {
fn write_env(&self) {
let s = self.clone() as u8;
std::env::set_var(CLOUT_TYPE_STR, s.to_string());
}
#[allow(dead_code)]
fn from_str(s: &str) -> Self {
match s {
"0" => CloudType::Local,
"1" => CloudType::Supabase,
"2" => CloudType::AppFlowyCloud,
_ => CloudType::Local,
}
}
#[allow(dead_code)]
pub fn from_env() -> Self {
let cloud_type_str = std::env::var(CLOUT_TYPE_STR).unwrap_or_default();
CloudType::from_str(&cloud_type_str)
}
}
impl AppFlowyEnv {
/// Parse the environment variable from the frontend application. The frontend will
/// pass the environment variable as a json string after launching.
pub fn write_env_from(env_str: &str) {
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
env.supabase_config.write_env();
env.appflowy_cloud_config.write_env();
let _ = env.cloud_type.write_env();
let is_valid = env.appflowy_cloud_config.write_env().is_ok();
// Note on Configuration Priority:
// If both Supabase config and AppFlowy cloud config are provided in the '.env' file,
// the AppFlowy cloud config will be prioritized and the Supabase config ignored.
// Ensure only one of these configurations is active at any given time.
if !is_valid {
let _ = env.supabase_config.write_env();
}
}
}
}

View File

@ -7,6 +7,7 @@ use lazy_static::lazy_static;
use parking_lot::Mutex;
use tracing::{error, trace};
use flowy_core::config::AppFlowyCoreConfig;
use flowy_core::*;
use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
use lib_dispatch::prelude::ToBytes;

View File

@ -5,7 +5,8 @@ use std::sync::Arc;
use nanoid::nanoid;
use parking_lot::RwLock;
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
use flowy_core::config::AppFlowyCoreConfig;
use flowy_core::AppFlowyCore;
use flowy_notification::register_notification_sender;
use flowy_user::entities::AuthTypePB;

View File

@ -13,8 +13,8 @@ use flowy_notification::entities::SubscribeObject;
use flowy_notification::NotificationSender;
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
use flowy_user::entities::{
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB, UpdateCloudConfigPB,
UpdateUserProfilePayloadPB, UserCloudConfigPB, UserProfilePB,
AuthTypePB, CloudSettingPB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB,
UpdateCloudConfigPB, UpdateUserProfilePayloadPB, UserProfilePB,
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent::*;
@ -29,7 +29,7 @@ impl EventIntegrationTest {
.event(GetCloudConfig)
.async_send()
.await
.parse::<UserCloudConfigPB>();
.parse::<CloudSettingPB>();
let update = UpdateCloudConfigPB {
enable_sync: None,
enable_encrypt: Some(true),

View File

@ -0,0 +1,86 @@
use std::fmt;
use std::path::Path;
use base64::Engine;
use tracing::{error, info};
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
use flowy_server_config::supabase_config::SupabaseConfiguration;
use flowy_user::manager::URL_SAFE_ENGINE;
use crate::integrate::log::create_log_filter;
use crate::integrate::util::copy_dir_recursive;
#[derive(Clone)]
pub struct AppFlowyCoreConfig {
/// Different `AppFlowyCoreConfig` instance should have different name
pub(crate) name: String,
/// Panics if the `root` path is not existing
pub storage_path: String,
pub(crate) log_filter: String,
cloud_config: Option<AFCloudConfiguration>,
}
impl fmt::Debug for AppFlowyCoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("AppFlowy Configuration");
debug.field("storage_path", &self.storage_path);
if let Some(config) = &self.cloud_config {
debug.field("base_url", &config.base_url);
debug.field("ws_url", &config.ws_base_url);
}
debug.finish()
}
}
fn migrate_local_version_data_folder(root: &str, url: &str) -> String {
// Isolate the user data folder by using the base url of AppFlowy cloud. This is to avoid
// the user data folder being shared by different AppFlowy cloud.
let server_base64 = URL_SAFE_ENGINE.encode(&url);
let storage_path = format!("{}_{}", root, server_base64);
// Copy the user data folder from the root path to the isolated path
// The root path without any suffix is the created by the local version AppFlowy
if !Path::new(&storage_path).exists() && Path::new(root).exists() {
info!("Copy dir from {} to {}", root, storage_path);
let src = Path::new(root);
match copy_dir_recursive(&src, Path::new(&storage_path)) {
Ok(_) => storage_path,
Err(err) => {
// when the copy dir failed, use the root path as the storage path
error!("Copy dir failed: {}", err);
root.to_string()
},
}
} else {
storage_path
}
}
impl AppFlowyCoreConfig {
pub fn new(root: &str, name: String) -> Self {
let cloud_config = AFCloudConfiguration::from_env().ok();
let storage_path = match &cloud_config {
None => {
let supabase_config = SupabaseConfiguration::from_env().ok();
match &supabase_config {
None => root.to_string(),
Some(config) => migrate_local_version_data_folder(root, &config.url),
}
},
Some(config) => migrate_local_version_data_folder(root, &config.base_url),
};
AppFlowyCoreConfig {
name,
storage_path,
log_filter: create_log_filter("info".to_owned(), vec![]),
cloud_config,
}
}
pub fn log_filter(mut self, level: &str, with_crates: Vec<String>) -> Self {
self.log_filter = create_log_filter(level.to_owned(), with_crates);
self
}
}

View File

@ -124,13 +124,7 @@ impl ServerProvider {
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
},
ServerType::Supabase => {
let config = match SupabaseConfiguration::from_env() {
Ok(config) => config,
Err(e) => {
*self.enable_sync.write() = false;
return Err(e);
},
};
let config = SupabaseConfiguration::from_env()?;
let uid = self.uid.clone();
tracing::trace!("🔑Supabase config: {:?}", config);
let encryption = Arc::downgrade(&*self.encryption.read());

View File

@ -21,6 +21,8 @@ use flowy_error::FlowyError;
use flowy_folder_deps::cloud::{
FolderCloudService, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
};
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
use flowy_server_config::supabase_config::SupabaseConfiguration;
use flowy_storage::{FileStorageService, StorageObject};
use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user_deps::cloud::UserCloudService;
@ -68,13 +70,10 @@ impl UserCloudServiceProvider for ServerProvider {
}
fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
match self.get_server(&self.get_server_type()) {
Ok(server) => {
server.set_enable_sync(uid, enable_sync);
*self.enable_sync.write() = enable_sync;
*self.uid.write() = Some(uid);
},
Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e),
if let Ok(server) = self.get_server(&self.get_server_type()) {
server.set_enable_sync(uid, enable_sync);
*self.enable_sync.write() = enable_sync;
*self.uid.write() = Some(uid);
}
}
@ -136,8 +135,16 @@ impl UserCloudServiceProvider for ServerProvider {
Ok(user_service)
}
fn service_name(&self) -> String {
self.get_server_type().to_string()
fn service_url(&self) -> String {
match self.get_server_type() {
ServerType::Local => "".to_string(),
ServerType::AFCloud => AFCloudConfiguration::from_env()
.map(|config| config.base_url)
.unwrap_or_default(),
ServerType::Supabase => SupabaseConfiguration::from_env()
.map(|config| config.url)
.unwrap_or_default(),
}
}
}

View File

@ -47,7 +47,7 @@ impl UserStatusCallback for UserStatusCallbackImpl {
self
.server_provider
.set_enable_sync(user_id, cloud_config.enable_sync);
if cloud_config.enable_encrypt() {
if cloud_config.enable_encrypt {
self
.server_provider
.set_encrypt_secret(cloud_config.encrypt_secret.clone());

View File

@ -1,11 +1,9 @@
#![allow(unused_doc_comments)]
use std::path::Path;
use std::sync::Arc;
use std::sync::Weak;
use std::time::Duration;
use std::{fmt, sync::Arc};
use base64::Engine;
use tokio::sync::RwLock;
use tracing::{debug, error, event, info, instrument};
@ -13,24 +11,24 @@ use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabSource};
use flowy_database2::DatabaseManager;
use flowy_document2::manager::DocumentManager;
use flowy_folder2::manager::FolderManager;
use flowy_server_config::af_cloud_config::AFCloudConfiguration;
use flowy_sqlite::kv::StorePreferences;
use flowy_storage::FileStorageService;
use flowy_task::{TaskDispatcher, TaskRunner};
use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user::manager::{UserManager, UserSessionConfig, URL_SAFE_ENGINE};
use flowy_user::manager::{UserManager, UserSessionConfig};
use lib_dispatch::prelude::*;
use lib_dispatch::runtime::AFPluginRuntime;
use module::make_plugins;
pub use module::*;
use crate::config::AppFlowyCoreConfig;
use crate::deps_resolve::*;
use crate::integrate::collab_interact::CollabInteractImpl;
use crate::integrate::log::{create_log_filter, init_log};
use crate::integrate::log::init_log;
use crate::integrate::server::{current_server_type, ServerProvider, ServerType};
use crate::integrate::user::UserStatusCallbackImpl;
use crate::integrate::util::copy_dir_recursive;
pub mod config;
mod deps_resolve;
mod integrate;
pub mod module;
@ -39,72 +37,6 @@ pub mod module;
/// Don't change this.
pub const DEFAULT_NAME: &str = "appflowy";
#[derive(Clone)]
pub struct AppFlowyCoreConfig {
/// Different `AppFlowyCoreConfig` instance should have different name
name: String,
/// Panics if the `root` path is not existing
pub storage_path: String,
log_filter: String,
cloud_config: Option<AFCloudConfiguration>,
}
impl fmt::Debug for AppFlowyCoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("AppFlowy Configuration");
debug.field("storage_path", &self.storage_path);
if let Some(config) = &self.cloud_config {
debug.field("base_url", &config.base_url);
debug.field("ws_url", &config.ws_base_url);
}
debug.finish()
}
}
impl AppFlowyCoreConfig {
pub fn new(root: &str, name: String) -> Self {
let cloud_config = AFCloudConfiguration::from_env().ok();
let storage_path = match &cloud_config {
None => root.to_string(),
Some(config) => {
// Isolate the user data folder by the base url of AppFlowy cloud. This is to avoid
// the user data folder being shared by different AppFlowy cloud.
let server_base64 = URL_SAFE_ENGINE.encode(&config.base_url);
let storage_path = format!("{}_{}", root, server_base64);
// Copy the user data folder from the root path to the isolated path
// The root path only exists when using the local version of appflowy
if !Path::new(&storage_path).exists() && Path::new(root).exists() {
info!("Copy dir from {} to {}", root, storage_path);
let src = Path::new(root);
match copy_dir_recursive(&src, Path::new(&storage_path)) {
Ok(_) => storage_path,
Err(err) => {
// when the copy dir failed, use the root path as the storage path
error!("Copy dir failed: {}", err);
root.to_string()
},
}
} else {
storage_path
}
},
};
AppFlowyCoreConfig {
name,
storage_path,
log_filter: create_log_filter("info".to_owned(), vec![]),
cloud_config: AFCloudConfiguration::from_env().ok(),
}
}
pub fn log_filter(mut self, level: &str, with_crates: Vec<String>) -> Self {
self.log_filter = create_log_filter(level.to_owned(), with_crates);
self
}
}
#[derive(Clone)]
pub struct AppFlowyCore {
#[allow(dead_code)]

View File

@ -159,7 +159,7 @@ impl FolderManager {
} => {
let is_exist = is_exist_in_local_disk(&self.user, &workspace_id).unwrap_or(false);
if is_exist {
event!(Level::INFO, "Restore folder from local disk");
event!(Level::INFO, "Init folder from local disk");
let collab = self
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
.await?;

View File

@ -2,11 +2,11 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use flowy_error::{ErrorCode, FlowyError};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
pub const APPFLOWY_CLOUD_BASE_URL: &str = "APPFLOWY_CLOUD_BASE_URL";
pub const APPFLOWY_CLOUD_WS_BASE_URL: &str = "APPFLOWY_CLOUD_WS_BASE_URL";
pub const APPFLOWY_CLOUD_GOTRUE_URL: &str = "APPFLOWY_CLOUD_GOTRUE_URL";
pub const APPFLOWY_CLOUD_BASE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL";
pub const APPFLOWY_CLOUD_WS_BASE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL";
pub const APPFLOWY_CLOUD_GOTRUE_URL: &str = "APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL";
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct AFCloudConfiguration {
@ -60,10 +60,25 @@ impl AFCloudConfiguration {
})
}
pub fn validate(&self) -> Result<(), FlowyError> {
if self.base_url.is_empty() || self.ws_base_url.is_empty() || self.gotrue_url.is_empty() {
return Err(FlowyError::new(
ErrorCode::InvalidAuthConfig,
format!(
"Invalid APPFLOWY_CLOUD_BASE_URL: {}, APPFLOWY_CLOUD_WS_BASE_URL: {}, APPFLOWY_CLOUD_GOTRUE_URL: {}",
self.base_url, self.ws_base_url, self.gotrue_url,
)),
);
}
Ok(())
}
/// Write the configuration to the environment variables.
pub fn write_env(&self) {
pub fn write_env(&self) -> FlowyResult<()> {
self.validate()?;
std::env::set_var(APPFLOWY_CLOUD_BASE_URL, &self.base_url);
std::env::set_var(APPFLOWY_CLOUD_WS_BASE_URL, &self.ws_base_url);
std::env::set_var(APPFLOWY_CLOUD_GOTRUE_URL, &self.gotrue_url);
Ok(())
}
}

View File

@ -1,15 +1,9 @@
use serde::{Deserialize, Serialize};
use flowy_error::{ErrorCode, FlowyError};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
pub const ENABLE_SUPABASE_SYNC: &str = "ENABLE_SUPABASE_SYNC";
pub const SUPABASE_URL: &str = "SUPABASE_URL";
pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
pub const SUPABASE_DB: &str = "SUPABASE_DB";
pub const SUPABASE_DB_USER: &str = "SUPABASE_DB_USER";
pub const SUPABASE_DB_PASSWORD: &str = "SUPABASE_DB_PASSWORD";
pub const SUPABASE_DB_PORT: &str = "SUPABASE_DB_PORT";
pub const SUPABASE_URL: &str = "APPFLOWY_CLOUD_ENV_SUPABASE_URL";
pub const SUPABASE_ANON_KEY: &str = "APPFLOWY_CLOUD_ENV_SUPABASE_ANON_KEY";
/// The configuration for the postgres database. It supports deserializing from the json string that
/// passed from the frontend application. [AppFlowyEnv::parser]
@ -39,9 +33,21 @@ impl SupabaseConfiguration {
Ok(Self { url, anon_key })
}
pub fn validate(&self) -> Result<(), FlowyError> {
if self.url.is_empty() || self.anon_key.is_empty() {
return Err(FlowyError::new(
ErrorCode::InvalidAuthConfig,
"Missing SUPABASE_URL or SUPABASE_ANON_KEY",
));
}
Ok(())
}
/// Write the configuration to the environment variables.
pub fn write_env(&self) {
pub fn write_env(&self) -> FlowyResult<()> {
self.validate()?;
std::env::set_var(SUPABASE_URL, &self.url);
std::env::set_var(SUPABASE_ANON_KEY, &self.anon_key);
Ok(())
}
}

View File

@ -16,7 +16,6 @@ const DB_NAME: &str = "cache.db";
pub struct StorePreferences {
database: Option<Database>,
}
impl StorePreferences {
#[tracing::instrument(level = "trace", err)]
pub fn new(root: &str) -> Result<Self, anyhow::Error> {
@ -86,7 +85,6 @@ impl StorePreferences {
.and_then(|v| serde_json::from_str(&v).ok())
}
#[allow(dead_code)]
pub fn remove(&self, key: &str) {
if let Some(conn) = self
.database

View File

@ -20,7 +20,7 @@ use crate::entities::{
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserCloudConfig {
pub enable_sync: bool,
enable_encrypt: bool,
pub enable_encrypt: bool,
// The secret used to encrypt the user's data
pub encrypt_secret: String,
}
@ -34,10 +34,6 @@ impl UserCloudConfig {
}
}
pub fn enable_encrypt(&self) -> bool {
self.enable_encrypt
}
pub fn with_enable_encrypt(mut self, enable_encrypt: bool) -> Self {
self.enable_encrypt = enable_encrypt;
// When the enable_encrypt is true, the encrypt_secret should not be empty

View File

@ -176,8 +176,8 @@ pub struct OauthProviderDataPB {
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
pub enum AuthTypePB {
Local = 0,
AFCloud = 1,
Supabase = 2,
Supabase = 1,
AFCloud = 2,
}
impl Default for AuthTypePB {

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_user_deps::cloud::UserCloudConfig;
use crate::entities::EncryptionTypePB;
@ -137,15 +136,18 @@ impl std::default::Default for AppearanceSettingsPB {
}
#[derive(Default, ProtoBuf)]
pub struct UserCloudConfigPB {
pub struct CloudSettingPB {
#[pb(index = 1)]
enable_sync: bool,
pub(crate) enable_sync: bool,
#[pb(index = 2)]
enable_encrypt: bool,
pub(crate) enable_encrypt: bool,
#[pb(index = 3)]
pub encrypt_secret: String,
#[pb(index = 4)]
pub server_url: String,
}
#[derive(Default, ProtoBuf)]
@ -178,16 +180,6 @@ pub struct UserEncryptionConfigurationPB {
pub require_secret: bool,
}
impl From<UserCloudConfig> for UserCloudConfigPB {
fn from(value: UserCloudConfig) -> Self {
Self {
enable_sync: value.enable_sync,
enable_encrypt: value.enable_encrypt(),
encrypt_secret: value.encrypt_secret,
}
}
}
#[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)]
pub enum NetworkTypePB {
#[default]

View File

@ -385,7 +385,6 @@ pub async fn set_cloud_config_handler(
manager
.set_encrypt_secret(session.user_id, encrypt_secret, encryption_type.clone())
.await?;
save_cloud_config(session.user_id, &store_preferences, config.clone())?;
let params =
UpdateUserProfileParams::new(session.user_id).with_encryption_type(encryption_type);
@ -393,28 +392,40 @@ pub async fn set_cloud_config_handler(
}
}
let config_pb = UserCloudConfigPB::from(config);
save_cloud_config(session.user_id, &store_preferences, config.clone())?;
let payload = CloudSettingPB {
enable_sync: config.enable_sync,
enable_encrypt: config.enable_encrypt,
encrypt_secret: config.encrypt_secret,
server_url: manager.cloud_services.service_url(),
};
send_notification(
&session.user_id.to_string(),
UserNotification::DidUpdateCloudConfig,
)
.payload(config_pb)
.payload(payload)
.send();
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
#[tracing::instrument(level = "info", skip_all, err)]
pub async fn get_cloud_config_handler(
manager: AFPluginState<Weak<UserManager>>,
store_preferences: AFPluginState<Weak<StorePreferences>>,
) -> DataResult<UserCloudConfigPB, FlowyError> {
) -> DataResult<CloudSettingPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let session = manager.get_session()?;
let store_preferences = upgrade_store_preferences(store_preferences)?;
// Generate the default config if the config is not exist
let config = get_or_create_cloud_config(session.user_id, &store_preferences);
data_result_ok(config.into())
data_result_ok(CloudSettingPB {
enable_sync: config.enable_sync,
enable_encrypt: config.enable_encrypt,
encrypt_secret: config.encrypt_secret,
server_url: manager.cloud_services.service_url(),
})
}
#[tracing::instrument(level = "debug", skip(manager), err)]

View File

@ -119,7 +119,7 @@ pub enum UserEvent {
#[event(input = "UpdateCloudConfigPB")]
SetCloudConfig = 13,
#[event(output = "UserCloudConfigPB")]
#[event(output = "CloudSettingPB")]
GetCloudConfig = 14,
#[event(input = "UserSecretPB")]
@ -248,7 +248,7 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
fn get_authenticator(&self) -> Authenticator;
fn set_device_id(&self, device_id: &str);
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError>;
fn service_name(&self) -> String;
fn service_url(&self) -> String;
}
impl<T> UserCloudServiceProvider for Arc<T>
@ -283,8 +283,8 @@ where
(**self).get_user_service()
}
fn service_name(&self) -> String {
(**self).service_name()
fn service_url(&self) -> String {
(**self).service_url()
}
}