mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: sign in with provider (#3592)
* chore: sign in with provider * feat: implement oauth flow of appflowy cloud * chore: rename env * chore: fix deadlock * fix: login bugs * chore: clippyt * chore: update client api * chore: fmt
This commit is contained in:
parent
92523321f1
commit
a59561aee3
8
frontend/.vscode/tasks.json
vendored
8
frontend/.vscode/tasks.json
vendored
@ -285,6 +285,14 @@
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/appflowy_flutter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "AF: Generate AppFlowyEnv",
|
||||
"type": "shell",
|
||||
"command": "dart run build_runner build --delete-conflicting-outputs",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/appflowy_flutter/packages/appflowy_backend"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -5,10 +5,11 @@
|
||||
|
||||
# Configuring Cloud Type
|
||||
# This configuration file is used to specify the cloud type and the necessary configurations for each cloud type. The available options are:
|
||||
# Supabase: Set CLOUD_TYPE to 1
|
||||
# AppFlowy Cloud: Set CLOUD_TYPE to 2
|
||||
# Local: 0
|
||||
# Supabase: 1
|
||||
# AppFlowy Cloud: 2
|
||||
|
||||
CLOUD_TYPE=1
|
||||
CLOUD_TYPE=0
|
||||
|
||||
# Supabase Configuration
|
||||
# If you're using Supabase (CLOUD_TYPE=1), you need to provide the following configurations:
|
||||
@ -18,4 +19,5 @@ SUPABASE_ANON_KEY=replace-with-your-supabase-key
|
||||
# AppFlowy Cloud Configuration
|
||||
# If you're using AppFlowy Cloud (CLOUD_TYPE=2), you need to provide the following configurations:
|
||||
APPFLOWY_CLOUD_BASE_URL=replace-with-your-appflowy-cloud-url
|
||||
APPFLOWY_CLOUD_BASE_WS_URL=replace-with-your-appflowy-cloud-ws-url
|
||||
APPFLOWY_CLOUD_WS_BASE_URL=replace-with-your-appflowy-cloud-ws-url
|
||||
APPFLOWY_CLOUD_GOTRUE_URL=replace-with-your-appflowy-cloud-gotrue-url
|
31
frontend/appflowy_flutter/lib/env/env.dart
vendored
31
frontend/appflowy_flutter/lib/env/env.dart
vendored
@ -35,10 +35,17 @@ abstract class Env {
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'APPFLOWY_CLOUD_BASE_WS_URL',
|
||||
varName: 'APPFLOWY_CLOUD_WS_BASE_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String afCloudBaseWSUrl = _Env.afCloudBaseWSUrl;
|
||||
static final String afCloudWSBaseUrl = _Env.afCloudWSBaseUrl;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'APPFLOWY_CLOUD_GOTRUE_URL',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String afCloudGoTrueUrl = _Env.afCloudGoTrueUrl;
|
||||
|
||||
// Supabase Configuration:
|
||||
@EnviedField(
|
||||
@ -64,6 +71,24 @@ bool get isCloudEnabled {
|
||||
}
|
||||
}
|
||||
|
||||
bool get isSupabaseEnabled {
|
||||
// Only enable supabase in release and develop mode.
|
||||
if (integrationMode().isRelease || integrationMode().isDevelop) {
|
||||
return currentCloudType() == CloudType.supabase;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get isAppFlowyCloudEnabled {
|
||||
// Only enable appflowy cloud in release and develop mode.
|
||||
if (integrationMode().isRelease || integrationMode().isDevelop) {
|
||||
return currentCloudType() == CloudType.appflowyCloud;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum CloudType {
|
||||
unknown,
|
||||
supabase,
|
||||
@ -84,7 +109,7 @@ CloudType currentCloudType() {
|
||||
}
|
||||
|
||||
if (value == 2) {
|
||||
if (Env.afCloudBaseUrl.isEmpty || Env.afCloudBaseWSUrl.isEmpty) {
|
||||
if (Env.afCloudBaseUrl.isEmpty || Env.afCloudWSBaseUrl.isEmpty) {
|
||||
Log.error("AppFlowy cloud is not configured");
|
||||
return CloudType.unknown;
|
||||
} else {
|
||||
|
@ -38,7 +38,8 @@ AppFlowyEnv getAppFlowyEnv() {
|
||||
|
||||
final appflowyCloudConfig = AppFlowyCloudConfiguration(
|
||||
base_url: Env.afCloudBaseUrl,
|
||||
base_ws_url: Env.afCloudBaseWSUrl,
|
||||
ws_base_url: Env.afCloudWSBaseUrl,
|
||||
gotrue_url: Env.afCloudGoTrueUrl,
|
||||
);
|
||||
|
||||
return AppFlowyEnv(
|
||||
|
@ -28,7 +28,7 @@ SupbaseRealtimeService? realtimeService;
|
||||
class InitSupabaseTask extends LaunchTask {
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
if (!isCloudEnabled) {
|
||||
if (!isSupabaseEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,23 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:appflowy/user/application/auth/backend_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.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:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'auth_error.dart';
|
||||
import 'device_id.dart';
|
||||
|
||||
class AFCloudAuthService implements AuthService {
|
||||
final _appLinks = AppLinks();
|
||||
StreamSubscription<Uri?>? _deeplinkSubscription;
|
||||
|
||||
AFCloudAuthService();
|
||||
|
||||
final BackendAuthService _backendAuthService = BackendAuthService(
|
||||
@ -38,8 +48,72 @@ class AFCloudAuthService implements AuthService {
|
||||
required String platform,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
//
|
||||
throw UnimplementedError();
|
||||
final provider = ProviderTypePBExtension.fromPlatform(platform);
|
||||
|
||||
// Get the oauth url from the backend
|
||||
final result = await UserEventGetOauthURLWithProvider(
|
||||
OauthProviderPB.create()..provider = provider,
|
||||
).send();
|
||||
|
||||
return result.fold(
|
||||
(data) async {
|
||||
// Open the webview with oauth url
|
||||
final uri = Uri.parse(data.oauthUrl);
|
||||
final isSuccess = await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
webOnlyWindowName: '_self',
|
||||
);
|
||||
|
||||
final completer = Completer<Either<FlowyError, UserProfilePB>>();
|
||||
_deeplinkSubscription = _appLinks.uriLinkStream.listen(
|
||||
(Uri? uri) async {
|
||||
await _handleUri(uri, completer);
|
||||
},
|
||||
onError: (Object err, StackTrace stackTrace) {
|
||||
Log.error('onDeepLinkError: ${err.toString()}', stackTrace);
|
||||
_deeplinkSubscription?.cancel();
|
||||
completer.complete(left(AuthError.deeplinkError));
|
||||
},
|
||||
);
|
||||
|
||||
if (!isSuccess) {
|
||||
_deeplinkSubscription?.cancel();
|
||||
completer.complete(left(AuthError.signInWithOauthError));
|
||||
}
|
||||
|
||||
return completer.future;
|
||||
},
|
||||
(r) => left(r),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleUri(
|
||||
Uri? uri,
|
||||
Completer<Either<FlowyError, UserProfilePB>> completer,
|
||||
) async {
|
||||
if (uri != null) {
|
||||
if (_isAuthCallbackDeeplink(uri)) {
|
||||
// Sign in with url
|
||||
final deviceId = await getDeviceId();
|
||||
final payload = OauthSignInPB(
|
||||
authType: AuthTypePB.AFCloud,
|
||||
map: {
|
||||
AuthServiceMapKeys.signInURL: uri.toString(),
|
||||
AuthServiceMapKeys.deviceId: deviceId
|
||||
},
|
||||
);
|
||||
final result = await UserEventOauthSignIn(payload)
|
||||
.send()
|
||||
.then((value) => value.swap());
|
||||
_deeplinkSubscription?.cancel();
|
||||
completer.complete(result);
|
||||
}
|
||||
} else {
|
||||
Log.error('onDeepLinkError: Unexpect empty deep link callback');
|
||||
_deeplinkSubscription?.cancel();
|
||||
completer.complete(left(AuthError.emptyDeeplink));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -67,3 +141,22 @@ class AFCloudAuthService implements AuthService {
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
}
|
||||
}
|
||||
|
||||
extension ProviderTypePBExtension on ProviderTypePB {
|
||||
static ProviderTypePB fromPlatform(String platform) {
|
||||
switch (platform) {
|
||||
case 'github':
|
||||
return ProviderTypePB.Github;
|
||||
case 'google':
|
||||
return ProviderTypePB.Google;
|
||||
case 'discord':
|
||||
return ProviderTypePB.Discord;
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isAuthCallbackDeeplink(Uri uri) {
|
||||
return (uri.fragment.contains('access_token'));
|
||||
}
|
||||
|
@ -17,4 +17,16 @@ class AuthError {
|
||||
static final supabaseGetUserError = FlowyError()
|
||||
..msg = 'unable to get user from supabase -10004'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
|
||||
static final signInWithOauthError = FlowyError()
|
||||
..msg = 'sign in with oauth error -10003'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
|
||||
static final emptyDeeplink = FlowyError()
|
||||
..msg = 'Unexpected empty deeplink'
|
||||
..code = ErrorCode.UnexpectedEmpty;
|
||||
|
||||
static final deeplinkError = FlowyError()
|
||||
..msg = 'Deeplink error'
|
||||
..code = ErrorCode.Internal;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ class AuthServiceMapKeys {
|
||||
static const String uuid = 'uuid';
|
||||
static const String email = 'email';
|
||||
static const String deviceId = 'device_id';
|
||||
static const String signInURL = 'sign_in_url';
|
||||
}
|
||||
|
||||
/// `AuthService` is an abstract class that defines methods related to user authentication.
|
||||
|
@ -169,12 +169,12 @@ class SupabaseAuthService implements AuthService {
|
||||
Future<Either<FlowyError, UserProfilePB>> _setupAuth({
|
||||
required Map<String, String> map,
|
||||
}) async {
|
||||
final payload = OAuthPB(
|
||||
final payload = OauthSignInPB(
|
||||
authType: AuthTypePB.Supabase,
|
||||
map: map,
|
||||
);
|
||||
|
||||
return UserEventOAuth(payload).send().then((value) => value.swap());
|
||||
return UserEventOauthSignIn(payload).send().then((value) => value.swap());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ class MockAuthService implements AuthService {
|
||||
final uuid = response.user!.id;
|
||||
final email = response.user!.email!;
|
||||
|
||||
final payload = OAuthPB(
|
||||
final payload = OauthSignInPB(
|
||||
authType: AuthTypePB.Supabase,
|
||||
map: {
|
||||
AuthServiceMapKeys.uuid: uuid,
|
||||
@ -65,7 +65,7 @@ class MockAuthService implements AuthService {
|
||||
},
|
||||
);
|
||||
|
||||
return UserEventOAuth(payload).send().then((value) => value.swap());
|
||||
return UserEventOauthSignIn(payload).send().then((value) => value.swap());
|
||||
} on AuthException catch (e) {
|
||||
Log.error(e);
|
||||
return Left(AuthError.supabaseSignInError);
|
||||
|
@ -1,10 +1,9 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
// Run `dart run build_runner build` to generate the json serialization If the
|
||||
// file `env_serde.i.dart` is existed, delete it first.
|
||||
// file `env_serde.g.dart` is existed, delete it first.
|
||||
//
|
||||
// the file `env_serde.g.dart` will be generated in the same directory. Rename
|
||||
// the file to `env_serde.i.dart` because the file is ignored by default.
|
||||
// the file `env_serde.g.dart` will be generated in the same directory.
|
||||
part 'env_serde.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
@ -45,11 +44,13 @@ class SupabaseConfiguration {
|
||||
@JsonSerializable()
|
||||
class AppFlowyCloudConfiguration {
|
||||
final String base_url;
|
||||
final String base_ws_url;
|
||||
final String ws_base_url;
|
||||
final String gotrue_url;
|
||||
|
||||
AppFlowyCloudConfiguration({
|
||||
required this.base_url,
|
||||
required this.base_ws_url,
|
||||
required this.ws_base_url,
|
||||
required this.gotrue_url,
|
||||
});
|
||||
|
||||
factory AppFlowyCloudConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -26,7 +26,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
app_links:
|
||||
dependency: "direct overridden"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: c64ce17
|
||||
|
@ -115,6 +115,7 @@ dependencies:
|
||||
# TODO: Consider implementing custom package
|
||||
# to gather notification handling for all platforms
|
||||
local_notifier: ^0.1.5
|
||||
app_links: ^3.4.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
|
56
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
56
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -762,7 +762,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -775,7 +775,6 @@ dependencies = [
|
||||
"gotrue-entity",
|
||||
"lib0",
|
||||
"mime",
|
||||
"opener",
|
||||
"parking_lot",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
@ -853,7 +852,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -872,7 +871,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -902,7 +901,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-define"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -916,7 +915,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -928,7 +927,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -948,7 +947,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -979,16 +978,18 @@ dependencies = [
|
||||
"collab-persistence",
|
||||
"collab-plugins",
|
||||
"futures",
|
||||
"lib-infra",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
@ -1009,7 +1010,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1036,7 +1037,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1289,7 +1290,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.6",
|
||||
"phf 0.11.2",
|
||||
"phf 0.8.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1435,13 +1436,15 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"collab-define",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"validator",
|
||||
]
|
||||
@ -2772,7 +2775,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2782,12 +2785,13 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -3220,7 +3224,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -4261,7 +4265,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",
|
||||
]
|
||||
|
||||
@ -4353,19 +4356,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"
|
||||
@ -5579,9 +5569,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared_entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"database-entity",
|
||||
"gotrue-entity",
|
||||
"opener",
|
||||
"reqwest",
|
||||
@ -6494,7 +6485,9 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
@ -6711,6 +6704,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
|
@ -38,7 +38,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 = "b0c213" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "926da91" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -48,14 +48,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b0c
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
|
||||
|
||||
|
||||
|
58
frontend/rust-lib/Cargo.lock
generated
58
frontend/rust-lib/Cargo.lock
generated
@ -660,7 +660,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -673,7 +673,6 @@ dependencies = [
|
||||
"gotrue-entity",
|
||||
"lib0",
|
||||
"mime",
|
||||
"opener",
|
||||
"parking_lot",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
@ -720,7 +719,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -739,7 +738,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -769,7 +768,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-define"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -783,7 +782,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -795,7 +794,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -815,7 +814,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -846,16 +845,18 @@ dependencies = [
|
||||
"collab-persistence",
|
||||
"collab-plugins",
|
||||
"futures",
|
||||
"lib-infra",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
@ -876,7 +877,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -903,7 +904,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=86c5e8#86c5e8891af93de2d035a57214894e854d39662a"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=82975da#82975da119a0c9d0e8b70585966bb89ed6c97cd3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1135,7 +1136,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.11.2",
|
||||
"phf 0.8.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1262,13 +1263,15 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"collab-define",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"validator",
|
||||
]
|
||||
@ -2434,7 +2437,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2444,12 +2447,13 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -2807,7 +2811,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -3570,7 +3574,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",
|
||||
]
|
||||
@ -3590,7 +3594,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",
|
||||
]
|
||||
|
||||
@ -3658,19 +3661,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"
|
||||
@ -4821,9 +4811,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared_entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b0c213#b0c213b5c0b941e2013b0126e200f98201a82f54"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=926da91#926da912eab806e5b325e12103d341cea536aa2b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"database-entity",
|
||||
"gotrue-entity",
|
||||
"opener",
|
||||
"reqwest",
|
||||
@ -5432,7 +5423,9 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
@ -5660,6 +5653,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
|
@ -82,7 +82,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 = "b0c213" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "926da91" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -92,11 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b0c
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "86c5e8" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "82975da" }
|
||||
|
@ -20,6 +20,8 @@ tracing = "0.1"
|
||||
parking_lot = "0.12.1"
|
||||
futures = "0.3"
|
||||
async-trait = "0.1.73"
|
||||
tokio = {version = "1.26", features = ["sync"]}
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
@ -11,8 +11,10 @@ use collab_plugins::cloud_storage::network_state::{CollabNetworkReachability, Co
|
||||
use collab_plugins::local_storage::rocksdb::RocksdbDiskPlugin;
|
||||
use collab_plugins::local_storage::CollabPersistenceConfig;
|
||||
use collab_plugins::snapshot::{CollabSnapshotPlugin, SnapshotPersistence};
|
||||
use futures::executor::block_on;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use tracing::trace;
|
||||
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CollabSource {
|
||||
@ -36,19 +38,17 @@ pub enum CollabPluginContext {
|
||||
},
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait CollabStorageProvider: Send + Sync + 'static {
|
||||
fn storage_source(&self) -> CollabSource;
|
||||
|
||||
async fn get_plugins(
|
||||
fn get_plugins(
|
||||
&self,
|
||||
context: CollabPluginContext,
|
||||
) -> Vec<Arc<dyn collab::core::collab_plugin::CollabPlugin>>;
|
||||
) -> Fut<Vec<Arc<dyn collab::core::collab_plugin::CollabPlugin>>>;
|
||||
|
||||
fn is_sync_enabled(&self) -> bool;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> CollabStorageProvider for Arc<T>
|
||||
where
|
||||
T: CollabStorageProvider,
|
||||
@ -57,8 +57,8 @@ where
|
||||
(**self).storage_source()
|
||||
}
|
||||
|
||||
async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
|
||||
(**self).get_plugins(context).await
|
||||
fn get_plugins(&self, context: CollabPluginContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
(**self).get_plugins(context)
|
||||
}
|
||||
|
||||
fn is_sync_enabled(&self) -> bool {
|
||||
@ -69,7 +69,7 @@ where
|
||||
pub struct AppFlowyCollabBuilder {
|
||||
network_reachability: CollabNetworkReachability,
|
||||
workspace_id: RwLock<Option<String>>,
|
||||
cloud_storage: RwLock<Arc<dyn CollabStorageProvider>>,
|
||||
cloud_storage: tokio::sync::RwLock<Arc<dyn CollabStorageProvider>>,
|
||||
snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>,
|
||||
device_id: Mutex<String>,
|
||||
}
|
||||
@ -79,7 +79,7 @@ impl AppFlowyCollabBuilder {
|
||||
Self {
|
||||
network_reachability: CollabNetworkReachability::new(),
|
||||
workspace_id: Default::default(),
|
||||
cloud_storage: RwLock::new(Arc::new(storage_provider)),
|
||||
cloud_storage: tokio::sync::RwLock::new(Arc::new(storage_provider)),
|
||||
snapshot_persistence: Default::default(),
|
||||
device_id: Default::default(),
|
||||
}
|
||||
@ -141,7 +141,7 @@ impl AppFlowyCollabBuilder {
|
||||
/// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
|
||||
/// - `collab_db`: A weak reference to the [RocksCollabDB].
|
||||
///
|
||||
pub fn build(
|
||||
pub async fn build(
|
||||
&self,
|
||||
uid: i64,
|
||||
object_id: &str,
|
||||
@ -149,14 +149,16 @@ impl AppFlowyCollabBuilder {
|
||||
raw_data: CollabRawData,
|
||||
collab_db: Weak<RocksCollabDB>,
|
||||
) -> Result<Arc<MutexCollab>, Error> {
|
||||
self.build_with_config(
|
||||
uid,
|
||||
object_id,
|
||||
object_type,
|
||||
collab_db,
|
||||
raw_data,
|
||||
&CollabPersistenceConfig::default(),
|
||||
)
|
||||
self
|
||||
.build_with_config(
|
||||
uid,
|
||||
object_id,
|
||||
object_type,
|
||||
collab_db,
|
||||
raw_data,
|
||||
&CollabPersistenceConfig::default(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a new collaboration builder with the custom configuration.
|
||||
@ -173,7 +175,7 @@ impl AppFlowyCollabBuilder {
|
||||
/// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
|
||||
/// - `collab_db`: A weak reference to the [RocksCollabDB].
|
||||
///
|
||||
pub fn build_with_config(
|
||||
pub async fn build_with_config(
|
||||
&self,
|
||||
uid: i64,
|
||||
object_id: &str,
|
||||
@ -194,21 +196,26 @@ impl AppFlowyCollabBuilder {
|
||||
.build()?,
|
||||
);
|
||||
{
|
||||
let cloud_storage = self.cloud_storage.read();
|
||||
let cloud_storage_type = cloud_storage.storage_source();
|
||||
let cloud_storage_type = self.cloud_storage.read().await.storage_source();
|
||||
let collab_object = self.collab_object(uid, object_id, object_type)?;
|
||||
match cloud_storage_type {
|
||||
CollabSource::AFCloud => {
|
||||
#[cfg(feature = "appflowy_cloud_integrate")]
|
||||
{
|
||||
trace!("init appflowy cloud collab plugins");
|
||||
let local_collab = Arc::downgrade(&collab);
|
||||
let plugins = block_on(
|
||||
cloud_storage.get_plugins(CollabPluginContext::AppFlowyCloud {
|
||||
let plugins = self
|
||||
.cloud_storage
|
||||
.read()
|
||||
.await
|
||||
.get_plugins(CollabPluginContext::AppFlowyCloud {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
|
||||
trace!("add appflowy cloud collab plugins: {}", plugins.len());
|
||||
for plugin in plugins {
|
||||
collab.lock().add_plugin(plugin);
|
||||
}
|
||||
@ -217,14 +224,20 @@ impl AppFlowyCollabBuilder {
|
||||
CollabSource::Supabase => {
|
||||
#[cfg(feature = "supabase_integrate")]
|
||||
{
|
||||
trace!("init supabase collab plugins");
|
||||
let local_collab = Arc::downgrade(&collab);
|
||||
let local_collab_db = collab_db.clone();
|
||||
let plugins = block_on(cloud_storage.get_plugins(CollabPluginContext::Supabase {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
local_collab_db,
|
||||
}));
|
||||
let plugins = self
|
||||
.cloud_storage
|
||||
.read()
|
||||
.await
|
||||
.get_plugins(CollabPluginContext::Supabase {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
local_collab_db,
|
||||
})
|
||||
.await;
|
||||
for plugin in plugins {
|
||||
collab.lock().add_plugin(plugin);
|
||||
}
|
||||
@ -248,7 +261,7 @@ impl AppFlowyCollabBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
block_on(collab.async_initialize());
|
||||
collab.lock().initialize();
|
||||
Ok(collab)
|
||||
}
|
||||
}
|
||||
@ -261,8 +274,8 @@ impl CollabStorageProvider for DefaultCollabStorageProvider {
|
||||
CollabSource::Local
|
||||
}
|
||||
|
||||
async fn get_plugins(&self, _context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
|
||||
vec![]
|
||||
fn get_plugins(&self, _context: CollabPluginContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
to_fut(async move { vec![] })
|
||||
}
|
||||
|
||||
fn is_sync_enabled(&self) -> bool {
|
||||
|
@ -20,8 +20,7 @@ use flowy_storage::{FileStorageService, StorageObject};
|
||||
use flowy_user::event_map::UserCloudServiceProvider;
|
||||
use flowy_user_deps::cloud::UserCloudService;
|
||||
use flowy_user_deps::entities::AuthType;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::future::{to_fut, Fut, FutureResult};
|
||||
|
||||
use crate::integrate::server::{ServerProvider, ServerType, SERVER_PROVIDER_TYPE_KEY};
|
||||
|
||||
@ -258,48 +257,53 @@ impl DocumentCloudService for ServerProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CollabStorageProvider for ServerProvider {
|
||||
fn storage_source(&self) -> CollabSource {
|
||||
self.get_server_type().into()
|
||||
}
|
||||
|
||||
async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
|
||||
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
|
||||
fn get_plugins(&self, context: CollabPluginContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
match context {
|
||||
CollabPluginContext::Local => {},
|
||||
CollabPluginContext::Local => to_fut(async move { vec![] }),
|
||||
CollabPluginContext::AppFlowyCloud {
|
||||
uid: _,
|
||||
collab_object,
|
||||
local_collab,
|
||||
} => {
|
||||
if let Ok(server) = self.get_server(&ServerType::AFCloud) {
|
||||
match server.collab_ws_channel(&collab_object.object_id).await {
|
||||
Ok(Some((channel, ws_connect_state))) => {
|
||||
let origin = CollabOrigin::Client(CollabClient::new(
|
||||
collab_object.uid,
|
||||
collab_object.device_id.clone(),
|
||||
));
|
||||
let sync_object = SyncObject::from(collab_object);
|
||||
let (sink, stream) = (channel.sink(), channel.stream());
|
||||
let sink_config = SinkConfig::new().with_timeout(6);
|
||||
let sync_plugin = SyncPlugin::new(
|
||||
origin,
|
||||
sync_object,
|
||||
local_collab,
|
||||
sink,
|
||||
sink_config,
|
||||
stream,
|
||||
Some(channel),
|
||||
ws_connect_state,
|
||||
);
|
||||
plugins.push(Arc::new(sync_plugin));
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::error!("🔴Failed to get collab ws channel: channel is none");
|
||||
},
|
||||
Err(err) => tracing::error!("🔴Failed to get collab ws channel: {:?}", err),
|
||||
}
|
||||
to_fut(async move {
|
||||
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
|
||||
match server.collab_ws_channel(&collab_object.object_id).await {
|
||||
Ok(Some((channel, ws_connect_state))) => {
|
||||
let origin = CollabOrigin::Client(CollabClient::new(
|
||||
collab_object.uid,
|
||||
collab_object.device_id.clone(),
|
||||
));
|
||||
let sync_object = SyncObject::from(collab_object);
|
||||
let (sink, stream) = (channel.sink(), channel.stream());
|
||||
let sink_config = SinkConfig::new().with_timeout(6);
|
||||
let sync_plugin = SyncPlugin::new(
|
||||
origin,
|
||||
sync_object,
|
||||
local_collab,
|
||||
sink,
|
||||
sink_config,
|
||||
stream,
|
||||
Some(channel),
|
||||
ws_connect_state,
|
||||
);
|
||||
plugins.push(Arc::new(sync_plugin));
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::error!("🔴Failed to get collab ws channel: channel is none");
|
||||
},
|
||||
Err(err) => tracing::error!("🔴Failed to get collab ws channel: {:?}", err),
|
||||
}
|
||||
|
||||
plugins
|
||||
})
|
||||
} else {
|
||||
to_fut(async move { vec![] })
|
||||
}
|
||||
},
|
||||
CollabPluginContext::Supabase {
|
||||
@ -308,6 +312,7 @@ impl CollabStorageProvider for ServerProvider {
|
||||
local_collab,
|
||||
local_collab_db,
|
||||
} => {
|
||||
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
|
||||
if let Some(remote_collab_storage) = self
|
||||
.get_server(&ServerType::Supabase)
|
||||
.ok()
|
||||
@ -322,9 +327,10 @@ impl CollabStorageProvider for ServerProvider {
|
||||
local_collab_db,
|
||||
)));
|
||||
}
|
||||
|
||||
to_fut(async move { plugins })
|
||||
},
|
||||
}
|
||||
plugins
|
||||
}
|
||||
|
||||
fn is_sync_enabled(&self) -> bool {
|
||||
|
@ -1,5 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use flowy_database2::DatabaseManager;
|
||||
use flowy_document2::manager::DocumentManager;
|
||||
@ -130,18 +132,22 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
},
|
||||
&user_workspace.id,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.context("FolderManager error")?;
|
||||
|
||||
database_manager
|
||||
.initialize_with_new_user(
|
||||
user_profile.uid,
|
||||
user_workspace.id.clone(),
|
||||
user_workspace.database_views_aggregate_id,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.context("DatabaseManager error")?;
|
||||
|
||||
document_manager
|
||||
.initialize_with_new_user(user_profile.uid, user_workspace.id)
|
||||
.await?;
|
||||
.await
|
||||
.context("DocumentManager error")?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ use collab_database::user::{
|
||||
};
|
||||
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
||||
use collab_define::CollabType;
|
||||
use futures::executor::block_on;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB};
|
||||
@ -87,7 +89,7 @@ impl DatabaseManager {
|
||||
|
||||
// If the workspace database not exist in disk, try to fetch from remote.
|
||||
if !self.is_collab_exist(uid, &collab_db, &database_views_aggregate_id) {
|
||||
tracing::trace!("workspace database not exist, try to fetch from remote");
|
||||
trace!("workspace database not exist, try to fetch from remote");
|
||||
match self
|
||||
.cloud_service
|
||||
.get_collab_update(&database_views_aggregate_id, CollabType::WorkspaceDatabase)
|
||||
@ -106,7 +108,7 @@ impl DatabaseManager {
|
||||
}
|
||||
|
||||
// Construct the workspace database.
|
||||
tracing::trace!("open workspace database: {}", &database_views_aggregate_id);
|
||||
trace!("open workspace database: {}", &database_views_aggregate_id);
|
||||
let collab = collab_builder.build_collab_with_config(
|
||||
uid,
|
||||
&database_views_aggregate_id,
|
||||
@ -125,6 +127,7 @@ impl DatabaseManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn initialize_with_new_user(
|
||||
&self,
|
||||
user_id: i64,
|
||||
@ -170,7 +173,7 @@ impl DatabaseManager {
|
||||
}
|
||||
|
||||
pub async fn open_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||
tracing::trace!("create new editor for database {}", database_id);
|
||||
trace!("create new editor for database {}", database_id);
|
||||
let mut editors = self.editors.write().await;
|
||||
|
||||
let wdb = self.get_workspace_database().await?;
|
||||
@ -353,7 +356,7 @@ fn subscribe_block_event(workspace_database: &WorkspaceDatabase) {
|
||||
match event {
|
||||
BlockEvent::DidFetchRow(row_details) => {
|
||||
for row_detail in row_details {
|
||||
tracing::trace!("Did fetch row: {:?}", row_detail.row.id);
|
||||
trace!("Did fetch row: {:?}", row_detail.row.id);
|
||||
let row_id = row_detail.row.id.clone();
|
||||
let pb = DidFetchRowPB::from(row_detail);
|
||||
send_notification(&row_id, DatabaseNotification::DidFetchRow)
|
||||
@ -426,16 +429,14 @@ impl DatabaseCollabService for UserDatabaseCollabServiceImpl {
|
||||
collab_raw_data: CollabRawData,
|
||||
config: &CollabPersistenceConfig,
|
||||
) -> Arc<MutexCollab> {
|
||||
self
|
||||
.collab_builder
|
||||
.build_with_config(
|
||||
uid,
|
||||
object_id,
|
||||
object_type,
|
||||
collab_db,
|
||||
collab_raw_data,
|
||||
config,
|
||||
)
|
||||
.unwrap()
|
||||
block_on(self.collab_builder.build_with_config(
|
||||
uid,
|
||||
object_id,
|
||||
object_type,
|
||||
collab_db,
|
||||
collab_raw_data,
|
||||
config,
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use collab_document::document::Document;
|
||||
use collab_document::document_data::default_document_data;
|
||||
use collab_document::YrsDocAction;
|
||||
use parking_lot::RwLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use collab_integrate::RocksCollabDB;
|
||||
@ -55,6 +56,7 @@ impl DocumentManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn initialize_with_new_user(&self, uid: i64, workspace_id: String) -> FlowyResult<()> {
|
||||
self.initialize(uid, workspace_id).await?;
|
||||
Ok(())
|
||||
@ -175,8 +177,22 @@ impl DocumentManager {
|
||||
let db = self.user.collab_db(uid)?;
|
||||
let collab = self
|
||||
.collab_builder
|
||||
.build(uid, doc_id, CollabType::Document, updates, db)?;
|
||||
.build(uid, doc_id, CollabType::Document, updates, db)
|
||||
.await?;
|
||||
Ok(collab)
|
||||
|
||||
// let doc_id = doc_id.to_string();
|
||||
// let (tx, rx) = oneshot::channel();
|
||||
// let collab_builder = self.collab_builder.clone();
|
||||
// tokio::spawn(async move {
|
||||
// let collab = collab_builder
|
||||
// .build(uid, &doc_id, CollabType::Document, updates, db)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let _ = tx.send(collab);
|
||||
// });
|
||||
//
|
||||
// Ok(rx.await.unwrap())
|
||||
}
|
||||
|
||||
fn is_doc_exist(&self, doc_id: &str) -> FlowyResult<bool> {
|
||||
|
@ -253,6 +253,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("Permission denied")]
|
||||
NotEnoughPermissions = 83,
|
||||
|
||||
#[error("Internal server error")]
|
||||
InternalServerError = 84,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -99,6 +99,7 @@ impl FlowyError {
|
||||
ErrorCode::UnexpectedCalendarFieldType
|
||||
);
|
||||
static_flowy_error!(collab_not_sync, ErrorCode::CollabDataNotSync);
|
||||
static_flowy_error!(server_error, ErrorCode::InternalServerError);
|
||||
}
|
||||
|
||||
impl std::convert::From<ErrorCode> for FlowyError {
|
||||
|
@ -8,12 +8,12 @@ impl From<AppError> for FlowyError {
|
||||
client_api::error::ErrorCode::Ok => ErrorCode::Internal,
|
||||
client_api::error::ErrorCode::Unhandled => ErrorCode::Internal,
|
||||
client_api::error::ErrorCode::RecordNotFound => ErrorCode::RecordNotFound,
|
||||
client_api::error::ErrorCode::FileNotFound => ErrorCode::RecordNotFound,
|
||||
client_api::error::ErrorCode::RecordAlreadyExists => ErrorCode::RecordAlreadyExists,
|
||||
client_api::error::ErrorCode::InvalidEmail => ErrorCode::EmailFormatInvalid,
|
||||
client_api::error::ErrorCode::InvalidPassword => ErrorCode::PasswordFormatInvalid,
|
||||
client_api::error::ErrorCode::OAuthError => ErrorCode::UserUnauthorized,
|
||||
client_api::error::ErrorCode::MissingPayload => ErrorCode::MissingPayload,
|
||||
client_api::error::ErrorCode::StorageError => ErrorCode::Internal,
|
||||
client_api::error::ErrorCode::OpenError => ErrorCode::Internal,
|
||||
client_api::error::ErrorCode::InvalidUrl => ErrorCode::InvalidURL,
|
||||
client_api::error::ErrorCode::InvalidRequestParams => ErrorCode::InvalidParams,
|
||||
|
@ -12,7 +12,7 @@ use collab_folder::core::{
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::{event, Level};
|
||||
use tracing::{event, info, instrument, Level};
|
||||
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
|
||||
@ -159,13 +159,17 @@ impl FolderManager {
|
||||
} => {
|
||||
let is_exist = is_exist_in_local_disk(&self.user, &workspace_id).unwrap_or(false);
|
||||
if is_exist {
|
||||
let collab = self.collab_for_folder(uid, &workspace_id, collab_db, vec![])?;
|
||||
let collab = self
|
||||
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
|
||||
.await?;
|
||||
Folder::open(collab, Some(folder_notifier))
|
||||
} else if create_if_not_exist {
|
||||
let folder_data =
|
||||
DefaultFolderBuilder::build(uid, workspace_id.to_string(), &self.operation_handlers)
|
||||
.await;
|
||||
let collab = self.collab_for_folder(uid, &workspace_id, collab_db, vec![])?;
|
||||
let collab = self
|
||||
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
|
||||
.await?;
|
||||
Folder::create(collab, Some(folder_notifier), Some(folder_data))
|
||||
} else {
|
||||
return Err(FlowyError::new(
|
||||
@ -178,11 +182,15 @@ impl FolderManager {
|
||||
if raw_data.is_empty() {
|
||||
return Err(workspace_data_not_sync_error(uid, &workspace_id));
|
||||
}
|
||||
let collab = self.collab_for_folder(uid, &workspace_id, collab_db, raw_data)?;
|
||||
let collab = self
|
||||
.collab_for_folder(uid, &workspace_id, collab_db, raw_data)
|
||||
.await?;
|
||||
Folder::open(collab, Some(folder_notifier))
|
||||
},
|
||||
FolderInitializeDataSource::FolderData(folder_data) => {
|
||||
let collab = self.collab_for_folder(uid, &workspace_id, collab_db, vec![])?;
|
||||
let collab = self
|
||||
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
|
||||
.await?;
|
||||
Folder::create(collab, Some(folder_notifier), Some(folder_data))
|
||||
},
|
||||
};
|
||||
@ -205,21 +213,24 @@ impl FolderManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collab_for_folder(
|
||||
async fn collab_for_folder(
|
||||
&self,
|
||||
uid: i64,
|
||||
workspace_id: &str,
|
||||
collab_db: Weak<RocksCollabDB>,
|
||||
raw_data: CollabRawData,
|
||||
) -> Result<Arc<MutexCollab>, FlowyError> {
|
||||
let collab = self.collab_builder.build_with_config(
|
||||
uid,
|
||||
workspace_id,
|
||||
CollabType::Folder,
|
||||
collab_db,
|
||||
raw_data,
|
||||
&CollabPersistenceConfig::new().enable_snapshot(true),
|
||||
)?;
|
||||
let collab = self
|
||||
.collab_builder
|
||||
.build_with_config(
|
||||
uid,
|
||||
workspace_id,
|
||||
CollabType::Folder,
|
||||
collab_db,
|
||||
raw_data,
|
||||
&CollabPersistenceConfig::new().enable_snapshot(true),
|
||||
)
|
||||
.await?;
|
||||
Ok(collab)
|
||||
}
|
||||
|
||||
@ -236,7 +247,7 @@ impl FolderManager {
|
||||
.get_folder_updates(workspace_id, user_id)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
info!(
|
||||
"Get folder updates via {}, number of updates: {}",
|
||||
self.cloud_service.service_name(),
|
||||
folder_updates.len()
|
||||
@ -254,6 +265,7 @@ impl FolderManager {
|
||||
|
||||
/// Initialize the folder for the new user.
|
||||
/// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn initialize_with_new_user(
|
||||
&self,
|
||||
user_id: i64,
|
||||
@ -263,29 +275,41 @@ impl FolderManager {
|
||||
workspace_id: &str,
|
||||
) -> FlowyResult<()> {
|
||||
// Create the default workspace if the user is new
|
||||
tracing::info!("initialize_when_sign_up: is_new: {}", is_new);
|
||||
info!("initialize_when_sign_up: is_new: {}", is_new);
|
||||
if is_new {
|
||||
self.initialize(user_id, workspace_id, data_source).await?;
|
||||
} else {
|
||||
// The folder updates should not be empty, as the folder data is stored
|
||||
// when the user signs up for the first time.
|
||||
let folder_updates = self
|
||||
let result = self
|
||||
.cloud_service
|
||||
.get_folder_updates(workspace_id, user_id)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(FlowyError::from);
|
||||
|
||||
tracing::info!(
|
||||
"Get folder updates via {}, number of updates: {}",
|
||||
self.cloud_service.service_name(),
|
||||
folder_updates.len()
|
||||
);
|
||||
self
|
||||
.initialize(
|
||||
user_id,
|
||||
workspace_id,
|
||||
FolderInitializeDataSource::Cloud(folder_updates),
|
||||
)
|
||||
.await?;
|
||||
match result {
|
||||
Ok(folder_updates) => {
|
||||
info!(
|
||||
"Get folder updates via {}, number of updates: {}",
|
||||
self.cloud_service.service_name(),
|
||||
folder_updates.len()
|
||||
);
|
||||
self
|
||||
.initialize(
|
||||
user_id,
|
||||
workspace_id,
|
||||
FolderInitializeDataSource::Cloud(folder_updates),
|
||||
)
|
||||
.await?;
|
||||
},
|
||||
Err(err) => {
|
||||
if err.is_record_not_found() {
|
||||
self.initialize(user_id, workspace_id, data_source).await?;
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,39 +2,47 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
|
||||
pub const AF_CLOUD_BASE_URL: &str = "AF_CLOUD_BASE_URL";
|
||||
pub const AF_CLOUD_WS_BASE_URL: &str = "AF_CLOUD_WS_BASE_URL";
|
||||
pub const AF_CLOUD_GOTRUE_URL: &str = "AF_GOTRUE_URL";
|
||||
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";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct AFCloudConfiguration {
|
||||
pub base_url: String,
|
||||
pub base_ws_url: String,
|
||||
pub ws_base_url: String,
|
||||
pub gotrue_url: String,
|
||||
}
|
||||
|
||||
impl AFCloudConfiguration {
|
||||
pub fn from_env() -> Result<Self, FlowyError> {
|
||||
let base_url = std::env::var(AF_CLOUD_BASE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_BASE_URL"))?;
|
||||
let base_url = std::env::var(APPFLOWY_CLOUD_BASE_URL).map_err(|_| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
"Missing APPFLOWY_CLOUD_BASE_URL",
|
||||
)
|
||||
})?;
|
||||
|
||||
let base_ws_url = std::env::var(AF_CLOUD_WS_BASE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_WS_BASE_URL"))?;
|
||||
let ws_base_url = std::env::var(APPFLOWY_CLOUD_WS_BASE_URL).map_err(|_| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
"Missing APPFLOWY_CLOUD_WS_BASE_URL",
|
||||
)
|
||||
})?;
|
||||
|
||||
let gotrue_url = std::env::var(AF_CLOUD_GOTRUE_URL)
|
||||
let gotrue_url = std::env::var(APPFLOWY_CLOUD_GOTRUE_URL)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidAuthConfig, "Missing AF_CLOUD_GOTRUE_URL"))?;
|
||||
|
||||
Ok(Self {
|
||||
base_url,
|
||||
base_ws_url,
|
||||
ws_base_url,
|
||||
gotrue_url,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the configuration to the environment variables.
|
||||
pub fn write_env(&self) {
|
||||
std::env::set_var(AF_CLOUD_BASE_URL, &self.base_url);
|
||||
std::env::set_var(AF_CLOUD_WS_BASE_URL, &self.base_ws_url);
|
||||
std::env::set_var(AF_CLOUD_GOTRUE_URL, &self.gotrue_url);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use collab_define::CollabType;
|
||||
use collab_document::document::Document;
|
||||
|
||||
use flowy_document_deps::cloud::*;
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::af_cloud::AFServer;
|
||||
@ -23,7 +24,10 @@ where
|
||||
object_id: document_id.to_string(),
|
||||
collab_type: CollabType::Document,
|
||||
};
|
||||
let data = try_get_client?.get_collab(params).await?;
|
||||
let data = try_get_client?
|
||||
.get_collab(params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
Ok(vec![data])
|
||||
})
|
||||
}
|
||||
@ -44,7 +48,10 @@ where
|
||||
object_id: document_id.clone(),
|
||||
collab_type: CollabType::Document,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
let updates = vec![try_get_client?
|
||||
.get_collab(params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?];
|
||||
let document = Document::from_updates(CollabOrigin::Empty, updates, &document_id, vec![])?;
|
||||
Ok(document.get_document_data().ok())
|
||||
})
|
||||
|
@ -3,6 +3,7 @@ use client_api::entity::QueryCollabParams;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_define::CollabType;
|
||||
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder_deps::cloud::{Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace};
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
@ -26,7 +27,10 @@ where
|
||||
object_id: workspace_id.clone(),
|
||||
collab_type: CollabType::Folder,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
let updates = vec![try_get_client?
|
||||
.get_collab(params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?];
|
||||
let folder =
|
||||
Folder::from_collab_raw_data(CollabOrigin::Empty, updates, &workspace_id, vec![])?;
|
||||
Ok(folder.get_folder_data())
|
||||
@ -49,8 +53,11 @@ where
|
||||
object_id: workspace_id,
|
||||
collab_type: CollabType::Folder,
|
||||
};
|
||||
let updates = vec![try_get_client?.get_collab(params).await?];
|
||||
Ok(updates)
|
||||
let update = try_get_client?
|
||||
.get_collab(params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
Ok(vec![update])
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,9 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use client_api::entity::dto::UserUpdateParams;
|
||||
use client_api::entity::{AFUserProfileView, AFWorkspace, AFWorkspaces, InsertCollabParams};
|
||||
use client_api::entity::{
|
||||
AFUserProfileView, AFWorkspace, AFWorkspaces, InsertCollabParams, OAuthProvider,
|
||||
};
|
||||
use collab_define::CollabObject;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
@ -54,7 +56,7 @@ where
|
||||
FutureResult::new(async move { Ok(try_get_client?.sign_out().await?) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, email: &str) -> FutureResult<String, Error> {
|
||||
fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, Error> {
|
||||
let email = email.to_string();
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
@ -62,7 +64,19 @@ where
|
||||
let admin_email = std::env::var("GOTRUE_ADMIN_EMAIL").unwrap();
|
||||
let admin_password = std::env::var("GOTRUE_ADMIN_PASSWORD").unwrap();
|
||||
let url = try_get_client?
|
||||
.generate_sign_in_callback_url(&admin_email, &admin_password, &email)
|
||||
.generate_sign_in_url_with_email(&admin_email, &admin_password, &email)
|
||||
.await?;
|
||||
Ok(url)
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_oauth_url_with_provider(&self, provider: &str) -> FutureResult<String, Error> {
|
||||
let provider = OAuthProvider::from(provider);
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let provider = provider.ok_or(anyhow!("invalid provider"))?;
|
||||
let url = try_get_client?
|
||||
.generate_oauth_url_with_provider(&provider)
|
||||
.await?;
|
||||
Ok(url)
|
||||
})
|
||||
@ -196,7 +210,6 @@ where
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let params = InsertCollabParams::new(
|
||||
collab_object.uid,
|
||||
collab_object.object_id.clone(),
|
||||
collab_object.collab_type.clone(),
|
||||
data,
|
||||
@ -219,7 +232,7 @@ pub async fn user_sign_in_with_url(
|
||||
client: Arc<AFCloudClient>,
|
||||
params: AFCloudOAuthParams,
|
||||
) -> Result<AuthResponse, FlowyError> {
|
||||
let is_new_user = client.sign_in_url(¶ms.sign_in_url).await?;
|
||||
let is_new_user = client.sign_in_with_url(¶ms.sign_in_url).await?;
|
||||
let (profile, af_workspaces) = tokio::try_join!(client.profile(), client.workspaces())?;
|
||||
|
||||
let latest_workspace = to_user_workspace(
|
||||
|
@ -7,7 +7,7 @@ use client_api::ws::{
|
||||
BusinessID, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel,
|
||||
};
|
||||
use client_api::Client;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::info;
|
||||
|
||||
use flowy_database_deps::cloud::DatabaseCloudService;
|
||||
use flowy_document_deps::cloud::DocumentCloudService;
|
||||
@ -33,7 +33,7 @@ pub struct AFCloudServer {
|
||||
enable_sync: AtomicBool,
|
||||
#[allow(dead_code)]
|
||||
device_id: Arc<parking_lot::RwLock<String>>,
|
||||
ws_client: Arc<RwLock<WSClient>>,
|
||||
ws_client: Arc<WSClient>,
|
||||
}
|
||||
|
||||
impl AFCloudServer {
|
||||
@ -42,13 +42,8 @@ impl AFCloudServer {
|
||||
enable_sync: bool,
|
||||
device_id: Arc<parking_lot::RwLock<String>>,
|
||||
) -> Self {
|
||||
let http_client = reqwest::Client::new();
|
||||
let api_client = client_api::Client::from(
|
||||
http_client,
|
||||
&config.base_url,
|
||||
&config.base_ws_url,
|
||||
&config.gotrue_url,
|
||||
);
|
||||
let api_client =
|
||||
client_api::Client::new(&config.base_url, &config.ws_base_url, &config.gotrue_url);
|
||||
let token_state_rx = api_client.subscribe_token_state();
|
||||
let enable_sync = AtomicBool::new(enable_sync);
|
||||
|
||||
@ -57,7 +52,7 @@ impl AFCloudServer {
|
||||
ping_per_secs: 8,
|
||||
retry_connect_per_pings: 5,
|
||||
});
|
||||
let ws_client = Arc::new(RwLock::new(ws_client));
|
||||
let ws_client = Arc::new(ws_client);
|
||||
let api_client = Arc::new(api_client);
|
||||
|
||||
spawn_ws_conn(&device_id, token_state_rx, &ws_client, &api_client);
|
||||
@ -81,7 +76,7 @@ impl AFCloudServer {
|
||||
|
||||
impl AppFlowyServer for AFCloudServer {
|
||||
fn set_enable_sync(&self, uid: i64, enable: bool) {
|
||||
tracing::info!("{} cloud sync: {}", uid, enable);
|
||||
info!("{} cloud sync: {}", uid, enable);
|
||||
self.enable_sync.store(enable, Ordering::SeqCst);
|
||||
}
|
||||
fn user_service(&self) -> Arc<dyn UserCloudService> {
|
||||
@ -115,14 +110,8 @@ impl AppFlowyServer for AFCloudServer {
|
||||
match weak_ws_client.upgrade() {
|
||||
None => Ok(None),
|
||||
Some(ws_client) => {
|
||||
let channel = ws_client
|
||||
.read()
|
||||
.await
|
||||
.subscribe(BusinessID::CollabId, object_id)
|
||||
.await
|
||||
.ok();
|
||||
let connect_state_recv = ws_client.read().await.subscribe_connect_state().await;
|
||||
|
||||
let channel = ws_client.subscribe(BusinessID::CollabId, object_id).ok();
|
||||
let connect_state_recv = ws_client.subscribe_connect_state();
|
||||
Ok(channel.map(|c| (c, connect_state_recv)))
|
||||
},
|
||||
}
|
||||
@ -145,7 +134,7 @@ impl AppFlowyServer for AFCloudServer {
|
||||
fn spawn_ws_conn(
|
||||
device_id: &Arc<parking_lot::RwLock<String>>,
|
||||
mut token_state_rx: TokenStateReceiver,
|
||||
ws_client: &Arc<RwLock<WSClient>>,
|
||||
ws_client: &Arc<WSClient>,
|
||||
api_client: &Arc<Client>,
|
||||
) {
|
||||
let weak_device_id = Arc::downgrade(device_id);
|
||||
@ -154,7 +143,7 @@ fn spawn_ws_conn(
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Some(ws_client) = weak_ws_client.upgrade() {
|
||||
let mut state_recv = ws_client.read().await.subscribe_connect_state().await;
|
||||
let mut state_recv = ws_client.subscribe_connect_state();
|
||||
while let Ok(state) = state_recv.recv().await {
|
||||
if !state.is_timeout() {
|
||||
continue;
|
||||
@ -166,8 +155,8 @@ fn spawn_ws_conn(
|
||||
{
|
||||
let device_id = device_id.read().clone();
|
||||
if let Ok(ws_addr) = api_client.ws_url(&device_id) {
|
||||
tracing::info!("🟢WebSocket Reconnecting");
|
||||
let _ = ws_client.write().await.connect(ws_addr).await;
|
||||
info!("🟢WebSocket Reconnecting");
|
||||
let _ = ws_client.connect(ws_addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,7 +168,7 @@ fn spawn_ws_conn(
|
||||
let weak_api_client = Arc::downgrade(api_client);
|
||||
tokio::spawn(async move {
|
||||
while let Ok(token_state) = token_state_rx.recv().await {
|
||||
tracing::info!("🟢Token state: {:?}", token_state);
|
||||
info!("🟢Token state: {:?}", token_state);
|
||||
match token_state {
|
||||
TokenState::Refresh => {
|
||||
if let (Some(api_client), Some(ws_client), Some(device_id)) = (
|
||||
@ -189,14 +178,14 @@ fn spawn_ws_conn(
|
||||
) {
|
||||
let device_id = device_id.read().clone();
|
||||
if let Ok(ws_addr) = api_client.ws_url(&device_id) {
|
||||
let _ = ws_client.write().await.connect(ws_addr).await;
|
||||
let _ = ws_client.connect(ws_addr).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
TokenState::Invalid => {
|
||||
if let Some(ws_client) = weak_ws_client.upgrade() {
|
||||
tracing::info!("🟡WebSocket Disconnecting");
|
||||
ws_client.write().await.disconnect().await;
|
||||
info!("🟡WebSocket Disconnecting");
|
||||
ws_client.disconnect().await;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
FutureResult::new(async {
|
||||
Err(anyhow::anyhow!(
|
||||
"Can't generate callback url when using offline mode"
|
||||
@ -84,6 +84,10 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
|
||||
})
|
||||
}
|
||||
|
||||
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 update_user(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
|
@ -165,7 +165,7 @@ where
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn generate_sign_in_callback_url(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
fn generate_sign_in_url_with_email(&self, _email: &str) -> FutureResult<String, Error> {
|
||||
FutureResult::new(async {
|
||||
Err(anyhow::anyhow!(
|
||||
"Can't generate callback url when using supabase"
|
||||
@ -173,6 +173,14 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_oauth_url_with_provider(&self, _provider: &str) -> FutureResult<String, Error> {
|
||||
FutureResult::new(async {
|
||||
Err(anyhow::anyhow!(
|
||||
"Can't generate oauth url when using supabase"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_credential: UserCredentials,
|
||||
|
@ -23,18 +23,13 @@ pub fn af_cloud_server(config: AFCloudConfiguration) -> Arc<AFCloudServer> {
|
||||
}
|
||||
|
||||
pub async fn generate_sign_in_url(user_email: &str, config: &AFCloudConfiguration) -> String {
|
||||
let http_client = reqwest::Client::new();
|
||||
let api_client = client_api::Client::from(
|
||||
http_client,
|
||||
&config.base_url,
|
||||
&config.base_ws_url,
|
||||
&config.gotrue_url,
|
||||
);
|
||||
let api_client =
|
||||
client_api::Client::new(&config.base_url, &config.ws_base_url, &config.gotrue_url);
|
||||
|
||||
let admin_email = std::env::var("GOTRUE_ADMIN_EMAIL").unwrap();
|
||||
let admin_password = std::env::var("GOTRUE_ADMIN_PASSWORD").unwrap();
|
||||
api_client
|
||||
.generate_sign_in_callback_url(&admin_email, &admin_password, user_email)
|
||||
.generate_sign_in_url_with_email(&admin_email, &admin_password, user_email)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ use flowy_notification::entities::SubscribeObject;
|
||||
use flowy_notification::{register_notification_sender, NotificationSender};
|
||||
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
|
||||
use flowy_user::entities::{
|
||||
AuthTypePB, OAuthCallbackRequestPB, OAuthCallbackResponsePB, OAuthPB, UpdateCloudConfigPB,
|
||||
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, UpdateCloudConfigPB,
|
||||
UserCloudConfigPB, UserProfilePB,
|
||||
};
|
||||
use flowy_user::errors::{FlowyError, FlowyResult};
|
||||
@ -169,13 +169,13 @@ impl FlowyCoreTest {
|
||||
|
||||
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
|
||||
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
|
||||
let payload = OAuthPB {
|
||||
let payload = OauthSignInPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
EventBuilder::new(self.clone())
|
||||
.event(OAuth)
|
||||
.event(OauthSignIn)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -198,28 +198,28 @@ impl FlowyCoreTest {
|
||||
}
|
||||
|
||||
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
|
||||
let payload = OAuthCallbackRequestPB {
|
||||
let payload = SignInUrlPayloadPB {
|
||||
email: email.to_string(),
|
||||
auth_type: AuthTypePB::AFCloud,
|
||||
};
|
||||
let sign_in_url = EventBuilder::new(self.clone())
|
||||
.event(OAuthCallbackURL)
|
||||
.event(GetSignInURL)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.try_parse::<OAuthCallbackResponsePB>()?
|
||||
.try_parse::<SignInUrlPB>()?
|
||||
.sign_in_url;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
|
||||
map.insert(USER_DEVICE_ID.to_string(), uuid::Uuid::new_v4().to_string());
|
||||
let payload = OAuthPB {
|
||||
let payload = OauthSignInPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::AFCloud,
|
||||
};
|
||||
|
||||
let user_profile = EventBuilder::new(self.clone())
|
||||
.event(OAuth)
|
||||
.event(OauthSignIn)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -240,13 +240,13 @@ impl FlowyCoreTest {
|
||||
USER_EMAIL.to_string(),
|
||||
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
|
||||
);
|
||||
let payload = OAuthPB {
|
||||
let payload = OauthSignInPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let user_profile = EventBuilder::new(self.clone())
|
||||
.event(OAuth)
|
||||
.event(OauthSignIn)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
|
@ -14,7 +14,7 @@ use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
|
||||
use flowy_test::document::document_event::DocumentEventTest;
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
use flowy_test::FlowyCoreTest;
|
||||
use flowy_user::entities::{AuthTypePB, OAuthPB, UpdateUserProfilePayloadPB, UserProfilePB};
|
||||
use flowy_user::entities::{AuthTypePB, OauthSignInPB, UpdateUserProfilePayloadPB, UserProfilePB};
|
||||
use flowy_user::errors::ErrorCode;
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
@ -30,13 +30,13 @@ async fn third_party_sign_up_test() {
|
||||
USER_EMAIL.to_string(),
|
||||
format!("{}@appflowy.io", nanoid!(6)),
|
||||
);
|
||||
let payload = OAuthPB {
|
||||
let payload = OauthSignInPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let response = EventBuilder::new(test.clone())
|
||||
.event(OAuth)
|
||||
.event(OauthSignIn)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
@ -72,8 +72,8 @@ async fn third_party_sign_up_with_duplicated_uuid() {
|
||||
map.insert(USER_EMAIL.to_string(), email.clone());
|
||||
|
||||
let response_1 = EventBuilder::new(test.clone())
|
||||
.event(OAuth)
|
||||
.payload(OAuthPB {
|
||||
.event(OauthSignIn)
|
||||
.payload(OauthSignInPB {
|
||||
map: map.clone(),
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
})
|
||||
@ -83,8 +83,8 @@ async fn third_party_sign_up_with_duplicated_uuid() {
|
||||
dbg!(&response_1);
|
||||
|
||||
let response_2 = EventBuilder::new(test.clone())
|
||||
.event(OAuth)
|
||||
.payload(OAuthPB {
|
||||
.event(OauthSignIn)
|
||||
.payload(OauthSignInPB {
|
||||
map: map.clone(),
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ use flowy_folder2::entities::WorkspaceSettingPB;
|
||||
use flowy_folder2::event_map::FolderEvent::GetCurrentWorkspace;
|
||||
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{AuthTypePB, OAuthPB, UserProfilePB};
|
||||
use flowy_user::entities::{AuthTypePB, OauthSignInPB, UserProfilePB};
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
use crate::util::*;
|
||||
@ -19,13 +19,13 @@ async fn initial_workspace_test() {
|
||||
USER_EMAIL.to_string(),
|
||||
format!("{}@gmail.com", uuid::Uuid::new_v4()),
|
||||
);
|
||||
let payload = OAuthPB {
|
||||
let payload = OauthSignInPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let _ = EventBuilder::new(test.clone())
|
||||
.event(OAuth)
|
||||
.event(OauthSignIn)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
|
@ -70,8 +70,15 @@ pub trait UserCloudService: Send + Sync + 'static {
|
||||
/// Sign out an account
|
||||
fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error>;
|
||||
|
||||
/// Generate a sign in callback url for the user with the given email
|
||||
fn generate_sign_in_callback_url(&self, email: &str) -> FutureResult<String, Error>;
|
||||
/// Generate a sign in url for the user with the given email
|
||||
fn generate_sign_in_url_with_email(&self, email: &str) -> FutureResult<String, Error>;
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// Using the user's token to update the user information
|
||||
fn update_user(
|
||||
|
@ -79,7 +79,7 @@ impl TryInto<SignUpParams> for SignUpPayloadPB {
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OAuthPB {
|
||||
pub struct OauthSignInPB {
|
||||
/// Use this field to store the third party auth information.
|
||||
/// Different auth type has different fields.
|
||||
/// Supabase:
|
||||
@ -93,7 +93,7 @@ pub struct OAuthPB {
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OAuthCallbackRequestPB {
|
||||
pub struct SignInUrlPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub email: String,
|
||||
|
||||
@ -102,11 +102,77 @@ pub struct OAuthCallbackRequestPB {
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OAuthCallbackResponsePB {
|
||||
pub struct SignInUrlPB {
|
||||
#[pb(index = 1)]
|
||||
pub sign_in_url: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OauthProviderPB {
|
||||
#[pb(index = 1)]
|
||||
pub provider: ProviderTypePB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone, Default)]
|
||||
pub enum ProviderTypePB {
|
||||
Apple = 0,
|
||||
Azure = 1,
|
||||
Bitbucket = 2,
|
||||
Discord = 3,
|
||||
Facebook = 4,
|
||||
Figma = 5,
|
||||
Github = 6,
|
||||
Gitlab = 7,
|
||||
#[default]
|
||||
Google = 8,
|
||||
Keycloak = 9,
|
||||
Kakao = 10,
|
||||
Linkedin = 11,
|
||||
Notion = 12,
|
||||
Spotify = 13,
|
||||
Slack = 14,
|
||||
Workos = 15,
|
||||
Twitch = 16,
|
||||
Twitter = 17,
|
||||
Email = 18,
|
||||
Phone = 19,
|
||||
Zoom = 20,
|
||||
}
|
||||
|
||||
impl ProviderTypePB {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
ProviderTypePB::Apple => "apple",
|
||||
ProviderTypePB::Azure => "azure",
|
||||
ProviderTypePB::Bitbucket => "bitbucket",
|
||||
ProviderTypePB::Discord => "discord",
|
||||
ProviderTypePB::Facebook => "facebook",
|
||||
ProviderTypePB::Figma => "figma",
|
||||
ProviderTypePB::Github => "github",
|
||||
ProviderTypePB::Gitlab => "gitlab",
|
||||
ProviderTypePB::Google => "google",
|
||||
ProviderTypePB::Keycloak => "keycloak",
|
||||
ProviderTypePB::Kakao => "kakao",
|
||||
ProviderTypePB::Linkedin => "linkedin",
|
||||
ProviderTypePB::Notion => "notion",
|
||||
ProviderTypePB::Spotify => "spotify",
|
||||
ProviderTypePB::Slack => "slack",
|
||||
ProviderTypePB::Workos => "workos",
|
||||
ProviderTypePB::Twitch => "twitch",
|
||||
ProviderTypePB::Twitter => "twitter",
|
||||
ProviderTypePB::Email => "email",
|
||||
ProviderTypePB::Phone => "phone",
|
||||
ProviderTypePB::Zoom => "zoom",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct OauthProviderDataPB {
|
||||
#[pb(index = 1)]
|
||||
pub oauth_url: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
pub enum AuthTypePB {
|
||||
Local = 0,
|
||||
|
@ -215,11 +215,9 @@ pub async fn get_user_setting(
|
||||
data_result_ok(user_setting)
|
||||
}
|
||||
|
||||
/// Only used for third party auth.
|
||||
/// Use [UserEvent::SignIn] or [UserEvent::SignUp] If the [AuthType] is Local or SelfHosted
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub async fn oauth_handler(
|
||||
data: AFPluginData<OAuthPB>,
|
||||
data: AFPluginData<OauthSignInPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<UserProfilePB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
@ -230,20 +228,33 @@ pub async fn oauth_handler(
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub async fn get_oauth_url_handler(
|
||||
data: AFPluginData<OAuthCallbackRequestPB>,
|
||||
pub async fn get_sign_in_url_handler(
|
||||
data: AFPluginData<SignInUrlPayloadPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<OAuthCallbackResponsePB, FlowyError> {
|
||||
) -> DataResult<SignInUrlPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params = data.into_inner();
|
||||
let auth_type: AuthType = params.auth_type.into();
|
||||
let sign_in_url = manager
|
||||
.generate_sign_in_callback_url(&auth_type, ¶ms.email)
|
||||
.generate_sign_in_url_with_email(&auth_type, ¶ms.email)
|
||||
.await?;
|
||||
let resp = OAuthCallbackResponsePB { sign_in_url };
|
||||
let resp = SignInUrlPB { sign_in_url };
|
||||
data_result_ok(resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn sign_in_with_provider_handler(
|
||||
data: AFPluginData<OauthProviderPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<OauthProviderDataPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
tracing::debug!("Sign in with provider: {:?}", data.provider.as_str());
|
||||
let sign_in_url = manager.generate_oauth_url(data.provider.as_str()).await?;
|
||||
data_result_ok(OauthProviderDataPB {
|
||||
oauth_url: sign_in_url,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn set_encrypt_secret_handler(
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
|
@ -37,8 +37,12 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
|
||||
.event(UserEvent::GetCloudConfig, get_cloud_config_handler)
|
||||
.event(UserEvent::SetEncryptionSecret, set_encrypt_secret_handler)
|
||||
.event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler)
|
||||
.event(UserEvent::OAuth, oauth_handler)
|
||||
.event(UserEvent::OAuthCallbackURL, get_oauth_url_handler)
|
||||
.event(UserEvent::OauthSignIn, oauth_handler)
|
||||
.event(UserEvent::GetSignInURL, get_sign_in_url_handler)
|
||||
.event(
|
||||
UserEvent::GetOauthURLWithProvider,
|
||||
sign_in_with_provider_handler,
|
||||
)
|
||||
.event(
|
||||
UserEvent::GetAllUserWorkspaces,
|
||||
get_all_user_workspace_handler,
|
||||
@ -230,13 +234,16 @@ pub enum UserEvent {
|
||||
#[event(output = "UserSettingPB")]
|
||||
GetUserSetting = 9,
|
||||
|
||||
#[event(input = "OAuthPB", output = "UserProfilePB")]
|
||||
OAuth = 10,
|
||||
#[event(input = "OauthSignInPB", output = "UserProfilePB")]
|
||||
OauthSignIn = 10,
|
||||
|
||||
/// Get the OAuth callback url
|
||||
/// Only use when the [AuthType] is AFCloud
|
||||
#[event(input = "OAuthCallbackRequestPB", output = "OAuthCallbackResponsePB")]
|
||||
OAuthCallbackURL = 11,
|
||||
#[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")]
|
||||
GetSignInURL = 11,
|
||||
|
||||
#[event(input = "OauthProviderPB", output = "OauthProviderDataPB")]
|
||||
GetOauthURLWithProvider = 12,
|
||||
|
||||
#[event(input = "UpdateCloudConfigPB")]
|
||||
SetCloudConfig = 13,
|
||||
|
@ -4,6 +4,7 @@ use std::sync::{Arc, Weak};
|
||||
use collab_user::core::MutexUserAwareness;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::{debug, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
@ -134,7 +135,7 @@ impl UserManager {
|
||||
{
|
||||
Ok(applied_migrations) => {
|
||||
if !applied_migrations.is_empty() {
|
||||
tracing::info!("Did apply migrations: {:?}", applied_migrations);
|
||||
info!("Did apply migrations: {:?}", applied_migrations);
|
||||
}
|
||||
},
|
||||
Err(e) => tracing::error!("User data migration failed: {:?}", e),
|
||||
@ -312,16 +313,16 @@ impl UserManager {
|
||||
UserAwarenessDataSource::Remote
|
||||
};
|
||||
|
||||
debug!("Sign up response: {:?}", response);
|
||||
if response.is_new_user {
|
||||
if let Some(old_user) = migration_user {
|
||||
let new_user = MigrationUser {
|
||||
user_profile: user_profile.clone(),
|
||||
session: new_session.clone(),
|
||||
};
|
||||
tracing::info!(
|
||||
info!(
|
||||
"Migrate old user data from {:?} to {:?}",
|
||||
old_user.user_profile.uid,
|
||||
new_user.user_profile.uid
|
||||
old_user.user_profile.uid, new_user.user_profile.uid
|
||||
);
|
||||
self
|
||||
.migrate_local_user_to_cloud(&old_user, &new_user)
|
||||
@ -524,7 +525,7 @@ impl UserManager {
|
||||
}
|
||||
|
||||
pub(crate) fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||
tracing::debug!("Set current user: {:?}", session);
|
||||
debug!("Set current user: {:?}", session);
|
||||
match &session {
|
||||
None => {
|
||||
self.current_session.write().take();
|
||||
@ -543,7 +544,7 @@ impl UserManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_sign_in_callback_url(
|
||||
pub(crate) async fn generate_sign_in_url_with_email(
|
||||
&self,
|
||||
auth_type: &AuthType,
|
||||
email: &str,
|
||||
@ -551,7 +552,22 @@ impl UserManager {
|
||||
self.update_auth_type(auth_type).await;
|
||||
|
||||
let auth_service = self.cloud_services.get_user_service()?;
|
||||
let url = auth_service.generate_sign_in_callback_url(email).await?;
|
||||
let url = auth_service
|
||||
.generate_sign_in_url_with_email(email)
|
||||
.await
|
||||
.map_err(|err| FlowyError::server_error().with_context(err))?;
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_oauth_url(
|
||||
&self,
|
||||
oauth_provider: &str,
|
||||
) -> Result<String, FlowyError> {
|
||||
self.update_auth_type(&AuthType::AFCloud).await;
|
||||
let auth_service = self.cloud_services.get_user_service()?;
|
||||
let url = auth_service
|
||||
.generate_oauth_url_with_provider(oauth_provider)
|
||||
.await?;
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
@ -588,7 +604,7 @@ impl UserManager {
|
||||
async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> {
|
||||
let session = self.get_session()?;
|
||||
if session.user_id == user_update.uid {
|
||||
tracing::debug!("Receive user update: {:?}", user_update);
|
||||
debug!("Receive user update: {:?}", user_update);
|
||||
let user_profile = self.get_user_profile(user_update.uid).await?;
|
||||
|
||||
if !is_user_encryption_sign_valid(&user_profile, &user_update.encryption_sign) {
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use anyhow::Context;
|
||||
use collab::core::collab::{CollabRawData, MutexCollab};
|
||||
use collab_define::reminder::Reminder;
|
||||
use collab_define::CollabType;
|
||||
use collab_user::core::{MutexUserAwareness, UserAwareness};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use collab_integrate::RocksCollabDB;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
@ -101,12 +103,8 @@ impl UserManager {
|
||||
source: UserAwarenessDataSource,
|
||||
) {
|
||||
match self.try_initial_user_awareness(session, source).await {
|
||||
Ok(_) => {
|
||||
tracing::trace!("User awareness initialized");
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to initialize user awareness: {:?}", e);
|
||||
},
|
||||
Ok(_) => trace!("User awareness initialized"),
|
||||
Err(e) => error!("Failed to initialize user awareness: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,11 +126,13 @@ impl UserManager {
|
||||
session: &Session,
|
||||
source: UserAwarenessDataSource,
|
||||
) -> FlowyResult<()> {
|
||||
tracing::trace!("Initializing user awareness from {:?}", source);
|
||||
trace!("Initializing user awareness from {:?}", source);
|
||||
let collab_db = self.get_collab_db(session.user_id)?;
|
||||
let user_awareness = match source {
|
||||
UserAwarenessDataSource::Local => {
|
||||
let collab = self.collab_for_user_awareness(session, collab_db, vec![])?;
|
||||
let collab = self
|
||||
.collab_for_user_awareness(session, collab_db, vec![])
|
||||
.await?;
|
||||
MutexUserAwareness::new(UserAwareness::create(collab, None))
|
||||
},
|
||||
UserAwarenessDataSource::Remote => {
|
||||
@ -141,7 +141,10 @@ impl UserManager {
|
||||
.get_user_service()?
|
||||
.get_user_awareness_updates(session.user_id)
|
||||
.await?;
|
||||
let collab = self.collab_for_user_awareness(session, collab_db, data)?;
|
||||
trace!("Get user awareness collab: {}", data.len());
|
||||
let collab = self
|
||||
.collab_for_user_awareness(session, collab_db, data)
|
||||
.await?;
|
||||
MutexUserAwareness::new(UserAwareness::create(collab, None))
|
||||
},
|
||||
};
|
||||
@ -154,7 +157,7 @@ impl UserManager {
|
||||
/// This function constructs a collaboration instance based on the given session and raw data,
|
||||
/// using a collaboration builder. This instance is specifically geared towards handling
|
||||
/// user awareness.
|
||||
fn collab_for_user_awareness(
|
||||
async fn collab_for_user_awareness(
|
||||
&self,
|
||||
session: &Session,
|
||||
collab_db: Weak<RocksCollabDB>,
|
||||
@ -164,13 +167,16 @@ impl UserManager {
|
||||
ErrorCode::Internal,
|
||||
"Unexpected error: collab builder is not available",
|
||||
))?;
|
||||
let collab = collab_builder.build(
|
||||
session.user_id,
|
||||
&session.user_id.to_string(),
|
||||
CollabType::UserAwareness,
|
||||
raw_data,
|
||||
collab_db,
|
||||
)?;
|
||||
let collab = collab_builder
|
||||
.build(
|
||||
session.user_id,
|
||||
&session.user_id.to_string(),
|
||||
CollabType::UserAwareness,
|
||||
raw_data,
|
||||
collab_db,
|
||||
)
|
||||
.await
|
||||
.context("Build collab for user awareness failed")?;
|
||||
Ok(collab)
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,3 @@
|
||||
use crate::{
|
||||
errors::{DispatchError, InternalError},
|
||||
module::{container::AFPluginStateMap, AFPluginState},
|
||||
request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest},
|
||||
response::{AFPluginEventResponse, AFPluginResponder},
|
||||
service::{
|
||||
factory, AFPluginHandler, AFPluginHandlerService, AFPluginServiceFactory, BoxService,
|
||||
BoxServiceFactory, Service, ServiceRequest, ServiceResponse,
|
||||
},
|
||||
};
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::ready;
|
||||
use nanoid::nanoid;
|
||||
use pin_project::pin_project;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -23,6 +9,23 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::ready;
|
||||
use nanoid::nanoid;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::service::AFPluginHandler;
|
||||
use crate::{
|
||||
errors::{DispatchError, InternalError},
|
||||
module::{container::AFPluginStateMap, AFPluginState},
|
||||
request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest},
|
||||
response::{AFPluginEventResponse, AFPluginResponder},
|
||||
service::{
|
||||
factory, AFPluginHandlerService, AFPluginServiceFactory, BoxService, BoxServiceFactory,
|
||||
Service, ServiceRequest, ServiceResponse,
|
||||
},
|
||||
};
|
||||
|
||||
pub type AFPluginMap = Arc<HashMap<AFPluginEvent, Arc<AFPlugin>>>;
|
||||
pub(crate) fn as_plugin_map(plugins: Vec<AFPlugin>) -> AFPluginMap {
|
||||
let mut plugin_map = HashMap::new();
|
||||
@ -93,6 +96,7 @@ impl AFPlugin {
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn event<E, H, T, R>(mut self, event: E, handler: H) -> Self
|
||||
where
|
||||
H: AFPluginHandler<T, R>,
|
||||
|
Loading…
Reference in New Issue
Block a user