chore: support localhost and development mode (#4384)

* chore: update local port for development

* chore: support localhost development

* chore: fix rust test

* chore: update setting

* fix: ci

* fix: ci

* fix: ci

* fix: ci

* chore: fix docker build

* chore: fix ci
This commit is contained in:
Nathan.fooo 2024-01-15 12:53:53 +08:00 committed by GitHub
parent d200c409d6
commit c41a7aaacf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 269 additions and 155 deletions

View File

@ -239,14 +239,10 @@ jobs:
working-directory: AppFlowy-Cloud
run: |
# log level
cp dev.env .env
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's/GOTRUE_SMTP_USER=.*/GOTRUE_SMTP_USER=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_USER }}/' .env
sed -i 's/GOTRUE_SMTP_PASS=.*/GOTRUE_SMTP_PASS=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_PASS }}/' .env
sed -i 's/GOTRUE_SMTP_ADMIN_EMAIL=.*/GOTRUE_SMTP_ADMIN_EMAIL=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_ADMIN_EMAIL }}/' .env
sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
cat .env
- name: Run Docker-Compose
working-directory: AppFlowy-Cloud
@ -254,7 +250,6 @@ jobs:
docker compose down -v --remove-orphans
docker pull appflowyinc/appflowy_cloud:latest
docker compose up -d
docker ps -a
- name: Checkout source code
uses: actions/checkout@v2

View File

@ -77,22 +77,26 @@ jobs:
working-directory: AppFlowy-Cloud
run: |
# log level
cp dev.env .env
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's/GOTRUE_SMTP_USER=.*/GOTRUE_SMTP_USER=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_USER }}/' .env
sed -i 's/GOTRUE_SMTP_PASS=.*/GOTRUE_SMTP_PASS=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_PASS }}/' .env
sed -i 's/GOTRUE_SMTP_ADMIN_EMAIL=.*/GOTRUE_SMTP_ADMIN_EMAIL=${{ secrets.INTEGRATION_TEST_GOTRUE_SMTP_ADMIN_EMAIL }}/' .env
sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
- name: Run Docker-Compose
working-directory: AppFlowy-Cloud
run: |
docker pull appflowyinc/appflowy_cloud:latest
docker compose up -d
- name: Run rust-lib tests
working-directory: frontend/rust-lib
run: RUST_LOG=info RUST_BACKTRACE=1 cargo test --no-default-features --features="rev-sqlite"
env:
RUST_LOG: info
RUST_BACKTRACE: 1
af_cloud_test_base_url: http://localhost
af_cloud_test_ws_url: ws://localhost/ws
af_cloud_test_gotrue_url: http://localhost/gotrue
run: cargo test --no-default-features --features="rev-sqlite" -- --nocapture
- name: rustfmt rust-lib
run: cargo fmt --all -- --check

View File

@ -59,14 +59,12 @@ extension AppFlowyTestBase on WidgetTester {
break;
case AuthenticatorType.supabase:
break;
case AuthenticatorType.appflowyCloud:
rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
break;
case AuthenticatorType.appflowyCloudSelfHost:
rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
break;
default:
throw Exception("not supported");
}
}
return rustEnvs;
@ -86,20 +84,14 @@ extension AppFlowyTestBase on WidgetTester {
() => SupabaseMockAuthService(),
);
break;
case AuthenticatorType.appflowyCloud:
await useAppFlowyCloud();
getIt.unregister<AuthService>();
getIt.registerFactory<AuthService>(
() => AppFlowyCloudMockAuthService(email: email),
);
break;
case AuthenticatorType.appflowyCloudSelfHost:
await useAppFlowyCloud();
getIt.unregister<AuthService>();
getIt.registerFactory<AuthService>(
() => AppFlowyCloudMockAuthService(email: email),
);
break;
default:
throw Exception("not supported");
}
}
},

View File

@ -27,11 +27,13 @@ Future<void> setAuthenticatorType(AuthenticatorType ty) async {
break;
case AuthenticatorType.appflowyCloud:
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 2.toString());
await setAppFlowyCloudUrl(const Some(kAppflowyCloudUrl));
break;
case AuthenticatorType.appflowyCloudSelfHost:
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 3.toString());
break;
case AuthenticatorType.appflowyCloudDevelop:
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 4.toString());
break;
}
}
@ -64,6 +66,8 @@ Future<AuthenticatorType> getAuthenticatorType() async {
return AuthenticatorType.appflowyCloud;
case "3":
return AuthenticatorType.appflowyCloudSelfHost;
case "4":
return AuthenticatorType.appflowyCloudDevelop;
default:
await setAuthenticatorType(AuthenticatorType.appflowyCloud);
return AuthenticatorType.appflowyCloud;
@ -87,8 +91,7 @@ bool get isAuthEnabled {
return env.supabaseConfig.isValid;
}
if (env.authenticatorType == AuthenticatorType.appflowyCloudSelfHost ||
env.authenticatorType == AuthenticatorType.appflowyCloud) {
if (env.authenticatorType.isAppFlowyCloudEnabled) {
return env.appflowyCloudConfig.isValid;
}
@ -117,12 +120,15 @@ enum AuthenticatorType {
local,
supabase,
appflowyCloud,
appflowyCloudSelfHost;
appflowyCloudSelfHost,
// The 'appflowyCloudDevelop' type is used for develop purposes only.
appflowyCloudDevelop;
bool get isLocal => this == AuthenticatorType.local;
bool get isAppFlowyCloudEnabled =>
this == AuthenticatorType.appflowyCloudSelfHost ||
this == AuthenticatorType.appflowyCloudDevelop ||
this == AuthenticatorType.appflowyCloud;
bool get isSupabaseEnabled => this == AuthenticatorType.supabase;
@ -137,6 +143,8 @@ enum AuthenticatorType {
return 2;
case AuthenticatorType.appflowyCloudSelfHost:
return 3;
case AuthenticatorType.appflowyCloudDevelop:
return 4;
}
}
@ -150,6 +158,8 @@ enum AuthenticatorType {
return AuthenticatorType.appflowyCloud;
case 3:
return AuthenticatorType.appflowyCloudSelfHost;
case 4:
return AuthenticatorType.appflowyCloudDevelop;
default:
return AuthenticatorType.local;
}
@ -185,26 +195,25 @@ class AppFlowyCloudSharedEnv {
// If [Env.enableCustomCloud] is true, then use the custom cloud configuration.
if (Env.enableCustomCloud) {
// Use the custom cloud configuration.
var cloudType = await getAuthenticatorType();
var authenticatorType = await getAuthenticatorType();
final appflowyCloudConfig = authenticatorType.isLocal
? AppFlowyCloudConfiguration.defaultConfig()
: await getAppFlowyCloudConfig(authenticatorType);
final supabaseCloudConfig = authenticatorType.isLocal
? SupabaseConfiguration.defaultConfig()
: await getSupabaseCloudConfig();
// In the backend, the value '2' represents the use of AppFlowy Cloud. However, in the frontend,
// we distinguish between [AuthenticatorType.appflowyCloudSelfHost] and [AuthenticatorType.appflowyCloud].
// When the cloud type is [AuthenticatorType.appflowyCloudSelfHost] in the frontend, it should be
// converted to [AuthenticatorType.appflowyCloud] to align with the backend representation,
// where both types are indicated by the value '2'.
if (cloudType == AuthenticatorType.appflowyCloudSelfHost) {
cloudType = AuthenticatorType.appflowyCloud;
if (authenticatorType.isAppFlowyCloudEnabled) {
authenticatorType = AuthenticatorType.appflowyCloud;
}
final appflowyCloudConfig = cloudType.isLocal
? AppFlowyCloudConfiguration.defaultConfig()
: await getAppFlowyCloudConfig();
final supabaseCloudConfig = cloudType.isLocal
? SupabaseConfiguration.defaultConfig()
: await getSupabaseCloudConfig();
return AppFlowyCloudSharedEnv(
authenticatorType: cloudType,
authenticatorType: authenticatorType,
appflowyCloudConfig: appflowyCloudConfig,
supabaseConfig: supabaseCloudConfig,
);
@ -235,13 +244,16 @@ class AppFlowyCloudSharedEnv {
Future<AppFlowyCloudConfiguration> configurationFromUri(
Uri baseUri,
String baseUrl,
AuthenticatorType authenticatorType,
) async {
// When the host is set to 'localhost', the application will utilize the local configuration. This setup assumes that 'localhost' does not employ a reverse proxy, therefore default port settings are used.
if (baseUri.host == "localhost") {
// In development mode, the app is configured to access the AppFlowy cloud server directly through specific ports.
// This setup bypasses the need for Nginx, meaning that the AppFlowy cloud should be running without an Nginx server
// in the development environment.
if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) {
return AppFlowyCloudConfiguration(
base_url: "$baseUrl:8000",
ws_base_url: "ws://${baseUri.host}:8000/ws",
gotrue_url: "$baseUrl:9998",
gotrue_url: "$baseUrl:9999",
);
} else {
return AppFlowyCloudConfiguration(
@ -252,12 +264,14 @@ Future<AppFlowyCloudConfiguration> configurationFromUri(
}
}
Future<AppFlowyCloudConfiguration> getAppFlowyCloudConfig() async {
Future<AppFlowyCloudConfiguration> getAppFlowyCloudConfig(
AuthenticatorType authenticatorType,
) async {
final baseURL = await getAppFlowyCloudUrl();
try {
final uri = Uri.parse(baseURL);
return await configurationFromUri(uri, baseURL);
return await configurationFromUri(uri, baseURL, authenticatorType);
} catch (e) {
Log.error("Failed to parse AppFlowy Cloud URL: $e");
return AppFlowyCloudConfiguration.defaultConfig();

View File

@ -10,7 +10,8 @@ abstract class Env {
static bool get enableCustomCloud {
return Env.authenticatorType ==
AuthenticatorType.appflowyCloudSelfHost.value ||
Env.authenticatorType == AuthenticatorType.appflowyCloud.value &&
Env.authenticatorType == AuthenticatorType.appflowyCloud.value ||
Env.authenticatorType == AuthenticatorType.appflowyCloudDevelop.value &&
_Env.afCloudUrl.isEmpty;
}

View File

@ -142,6 +142,7 @@ void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
break;
case AuthenticatorType.appflowyCloud:
case AuthenticatorType.appflowyCloudSelfHost:
case AuthenticatorType.appflowyCloudDevelop:
getIt.registerFactory<AuthService>(() => AppFlowyCloudAuthService());
break;
}

View File

@ -19,8 +19,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart';
class AppFlowyCloudViewSetting extends StatelessWidget {
final String serverURL;
final AuthenticatorType authenticatorType;
final VoidCallback restartAppFlowy;
const AppFlowyCloudViewSetting({required this.restartAppFlowy, super.key});
const AppFlowyCloudViewSetting({
required this.restartAppFlowy,
super.key,
this.serverURL = kAppflowyCloudUrl,
this.authenticatorType = AuthenticatorType.appflowyCloud,
});
@override
Widget build(BuildContext context) {
@ -58,11 +65,8 @@ class AppFlowyCloudViewSetting extends StatelessWidget {
NavigatorAlertDialog(
title: LocaleKeys.settings_menu_restartAppTip.tr(),
confirm: () async {
await setAppFlowyCloudUrl(
const Some(kAppflowyCloudUrl),
);
await setAuthenticatorType(AuthenticatorType.appflowyCloud);
await setAppFlowyCloudUrl(Some(serverURL));
await setAuthenticatorType(authenticatorType);
restartAppFlowy();
},
).show(context);

View File

@ -2,6 +2,7 @@ import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_local_cloud.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
@ -86,6 +87,12 @@ class SettingCloud extends StatelessWidget {
return CustomAppFlowyCloudView(
restartAppFlowy: didResetServerUrl,
);
case AuthenticatorType.appflowyCloudDevelop:
return AppFlowyCloudViewSetting(
serverURL: "http://localhost",
authenticatorType: AuthenticatorType.appflowyCloudDevelop,
restartAppFlowy: didResetServerUrl,
);
}
}
}
@ -111,16 +118,26 @@ class CloudTypeSwitcher extends StatelessWidget {
onPressed: () {},
),
popupBuilder: (BuildContext context) {
final isDevelopMode = integrationMode().isDevelop;
// Only show the appflowyCloudDevelop in develop mode
final values = AuthenticatorType.values
.where(
(element) =>
isDevelopMode ||
element != AuthenticatorType.appflowyCloudDevelop,
)
.toList();
return ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return CloudTypeItem(
cloudType: AuthenticatorType.values[index],
cloudType: values[index],
currentCloudtype: cloudType,
onSelected: onSelected,
);
},
itemCount: AuthenticatorType.values.length,
itemCount: values.length,
);
},
);
@ -171,5 +188,7 @@ String titleFromCloudType(AuthenticatorType cloudType) {
return LocaleKeys.settings_menu_cloudAppFlowy.tr();
case AuthenticatorType.appflowyCloudSelfHost:
return LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr();
case AuthenticatorType.appflowyCloudDevelop:
return "AppFlowyCloud Develop";
}
}

View File

@ -49,13 +49,18 @@ class _ImportAppFlowyDataState extends State<ImportAppFlowyData> {
},
child: BlocBuilder<SettingFileImportBloc, SettingFileImportState>(
builder: (context, state) {
return const Column(
children: [
ImportAppFlowyDataButton(),
VSpace(6),
AppFlowyDataImportTip(),
],
);
final List<Widget> children = [
const ImportAppFlowyDataButton(),
const VSpace(6),
];
if (state.loadingState.isLoading()) {
children.add(const AppFlowyDataImportingTip());
} else {
children.add(const AppFlowyDataImportTip());
}
return Column(children: children);
},
),
),
@ -127,6 +132,7 @@ class _ImportAppFlowyDataButtonState extends State<ImportAppFlowyDataButton> {
SizedBox(
height: 40,
child: FlowyButton(
disable: state.loadingState.isLoading(),
text:
FlowyText(LocaleKeys.settings_menu_importAppFlowyData.tr()),
onTap: () async {
@ -150,3 +156,24 @@ class _ImportAppFlowyDataButtonState extends State<ImportAppFlowyDataButton> {
);
}
}
class AppFlowyDataImportingTip extends StatelessWidget {
const AppFlowyDataImportingTip({super.key});
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.6,
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: LocaleKeys.settings_menu_importingAppFlowyDataTip.tr(),
style: Theme.of(context).textTheme.bodySmall!,
),
],
),
),
);
}
}

View File

@ -139,7 +139,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -685,7 +685,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -1261,7 +1261,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -2496,7 +2496,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"futures-util",
@ -2512,7 +2512,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -2931,7 +2931,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"reqwest",
@ -4590,7 +4590,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -4612,7 +4612,7 @@ dependencies = [
[[package]]
name = "realtime-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -5260,7 +5260,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -7064,7 +7064,7 @@ dependencies = [
[[package]]
name = "workspace-template"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"async-trait",

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

View File

@ -304,6 +304,7 @@
"openHistoricalUser": "Click to open the anonymous account",
"customPathPrompt": "Storing the AppFlowy data folder in a cloud-synced folder such as Google Drive can pose risks. If the database within this folder is accessed or modified from multiple locations at the same time, it may result in synchronization conflicts and potential data corruption",
"importAppFlowyData": "Import Data from External AppFlowy Folder",
"importingAppFlowyDataTip": "Data import is in progress. Please do not close the app",
"importAppFlowyDataDescription": "Copy data from an external AppFlowy data folder and import it into the current AppFlowy data folder",
"importSuccess": "Successfully imported the AppFlowy data folder",
"importFailed": "Importing the AppFlowy data folder failed",

View File

@ -140,7 +140,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -658,7 +658,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -1194,7 +1194,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -2331,7 +2331,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"futures-util",
@ -2347,7 +2347,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -2705,7 +2705,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"reqwest",
@ -4076,7 +4076,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -4098,7 +4098,7 @@ dependencies = [
[[package]]
name = "realtime-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"bincode",
@ -4677,7 +4677,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"app-error",
@ -5996,7 +5996,7 @@ dependencies = [
[[package]]
name = "workspace-template"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=79702b1ced7b4497afef4e7d42e9fed1084e33e5#79702b1ced7b4497afef4e7d42e9fed1084e33e5"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5a353856afbcb29ee357c4f2bda183a94d4f811d#5a353856afbcb29ee357c4f2bda183a94d4f811d"
dependencies = [
"anyhow",
"async-trait",

View File

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

View File

@ -326,12 +326,26 @@ pub struct SignUpContext {
pub async fn user_localhost_af_cloud() {
AuthenticatorType::AppFlowyCloud.write_env();
let base_url =
std::env::var("af_cloud_test_base_url").unwrap_or("http://localhost:8000".to_string());
let ws_base_url =
std::env::var("af_cloud_test_ws_url").unwrap_or("ws://localhost:8000/ws".to_string());
let gotrue_url =
std::env::var("af_cloud_test_gotrue_url").unwrap_or("http://localhost:9999".to_string());
AFCloudConfiguration {
base_url: "http://localhost:8000".to_string(),
ws_base_url: "ws://localhost:8000/ws".to_string(),
gotrue_url: "http://localhost:9998".to_string(),
base_url,
ws_base_url,
gotrue_url,
}
.write_env();
std::env::set_var("GOTRUE_ADMIN_EMAIL", "admin@example.com");
std::env::set_var("GOTRUE_ADMIN_PASSWORD", "password");
}
#[allow(dead_code)]
pub async fn user_localhost_af_cloud_with_nginx() {
std::env::set_var("af_cloud_test_base_url", "http://localhost");
std::env::set_var("af_cloud_test_ws_url", "ws://localhost/ws");
std::env::set_var("af_cloud_test_gotrue_url", "http://localhost/gotrue");
user_localhost_af_cloud().await
}

View File

@ -6,6 +6,7 @@ use crate::util::generate_test_email;
#[tokio::test]
async fn af_cloud_sign_up_test() {
// user_localhost_af_cloud_with_nginx().await;
user_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let email = generate_test_email();

View File

@ -110,7 +110,8 @@ impl DocumentManager {
))
} else {
let doc_state =
doc_state_from_document_data(doc_id, data.unwrap_or_else(default_document_data))?
doc_state_from_document_data(doc_id, data.unwrap_or_else(default_document_data))
.await?
.doc_state
.to_vec();
let collab = self
@ -290,15 +291,21 @@ impl DocumentManager {
}
}
fn doc_state_from_document_data(
async fn doc_state_from_document_data(
doc_id: &str,
data: DocumentData,
) -> Result<EncodedCollab, FlowyError> {
let collab = Arc::new(MutexCollab::from_collab(Collab::new_with_origin(
CollabOrigin::Empty,
doc_id,
vec![],
)));
let _ = Document::create_with_data(collab.clone(), data).map_err(internal_error)?;
Ok(collab.encode_collab_v1())
let doc_id = doc_id.to_string();
// spawn_blocking is used to avoid blocking the tokio thread pool if the document is large.
let encoded_collab = tokio::task::spawn_blocking(move || {
let collab = Arc::new(MutexCollab::from_collab(Collab::new_with_origin(
CollabOrigin::Empty,
doc_id,
vec![],
)));
let _ = Document::create_with_data(collab.clone(), data).map_err(internal_error)?;
Ok::<_, FlowyError>(collab.encode_collab_v1())
})
.await??;
Ok(encoded_collab)
}

View File

@ -20,7 +20,7 @@ use collab_document::document_data::default_document_collab_data;
use collab_entity::CollabType;
use collab_folder::{Folder, UserId, View, ViewIdentifier, ViewLayout};
use collab_integrate::{CollabKVAction, CollabKVDB, PersistenceError};
use flowy_error::{internal_error, FlowyError};
use flowy_error::FlowyError;
use flowy_folder_pub::cloud::gen_view_id;
use flowy_folder_pub::entities::{AppFlowyData, ImportData};
use flowy_folder_pub::folder_builder::{ParentChildViews, ViewBuilder};
@ -31,8 +31,8 @@ use parking_lot::{Mutex, RwLock};
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::sync::Arc;
use tracing::{debug, error, event, info, instrument};
use std::sync::{Arc, Weak};
use tracing::{debug, error, event, info, instrument, warn};
pub(crate) struct ImportContext {
pub imported_session: Session,
@ -618,7 +618,7 @@ impl DerefMut for OldToNewIdMap {
#[instrument(level = "debug", skip_all)]
pub async fn upload_collab_objects_data(
uid: i64,
user_collab_db: Arc<CollabKVDB>,
user_collab_db: Weak<CollabKVDB>,
workspace_id: &str,
user_authenticator: &Authenticator,
appflowy_data: AppFlowyData,
@ -637,6 +637,10 @@ pub async fn upload_collab_objects_data(
database_object_ids,
} => {
let object_by_collab_type = tokio::task::spawn_blocking(move || {
let user_collab_db = user_collab_db.upgrade().ok_or_else(|| {
FlowyError::internal().with_context("The collab db has been dropped, indicating that the user has switched to a new account")
})?;
let collab_read = user_collab_db.read_txn();
let mut object_by_collab_type = HashMap::new();
@ -657,13 +661,10 @@ pub async fn upload_collab_objects_data(
CollabType::DatabaseRow,
load_and_process_collab_data(uid, &collab_read, &row_object_ids),
);
object_by_collab_type
Ok::<_, FlowyError>(object_by_collab_type)
})
.await
.map_err(internal_error)?;
.await??;
// Upload
let mut size_counter = 0;
let mut objects: Vec<UserCollabParams> = vec![];
for (collab_type, encoded_collab_by_oid) in object_by_collab_type {
@ -679,6 +680,9 @@ pub async fn upload_collab_objects_data(
}
}
// Spawn a new task to upload the collab objects data in the background. If the
// upload fails, we will retry the upload later.
// af_spawn(async move {
if !objects.is_empty() {
batch_create(
uid,
@ -689,6 +693,7 @@ pub async fn upload_collab_objects_data(
)
.await;
}
// });
},
}
@ -707,20 +712,19 @@ async fn batch_create(
.map(|o| o.object_id.clone())
.collect::<Vec<_>>()
.join(", ");
match user_cloud_service
.batch_create_collab_object(workspace_id, objects)
.await
{
Ok(_) => {
info!(
"Batch creating collab objects success: {}, payload size: {}",
ids, size_counter
"Batch creating collab objects success, origin payload size: {}",
size_counter
);
},
Err(err) => {
error!(
"Batch creating collab objects fail:{}, payload size: {}, workspace_id:{}, uid: {}, error: {:?}",
"Batch creating collab objects fail:{}, origin payload size: {}, workspace_id:{}, uid: {}, error: {:?}",
ids, size_counter, workspace_id, uid,err
);
},

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use collab_entity::{CollabObject, CollabType};
use collab_integrate::CollabKVDB;
use tracing::{error, instrument};
use tracing::{error, info, instrument};
use flowy_error::{FlowyError, FlowyResult};
use flowy_folder_pub::entities::{AppFlowyData, ImportData};
@ -16,6 +16,7 @@ use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB};
use crate::migrations::AnonUser;
use crate::notification::{send_notification, UserNotification};
use crate::services::data_import::{upload_collab_objects_data, ImportContext};
use crate::services::entities::Session;
use crate::services::sqlite_sql::workspace_sql::UserWorkspaceTable;
use crate::user_manager::UserManager;
@ -33,52 +34,81 @@ impl UserManager {
match import_data {
ImportData::AppFlowyDataFolder { items } => {
for item in items {
match item {
AppFlowyData::Folder {
views,
database_view_ids_by_database_id,
} => {
let (tx, rx) = tokio::sync::oneshot::channel();
let cloned_workspace_service = self.user_workspace_service.clone();
tokio::spawn(async move {
let result = async {
cloned_workspace_service
.did_import_database_views(database_view_ids_by_database_id)
.await?;
cloned_workspace_service.did_import_views(views).await?;
Ok::<(), FlowyError>(())
}
.await;
let _ = tx.send(result);
})
self.upload_appflowy_data_item(&session, item).await?;
}
},
}
Ok(())
}
async fn upload_appflowy_data_item(
&self,
session: &Session,
item: AppFlowyData,
) -> Result<(), FlowyError> {
match item {
AppFlowyData::Folder {
views,
database_view_ids_by_database_id,
} => {
// Since `async_trait` does not implement `Sync`, and the handler requires `Sync`, we use a
// channel to synchronize the operation. This approach allows asynchronous trait methods to be compatible
// with synchronous handler requirements."
let (tx, rx) = tokio::sync::oneshot::channel();
let cloned_workspace_service = self.user_workspace_service.clone();
tokio::spawn(async move {
let result = async {
cloned_workspace_service
.did_import_database_views(database_view_ids_by_database_id)
.await?;
rx.await??;
},
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
} => {
let user = self.get_user_profile_from_disk(session.user_id).await?;
let user_collab_db = self
.get_collab_db(session.user_id)?
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?;
upload_collab_objects_data(
session.user_id,
user_collab_db,
&user.workspace_id,
&user.authenticator,
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
},
self.cloud_services.get_user_service()?,
)
.await?;
},
cloned_workspace_service.did_import_views(views).await?;
Ok::<(), FlowyError>(())
}
.await;
let _ = tx.send(result);
})
.await?;
rx.await??;
},
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
} => {
let user = self.get_user_profile_from_disk(session.user_id).await?;
let user_collab_db = self
.get_collab_db(session.user_id)?
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?;
let user_id = session.user_id;
let weak_user_collab_db = Arc::downgrade(&user_collab_db);
let weak_user_cloud_service = self.cloud_services.get_user_service()?;
match upload_collab_objects_data(
user_id,
weak_user_collab_db,
&user.workspace_id,
&user.authenticator,
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
},
weak_user_cloud_service,
)
.await
{
Ok(_) => info!(
"Successfully uploaded collab objects data for user:{}",
user_id
),
Err(err) => {
error!(
"Failed to upload collab objects data: {:?} for user:{}",
err, user_id
);
// TODO(nathan): retry uploading the collab objects data.
},
}
},
}

View File

@ -32,8 +32,8 @@ RUN yay -S --noconfirm curl base-devel openssl clang cmake ninja pkg-config xdg-
RUN xdg-user-dirs-update
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
RUN source ~/.cargo/env && \
rustup toolchain install 1.70 && \
rustup default 1.70
rustup toolchain install 1.75 && \
rustup default 1.75
# Install Flutter
RUN sudo pacman -S --noconfirm git tar gtk3