mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: integrate postgres storage (#2604)
* chore: env config * chore: get user workspace * feat: enable postgres storage * chore: add new env * chore: add set env ffi * chore: pass env before backend init * chore: update * fix: ci tests * chore: commit the generate env file * chore: remove unused import
This commit is contained in:
parent
51a7954af7
commit
056e2d49d0
@ -16,4 +16,23 @@ class Config {
|
||||
..jwtSecret = secret,
|
||||
).send();
|
||||
}
|
||||
|
||||
static Future<void> setSupabaseCollabPluginConfig({
|
||||
required String url,
|
||||
required String key,
|
||||
required String jwtSecret,
|
||||
required String collabTable,
|
||||
}) async {
|
||||
final payload = CollabPluginConfigPB.create();
|
||||
final collabTableConfig = CollabTableConfigPB.create()
|
||||
..tableName = collabTable;
|
||||
|
||||
payload.supabaseConfig = SupabaseDBConfigPB.create()
|
||||
..supabaseUrl = url
|
||||
..key = key
|
||||
..jwtSecret = jwtSecret
|
||||
..collabTableConfig = collabTableConfig;
|
||||
|
||||
await ConfigEventSetCollabPluginConfig(payload).send();
|
||||
}
|
||||
}
|
||||
|
7
frontend/appflowy_flutter/lib/env/env.dart
vendored
7
frontend/appflowy_flutter/lib/env/env.dart
vendored
@ -29,6 +29,13 @@ abstract class Env {
|
||||
defaultValue: '',
|
||||
)
|
||||
static final supabaseJwtSecret = _Env.supabaseJwtSecret;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_COLLAB_TABLE',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final supabaseCollabTable = _Env.supabaseCollabTable;
|
||||
}
|
||||
|
||||
bool get isSupabaseEnable =>
|
||||
|
@ -6,7 +6,6 @@ import 'package:appflowy/util/json_print.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:appflowy/workspace/application/doc/doc_listener.dart';
|
||||
import 'package:appflowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
@ -153,7 +152,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
editorState.logConfiguration
|
||||
..level = LogLevel.all
|
||||
..handler = (log) {
|
||||
Log.debug(log);
|
||||
// Log.debug(log);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ class FlowyRunner {
|
||||
anonKey: Env.supabaseAnonKey,
|
||||
key: Env.supabaseKey,
|
||||
jwtSecret: Env.supabaseJwtSecret,
|
||||
collabTable: Env.supabaseCollabTable,
|
||||
),
|
||||
const InitAppWidgetTask(),
|
||||
const InitPlatformServiceTask()
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||
import 'package:appflowy_backend/env_serde.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@ -20,10 +22,35 @@ class InitRustSDKTask extends LaunchTask {
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
final dir = directory ?? await appFlowyDocumentDirectory();
|
||||
|
||||
context.getIt<FlowySDK>().setEnv(getAppFlowyEnv());
|
||||
await context.getIt<FlowySDK>().init(dir);
|
||||
}
|
||||
}
|
||||
|
||||
AppFlowyEnv getAppFlowyEnv() {
|
||||
final supabaseConfig = SupabaseConfiguration(
|
||||
url: Env.supabaseUrl,
|
||||
key: Env.supabaseKey,
|
||||
jwt_secret: Env.supabaseJwtSecret,
|
||||
);
|
||||
|
||||
final collabTableConfig =
|
||||
CollabTableConfig(enable: true, table_name: Env.supabaseCollabTable);
|
||||
|
||||
final supbaseDBConfig = SupabaseDBConfig(
|
||||
url: Env.supabaseUrl,
|
||||
key: Env.supabaseKey,
|
||||
jwt_secret: Env.supabaseJwtSecret,
|
||||
collab_table_config: collabTableConfig,
|
||||
);
|
||||
|
||||
return AppFlowyEnv(
|
||||
supabase_config: supabaseConfig,
|
||||
supabase_db_config: supbaseDBConfig,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Directory> appFlowyDocumentDirectory() async {
|
||||
switch (integrationEnv()) {
|
||||
case IntegrationMode.develop:
|
||||
|
@ -13,12 +13,14 @@ class InitSupabaseTask extends LaunchTask {
|
||||
required this.anonKey,
|
||||
required this.key,
|
||||
required this.jwtSecret,
|
||||
this.collabTable = "",
|
||||
});
|
||||
|
||||
final String url;
|
||||
final String anonKey;
|
||||
final String key;
|
||||
final String jwtSecret;
|
||||
final String collabTable;
|
||||
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
@ -33,6 +35,7 @@ class InitSupabaseTask extends LaunchTask {
|
||||
await Supabase.initialize(
|
||||
url: url,
|
||||
anonKey: anonKey,
|
||||
debug: false,
|
||||
);
|
||||
await Config.setSupabaseConfig(
|
||||
url: url,
|
||||
|
@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
|
||||
void link_me_please(void);
|
||||
|
||||
void backend_log(int64_t level, const char *data);
|
||||
|
||||
void set_env(const char *data);
|
||||
|
@ -1,9 +1,11 @@
|
||||
export 'package:async/async.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:ffi';
|
||||
import 'env_serde.dart';
|
||||
import 'ffi.dart' as ffi;
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
@ -34,4 +36,9 @@ class FlowySDK {
|
||||
ffi.store_dart_post_cobject(NativeApi.postCObject);
|
||||
ffi.init_sdk(sdkDir.path.toNativeUtf8());
|
||||
}
|
||||
|
||||
void setEnv(AppFlowyEnv env) {
|
||||
final jsonStr = jsonEncode(env.toJson());
|
||||
ffi.set_env(jsonStr.toNativeUtf8());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'env_serde.l.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyEnv {
|
||||
final SupabaseConfiguration supabase_config;
|
||||
final SupabaseDBConfig supabase_db_config;
|
||||
|
||||
AppFlowyEnv(
|
||||
{required this.supabase_config, required this.supabase_db_config});
|
||||
|
||||
factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppFlowyEnvFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$AppFlowyEnvToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class SupabaseConfiguration {
|
||||
final String url;
|
||||
final String key;
|
||||
final String jwt_secret;
|
||||
|
||||
SupabaseConfiguration(
|
||||
{required this.url, required this.key, required this.jwt_secret});
|
||||
|
||||
factory SupabaseConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
_$SupabaseConfigurationFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class SupabaseDBConfig {
|
||||
final String url;
|
||||
final String key;
|
||||
final String jwt_secret;
|
||||
final CollabTableConfig collab_table_config;
|
||||
|
||||
SupabaseDBConfig(
|
||||
{required this.url,
|
||||
required this.key,
|
||||
required this.jwt_secret,
|
||||
required this.collab_table_config});
|
||||
|
||||
factory SupabaseDBConfig.fromJson(Map<String, dynamic> json) =>
|
||||
_$SupabaseDBConfigFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SupabaseDBConfigToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CollabTableConfig {
|
||||
final String table_name;
|
||||
final bool enable;
|
||||
|
||||
CollabTableConfig({required this.table_name, required this.enable});
|
||||
|
||||
factory CollabTableConfig.fromJson(Map<String, dynamic> json) =>
|
||||
_$CollabTableConfigFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CollabTableConfigToJson(this);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'env_serde.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AppFlowyEnv _$AppFlowyEnvFromJson(Map<String, dynamic> json) => AppFlowyEnv(
|
||||
supabase_config: SupabaseConfiguration.fromJson(
|
||||
json['supabase_config'] as Map<String, dynamic>),
|
||||
supabase_db_config: SupabaseDBConfig.fromJson(
|
||||
json['supabase_db_config'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppFlowyEnvToJson(AppFlowyEnv instance) =>
|
||||
<String, dynamic>{
|
||||
'supabase_config': instance.supabase_config,
|
||||
'supabase_db_config': instance.supabase_db_config,
|
||||
};
|
||||
|
||||
SupabaseConfiguration _$SupabaseConfigurationFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SupabaseConfiguration(
|
||||
url: json['url'] as String,
|
||||
key: json['key'] as String,
|
||||
jwt_secret: json['jwt_secret'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SupabaseConfigurationToJson(
|
||||
SupabaseConfiguration instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'key': instance.key,
|
||||
'jwt_secret': instance.jwt_secret,
|
||||
};
|
||||
|
||||
SupabaseDBConfig _$SupabaseDBConfigFromJson(Map<String, dynamic> json) =>
|
||||
SupabaseDBConfig(
|
||||
url: json['url'] as String,
|
||||
key: json['key'] as String,
|
||||
jwt_secret: json['jwt_secret'] as String,
|
||||
collab_table_config: CollabTableConfig.fromJson(
|
||||
json['collab_table_config'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SupabaseDBConfigToJson(SupabaseDBConfig instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'key': instance.key,
|
||||
'jwt_secret': instance.jwt_secret,
|
||||
'collab_table_config': instance.collab_table_config,
|
||||
};
|
||||
|
||||
CollabTableConfig _$CollabTableConfigFromJson(Map<String, dynamic> json) =>
|
||||
CollabTableConfig(
|
||||
table_name: json['table_name'] as String,
|
||||
enable: json['enable'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CollabTableConfigToJson(CollabTableConfig instance) =>
|
||||
<String, dynamic>{
|
||||
'table_name': instance.table_name,
|
||||
'enable': instance.enable,
|
||||
};
|
@ -151,3 +151,19 @@ typedef _invoke_log_Dart = void Function(
|
||||
int level,
|
||||
Pointer<ffi.Utf8>,
|
||||
);
|
||||
|
||||
/// C function `set_env`.
|
||||
void set_env(
|
||||
Pointer<ffi.Utf8> data,
|
||||
) {
|
||||
_set_env(data);
|
||||
}
|
||||
|
||||
final _set_env_Dart _set_env =
|
||||
_dart_ffi_lib.lookupFunction<_set_env_C, _set_env_Dart>('set_env');
|
||||
typedef _set_env_C = Void Function(
|
||||
Pointer<ffi.Utf8> data,
|
||||
);
|
||||
typedef _set_env_Dart = void Function(
|
||||
Pointer<ffi.Utf8> data,
|
||||
);
|
||||
|
@ -15,3 +15,5 @@ int32_t set_stream_port(int64_t port);
|
||||
void link_me_please(void);
|
||||
|
||||
void backend_log(int64_t level, const char *data);
|
||||
|
||||
void set_env(const char *data);
|
||||
|
@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
|
||||
void link_me_please(void);
|
||||
|
||||
void backend_log(int64_t level, const char *data);
|
||||
|
||||
void set_env(const char *data);
|
||||
|
@ -18,6 +18,7 @@ dependencies:
|
||||
freezed_annotation:
|
||||
logger: ^1.0.0
|
||||
plugin_platform_interface: ^2.1.3
|
||||
json_annotation: ^4.7.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -25,6 +26,7 @@ dev_dependencies:
|
||||
build_runner:
|
||||
freezed:
|
||||
flutter_lints: ^2.0.1
|
||||
json_serializable: ^6.6.2
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
32
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
32
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -99,8 +99,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
[[package]]
|
||||
name = "appflowy-integrate"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
"collab-database",
|
||||
"collab-document",
|
||||
@ -109,6 +110,7 @@ dependencies = [
|
||||
"collab-plugins",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1021,7 +1023,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -1038,7 +1040,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-client-ws"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab-sync",
|
||||
@ -1056,7 +1058,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1075,12 +1077,13 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1092,7 +1095,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1109,7 +1112,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1127,7 +1130,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
@ -1147,21 +1150,25 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws-sdk-dynamodb",
|
||||
"base64 0.21.0",
|
||||
"collab",
|
||||
"collab-client-ws",
|
||||
"collab-persistence",
|
||||
"collab-sync",
|
||||
"futures-util",
|
||||
"parking_lot 0.12.1",
|
||||
"postgrest",
|
||||
"rand 0.8.5",
|
||||
"rusoto_credential",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-retry",
|
||||
@ -1173,7 +1180,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-sync"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab",
|
||||
@ -1773,6 +1780,7 @@ dependencies = [
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
"flowy-server",
|
||||
"flowy-sqlite",
|
||||
"lib-dispatch",
|
||||
"protobuf",
|
||||
@ -1803,6 +1811,7 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@ -1972,9 +1981,10 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"config",
|
||||
"flowy-config",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-user",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
|
@ -34,12 +34,12 @@ default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
[patch.crates-io]
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
|
||||
#collab = { path = "../../AppFlowy-Collab/collab" }
|
||||
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
||||
|
90
frontend/rust-lib/Cargo.lock
generated
90
frontend/rust-lib/Cargo.lock
generated
@ -85,8 +85,9 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
[[package]]
|
||||
name = "appflowy-integrate"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
"collab-database",
|
||||
"collab-document",
|
||||
@ -95,6 +96,7 @@ dependencies = [
|
||||
"collab-plugins",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -884,7 +886,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -901,7 +903,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-client-ws"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab-sync",
|
||||
@ -919,7 +921,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -938,12 +940,13 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -955,7 +958,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -972,7 +975,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -990,7 +993,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
@ -1010,21 +1013,25 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws-sdk-dynamodb",
|
||||
"base64 0.21.0",
|
||||
"collab",
|
||||
"collab-client-ws",
|
||||
"collab-persistence",
|
||||
"collab-sync",
|
||||
"futures-util",
|
||||
"parking_lot 0.12.1",
|
||||
"postgrest",
|
||||
"rand 0.8.5",
|
||||
"rusoto_credential",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-retry",
|
||||
@ -1036,7 +1043,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-sync"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab",
|
||||
@ -1254,6 +1261,7 @@ name = "dart-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"allo-isolate",
|
||||
"appflowy-integrate",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crossbeam-utils",
|
||||
@ -1463,11 +1471,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fake"
|
||||
version = "2.5.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d68f517805463f3a896a9d29c1d6ff09d3579ded64a7201b4069f8f9c0d52fd"
|
||||
checksum = "0a44c765350db469b774425ff1c833890b16ceb9612fb5d7c4bbdf4a1b55f876"
|
||||
dependencies = [
|
||||
"rand 0.8.5",
|
||||
"unidecode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1551,6 +1560,7 @@ dependencies = [
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
"flowy-server",
|
||||
"flowy-sqlite",
|
||||
"lib-dispatch",
|
||||
"protobuf",
|
||||
@ -1582,6 +1592,7 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@ -1757,10 +1768,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"config",
|
||||
"dotenv",
|
||||
"flowy-config",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-user",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
@ -1820,28 +1832,26 @@ name = "flowy-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fake",
|
||||
"dotenv",
|
||||
"flowy-core",
|
||||
"flowy-folder2",
|
||||
"flowy-net",
|
||||
"flowy-server",
|
||||
"flowy-user",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"log",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"quickcheck",
|
||||
"quickcheck_macros 0.9.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"tempdir",
|
||||
"thread-id",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1859,7 +1869,6 @@ dependencies = [
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-sqlite",
|
||||
"flowy-test",
|
||||
"lazy_static",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
@ -1869,7 +1878,7 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"quickcheck",
|
||||
"quickcheck_macros 1.0.0",
|
||||
"quickcheck_macros",
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
@ -3483,17 +3492,6 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck_macros"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck_macros"
|
||||
version = "1.0.0"
|
||||
@ -4121,28 +4119,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"parking_lot 0.11.2",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.8"
|
||||
@ -4978,6 +4954,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unidecode"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
|
@ -33,11 +33,11 @@ opt-level = 3
|
||||
incremental = false
|
||||
|
||||
[patch.crates-io]
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
|
||||
|
||||
#collab = { path = "../AppFlowy-Collab/collab" }
|
||||
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
||||
|
@ -24,7 +24,7 @@ crossbeam-utils = "0.8.15"
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.12.1"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
|
||||
appflowy-integrate = {version = "0.1.0" }
|
||||
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
flowy-core = { path = "../flowy-core" }
|
||||
|
@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
|
||||
void link_me_please(void);
|
||||
|
||||
void backend_log(int64_t level, const char *data);
|
||||
|
||||
void set_env(const char *data);
|
||||
|
19
frontend/rust-lib/dart-ffi/src/env_serde.rs
Normal file
19
frontend/rust-lib/dart-ffi/src/env_serde.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use appflowy_integrate::SupabaseDBConfig;
|
||||
use flowy_server::supabase::SupabaseConfiguration;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AppFlowyEnv {
|
||||
supabase_config: SupabaseConfiguration,
|
||||
supabase_db_config: SupabaseDBConfig,
|
||||
}
|
||||
|
||||
impl AppFlowyEnv {
|
||||
pub fn parser(env_str: &str) {
|
||||
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
|
||||
dbg!(&env);
|
||||
env.supabase_config.write_env();
|
||||
env.supabase_db_config.write_env();
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use flowy_notification::register_notification_sender;
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
use lib_dispatch::prelude::*;
|
||||
|
||||
use crate::env_serde::AppFlowyEnv;
|
||||
use crate::notification::DartNotificationSender;
|
||||
use crate::{
|
||||
c::{extend_front_four_bytes_into_bytes, forget_rust},
|
||||
@ -17,6 +18,7 @@ use crate::{
|
||||
};
|
||||
|
||||
mod c;
|
||||
mod env_serde;
|
||||
mod model;
|
||||
mod notification;
|
||||
mod protobuf;
|
||||
@ -134,3 +136,10 @@ pub extern "C" fn backend_log(level: i64, data: *const c_char) {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_env(data: *const c_char) {
|
||||
let c_str = unsafe { CStr::from_ptr(data) };
|
||||
let serde_str = c_str.to_str().unwrap();
|
||||
AppFlowyEnv::parser(serde_str);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ bytes = { version = "1.4" }
|
||||
flowy-error = { path = "../flowy-error" }
|
||||
strum_macros = "0.21"
|
||||
appflowy-integrate = {version = "0.1.0" }
|
||||
flowy-server = { path = "../flowy-server" }
|
||||
|
||||
[build-dependencies]
|
||||
flowy-codegen = { path = "../../../shared-lib/flowy-codegen"}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use appflowy_integrate::config::AWSDynamoDBConfig;
|
||||
use appflowy_integrate::{CollabTableConfig, SupabaseDBConfig};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_server::supabase::SupabaseConfiguration;
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct KeyValuePB {
|
||||
@ -15,11 +19,6 @@ pub struct KeyPB {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
pub const SUPABASE_URL: &str = "SUPABASE_URL";
|
||||
pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
|
||||
pub const SUPABASE_KEY: &str = "SUPABASE_KEY";
|
||||
pub const SUPABASE_JWT_SECRET: &str = "SUPABASE_JWT_SECRET";
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct SupabaseConfigPB {
|
||||
#[pb(index = 1)]
|
||||
@ -35,28 +34,97 @@ pub struct SupabaseConfigPB {
|
||||
jwt_secret: String,
|
||||
}
|
||||
|
||||
impl SupabaseConfigPB {
|
||||
pub(crate) fn write_to_env(self) {
|
||||
std::env::set_var(SUPABASE_URL, self.supabase_url);
|
||||
std::env::set_var(SUPABASE_ANON_KEY, self.anon_key);
|
||||
std::env::set_var(SUPABASE_KEY, self.key);
|
||||
std::env::set_var(SUPABASE_JWT_SECRET, self.jwt_secret);
|
||||
impl TryFrom<SupabaseConfigPB> for SupabaseConfiguration {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: SupabaseConfigPB) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
url: value.supabase_url,
|
||||
key: value.key,
|
||||
jwt_secret: value.jwt_secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct AppFlowyCollabConfigPB {
|
||||
pub struct CollabPluginConfigPB {
|
||||
#[pb(index = 1, one_of)]
|
||||
aws_config: Option<AWSDynamoDBConfigPB>,
|
||||
pub aws_config: Option<AWSDynamoDBConfigPB>,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub supabase_config: Option<SupabaseDBConfigPB>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct AWSDynamoDBConfigPB {
|
||||
#[pb(index = 1)]
|
||||
pub access_key_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub secret_access_key: String,
|
||||
// Region list: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
|
||||
#[pb(index = 3)]
|
||||
pub region: String,
|
||||
}
|
||||
|
||||
impl TryFrom<AWSDynamoDBConfigPB> for AWSDynamoDBConfig {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(config: AWSDynamoDBConfigPB) -> Result<Self, Self::Error> {
|
||||
Ok(AWSDynamoDBConfig {
|
||||
access_key_id: config.access_key_id,
|
||||
secret_access_key: config.secret_access_key,
|
||||
region: config.region,
|
||||
enable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct SupabaseDBConfigPB {
|
||||
#[pb(index = 1)]
|
||||
pub supabase_url: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub key: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub jwt_secret: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub collab_table_config: CollabTableConfigPB,
|
||||
}
|
||||
|
||||
impl TryFrom<SupabaseDBConfigPB> for SupabaseDBConfig {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(config: SupabaseDBConfigPB) -> Result<Self, Self::Error> {
|
||||
let update_table_config = CollabTableConfig::try_from(config.collab_table_config)?;
|
||||
Ok(SupabaseDBConfig {
|
||||
url: config.supabase_url,
|
||||
key: config.key,
|
||||
jwt_secret: config.jwt_secret,
|
||||
collab_table_config: update_table_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct CollabTableConfigPB {
|
||||
#[pb(index = 1)]
|
||||
pub table_name: String,
|
||||
}
|
||||
|
||||
impl TryFrom<CollabTableConfigPB> for CollabTableConfig {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(config: CollabTableConfigPB) -> Result<Self, Self::Error> {
|
||||
if config.table_name.is_empty() {
|
||||
return Err(FlowyError::internal().context("table_name is empty"));
|
||||
}
|
||||
Ok(CollabTableConfig {
|
||||
table_name: config.table_name,
|
||||
enable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use appflowy_integrate::config::AWSDynamoDBConfig;
|
||||
use appflowy_integrate::SupabaseDBConfig;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_server::supabase::SupabaseConfiguration;
|
||||
use flowy_sqlite::kv::KV;
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult};
|
||||
|
||||
use crate::entities::{KeyPB, KeyValuePB, SupabaseConfigPB};
|
||||
use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB, SupabaseConfigPB};
|
||||
|
||||
pub(crate) async fn set_key_value_handler(data: AFPluginData<KeyValuePB>) -> FlowyResult<()> {
|
||||
let data = data.into_inner();
|
||||
@ -35,7 +38,24 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData<KeyPB>) -> Flowy
|
||||
pub(crate) async fn set_supabase_config_handler(
|
||||
data: AFPluginData<SupabaseConfigPB>,
|
||||
) -> FlowyResult<()> {
|
||||
let config = data.into_inner();
|
||||
config.write_to_env();
|
||||
let config = SupabaseConfiguration::try_from(data.into_inner())?;
|
||||
config.write_env();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_collab_plugin_config_handler(
|
||||
data: AFPluginData<CollabPluginConfigPB>,
|
||||
) -> FlowyResult<()> {
|
||||
let config = data.into_inner();
|
||||
if let Some(aws_config_pb) = config.aws_config {
|
||||
if let Ok(aws_config) = AWSDynamoDBConfig::try_from(aws_config_pb) {
|
||||
aws_config.write_env();
|
||||
}
|
||||
}
|
||||
if let Some(supabase_config_pb) = config.supabase_config {
|
||||
if let Ok(supabase_config) = SupabaseDBConfig::try_from(supabase_config_pb) {
|
||||
supabase_config.write_env();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -12,6 +12,10 @@ pub fn init() -> AFPlugin {
|
||||
.event(ConfigEvent::GetKeyValue, get_key_value_handler)
|
||||
.event(ConfigEvent::RemoveKeyValue, remove_key_value_handler)
|
||||
.event(ConfigEvent::SetSupabaseConfig, set_supabase_config_handler)
|
||||
.event(
|
||||
ConfigEvent::SetCollabPluginConfig,
|
||||
set_collab_plugin_config_handler,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -30,4 +34,7 @@ pub enum ConfigEvent {
|
||||
/// Check out the `write_to_env` of [SupabaseConfigPB].
|
||||
#[event(input = "SupabaseConfigPB")]
|
||||
SetSupabaseConfig = 3,
|
||||
|
||||
#[event(input = "CollabPluginConfigPB")]
|
||||
SetCollabPluginConfig = 4,
|
||||
}
|
||||
|
@ -11,12 +11,9 @@ lib-log = { path = "../lib-log" }
|
||||
flowy-user = { path = "../flowy-user" }
|
||||
flowy-net = { path = "../flowy-net" }
|
||||
flowy-folder2 = { path = "../flowy-folder2" }
|
||||
#flowy-database = { path = "../flowy-database" }
|
||||
flowy-database2 = { path = "../flowy-database2" }
|
||||
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
|
||||
#flowy-document = { path = "../flowy-document" }
|
||||
flowy-document2 = { path = "../flowy-document2" }
|
||||
#flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-error = { path = "../flowy-error" }
|
||||
flowy-task = { path = "../flowy-task" }
|
||||
flowy-server = { path = "../flowy-server" }
|
||||
@ -34,6 +31,7 @@ lib-ws = { path = "../../../shared-lib/lib-ws" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["rev-sqlite"]
|
||||
|
@ -13,8 +13,9 @@ use flowy_document2::document_data::DocumentDataWrapper;
|
||||
use flowy_document2::entities::DocumentDataPB;
|
||||
use flowy_document2::manager::DocumentManager;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder2::deps::{FolderCloudService, FolderUser};
|
||||
use flowy_folder2::entities::ViewLayoutPB;
|
||||
use flowy_folder2::manager::{Folder2Manager, FolderUser};
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
use flowy_folder2::view_ext::{ViewDataProcessor, ViewDataProcessorMap};
|
||||
use flowy_folder2::ViewLayout;
|
||||
use flowy_user::services::UserSession;
|
||||
@ -27,13 +28,14 @@ impl Folder2DepsResolver {
|
||||
document_manager: &Arc<DocumentManager>,
|
||||
database_manager: &Arc<DatabaseManager2>,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
folder_cloud: Arc<dyn FolderCloudService>,
|
||||
) -> Arc<Folder2Manager> {
|
||||
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl(user_session.clone()));
|
||||
|
||||
let view_data_processor =
|
||||
let view_processors =
|
||||
make_view_data_processor(document_manager.clone(), database_manager.clone());
|
||||
Arc::new(
|
||||
Folder2Manager::new(user.clone(), collab_builder, view_data_processor)
|
||||
Folder2Manager::new(user.clone(), collab_builder, view_processors, folder_cloud)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
|
@ -1,64 +1,134 @@
|
||||
use lib_infra::future::FutureResult;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_folder2::deps::{FolderCloudService, Workspace};
|
||||
use flowy_server::local_server::LocalServer;
|
||||
use flowy_server::self_host::configuration::self_host_server_configuration;
|
||||
use flowy_server::self_host::SelfHostServer;
|
||||
use flowy_server::supabase::{SupabaseConfiguration, SupabaseServer};
|
||||
use flowy_server::AppFlowyServer;
|
||||
use flowy_sqlite::kv::KV;
|
||||
use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
|
||||
use flowy_user::services::AuthType;
|
||||
|
||||
use serde_repr::*;
|
||||
|
||||
const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum ServerProviderType {
|
||||
/// Local server provider.
|
||||
/// Offline mode, no user authentication and the data is stored locally.
|
||||
Local = 0,
|
||||
/// Self-hosted server provider.
|
||||
/// The [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Server) is still a work in
|
||||
/// progress.
|
||||
SelfHosted = 1,
|
||||
/// Supabase server provider.
|
||||
/// It uses supabase's postgresql database to store data and user authentication.
|
||||
Supabase = 2,
|
||||
}
|
||||
|
||||
/// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
|
||||
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
|
||||
/// exist.
|
||||
/// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
|
||||
#[derive(Default)]
|
||||
pub struct AppFlowyServerProvider {
|
||||
providers: RwLock<HashMap<AuthType, Arc<dyn AppFlowyServer>>>,
|
||||
provider_type: RwLock<ServerProviderType>,
|
||||
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
|
||||
}
|
||||
|
||||
impl AppFlowyServerProvider {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn provider_type(&self) -> ServerProviderType {
|
||||
self.provider_type.read().clone()
|
||||
}
|
||||
|
||||
/// Returns a [AppFlowyServer] trait implementation base on the provider_type.
|
||||
fn get_provider(
|
||||
&self,
|
||||
provider_type: &ServerProviderType,
|
||||
) -> FlowyResult<Arc<dyn AppFlowyServer>> {
|
||||
if let Some(provider) = self.providers.read().get(provider_type) {
|
||||
return Ok(provider.clone());
|
||||
}
|
||||
|
||||
let server = server_from_auth_type(provider_type)?;
|
||||
self
|
||||
.providers
|
||||
.write()
|
||||
.insert(provider_type.clone(), server.clone());
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppFlowyServerProvider {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider_type: RwLock::new(current_server_provider()),
|
||||
providers: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserCloudServiceProvider for AppFlowyServerProvider {
|
||||
/// Returns the [UserAuthService] base on the current [AuthType].
|
||||
/// Creates a new [AppFlowyServer] if it doesn't exist.
|
||||
fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
|
||||
if let Some(provider) = self.providers.read().get(auth_type) {
|
||||
return Ok(provider.user_service());
|
||||
/// When user login, the provider type is set by the [AuthType].
|
||||
/// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
|
||||
/// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerProviderType] is set,
|
||||
/// it will be used when user open the app again.
|
||||
fn set_auth_type(&self, auth_type: AuthType) {
|
||||
let provider_type: ServerProviderType = auth_type.into();
|
||||
match KV::set_object(SERVER_PROVIDER_TYPE_KEY, provider_type.clone()) {
|
||||
Ok(_) => tracing::trace!("Update server provider type to: {:?}", provider_type),
|
||||
Err(e) => {
|
||||
tracing::error!("🔴Failed to update server provider type: {:?}", e);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let server = server_from_auth_type(auth_type)?;
|
||||
let user_service = server.user_service();
|
||||
self.providers.write().insert(auth_type.clone(), server);
|
||||
Ok(user_service)
|
||||
/// Returns the [UserAuthService] base on the current [ServerProviderType].
|
||||
/// Creates a new [AppFlowyServer] if it doesn't exist.
|
||||
fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
|
||||
let provider_type: ServerProviderType = auth_type.into();
|
||||
Ok(self.get_provider(&provider_type)?.user_service())
|
||||
}
|
||||
}
|
||||
|
||||
fn server_from_auth_type(auth_type: &AuthType) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
|
||||
match auth_type {
|
||||
AuthType::Local => {
|
||||
impl FolderCloudService for AppFlowyServerProvider {
|
||||
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
|
||||
let server = self.get_provider(&self.provider_type.read());
|
||||
let name = name.to_string();
|
||||
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
|
||||
}
|
||||
}
|
||||
|
||||
fn server_from_auth_type(
|
||||
provider: &ServerProviderType,
|
||||
) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
|
||||
match provider {
|
||||
ServerProviderType::Local => {
|
||||
let server = Arc::new(LocalServer::new());
|
||||
Ok(server)
|
||||
},
|
||||
AuthType::SelfHosted => {
|
||||
ServerProviderType::SelfHosted => {
|
||||
let config = self_host_server_configuration().map_err(|e| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
format!("Missing self host config: {:?}. Error: {:?}", auth_type, e),
|
||||
format!("Missing self host config: {:?}. Error: {:?}", provider, e),
|
||||
)
|
||||
})?;
|
||||
let server = Arc::new(SelfHostServer::new(config));
|
||||
Ok(server)
|
||||
},
|
||||
AuthType::Supabase => {
|
||||
ServerProviderType::Supabase => {
|
||||
// init the SupabaseServerConfiguration from the environment variables.
|
||||
let config = SupabaseConfiguration::from_env()?;
|
||||
let server = Arc::new(SupabaseServer::new(config));
|
||||
@ -66,3 +136,26 @@ fn server_from_auth_type(auth_type: &AuthType) -> Result<Arc<dyn AppFlowyServer>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthType> for ServerProviderType {
|
||||
fn from(auth_provider: AuthType) -> Self {
|
||||
match auth_provider {
|
||||
AuthType::Local => ServerProviderType::Local,
|
||||
AuthType::SelfHosted => ServerProviderType::SelfHosted,
|
||||
AuthType::Supabase => ServerProviderType::Supabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthType> for ServerProviderType {
|
||||
fn from(auth_provider: &AuthType) -> Self {
|
||||
Self::from(auth_provider.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn current_server_provider() -> ServerProviderType {
|
||||
match KV::get_object::<ServerProviderType>(SERVER_PROVIDER_TYPE_KEY) {
|
||||
None => ServerProviderType::Local,
|
||||
Some(provider_type) => provider_type,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![allow(unused_doc_comments)]
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fmt,
|
||||
@ -10,8 +9,8 @@ use std::{
|
||||
},
|
||||
};
|
||||
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::config::{AWSDynamoDBConfig, AppFlowyCollabConfig};
|
||||
use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CloudStorageType};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use flowy_database2::DatabaseManager2;
|
||||
@ -28,9 +27,10 @@ use lib_dispatch::runtime::tokio_default_runtime;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use module::make_plugins;
|
||||
pub use module::*;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::deps_resolve::*;
|
||||
use crate::integrate::server::AppFlowyServerProvider;
|
||||
use crate::integrate::server::{AppFlowyServerProvider, ServerProviderType};
|
||||
|
||||
mod deps_resolve;
|
||||
mod integrate;
|
||||
@ -92,10 +92,8 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
|
||||
filters.push(format!("flowy_document2={}", level));
|
||||
filters.push(format!("flowy_database2={}", level));
|
||||
filters.push(format!("flowy_notification={}", "info"));
|
||||
filters.push(format!("lib_ot={}", level));
|
||||
filters.push(format!("lib_infra={}", level));
|
||||
filters.push(format!("flowy_task={}", level));
|
||||
// filters.push(format!("lib_dispatch={}", level));
|
||||
|
||||
filters.push(format!("dart_ffi={}", "info"));
|
||||
filters.push(format!("flowy_sqlite={}", "info"));
|
||||
@ -136,22 +134,19 @@ impl AppFlowyCore {
|
||||
// Init the key value database
|
||||
init_kv(&config.storage_path);
|
||||
|
||||
// The collab config is used to build the [Collab] instance that used in document,
|
||||
// database, folder, etc.
|
||||
let collab_config = get_collab_config();
|
||||
inject_aws_env(collab_config.aws_config());
|
||||
|
||||
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
|
||||
/// on demand based on the [AppFlowyCollabConfig].
|
||||
let collab_builder = Arc::new(AppFlowyCollabBuilder::new(collab_config));
|
||||
|
||||
tracing::debug!("🔥 {:?}", config);
|
||||
debug!("🔥 {:?}", &config);
|
||||
let runtime = tokio_default_runtime().unwrap();
|
||||
let task_scheduler = TaskDispatcher::new(Duration::from_secs(2));
|
||||
let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
|
||||
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
||||
|
||||
let server_provider = Arc::new(AppFlowyServerProvider::new());
|
||||
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
|
||||
/// on demand based on the [CollabPluginConfig].
|
||||
let cloud_storage_type =
|
||||
collab_storage_type_from_server_provider_type(&server_provider.provider_type());
|
||||
let collab_builder = Arc::new(AppFlowyCollabBuilder::new(cloud_storage_type));
|
||||
|
||||
let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
|
||||
runtime.block_on(async {
|
||||
let user_session = mk_user_session(&config, server_provider.clone());
|
||||
@ -173,6 +168,7 @@ impl AppFlowyCore {
|
||||
&document_manager2,
|
||||
&database_manager2,
|
||||
collab_builder.clone(),
|
||||
server_provider.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -233,23 +229,6 @@ fn init_kv(root: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_collab_config() -> AppFlowyCollabConfig {
|
||||
match KV::get_str("collab_config") {
|
||||
None => AppFlowyCollabConfig::default(),
|
||||
Some(s) => AppFlowyCollabConfig::from_str(&s).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_aws_env(aws_config: Option<&AWSDynamoDBConfig>) {
|
||||
if let Some(aws_config) = aws_config {
|
||||
std::env::set_var("AWS_ACCESS_KEY_ID", aws_config.access_key_id.clone());
|
||||
std::env::set_var(
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
aws_config.secret_access_key.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_log(config: &AppFlowyCoreConfig) {
|
||||
if !INIT_LOG.load(Ordering::SeqCst) {
|
||||
INIT_LOG.store(true, Ordering::SeqCst);
|
||||
@ -334,3 +313,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
to_fut(async move { listener.did_expired(&token, user_id).await })
|
||||
}
|
||||
}
|
||||
|
||||
fn collab_storage_type_from_server_provider_type(
|
||||
server_provider_type: &ServerProviderType,
|
||||
) -> CloudStorageType {
|
||||
match server_provider_type {
|
||||
ServerProviderType::Local => CloudStorageType::Local,
|
||||
ServerProviderType::SelfHosted => CloudStorageType::Local,
|
||||
ServerProviderType::Supabase => CloudStorageType::Supabase,
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ pub(crate) async fn create_row_handler(
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
// #[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_cell_handler(
|
||||
data: AFPluginData<CellIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager2>>,
|
||||
@ -560,7 +560,6 @@ pub(crate) async fn set_layout_setting_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_layout_setting_handler(
|
||||
data: AFPluginData<DatabaseLayoutIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager2>>,
|
||||
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::{RocksCollabDB, RocksDBConfig};
|
||||
use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
|
||||
use collab::core::collab::MutexCollab;
|
||||
use collab_database::database::DatabaseData;
|
||||
use collab_database::user::{UserDatabase as InnerUserDatabase, UserDatabaseCollabBuilder};
|
||||
@ -51,7 +51,7 @@ impl DatabaseManager2 {
|
||||
*self.user_database.lock() = Some(InnerUserDatabase::new(
|
||||
user_id,
|
||||
db,
|
||||
RocksDBConfig::default(),
|
||||
CollabPersistenceConfig::default(),
|
||||
UserDatabaseCollabBuilderImpl(self.collab_builder.clone()),
|
||||
));
|
||||
// do nothing
|
||||
@ -229,7 +229,7 @@ impl UserDatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
|
||||
uid: i64,
|
||||
object_id: &str,
|
||||
db: Arc<RocksCollabDB>,
|
||||
config: &RocksDBConfig,
|
||||
config: &CollabPersistenceConfig,
|
||||
) -> Arc<MutexCollab> {
|
||||
self.0.build_with_config(uid, object_id, db, config)
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ use std::sync::Arc;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::RowId;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::database_view::DatabaseViewData;
|
||||
@ -42,9 +43,10 @@ pub async fn new_group_controller(
|
||||
let fields = delegate.get_fields(&view_id, None).await;
|
||||
let rows = delegate.get_rows(&view_id).await;
|
||||
let layout = delegate.get_layout_for_view(&view_id);
|
||||
trace!(?fields, ?rows, ?layout, "new_group_controller");
|
||||
|
||||
// Read the grouping field or find a new grouping field
|
||||
let grouping_field = setting_reader
|
||||
let mut grouping_field = setting_reader
|
||||
.get_group_setting(&view_id)
|
||||
.await
|
||||
.and_then(|setting| {
|
||||
@ -52,17 +54,25 @@ pub async fn new_group_controller(
|
||||
.iter()
|
||||
.find(|field| field.id == setting.field_id)
|
||||
.cloned()
|
||||
})
|
||||
.unwrap_or_else(|| find_new_grouping_field(&fields, &layout).unwrap());
|
||||
});
|
||||
|
||||
if grouping_field.is_none() {
|
||||
grouping_field = find_new_grouping_field(&fields, &layout);
|
||||
}
|
||||
|
||||
match grouping_field {
|
||||
None => Err(FlowyError::internal().context("No grouping field found".to_owned())),
|
||||
Some(_) => {
|
||||
make_group_controller(
|
||||
view_id,
|
||||
grouping_field,
|
||||
grouping_field.unwrap(),
|
||||
rows,
|
||||
setting_reader,
|
||||
setting_writer,
|
||||
)
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewData>);
|
||||
|
@ -7,15 +7,9 @@ use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CURRENCY_SYMBOL: Vec<String> = sorted_symbol();
|
||||
}
|
||||
|
||||
fn sorted_symbol() -> Vec<String> {
|
||||
let mut symbols = NumberFormat::iter()
|
||||
pub static ref CURRENCY_SYMBOL: Vec<String> = NumberFormat::iter()
|
||||
.map(|format| format.symbol())
|
||||
.collect::<Vec<String>>();
|
||||
symbols.sort_by(|a, b| b.len().cmp(&a.len()));
|
||||
symbols
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize)]
|
||||
|
@ -137,9 +137,7 @@ impl NumberTypeOption {
|
||||
};
|
||||
|
||||
match Decimal::from_str(&num_str) {
|
||||
Ok(decimal, ..) => {
|
||||
return Ok(NumberCellFormat::from_decimal(decimal));
|
||||
},
|
||||
Ok(decimal, ..) => Ok(NumberCellFormat::from_decimal(decimal)),
|
||||
Err(_) => Ok(NumberCellFormat::new()),
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ impl NumberCellFormat {
|
||||
return Ok(Self::default());
|
||||
}
|
||||
// If the first char is not '-', then it is a sign.
|
||||
let sign_positive = match num_str.find("-") {
|
||||
let sign_positive = match num_str.find('-') {
|
||||
None => true,
|
||||
Some(offset) => offset != 0,
|
||||
};
|
||||
|
@ -162,7 +162,7 @@ where
|
||||
) {
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let key = CellDataCacheKey::new(field, field_type.clone(), cell);
|
||||
let key = CellDataCacheKey::new(field, field_type, cell);
|
||||
// tracing::trace!(
|
||||
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
|
||||
// field_type,
|
||||
|
@ -85,7 +85,7 @@ impl SortController {
|
||||
self.gen_task(task_type, QualityOfService::Background).await;
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "process_sort_task", level = "debug", skip_all, err)]
|
||||
// #[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)]
|
||||
pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> {
|
||||
let event_type = SortEvent::from_str(predicate).unwrap();
|
||||
let mut rows = self.delegate.get_rows(&self.view_id).await;
|
||||
|
@ -13,13 +13,13 @@ use flowy_database2::services::field::{
|
||||
SelectOptionCellChangeset, SingleSelectTypeOption,
|
||||
};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use flowy_test::folder_event::ViewTest;
|
||||
use flowy_test::FlowyCoreTest;
|
||||
|
||||
use crate::database::mock_data::{make_test_board, make_test_calendar, make_test_grid};
|
||||
|
||||
pub struct DatabaseEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub sdk: FlowyCoreTest,
|
||||
pub app_id: String,
|
||||
pub view_id: String,
|
||||
pub editor: Arc<DatabaseEditor>,
|
||||
@ -43,7 +43,7 @@ impl DatabaseEditorTest {
|
||||
}
|
||||
|
||||
pub async fn new(layout: DatabaseLayoutPB) -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let _ = sdk.init_user().await;
|
||||
let test = match layout {
|
||||
DatabaseLayoutPB::Grid => {
|
||||
|
@ -1,425 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
use database_model::entities::{
|
||||
BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType,
|
||||
GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset,
|
||||
RowOrder, TypeOptionDataFormat,
|
||||
};
|
||||
use flowy_client_sync::client_grid::GridBuilder;
|
||||
use flowy_database::services::field::*;
|
||||
use flowy_database::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder};
|
||||
use flowy_database::services::row::CreateRowMetaPayload;
|
||||
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
|
||||
use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::EnumCount;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub enum EditorScript {
|
||||
CreateField {
|
||||
params: InsertFieldParams,
|
||||
},
|
||||
UpdateField {
|
||||
changeset: FieldChangesetParams,
|
||||
},
|
||||
DeleteField {
|
||||
field_meta: FieldMeta,
|
||||
},
|
||||
AssertFieldCount(usize),
|
||||
AssertFieldEqual {
|
||||
field_index: usize,
|
||||
field_meta: FieldMeta,
|
||||
},
|
||||
CreateBlock {
|
||||
block: GridBlockMetaSnapshot,
|
||||
},
|
||||
UpdateBlock {
|
||||
changeset: GridBlockInfoChangeset,
|
||||
},
|
||||
AssertBlockCount(usize),
|
||||
AssertBlock {
|
||||
block_index: usize,
|
||||
row_count: i32,
|
||||
start_row_index: i32,
|
||||
},
|
||||
AssertBlockEqual {
|
||||
block_index: usize,
|
||||
block: GridBlockMetaSnapshot,
|
||||
},
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
context: CreateRowMetaPayload,
|
||||
},
|
||||
UpdateRow {
|
||||
changeset: RowMetaChangeset,
|
||||
},
|
||||
AssertRow {
|
||||
changeset: RowMetaChangeset,
|
||||
},
|
||||
DeleteRow {
|
||||
row_ids: Vec<String>,
|
||||
},
|
||||
UpdateCell {
|
||||
changeset: CellChangeset,
|
||||
is_err: bool,
|
||||
},
|
||||
AssertRowCount(usize),
|
||||
// AssertRowEqual{ row_index: usize, row: RowMeta},
|
||||
AssertGridMetaPad,
|
||||
}
|
||||
|
||||
pub struct GridEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub grid_id: String,
|
||||
pub editor: Arc<GridMetaEditor>,
|
||||
pub field_metas: Vec<FieldMeta>,
|
||||
pub grid_blocks: Vec<GridBlockMetaSnapshot>,
|
||||
pub row_metas: Vec<Arc<RowMeta>>,
|
||||
pub field_count: usize,
|
||||
|
||||
pub row_order_by_row_id: HashMap<String, RowOrder>,
|
||||
}
|
||||
|
||||
impl GridEditorTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let build_context = make_template_1_grid();
|
||||
let view_data: Bytes = build_context.into();
|
||||
let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
|
||||
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
|
||||
let field_metas = editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
let grid_blocks = editor.get_block_metas().await.unwrap();
|
||||
let row_metas = get_row_metas(&editor).await;
|
||||
|
||||
let grid_id = test.view.id;
|
||||
Self {
|
||||
sdk,
|
||||
grid_id,
|
||||
editor,
|
||||
field_metas,
|
||||
grid_blocks,
|
||||
row_metas,
|
||||
field_count: FieldType::COUNT,
|
||||
row_order_by_row_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_scripts(&mut self, scripts: Vec<EditorScript>) {
|
||||
for script in scripts {
|
||||
self.run_script(script).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_script(&mut self, script: EditorScript) {
|
||||
let grid_manager = self.sdk.grid_manager.clone();
|
||||
let pool = self.sdk.user_session.db_pool().unwrap();
|
||||
let rev_manager = self.editor.rev_manager();
|
||||
let _cache = rev_manager.revision_cache().await;
|
||||
|
||||
match script {
|
||||
EditorScript::CreateField { params } => {
|
||||
if !self.editor.contain_field(¶ms.field.id).await {
|
||||
self.field_count += 1;
|
||||
}
|
||||
|
||||
self.editor.insert_field(params).await.unwrap();
|
||||
self.field_metas = self
|
||||
.editor
|
||||
.get_field_metas::<FieldOrder>(None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(self.field_count, self.field_metas.len());
|
||||
},
|
||||
EditorScript::UpdateField { changeset: change } => {
|
||||
self.editor.update_field(change).await.unwrap();
|
||||
self.field_metas = self
|
||||
.editor
|
||||
.get_field_metas::<FieldOrder>(None)
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
EditorScript::DeleteField { field_meta } => {
|
||||
if self.editor.contain_field(&field_meta.id).await {
|
||||
self.field_count -= 1;
|
||||
}
|
||||
|
||||
self.editor.delete_field(&field_meta.id).await.unwrap();
|
||||
self.field_metas = self
|
||||
.editor
|
||||
.get_field_metas::<FieldOrder>(None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(self.field_count, self.field_metas.len());
|
||||
},
|
||||
EditorScript::AssertFieldCount(count) => {
|
||||
assert_eq!(
|
||||
self
|
||||
.editor
|
||||
.get_field_metas::<FieldOrder>(None)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
count
|
||||
);
|
||||
},
|
||||
EditorScript::AssertFieldEqual {
|
||||
field_index,
|
||||
field_meta,
|
||||
} => {
|
||||
let field_metas = self
|
||||
.editor
|
||||
.get_field_metas::<FieldOrder>(None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(field_metas[field_index].clone(), field_meta);
|
||||
},
|
||||
EditorScript::CreateBlock { block } => {
|
||||
self.editor.create_block(block).await.unwrap();
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
},
|
||||
EditorScript::UpdateBlock { changeset: change } => {
|
||||
self.editor.update_block(change).await.unwrap();
|
||||
},
|
||||
EditorScript::AssertBlockCount(count) => {
|
||||
assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count);
|
||||
},
|
||||
EditorScript::AssertBlock {
|
||||
block_index,
|
||||
row_count,
|
||||
start_row_index,
|
||||
} => {
|
||||
assert_eq!(self.grid_blocks[block_index].row_count, row_count);
|
||||
assert_eq!(
|
||||
self.grid_blocks[block_index].start_row_index,
|
||||
start_row_index
|
||||
);
|
||||
},
|
||||
EditorScript::AssertBlockEqual { block_index, block } => {
|
||||
let blocks = self.editor.get_block_metas().await.unwrap();
|
||||
let compared_block = blocks[block_index].clone();
|
||||
assert_eq!(compared_block, block);
|
||||
},
|
||||
EditorScript::CreateEmptyRow => {
|
||||
let row_order = self.editor.create_row(None).await.unwrap();
|
||||
self
|
||||
.row_order_by_row_id
|
||||
.insert(row_order.row_id.clone(), row_order);
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
},
|
||||
EditorScript::CreateRow { context } => {
|
||||
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
|
||||
for row_order in row_orders {
|
||||
self
|
||||
.row_order_by_row_id
|
||||
.insert(row_order.row_id.clone(), row_order);
|
||||
}
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
},
|
||||
EditorScript::UpdateRow { changeset: change } => {
|
||||
self.editor.update_row(change).await.unwrap()
|
||||
},
|
||||
EditorScript::DeleteRow { row_ids } => {
|
||||
let row_orders = row_ids
|
||||
.into_iter()
|
||||
.map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone())
|
||||
.collect::<Vec<RowOrder>>();
|
||||
|
||||
self.editor.delete_rows(row_orders).await.unwrap();
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
},
|
||||
EditorScript::AssertRow { changeset } => {
|
||||
let row = self
|
||||
.row_metas
|
||||
.iter()
|
||||
.find(|row| row.id == changeset.row_id)
|
||||
.unwrap();
|
||||
|
||||
if let Some(visibility) = changeset.visibility {
|
||||
assert_eq!(row.visibility, visibility);
|
||||
}
|
||||
|
||||
if let Some(height) = changeset.height {
|
||||
assert_eq!(row.height, height);
|
||||
}
|
||||
},
|
||||
EditorScript::UpdateCell { changeset, is_err } => {
|
||||
let result = self.editor.update_cell(changeset).await;
|
||||
if is_err {
|
||||
assert!(result.is_err())
|
||||
} else {
|
||||
let _ = result.unwrap();
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
}
|
||||
},
|
||||
EditorScript::AssertRowCount(count) => {
|
||||
assert_eq!(self.row_metas.len(), count);
|
||||
},
|
||||
EditorScript::AssertGridMetaPad => {
|
||||
sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
|
||||
let mut grid_rev_manager = grid_manager
|
||||
.make_grid_rev_manager(&self.grid_id, pool.clone())
|
||||
.unwrap();
|
||||
let grid_pad = grid_rev_manager.load::<GridPadBuilder>(None).await.unwrap();
|
||||
println!("{}", grid_pad.delta_str());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_row_metas(&self) -> Vec<Arc<RowMeta>> {
|
||||
get_row_metas(&self.editor).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_row_metas(editor: &Arc<GridMetaEditor>) -> Vec<Arc<RowMeta>> {
|
||||
editor
|
||||
.grid_block_snapshots(None)
|
||||
.await
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
.row_metas
|
||||
}
|
||||
|
||||
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
|
||||
let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
let cloned_field_meta = field_meta.clone();
|
||||
|
||||
let type_option_data = field_meta
|
||||
.get_type_option_entry::<RichTextTypeOptionPB>(&field_meta.field_type)
|
||||
.unwrap()
|
||||
.protobuf_bytes()
|
||||
.to_vec();
|
||||
|
||||
let field = Field {
|
||||
id: field_meta.id,
|
||||
name: field_meta.name,
|
||||
desc: field_meta.desc,
|
||||
field_type: field_meta.field_type,
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
grid_id: grid_id.to_owned(),
|
||||
field,
|
||||
type_option_data,
|
||||
start_field_id: None,
|
||||
};
|
||||
(params, cloned_field_meta)
|
||||
}
|
||||
|
||||
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Done"))
|
||||
.option(SelectOption::new("Progress"));
|
||||
|
||||
let field_meta = FieldBuilder::new(single_select)
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
let cloned_field_meta = field_meta.clone();
|
||||
let type_option_data = field_meta
|
||||
.get_type_option_entry::<SingleSelectTypeOption>(&field_meta.field_type)
|
||||
.unwrap()
|
||||
.protobuf_bytes()
|
||||
.to_vec();
|
||||
|
||||
let field = Field {
|
||||
id: field_meta.id,
|
||||
name: field_meta.name,
|
||||
desc: field_meta.desc,
|
||||
field_type: field_meta.field_type,
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
grid_id: grid_id.to_owned(),
|
||||
field,
|
||||
type_option_data,
|
||||
start_field_id: None,
|
||||
};
|
||||
(params, cloned_field_meta)
|
||||
}
|
||||
|
||||
fn make_template_1_grid() -> BuildGridContext {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Live"))
|
||||
.option(SelectOption::new("Completed"))
|
||||
.option(SelectOption::new("Planned"))
|
||||
.option(SelectOption::new("Paused"));
|
||||
let single_select_field = FieldBuilder::new(single_select)
|
||||
.name("Status")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Google"))
|
||||
.option(SelectOption::new("Facebook"))
|
||||
.option(SelectOption::new("Twitter"));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number)
|
||||
.name("Price")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date)
|
||||
.name("Time")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox)
|
||||
.name("is done")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
|
||||
GridBuilder::default()
|
||||
.add_field(text_field)
|
||||
.add_field(single_select_field)
|
||||
.add_field(multi_select_field)
|
||||
.add_field(number_field)
|
||||
.add_field(date_field)
|
||||
.add_field(checkbox_field)
|
||||
.add_field(url_field)
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.build()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::config::AppFlowyCollabConfig;
|
||||
use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CloudStorageType};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
@ -50,6 +50,6 @@ pub fn db() -> Arc<RocksCollabDB> {
|
||||
}
|
||||
|
||||
pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
|
||||
let builder = AppFlowyCollabBuilder::new(AppFlowyCollabConfig::default());
|
||||
let builder = AppFlowyCollabBuilder::new(CloudStorageType::Local);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
17
frontend/rust-lib/flowy-folder2/src/deps.rs
Normal file
17
frontend/rust-lib/flowy-folder2/src/deps.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
pub use collab_folder::core::Workspace;
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::future::FutureResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// [FolderUser] represents the user for folder.
|
||||
pub trait FolderUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<Option<String>, FlowyError>;
|
||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||
}
|
||||
|
||||
/// [FolderCloudService] represents the cloud service for folder.
|
||||
pub trait FolderCloudService: Send + Sync + 'static {
|
||||
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError>;
|
||||
}
|
@ -106,7 +106,7 @@ impl WorkspaceIdPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Clone)]
|
||||
#[derive(Default, ProtoBuf, Debug, Clone)]
|
||||
pub struct WorkspaceSettingPB {
|
||||
#[pb(index = 1)]
|
||||
pub workspace: WorkspacePB,
|
||||
|
@ -3,6 +3,7 @@ use crate::manager::Folder2Manager;
|
||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||
|
||||
use lib_dispatch::prelude::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use strum_macros::Display;
|
||||
|
||||
|
@ -7,7 +7,9 @@ pub mod protobuf;
|
||||
mod user_default;
|
||||
pub mod view_ext;
|
||||
|
||||
pub mod deps;
|
||||
#[cfg(feature = "test_helper")]
|
||||
mod test_helper;
|
||||
|
||||
pub use collab_folder::core::ViewLayout;
|
||||
pub use user_default::gen_workspace_id;
|
||||
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
|
||||
use collab_folder::core::{
|
||||
Folder as InnerFolder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, TrashRecord,
|
||||
View, ViewChange, ViewChangeReceiver, ViewLayout, Workspace,
|
||||
@ -11,6 +11,7 @@ use collab_folder::core::{
|
||||
use parking_lot::Mutex;
|
||||
use tracing::{event, Level};
|
||||
|
||||
use crate::deps::{FolderCloudService, FolderUser};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
@ -22,22 +23,17 @@ use crate::notification::{
|
||||
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
||||
FolderNotification,
|
||||
};
|
||||
use crate::user_default::{gen_workspace_id, DefaultFolderBuilder};
|
||||
use crate::user_default::DefaultFolderBuilder;
|
||||
use crate::view_ext::{
|
||||
gen_view_id, view_from_create_view_params, ViewDataProcessor, ViewDataProcessorMap,
|
||||
};
|
||||
|
||||
pub trait FolderUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<Option<String>, FlowyError>;
|
||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||
}
|
||||
|
||||
pub struct Folder2Manager {
|
||||
folder: Folder,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
user: Arc<dyn FolderUser>,
|
||||
view_processors: ViewDataProcessorMap,
|
||||
cloud_service: Arc<dyn FolderCloudService>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Folder2Manager {}
|
||||
@ -48,6 +44,7 @@ impl Folder2Manager {
|
||||
user: Arc<dyn FolderUser>,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
view_processors: ViewDataProcessorMap,
|
||||
cloud_service: Arc<dyn FolderCloudService>,
|
||||
) -> FlowyResult<Self> {
|
||||
let folder = Folder::default();
|
||||
let manager = Self {
|
||||
@ -55,6 +52,7 @@ impl Folder2Manager {
|
||||
folder,
|
||||
collab_builder,
|
||||
view_processors,
|
||||
cloud_service,
|
||||
};
|
||||
|
||||
Ok(manager)
|
||||
@ -90,7 +88,7 @@ impl Folder2Manager {
|
||||
}
|
||||
|
||||
/// Called immediately after the application launched fi the user already sign in/sign up.
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn initialize(&self, uid: i64, workspace_id: &str) -> FlowyResult<()> {
|
||||
if let Ok(collab_db) = self.user.collab_db() {
|
||||
let collab = self.collab_builder.build(uid, workspace_id, collab_db);
|
||||
@ -139,13 +137,10 @@ impl Folder2Manager {
|
||||
pub async fn clear(&self, _user_id: i64) {}
|
||||
|
||||
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> FlowyResult<Workspace> {
|
||||
let workspace = Workspace {
|
||||
id: gen_workspace_id(),
|
||||
name: params.name,
|
||||
belongings: Default::default(),
|
||||
created_at: timestamp(),
|
||||
};
|
||||
|
||||
let workspace = self
|
||||
.cloud_service
|
||||
.create_workspace(self.user.user_id()?, ¶ms.name)
|
||||
.await?;
|
||||
self.with_folder((), |folder| {
|
||||
folder.workspaces.create_workspace(workspace.clone());
|
||||
folder.set_current_workspace(&workspace.id);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest};
|
||||
use collab_folder::core::ViewLayout;
|
||||
use flowy_folder2::entities::CreateWorkspacePayloadPB;
|
||||
use flowy_test::{event_builder::*, FlowySDKTest};
|
||||
use flowy_test::{event_builder::*, FlowyCoreTest};
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_read_all() {
|
||||
@ -63,18 +63,19 @@ async fn workspace_create_with_apps() {
|
||||
#[tokio::test]
|
||||
async fn workspace_create_with_invalid_name() {
|
||||
for (name, code) in invalid_workspace_name_test_case() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name,
|
||||
desc: "".to_owned(),
|
||||
};
|
||||
assert_eq!(
|
||||
Folder2EventBuilder::new(sdk)
|
||||
EventBuilder::new(sdk)
|
||||
.event(flowy_folder2::event_map::FolderEvent::CreateWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
code.value()
|
||||
)
|
||||
|
@ -2,8 +2,8 @@ use collab_folder::core::ViewLayout;
|
||||
use flowy_error::ErrorCode;
|
||||
use flowy_folder2::entities::*;
|
||||
use flowy_folder2::event_map::FolderEvent::*;
|
||||
use flowy_test::event_builder::Folder2EventBuilder;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
use flowy_test::FlowyCoreTest;
|
||||
|
||||
pub enum FolderScript {
|
||||
// Workspace
|
||||
@ -51,7 +51,7 @@ pub enum FolderScript {
|
||||
}
|
||||
|
||||
pub struct FolderTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub sdk: FlowyCoreTest,
|
||||
pub all_workspace: Vec<WorkspacePB>,
|
||||
pub workspace: WorkspacePB,
|
||||
pub parent_view: ViewPB,
|
||||
@ -61,7 +61,7 @@ pub struct FolderTest {
|
||||
|
||||
impl FolderTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let _ = sdk.init_user().await;
|
||||
let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
|
||||
let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
|
||||
@ -170,13 +170,13 @@ pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
|
||||
]
|
||||
}
|
||||
|
||||
pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB {
|
||||
pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name: name.to_owned(),
|
||||
desc: desc.to_owned(),
|
||||
};
|
||||
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
@ -184,11 +184,11 @@ pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Wor
|
||||
.parse::<WorkspacePB>()
|
||||
}
|
||||
|
||||
pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
|
||||
pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
|
||||
let request = WorkspaceIdPB {
|
||||
value: workspace_id,
|
||||
};
|
||||
let repeated_workspace = Folder2EventBuilder::new(sdk.clone())
|
||||
let repeated_workspace = EventBuilder::new(sdk.clone())
|
||||
.event(ReadWorkspaces)
|
||||
.payload(request.clone())
|
||||
.async_send()
|
||||
@ -210,7 +210,7 @@ pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option<String>) ->
|
||||
workspaces
|
||||
}
|
||||
|
||||
pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
|
||||
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
|
||||
let create_view_request = CreateViewPayloadPB {
|
||||
belong_to_id: workspace_id.to_owned(),
|
||||
name: name.to_string(),
|
||||
@ -221,7 +221,7 @@ pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc
|
||||
ext: Default::default(),
|
||||
};
|
||||
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(create_view_request)
|
||||
.async_send()
|
||||
@ -230,7 +230,7 @@ pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc
|
||||
}
|
||||
|
||||
pub async fn create_view(
|
||||
sdk: &FlowySDKTest,
|
||||
sdk: &FlowyCoreTest,
|
||||
app_id: &str,
|
||||
name: &str,
|
||||
desc: &str,
|
||||
@ -245,7 +245,7 @@ pub async fn create_view(
|
||||
initial_data: vec![],
|
||||
ext: Default::default(),
|
||||
};
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
@ -253,9 +253,9 @@ pub async fn create_view(
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB {
|
||||
pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
|
||||
let view_id: ViewIdPB = view_id.into();
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ReadView)
|
||||
.payload(view_id)
|
||||
.async_send()
|
||||
@ -264,7 +264,7 @@ pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB {
|
||||
}
|
||||
|
||||
pub async fn update_view(
|
||||
sdk: &FlowySDKTest,
|
||||
sdk: &FlowyCoreTest,
|
||||
view_id: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
@ -275,54 +275,54 @@ pub async fn update_view(
|
||||
desc,
|
||||
thumbnail: None,
|
||||
};
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(UpdateView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec<String>) {
|
||||
pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) {
|
||||
let request = RepeatedViewIdPB { items: view_ids };
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(DeleteView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrashPB {
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB {
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ReadTrash)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedTrashPB>()
|
||||
}
|
||||
|
||||
pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) {
|
||||
pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
|
||||
let id = TrashIdPB {
|
||||
id: app_id.to_owned(),
|
||||
};
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(PutbackTrash)
|
||||
.payload(id)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn restore_view_from_trash(sdk: &FlowySDKTest, view_id: &str) {
|
||||
pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
|
||||
let id = TrashIdPB {
|
||||
id: view_id.to_owned(),
|
||||
};
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(PutbackTrash)
|
||||
.payload(id)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_all_trash(sdk: &FlowySDKTest) {
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
pub async fn delete_all_trash(sdk: &FlowyCoreTest) {
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(DeleteAllTrash)
|
||||
.async_send()
|
||||
.await;
|
||||
|
@ -24,11 +24,12 @@ postgrest = "1.0"
|
||||
tokio-retry = "0.3"
|
||||
anyhow = "1.0"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
chrono = "0.4.24"
|
||||
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
flowy-user = { path = "../flowy-user" }
|
||||
flowy-folder2 = { path = "../flowy-folder2" }
|
||||
flowy-error = { path = "../flowy-error" }
|
||||
flowy-config = { path = "../flowy-config" }
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
|
@ -1,3 +1,4 @@
|
||||
use flowy_folder2::deps::FolderCloudService;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
@ -8,6 +9,21 @@ mod response;
|
||||
pub mod self_host;
|
||||
pub mod supabase;
|
||||
|
||||
/// In order to run this the supabase test, you need to create a .env file in the root directory of this project
|
||||
/// and add the following environment variables:
|
||||
/// - SUPABASE_URL
|
||||
/// - SUPABASE_ANON_KEY
|
||||
/// - SUPABASE_KEY
|
||||
/// - SUPABASE_JWT_SECRET
|
||||
///
|
||||
/// the .env file should look like this:
|
||||
/// SUPABASE_URL=https://<your-supabase-url>.supabase.co
|
||||
/// SUPABASE_ANON_KEY=<your-supabase-anon-key>
|
||||
/// SUPABASE_KEY=<your-supabase-key>
|
||||
/// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
|
||||
///
|
||||
|
||||
pub trait AppFlowyServer: Send + Sync + 'static {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService>;
|
||||
fn folder_service(&self) -> Arc<dyn FolderCloudService>;
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder2::deps::{FolderCloudService, Workspace};
|
||||
use flowy_folder2::gen_workspace_id;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
pub(crate) struct LocalServerFolderCloudServiceImpl();
|
||||
|
||||
impl FolderCloudService for LocalServerFolderCloudServiceImpl {
|
||||
fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
|
||||
let name = name.to_string();
|
||||
FutureResult::new(async move {
|
||||
Ok(Workspace {
|
||||
id: gen_workspace_id(),
|
||||
name: name.to_string(),
|
||||
belongings: Default::default(),
|
||||
created_at: timestamp(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
mod folder;
|
||||
mod user;
|
||||
|
||||
pub(crate) use folder::*;
|
||||
pub(crate) use user::*;
|
@ -1,5 +1,5 @@
|
||||
pub use server::*;
|
||||
|
||||
pub mod impls;
|
||||
mod server;
|
||||
pub(crate) mod uid;
|
||||
mod user;
|
||||
|
@ -1,11 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_folder2::deps::FolderCloudService;
|
||||
use parking_lot::RwLock;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
|
||||
use crate::local_server::user::LocalServerUserAuthServiceImpl;
|
||||
use crate::local_server::impls::{
|
||||
LocalServerFolderCloudServiceImpl, LocalServerUserAuthServiceImpl,
|
||||
};
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
#[derive(Default)]
|
||||
@ -31,4 +34,8 @@ impl AppFlowyServer for LocalServer {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService> {
|
||||
Arc::new(LocalServerUserAuthServiceImpl())
|
||||
}
|
||||
|
||||
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
|
||||
Arc::new(LocalServerFolderCloudServiceImpl())
|
||||
}
|
||||
}
|
||||
|
21
frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs
Normal file
21
frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder2::deps::{FolderCloudService, Workspace};
|
||||
use flowy_folder2::gen_workspace_id;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
pub(crate) struct SelfHostedServerFolderCloudServiceImpl();
|
||||
|
||||
impl FolderCloudService for SelfHostedServerFolderCloudServiceImpl {
|
||||
fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
|
||||
let name = name.to_string();
|
||||
FutureResult::new(async move {
|
||||
Ok(Workspace {
|
||||
id: gen_workspace_id(),
|
||||
name: name.to_string(),
|
||||
belongings: Default::default(),
|
||||
created_at: timestamp(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
mod folder;
|
||||
mod user;
|
||||
|
||||
pub(crate) use folder::*;
|
||||
pub(crate) use user::*;
|
@ -1,6 +1,5 @@
|
||||
pub use server::*;
|
||||
pub use user::*;
|
||||
|
||||
pub mod configuration;
|
||||
pub mod impls;
|
||||
mod server;
|
||||
mod user;
|
||||
|
@ -1,9 +1,12 @@
|
||||
use flowy_folder2::deps::FolderCloudService;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
|
||||
use crate::self_host::configuration::SelfHostedConfiguration;
|
||||
use crate::self_host::SelfHostedUserAuthServiceImpl;
|
||||
use crate::self_host::impls::{
|
||||
SelfHostedServerFolderCloudServiceImpl, SelfHostedUserAuthServiceImpl,
|
||||
};
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
pub struct SelfHostServer {
|
||||
@ -20,4 +23,8 @@ impl AppFlowyServer for SelfHostServer {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService> {
|
||||
Arc::new(SelfHostedUserAuthServiceImpl::new(self.config.clone()))
|
||||
}
|
||||
|
||||
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
|
||||
Arc::new(SelfHostedServerFolderCloudServiceImpl())
|
||||
}
|
||||
}
|
||||
|
54
frontend/rust-lib/flowy-server/src/supabase/impls/folder.rs
Normal file
54
frontend/rust-lib/flowy-server/src/supabase/impls/folder.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use crate::supabase::request::create_workspace_with_uid;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_folder2::deps::{FolderCloudService, Workspace};
|
||||
use lib_infra::future::FutureResult;
|
||||
use postgrest::Postgrest;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) const WORKSPACE_TABLE: &str = "af_workspace";
|
||||
pub(crate) const WORKSPACE_NAME_COLUMN: &str = "workspace_name";
|
||||
pub(crate) struct SupabaseFolderCloudServiceImpl {
|
||||
postgrest: Arc<Postgrest>,
|
||||
}
|
||||
|
||||
impl FolderCloudService for SupabaseFolderCloudServiceImpl {
|
||||
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
|
||||
let name = name.to_string();
|
||||
let postgrest = self.postgrest.clone();
|
||||
FutureResult::new(async move { create_workspace_with_uid(postgrest, uid, &name).await })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::supabase::request::{
|
||||
create_user_with_uuid, create_workspace_with_uid, get_user_workspace_with_uid,
|
||||
};
|
||||
use crate::supabase::{SupabaseConfiguration, SupabaseServer};
|
||||
use dotenv::dotenv;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_user_workspace() {
|
||||
dotenv().ok();
|
||||
if let Ok(config) = SupabaseConfiguration::from_env() {
|
||||
let server = Arc::new(SupabaseServer::new(config));
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
let uid = create_user_with_uuid(server.postgres.clone(), uuid.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.uid;
|
||||
|
||||
create_workspace_with_uid(server.postgres.clone(), uid, "test")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let workspaces = get_user_workspace_with_uid(server.postgres.clone(), uid)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(workspaces.len(), 2);
|
||||
assert_eq!(workspaces[0].name, "My workspace");
|
||||
assert_eq!(workspaces[1].name, "test");
|
||||
}
|
||||
}
|
||||
}
|
5
frontend/rust-lib/flowy-server/src/supabase/impls/mod.rs
Normal file
5
frontend/rust-lib/flowy-server/src/supabase/impls/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod folder;
|
||||
mod user;
|
||||
|
||||
pub(crate) use folder::*;
|
||||
pub(crate) use user::*;
|
@ -12,7 +12,8 @@ use crate::supabase::request::*;
|
||||
|
||||
pub(crate) const USER_TABLE: &str = "af_user";
|
||||
pub(crate) const USER_PROFILE_TABLE: &str = "af_user_profile";
|
||||
pub(crate) const USER_WORKSPACE_TABLE: &str = "af_user_workspace_view";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const USER_WORKSPACE_TABLE: &str = "af_workspace";
|
||||
pub(crate) struct PostgrestUserAuthServiceImpl {
|
||||
postgrest: Arc<Postgrest>,
|
||||
}
|
||||
@ -41,14 +42,12 @@ impl UserAuthService for PostgrestUserAuthServiceImpl {
|
||||
let postgrest = self.postgrest.clone();
|
||||
FutureResult::new(async move {
|
||||
let uuid = uuid_from_box_any(params)?;
|
||||
match get_user_workspace_with_uuid(postgrest, uuid).await? {
|
||||
None => Err(FlowyError::user_not_exist()),
|
||||
Some(user) => Ok(SignInResponse {
|
||||
user_id: user.uid,
|
||||
workspace_id: user.workspace_id,
|
||||
let user_profile = get_user_profile(postgrest, GetUserProfileParams::Uuid(uuid)).await?;
|
||||
Ok(SignInResponse {
|
||||
user_id: user_profile.uid,
|
||||
workspace_id: user_profile.workspace_id,
|
||||
..Default::default()
|
||||
}),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -76,18 +75,19 @@ impl UserAuthService for PostgrestUserAuthServiceImpl {
|
||||
) -> FutureResult<Option<UserProfile>, FlowyError> {
|
||||
let postgrest = self.postgrest.clone();
|
||||
FutureResult::new(async move {
|
||||
let profile = get_user_workspace_with_uid(postgrest, uid)
|
||||
.await?
|
||||
.map(|user_workspace| UserProfile {
|
||||
id: user_workspace.uid,
|
||||
email: "".to_string(),
|
||||
name: user_workspace.name,
|
||||
let user_profile_resp = get_user_profile(postgrest, GetUserProfileParams::Uid(uid)).await?;
|
||||
|
||||
let profile = UserProfile {
|
||||
id: user_profile_resp.uid,
|
||||
email: user_profile_resp.email,
|
||||
name: user_profile_resp.name,
|
||||
token: "".to_string(),
|
||||
icon_url: "".to_string(),
|
||||
openai_key: "".to_string(),
|
||||
workspace_id: user_workspace.workspace_id,
|
||||
});
|
||||
Ok(profile)
|
||||
workspace_id: user_profile_resp.workspace_id,
|
||||
};
|
||||
|
||||
Ok(Some(profile))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -100,8 +100,10 @@ mod tests {
|
||||
|
||||
use flowy_user::entities::UpdateUserProfileParams;
|
||||
|
||||
use crate::supabase::request::{get_user_profile, get_user_workspace_with_uid};
|
||||
use crate::supabase::user::{create_user_with_uuid, get_user_id_with_uuid, update_user_profile};
|
||||
use crate::supabase::request::{
|
||||
create_user_with_uuid, get_user_id_with_uuid, get_user_profile, get_user_workspace_with_uid,
|
||||
update_user_profile, GetUserProfileParams,
|
||||
};
|
||||
use crate::supabase::{SupabaseConfiguration, SupabaseServer};
|
||||
|
||||
#[tokio::test]
|
||||
@ -151,17 +153,15 @@ mod tests {
|
||||
.unwrap();
|
||||
println!("result: {:?}", result);
|
||||
|
||||
let result = get_user_profile(server.postgres.clone(), uid)
|
||||
let result = get_user_profile(server.postgres.clone(), GetUserProfileParams::Uid(uid))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(result.name, "nathan".to_string());
|
||||
|
||||
let result = get_user_workspace_with_uid(server.postgres.clone(), uid)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(!result.workspace_id.is_empty());
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
pub use server::*;
|
||||
|
||||
pub mod impls;
|
||||
mod request;
|
||||
mod response;
|
||||
mod retry;
|
||||
mod server;
|
||||
pub mod user;
|
||||
|
@ -5,13 +5,16 @@ use postgrest::Postgrest;
|
||||
use serde_json::json;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_folder2::deps::Workspace;
|
||||
use flowy_user::entities::UpdateUserProfileParams;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::supabase::response::{
|
||||
InsertResponse, PostgrestError, UserProfile, UserProfileList, UserWorkspace, UserWorkspaceList,
|
||||
use crate::supabase::impls::{
|
||||
USER_PROFILE_TABLE, USER_TABLE, USER_WORKSPACE_TABLE, WORKSPACE_NAME_COLUMN, WORKSPACE_TABLE,
|
||||
};
|
||||
use crate::supabase::response::{
|
||||
InsertResponse, PostgrestError, UserProfileResponse, UserProfileResponseList, UserWorkspaceList,
|
||||
};
|
||||
use crate::supabase::user::{USER_PROFILE_TABLE, USER_TABLE, USER_WORKSPACE_TABLE};
|
||||
|
||||
const USER_ID: &str = "uid";
|
||||
const USER_UUID: &str = "uuid";
|
||||
@ -19,13 +22,15 @@ const USER_UUID: &str = "uuid";
|
||||
pub(crate) async fn create_user_with_uuid(
|
||||
postgrest: Arc<Postgrest>,
|
||||
uuid: String,
|
||||
) -> Result<UserWorkspace, FlowyError> {
|
||||
let insert = format!("{{\"{}\": \"{}\"}}", USER_UUID, &uuid);
|
||||
) -> Result<UserProfileResponse, FlowyError> {
|
||||
let mut insert = serde_json::Map::new();
|
||||
insert.insert(USER_UUID.to_string(), json!(&uuid));
|
||||
let insert_query = serde_json::to_string(&insert).unwrap();
|
||||
|
||||
// Create a new user with uuid.
|
||||
let resp = postgrest
|
||||
.from(USER_TABLE)
|
||||
.insert(insert)
|
||||
.insert(insert_query)
|
||||
.execute()
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::HttpError, e))?;
|
||||
@ -44,13 +49,7 @@ pub(crate) async fn create_user_with_uuid(
|
||||
.map_err(|e| FlowyError::serde().context(e))?
|
||||
.first_or_error()?;
|
||||
|
||||
match get_user_workspace_with_uid(postgrest, record.uid).await {
|
||||
Ok(Some(user)) => Ok(user),
|
||||
_ => Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Failed to get user workspace",
|
||||
)),
|
||||
}
|
||||
get_user_profile(postgrest, GetUserProfileParams::Uid(record.uid)).await
|
||||
} else {
|
||||
let err = serde_json::from_str::<PostgrestError>(&content)
|
||||
.map_err(|e| FlowyError::serde().context(e))?;
|
||||
@ -58,8 +57,8 @@ pub(crate) async fn create_user_with_uuid(
|
||||
// If there is a unique violation, try to get the user id with uuid. At this point, the user
|
||||
// should exist.
|
||||
if err.is_unique_violation() {
|
||||
match get_user_workspace_with_uuid(postgrest, uuid).await {
|
||||
Ok(Some(user)) => Ok(user),
|
||||
match get_user_profile(postgrest, GetUserProfileParams::Uuid(uuid)).await {
|
||||
Ok(user) => Ok(user),
|
||||
_ => Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Failed to get user workspace",
|
||||
@ -112,14 +111,21 @@ pub(crate) fn uuid_from_box_any(any: BoxAny) -> Result<String, FlowyError> {
|
||||
Ok(uuid.to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum GetUserProfileParams {
|
||||
Uid(i64),
|
||||
Uuid(String),
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_profile(
|
||||
postgrest: Arc<Postgrest>,
|
||||
uid: i64,
|
||||
) -> Result<Option<UserProfile>, FlowyError> {
|
||||
let resp = postgrest
|
||||
.from(USER_PROFILE_TABLE)
|
||||
.eq(USER_ID, uid.to_string())
|
||||
params: GetUserProfileParams,
|
||||
) -> Result<UserProfileResponse, FlowyError> {
|
||||
let mut builder = postgrest.from(USER_PROFILE_TABLE);
|
||||
match params {
|
||||
GetUserProfileParams::Uid(uid) => builder = builder.eq(USER_ID, uid.to_string()),
|
||||
GetUserProfileParams::Uuid(uuid) => builder = builder.eq(USER_UUID, uuid),
|
||||
}
|
||||
let resp = builder
|
||||
.select("*")
|
||||
.execute()
|
||||
.await
|
||||
@ -129,19 +135,35 @@ pub(crate) async fn get_user_profile(
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
|
||||
let resp = serde_json::from_str::<UserProfileList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserProfileList failed"))?;
|
||||
Ok(resp.0.first().cloned())
|
||||
let mut user_profiles =
|
||||
serde_json::from_str::<UserProfileResponseList>(&content).map_err(|_e| {
|
||||
FlowyError::new(
|
||||
ErrorCode::Serde,
|
||||
"Deserialize UserProfileResponseList failed",
|
||||
)
|
||||
})?;
|
||||
if user_profiles.0.is_empty() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Failed to get user profile",
|
||||
));
|
||||
}
|
||||
Ok(user_profiles.0.remove(0))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_workspace_with_uuid(
|
||||
pub(crate) async fn create_workspace_with_uid(
|
||||
postgrest: Arc<Postgrest>,
|
||||
uuid: String,
|
||||
) -> Result<Option<UserWorkspace>, FlowyError> {
|
||||
uid: i64,
|
||||
name: &str,
|
||||
) -> Result<Workspace, FlowyError> {
|
||||
let mut insert = serde_json::Map::new();
|
||||
insert.insert(USER_ID.to_string(), json!(uid));
|
||||
insert.insert(WORKSPACE_NAME_COLUMN.to_string(), json!(name));
|
||||
let insert_query = serde_json::to_string(&insert).unwrap();
|
||||
|
||||
let resp = postgrest
|
||||
.from(USER_WORKSPACE_TABLE)
|
||||
.eq(USER_UUID, uuid)
|
||||
.select("*")
|
||||
.from(WORKSPACE_TABLE)
|
||||
.insert(insert_query)
|
||||
.execute()
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::HttpError, e))?;
|
||||
@ -150,15 +172,31 @@ pub(crate) async fn get_user_workspace_with_uuid(
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
|
||||
let resp = serde_json::from_str::<UserWorkspaceList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?;
|
||||
Ok(resp.0.first().cloned())
|
||||
let mut workspace_list = serde_json::from_str::<UserWorkspaceList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?
|
||||
.into_inner();
|
||||
|
||||
debug_assert!(workspace_list.len() == 1);
|
||||
if workspace_list.is_empty() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Failed to create workspace",
|
||||
));
|
||||
}
|
||||
let user_workspace = workspace_list.remove(0);
|
||||
Ok(Workspace {
|
||||
id: user_workspace.workspace_id,
|
||||
name: user_workspace.workspace_name,
|
||||
belongings: Default::default(),
|
||||
created_at: user_workspace.created_at.timestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn get_user_workspace_with_uid(
|
||||
postgrest: Arc<Postgrest>,
|
||||
uid: i64,
|
||||
) -> Result<Option<UserWorkspace>, FlowyError> {
|
||||
) -> Result<Vec<Workspace>, FlowyError> {
|
||||
let resp = postgrest
|
||||
.from(USER_WORKSPACE_TABLE)
|
||||
.eq(USER_ID, uid.to_string())
|
||||
@ -171,16 +209,27 @@ pub(crate) async fn get_user_workspace_with_uid(
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
|
||||
let resp = serde_json::from_str::<UserWorkspaceList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?;
|
||||
Ok(resp.0.first().cloned())
|
||||
let user_workspaces = serde_json::from_str::<UserWorkspaceList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?
|
||||
.0;
|
||||
Ok(
|
||||
user_workspaces
|
||||
.into_iter()
|
||||
.map(|user_workspace| Workspace {
|
||||
id: user_workspace.workspace_id,
|
||||
name: user_workspace.workspace_name,
|
||||
belongings: Default::default(),
|
||||
created_at: user_workspace.created_at.timestamp(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn update_user_profile(
|
||||
postgrest: Arc<Postgrest>,
|
||||
params: UpdateUserProfileParams,
|
||||
) -> Result<Option<UserProfile>, FlowyError> {
|
||||
) -> Result<Option<UserProfileResponse>, FlowyError> {
|
||||
if params.is_empty() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::UnexpectedEmpty,
|
||||
@ -206,7 +255,7 @@ pub(crate) async fn update_user_profile(
|
||||
.await
|
||||
.map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
|
||||
|
||||
let resp = serde_json::from_str::<UserProfileList>(&content)
|
||||
let resp = serde_json::from_str::<UserProfileResponseList>(&content)
|
||||
.map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserProfileList failed"))?;
|
||||
Ok(resp.0.first().cloned())
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
@ -56,27 +57,39 @@ pub(crate) struct InsertRecord {
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub(crate) struct UserProfile {
|
||||
pub(crate) struct UserProfileResponse {
|
||||
pub uid: i64,
|
||||
#[serde(deserialize_with = "deserialize_null_or_default")]
|
||||
pub name: String,
|
||||
|
||||
#[serde(deserialize_with = "deserialize_null_or_default")]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct UserProfileList(pub Vec<UserProfile>);
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub(crate) struct UserWorkspace {
|
||||
pub uid: i64,
|
||||
#[serde(deserialize_with = "deserialize_null_or_default")]
|
||||
pub name: String,
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct UserWorkspaceList(pub Vec<UserWorkspace>);
|
||||
pub(crate) struct UserProfileResponseList(pub Vec<UserProfileResponse>);
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub(crate) struct UserWorkspace {
|
||||
#[allow(dead_code)]
|
||||
pub uid: i64,
|
||||
#[serde(deserialize_with = "deserialize_null_or_default")]
|
||||
pub workspace_name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct UserWorkspaceList(pub(crate) Vec<UserWorkspace>);
|
||||
|
||||
impl UserWorkspaceList {
|
||||
pub(crate) fn into_inner(self) -> Vec<UserWorkspace> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the case where the value is null. If the value is null, return the default value of the
|
||||
/// type. Otherwise, deserialize the value.
|
||||
|
@ -1,15 +1,21 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use postgrest::Postgrest;
|
||||
use serde::Deserialize;
|
||||
|
||||
use flowy_config::entities::{SUPABASE_JWT_SECRET, SUPABASE_KEY, SUPABASE_URL};
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_folder2::deps::FolderCloudService;
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
|
||||
use crate::supabase::user::PostgrestUserAuthServiceImpl;
|
||||
use crate::supabase::impls::PostgrestUserAuthServiceImpl;
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub const SUPABASE_URL: &str = "SUPABASE_URL";
|
||||
pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
|
||||
pub const SUPABASE_KEY: &str = "SUPABASE_KEY";
|
||||
pub const SUPABASE_JWT_SECRET: &str = "SUPABASE_JWT_SECRET";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SupabaseConfiguration {
|
||||
/// The url of the supabase server.
|
||||
pub url: String,
|
||||
@ -20,6 +26,11 @@ pub struct SupabaseConfiguration {
|
||||
}
|
||||
|
||||
impl SupabaseConfiguration {
|
||||
/// Load the configuration from the environment variables.
|
||||
/// SUPABASE_URL=https://<your-supabase-url>.supabase.co
|
||||
/// SUPABASE_KEY=<your-supabase-key>
|
||||
/// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
|
||||
///
|
||||
pub fn from_env() -> Result<Self, FlowyError> {
|
||||
Ok(Self {
|
||||
url: std::env::var(SUPABASE_URL)
|
||||
@ -31,6 +42,12 @@ impl SupabaseConfiguration {
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_env(&self) {
|
||||
std::env::set_var(SUPABASE_URL, &self.url);
|
||||
std::env::set_var(SUPABASE_KEY, &self.key);
|
||||
std::env::set_var(SUPABASE_JWT_SECRET, &self.jwt_secret);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SupabaseServer {
|
||||
@ -53,4 +70,8 @@ impl AppFlowyServer for SupabaseServer {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService> {
|
||||
Arc::new(PostgrestUserAuthServiceImpl::new(self.postgres.clone()))
|
||||
}
|
||||
|
||||
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ flowy-core = { path = "../flowy-core" }
|
||||
flowy-user = { path = "../flowy-user"}
|
||||
flowy-net = { path = "../flowy-net"}
|
||||
flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] }
|
||||
#flowy-document= { path = "../flowy-document" }
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
lib-ot = { path = "../../../shared-lib/lib-ot" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
@ -19,21 +18,18 @@ flowy-server = { path = "../flowy-server" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
protobuf = {version = "2.28.0"}
|
||||
#claim = "0.5.0"
|
||||
tokio = { version = "1.26", features = ["full"]}
|
||||
futures-util = "0.3.26"
|
||||
thread-id = "3.3.0"
|
||||
log = "0.4"
|
||||
bytes = "1.4"
|
||||
nanoid = "0.4.0"
|
||||
tempdir = "0.3.7"
|
||||
tracing = { version = "0.1.27" }
|
||||
parking_lot = "0.12.1"
|
||||
dotenv = "0.15.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "0.9.1"
|
||||
fake = "2.5.0"
|
||||
futures = "0.3.26"
|
||||
serial_test = "0.5.1"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
|
||||
[features]
|
||||
dart = ["flowy-core/dart"]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::FlowySDKTest;
|
||||
use flowy_user::{entities::UserProfilePB, errors::FlowyError};
|
||||
use crate::FlowyCoreTest;
|
||||
use flowy_user::errors::FlowyError;
|
||||
use lib_dispatch::prelude::{
|
||||
AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, StatusCode,
|
||||
ToBytes, *,
|
||||
@ -8,38 +8,18 @@ use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{Debug, Display},
|
||||
hash::Hash,
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub type Folder2EventBuilder = EventBuilder<FlowyError>;
|
||||
impl Folder2EventBuilder {
|
||||
pub fn new(sdk: FlowySDKTest) -> Self {
|
||||
EventBuilder::test(TestContext::new(sdk))
|
||||
}
|
||||
pub fn user_profile(&self) -> &Option<UserProfilePB> {
|
||||
&self.user_profile
|
||||
}
|
||||
}
|
||||
|
||||
pub type UserModuleEventBuilder = Folder2EventBuilder;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventBuilder<E> {
|
||||
pub struct EventBuilder {
|
||||
context: TestContext,
|
||||
user_profile: Option<UserProfilePB>,
|
||||
err_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> EventBuilder<E>
|
||||
where
|
||||
E: AFPluginFromBytes + Debug,
|
||||
{
|
||||
fn test(context: TestContext) -> Self {
|
||||
impl EventBuilder {
|
||||
pub fn new(sdk: FlowyCoreTest) -> Self {
|
||||
Self {
|
||||
context,
|
||||
user_profile: None,
|
||||
err_phantom: PhantomData,
|
||||
context: TestContext::new(sdk),
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +33,7 @@ where
|
||||
self.context.request = Some(module_request.payload(bytes))
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Set payload failed: {:?}", e);
|
||||
tracing::error!("Set payload failed: {:?}", e);
|
||||
},
|
||||
}
|
||||
self
|
||||
@ -86,7 +66,7 @@ where
|
||||
R: AFPluginFromBytes,
|
||||
{
|
||||
let response = self.get_response();
|
||||
match response.clone().parse::<R, E>() {
|
||||
match response.clone().parse::<R, FlowyError>() {
|
||||
Ok(Ok(data)) => data,
|
||||
Ok(Err(e)) => {
|
||||
panic!(
|
||||
@ -105,22 +85,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(self) -> E {
|
||||
pub fn error(self) -> Option<FlowyError> {
|
||||
let response = self.get_response();
|
||||
assert_eq!(response.status_code, StatusCode::Err);
|
||||
<AFPluginData<E>>::try_from(response.payload)
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
pub fn assert_error(self) -> Self {
|
||||
// self.context.assert_error();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn assert_success(self) -> Self {
|
||||
// self.context.assert_success();
|
||||
self
|
||||
<AFPluginData<FlowyError>>::try_from(response.payload)
|
||||
.ok()
|
||||
.map(|data| data.into_inner())
|
||||
}
|
||||
|
||||
fn dispatch(&self) -> Arc<AFPluginDispatcher> {
|
||||
@ -132,7 +102,7 @@ where
|
||||
.context
|
||||
.response
|
||||
.as_ref()
|
||||
.expect("must call sync_send first")
|
||||
.expect("must call sync_send/async_send first")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@ -143,13 +113,13 @@ where
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestContext {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub sdk: FlowyCoreTest,
|
||||
request: Option<AFPluginRequest>,
|
||||
response: Option<AFPluginEventResponse>,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
pub fn new(sdk: FlowySDKTest) -> Self {
|
||||
pub fn new(sdk: FlowyCoreTest) -> Self {
|
||||
Self {
|
||||
sdk,
|
||||
request: None,
|
||||
|
111
frontend/rust-lib/flowy-test/src/folder_event.rs
Normal file
111
frontend/rust-lib/flowy-test/src/folder_event.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::event_builder::EventBuilder;
|
||||
use crate::FlowyCoreTest;
|
||||
use flowy_folder2::entities::*;
|
||||
use flowy_folder2::event_map::FolderEvent::*;
|
||||
|
||||
pub struct ViewTest {
|
||||
pub sdk: FlowyCoreTest,
|
||||
pub workspace: WorkspacePB,
|
||||
pub parent_view: ViewPB,
|
||||
pub child_view: ViewPB,
|
||||
}
|
||||
|
||||
impl ViewTest {
|
||||
#[allow(dead_code)]
|
||||
pub async fn new(sdk: &FlowyCoreTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
|
||||
let workspace = create_workspace(sdk, "Workspace", "").await;
|
||||
open_workspace(sdk, &workspace.id).await;
|
||||
let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
|
||||
let view = create_view(sdk, &app.id, layout, data).await;
|
||||
Self {
|
||||
sdk: sdk.clone(),
|
||||
workspace,
|
||||
parent_view: app,
|
||||
child_view: view,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_grid_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Grid, data).await
|
||||
}
|
||||
|
||||
pub async fn new_board_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Board, data).await
|
||||
}
|
||||
|
||||
pub async fn new_calendar_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Calendar, data).await
|
||||
}
|
||||
|
||||
pub async fn new_document_view(sdk: &FlowyCoreTest) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Document, vec![]).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name: name.to_owned(),
|
||||
desc: desc.to_owned(),
|
||||
};
|
||||
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<WorkspacePB>()
|
||||
}
|
||||
|
||||
async fn open_workspace(sdk: &FlowyCoreTest, workspace_id: &str) {
|
||||
let payload = WorkspaceIdPB {
|
||||
value: Some(workspace_id.to_owned()),
|
||||
};
|
||||
let _ = EventBuilder::new(sdk.clone())
|
||||
.event(OpenWorkspace)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
|
||||
let create_app_request = CreateViewPayloadPB {
|
||||
belong_to_id: workspace_id.to_owned(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
layout: ViewLayoutPB::Document,
|
||||
initial_data: vec![],
|
||||
ext: Default::default(),
|
||||
};
|
||||
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(create_app_request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
async fn create_view(
|
||||
sdk: &FlowyCoreTest,
|
||||
app_id: &str,
|
||||
layout: ViewLayoutPB,
|
||||
data: Vec<u8>,
|
||||
) -> ViewPB {
|
||||
let payload = CreateViewPayloadPB {
|
||||
belong_to_id: app_id.to_string(),
|
||||
name: "View A".to_string(),
|
||||
desc: "".to_string(),
|
||||
thumbnail: Some("http://1.png".to_string()),
|
||||
layout,
|
||||
initial_data: data,
|
||||
ext: Default::default(),
|
||||
};
|
||||
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_folder2::entities::{
|
||||
CreateViewPayloadPB, CreateWorkspacePayloadPB, ViewLayoutPB, ViewPB, WorkspaceIdPB, WorkspacePB,
|
||||
};
|
||||
use flowy_folder2::event_map::FolderEvent::{CreateView, CreateWorkspace, OpenWorkspace};
|
||||
use flowy_user::entities::AuthTypePB;
|
||||
use flowy_user::{
|
||||
entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB},
|
||||
errors::FlowyError,
|
||||
event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp},
|
||||
};
|
||||
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct ViewTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub workspace: WorkspacePB,
|
||||
pub parent_view: ViewPB,
|
||||
pub child_view: ViewPB,
|
||||
}
|
||||
|
||||
impl ViewTest {
|
||||
#[allow(dead_code)]
|
||||
pub async fn new(sdk: &FlowySDKTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
|
||||
let workspace = create_workspace(sdk, "Workspace", "").await;
|
||||
open_workspace(sdk, &workspace.id).await;
|
||||
let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
|
||||
let view = create_view(sdk, &app.id, layout, data).await;
|
||||
Self {
|
||||
sdk: sdk.clone(),
|
||||
workspace,
|
||||
parent_view: app,
|
||||
child_view: view,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_grid_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Grid, data).await
|
||||
}
|
||||
|
||||
pub async fn new_board_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Board, data).await
|
||||
}
|
||||
|
||||
pub async fn new_calendar_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Calendar, data).await
|
||||
}
|
||||
|
||||
pub async fn new_document_view(sdk: &FlowySDKTest) -> Self {
|
||||
Self::new(sdk, ViewLayoutPB::Document, vec![]).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB {
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name: name.to_owned(),
|
||||
desc: desc.to_owned(),
|
||||
};
|
||||
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
.event(CreateWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<WorkspacePB>()
|
||||
}
|
||||
|
||||
async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) {
|
||||
let payload = WorkspaceIdPB {
|
||||
value: Some(workspace_id.to_owned()),
|
||||
};
|
||||
let _ = Folder2EventBuilder::new(sdk.clone())
|
||||
.event(OpenWorkspace)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
|
||||
let create_app_request = CreateViewPayloadPB {
|
||||
belong_to_id: workspace_id.to_owned(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
layout: ViewLayoutPB::Document,
|
||||
initial_data: vec![],
|
||||
ext: Default::default(),
|
||||
};
|
||||
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(create_app_request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
async fn create_view(
|
||||
sdk: &FlowySDKTest,
|
||||
app_id: &str,
|
||||
layout: ViewLayoutPB,
|
||||
data: Vec<u8>,
|
||||
) -> ViewPB {
|
||||
let payload = CreateViewPayloadPB {
|
||||
belong_to_id: app_id.to_string(),
|
||||
name: "View A".to_string(),
|
||||
desc: "".to_string(),
|
||||
thumbnail: Some("http://1.png".to_string()),
|
||||
layout,
|
||||
initial_data: data,
|
||||
ext: Default::default(),
|
||||
};
|
||||
|
||||
Folder2EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
pub fn random_email() -> String {
|
||||
format!("{}@appflowy.io", nanoid!(20))
|
||||
}
|
||||
|
||||
pub fn login_email() -> String {
|
||||
"annie2@appflowy.io".to_string()
|
||||
}
|
||||
|
||||
pub fn login_password() -> String {
|
||||
"HelloWorld!123".to_string()
|
||||
}
|
||||
|
||||
pub struct SignUpContext {
|
||||
pub user_profile: UserProfilePB,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
|
||||
let password = login_password();
|
||||
let payload = SignUpPayloadPB {
|
||||
email: random_email(),
|
||||
name: "app flowy".to_string(),
|
||||
password: password.clone(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignUp).payload(payload);
|
||||
let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
SignUpContext {
|
||||
user_profile,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn async_sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
|
||||
let password = login_password();
|
||||
let email = random_email();
|
||||
let payload = SignUpPayloadPB {
|
||||
email,
|
||||
name: "app flowy".to_string(),
|
||||
password: password.clone(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignUp).payload(payload);
|
||||
let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
|
||||
.await
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
|
||||
SignUpContext {
|
||||
user_profile,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
|
||||
let request = AFPluginRequest::new(InitUser);
|
||||
let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn sign_in(dispatch: Arc<AFPluginDispatcher>) -> UserProfilePB {
|
||||
let payload = SignInPayloadPB {
|
||||
email: login_email(),
|
||||
password: login_password(),
|
||||
name: "rust".to_owned(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignIn).payload(payload);
|
||||
AFPluginDispatcher::sync_send(dispatch, request)
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn logout(dispatch: Arc<AFPluginDispatcher>) {
|
||||
let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut));
|
||||
}
|
@ -1,57 +1,62 @@
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::RwLock;
|
||||
use std::env::temp_dir;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
|
||||
use flowy_user::entities::UserProfilePB;
|
||||
|
||||
use crate::helper::*;
|
||||
use flowy_user::entities::{AuthTypePB, UserProfilePB};
|
||||
|
||||
use crate::user_event::{async_sign_up, init_user_setting, SignUpContext};
|
||||
pub mod event_builder;
|
||||
pub mod helper;
|
||||
|
||||
pub mod prelude {
|
||||
pub use lib_dispatch::prelude::*;
|
||||
|
||||
pub use crate::{event_builder::*, helper::*, *};
|
||||
}
|
||||
pub mod folder_event;
|
||||
pub mod user_event;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlowySDKTest {
|
||||
pub inner: AppFlowyCore,
|
||||
pub struct FlowyCoreTest {
|
||||
auth_type: Arc<RwLock<AuthTypePB>>,
|
||||
inner: AppFlowyCore,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for FlowySDKTest {
|
||||
impl Default for FlowyCoreTest {
|
||||
fn default() -> Self {
|
||||
let temp_dir = temp_dir();
|
||||
let config =
|
||||
AppFlowyCoreConfig::new(temp_dir.to_str().unwrap(), nanoid!(6)).log_filter("info", vec![]);
|
||||
let inner = std::thread::spawn(|| AppFlowyCore::new(config))
|
||||
.join()
|
||||
.unwrap();
|
||||
let auth_type = Arc::new(RwLock::new(AuthTypePB::Local));
|
||||
std::mem::forget(inner.dispatcher());
|
||||
Self { inner, auth_type }
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowyCoreTest {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn sign_up(&self) -> SignUpContext {
|
||||
let auth_type = self.auth_type.read().clone();
|
||||
async_sign_up(self.inner.dispatcher(), auth_type).await
|
||||
}
|
||||
|
||||
pub fn set_auth_type(&self, auth_type: AuthTypePB) {
|
||||
*self.auth_type.write() = auth_type;
|
||||
}
|
||||
|
||||
pub async fn init_user(&self) -> UserProfilePB {
|
||||
let auth_type = self.auth_type.read().clone();
|
||||
let context = async_sign_up(self.inner.dispatcher(), auth_type).await;
|
||||
init_user_setting(self.inner.dispatcher()).await;
|
||||
context.user_profile
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for FlowyCoreTest {
|
||||
type Target = AppFlowyCore;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for FlowySDKTest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowySDKTest {
|
||||
pub fn new() -> Self {
|
||||
let config =
|
||||
AppFlowyCoreConfig::new(temp_dir().to_str().unwrap(), nanoid!(6)).log_filter("info", vec![]);
|
||||
let sdk = std::thread::spawn(|| AppFlowyCore::new(config))
|
||||
.join()
|
||||
.unwrap();
|
||||
std::mem::forget(sdk.dispatcher());
|
||||
Self { inner: sdk }
|
||||
}
|
||||
|
||||
pub async fn sign_up(&self) -> SignUpContext {
|
||||
async_sign_up(self.inner.dispatcher()).await
|
||||
}
|
||||
|
||||
pub async fn init_user(&self) -> UserProfilePB {
|
||||
let context = async_sign_up(self.inner.dispatcher()).await;
|
||||
init_user_setting(self.inner.dispatcher()).await;
|
||||
context.user_profile
|
||||
}
|
||||
}
|
||||
|
103
frontend/rust-lib/flowy-test/src/user_event.rs
Normal file
103
frontend/rust-lib/flowy-test/src/user_event.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB, UserProfilePB};
|
||||
use flowy_user::errors::FlowyError;
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
|
||||
use nanoid::nanoid;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn random_email() -> String {
|
||||
format!("{}@appflowy.io", nanoid!(20))
|
||||
}
|
||||
|
||||
pub fn login_email() -> String {
|
||||
"annie2@appflowy.io".to_string()
|
||||
}
|
||||
|
||||
pub fn login_password() -> String {
|
||||
"HelloWorld!123".to_string()
|
||||
}
|
||||
|
||||
pub struct SignUpContext {
|
||||
pub user_profile: UserProfilePB,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
|
||||
let password = login_password();
|
||||
let payload = SignUpPayloadPB {
|
||||
email: random_email(),
|
||||
name: "app flowy".to_string(),
|
||||
password: password.clone(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignUp).payload(payload);
|
||||
let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
SignUpContext {
|
||||
user_profile,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn async_sign_up(
|
||||
dispatch: Arc<AFPluginDispatcher>,
|
||||
auth_type: AuthTypePB,
|
||||
) -> SignUpContext {
|
||||
let password = login_password();
|
||||
let email = random_email();
|
||||
let payload = SignUpPayloadPB {
|
||||
email,
|
||||
name: "app flowy".to_string(),
|
||||
password: password.clone(),
|
||||
auth_type,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignUp).payload(payload);
|
||||
let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
|
||||
.await
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
|
||||
SignUpContext {
|
||||
user_profile,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
|
||||
let request = AFPluginRequest::new(InitUser);
|
||||
let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn sign_in(dispatch: Arc<AFPluginDispatcher>) -> UserProfilePB {
|
||||
let payload = SignInPayloadPB {
|
||||
email: login_email(),
|
||||
password: login_password(),
|
||||
name: "rust".to_owned(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
}
|
||||
.into_bytes()
|
||||
.unwrap();
|
||||
|
||||
let request = AFPluginRequest::new(SignIn).payload(payload);
|
||||
AFPluginDispatcher::sync_send(dispatch, request)
|
||||
.parse::<UserProfilePB, FlowyError>()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn logout(dispatch: Arc<AFPluginDispatcher>) {
|
||||
let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut));
|
||||
}
|
1
frontend/rust-lib/flowy-test/tests/main.rs
Normal file
1
frontend/rust-lib/flowy-test/tests/main.rs
Normal file
@ -0,0 +1 @@
|
||||
mod user;
|
@ -1,13 +1,15 @@
|
||||
use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
|
||||
use flowy_test::user_event::*;
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB, UserProfilePB};
|
||||
use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
|
||||
use flowy_user::errors::ErrorCode;
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
|
||||
use crate::helper::*;
|
||||
use crate::user::local_test::helper::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_up_with_invalid_email() {
|
||||
for email in invalid_email_test_case() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let request = SignUpPayloadPB {
|
||||
email: email.to_string(),
|
||||
name: valid_name(),
|
||||
@ -16,43 +18,45 @@ async fn sign_up_with_invalid_email() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
UserModuleEventBuilder::new(sdk)
|
||||
EventBuilder::new(sdk)
|
||||
.event(SignUp)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn sign_up_with_invalid_password() {
|
||||
for password in invalid_password_test_case() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
async fn sign_up_with_long_password() {
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let request = SignUpPayloadPB {
|
||||
email: random_email(),
|
||||
name: valid_name(),
|
||||
password,
|
||||
password: "1234".repeat(100).as_str().to_string(),
|
||||
auth_type: AuthTypePB::Local,
|
||||
};
|
||||
|
||||
UserModuleEventBuilder::new(sdk)
|
||||
assert_eq!(
|
||||
EventBuilder::new(sdk)
|
||||
.event(SignUp)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.assert_error();
|
||||
}
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::PasswordTooLong.value()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_in_success() {
|
||||
let test = FlowySDKTest::default();
|
||||
let _ = UserModuleEventBuilder::new(test.clone())
|
||||
.event(SignOut)
|
||||
.sync_send();
|
||||
let test = FlowyCoreTest::new();
|
||||
let _ = EventBuilder::new(test.clone()).event(SignOut).sync_send();
|
||||
let sign_up_context = test.sign_up().await;
|
||||
|
||||
let request = SignInPayloadPB {
|
||||
@ -62,7 +66,7 @@ async fn sign_in_success() {
|
||||
auth_type: AuthTypePB::Local,
|
||||
};
|
||||
|
||||
let response = UserModuleEventBuilder::new(test.clone())
|
||||
let response = EventBuilder::new(test.clone())
|
||||
.event(SignIn)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
@ -74,7 +78,7 @@ async fn sign_in_success() {
|
||||
#[tokio::test]
|
||||
async fn sign_in_with_invalid_email() {
|
||||
for email in invalid_email_test_case() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let request = SignInPayloadPB {
|
||||
email: email.to_string(),
|
||||
password: login_password(),
|
||||
@ -83,12 +87,13 @@ async fn sign_in_with_invalid_email() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
UserModuleEventBuilder::new(sdk)
|
||||
EventBuilder::new(sdk)
|
||||
.event(SignIn)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
);
|
||||
@ -98,7 +103,7 @@ async fn sign_in_with_invalid_email() {
|
||||
#[tokio::test]
|
||||
async fn sign_in_with_invalid_password() {
|
||||
for password in invalid_password_test_case() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
|
||||
let request = SignInPayloadPB {
|
||||
email: random_email(),
|
||||
@ -107,11 +112,12 @@ async fn sign_in_with_invalid_password() {
|
||||
auth_type: AuthTypePB::Local,
|
||||
};
|
||||
|
||||
UserModuleEventBuilder::new(sdk)
|
||||
assert!(EventBuilder::new(sdk)
|
||||
.event(SignIn)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.assert_error();
|
||||
.error()
|
||||
.is_some())
|
||||
}
|
||||
}
|
@ -1,8 +1,3 @@
|
||||
pub use flowy_test::{
|
||||
event_builder::*,
|
||||
prelude::{login_password, random_email},
|
||||
};
|
||||
|
||||
pub(crate) fn invalid_email_test_case() -> Vec<String> {
|
||||
// https://gist.github.com/cjaoude/fd9910626629b53c4d25
|
||||
vec![
|
@ -1,5 +1,5 @@
|
||||
use crate::helper::*;
|
||||
use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
|
||||
use crate::user::local_test::helper::*;
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB};
|
||||
use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
|
||||
use nanoid::nanoid;
|
||||
@ -8,20 +8,20 @@ use nanoid::nanoid;
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_profile_get_failed() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let result = UserModuleEventBuilder::new(sdk)
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let result = EventBuilder::new(sdk)
|
||||
.event(GetUserProfile)
|
||||
.assert_error()
|
||||
.async_send()
|
||||
.await;
|
||||
assert!(result.user_profile().is_none())
|
||||
.await
|
||||
.error();
|
||||
assert!(result.is_some())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_profile_get() {
|
||||
let test = FlowySDKTest::default();
|
||||
let test = FlowyCoreTest::new();
|
||||
let user_profile = test.init_user().await;
|
||||
let user = UserModuleEventBuilder::new(test.clone())
|
||||
let user = EventBuilder::new(test.clone())
|
||||
.event(GetUserProfile)
|
||||
.sync_send()
|
||||
.parse::<UserProfilePB>();
|
||||
@ -30,18 +30,17 @@ async fn user_profile_get() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_name() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let user = sdk.init_user().await;
|
||||
let new_name = "hello_world".to_owned();
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name);
|
||||
let _ = UserModuleEventBuilder::new(sdk.clone())
|
||||
let _ = EventBuilder::new(sdk.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send();
|
||||
|
||||
let user_profile = UserModuleEventBuilder::new(sdk.clone())
|
||||
let user_profile = EventBuilder::new(sdk.clone())
|
||||
.event(GetUserProfile)
|
||||
.assert_error()
|
||||
.sync_send()
|
||||
.parse::<UserProfilePB>();
|
||||
|
||||
@ -50,49 +49,35 @@ async fn user_update_with_name() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_email() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let sdk = FlowyCoreTest::new();
|
||||
let user = sdk.init_user().await;
|
||||
let new_email = format!("{}@gmail.com", nanoid!(6));
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email);
|
||||
let _ = UserModuleEventBuilder::new(sdk.clone())
|
||||
let _ = EventBuilder::new(sdk.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send();
|
||||
let user_profile = UserModuleEventBuilder::new(sdk.clone())
|
||||
let user_profile = EventBuilder::new(sdk.clone())
|
||||
.event(GetUserProfile)
|
||||
.assert_error()
|
||||
.sync_send()
|
||||
.parse::<UserProfilePB>();
|
||||
|
||||
assert_eq!(user_profile.email, new_email,);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_password() {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let user = sdk.init_user().await;
|
||||
let new_password = "H123world!".to_owned();
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).password(&new_password);
|
||||
|
||||
let _ = UserModuleEventBuilder::new(sdk.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send()
|
||||
.assert_success();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_invalid_email() {
|
||||
let test = FlowySDKTest::default();
|
||||
let test = FlowyCoreTest::new();
|
||||
let user = test.init_user().await;
|
||||
for email in invalid_email_test_case() {
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).email(&email);
|
||||
assert_eq!(
|
||||
UserModuleEventBuilder::new(test.clone())
|
||||
EventBuilder::new(test.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send()
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
);
|
||||
@ -101,27 +86,30 @@ async fn user_update_with_invalid_email() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_invalid_password() {
|
||||
let test = FlowySDKTest::default();
|
||||
let test = FlowyCoreTest::new();
|
||||
let user = test.init_user().await;
|
||||
for password in invalid_password_test_case() {
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).password(&password);
|
||||
|
||||
UserModuleEventBuilder::new(test.clone())
|
||||
assert!(EventBuilder::new(test.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send()
|
||||
.assert_error();
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_update_with_invalid_name() {
|
||||
let test = FlowySDKTest::default();
|
||||
let test = FlowyCoreTest::new();
|
||||
let user = test.init_user().await;
|
||||
let request = UpdateUserProfilePayloadPB::new(user.id).name("");
|
||||
UserModuleEventBuilder::new(test.clone())
|
||||
assert!(EventBuilder::new(test.clone())
|
||||
.event(UpdateUserProfile)
|
||||
.payload(request)
|
||||
.sync_send()
|
||||
.assert_error();
|
||||
.error()
|
||||
.is_some())
|
||||
}
|
2
frontend/rust-lib/flowy-test/tests/user/mod.rs
Normal file
2
frontend/rust-lib/flowy-test/tests/user/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod local_test;
|
||||
mod supabase_test;
|
@ -0,0 +1,28 @@
|
||||
use crate::user::supabase_test::helper::get_supabase_config;
|
||||
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{AuthTypePB, ThirdPartyAuthPB, UserProfilePB};
|
||||
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_up_test() {
|
||||
if get_supabase_config().is_some() {
|
||||
let test = FlowyCoreTest::new();
|
||||
let mut map = HashMap::new();
|
||||
map.insert("uuid".to_string(), uuid::Uuid::new_v4().to_string());
|
||||
let payload = ThirdPartyAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let response = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<UserProfilePB>();
|
||||
dbg!(&response);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
use dotenv::dotenv;
|
||||
use flowy_server::supabase::SupabaseConfiguration;
|
||||
|
||||
/// In order to run this test, you need to create a .env file in the root directory of this project
|
||||
/// and add the following environment variables:
|
||||
/// - SUPABASE_URL
|
||||
/// - SUPABASE_ANON_KEY
|
||||
/// - SUPABASE_KEY
|
||||
/// - SUPABASE_JWT_SECRET
|
||||
///
|
||||
/// the .env file should look like this:
|
||||
/// SUPABASE_URL=https://<your-supabase-url>.supabase.co
|
||||
/// SUPABASE_ANON_KEY=<your-supabase-anon-key>
|
||||
/// SUPABASE_KEY=<your-supabase-key>
|
||||
/// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
|
||||
///
|
||||
pub fn get_supabase_config() -> Option<SupabaseConfiguration> {
|
||||
dotenv().ok()?;
|
||||
SupabaseConfiguration::from_env().ok()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
mod auth_test;
|
||||
mod helper;
|
||||
mod workspace_test;
|
@ -0,0 +1,38 @@
|
||||
use crate::user::supabase_test::helper::get_supabase_config;
|
||||
use flowy_folder2::entities::WorkspaceSettingPB;
|
||||
use flowy_folder2::event_map::FolderEvent::ReadCurrentWorkspace;
|
||||
|
||||
use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
|
||||
use flowy_user::entities::{AuthTypePB, ThirdPartyAuthPB, UserProfilePB};
|
||||
|
||||
use flowy_user::event_map::UserEvent::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn initial_workspace_test() {
|
||||
if get_supabase_config().is_some() {
|
||||
let test = FlowyCoreTest::new();
|
||||
let mut map = HashMap::new();
|
||||
map.insert("uuid".to_string(), uuid::Uuid::new_v4().to_string());
|
||||
let payload = ThirdPartyAuthPB {
|
||||
map,
|
||||
auth_type: AuthTypePB::Supabase,
|
||||
};
|
||||
|
||||
let _ = EventBuilder::new(test.clone())
|
||||
.event(ThirdPartyAuth)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<UserProfilePB>();
|
||||
|
||||
let workspace_settings = EventBuilder::new(test.clone())
|
||||
.event(ReadCurrentWorkspace)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<WorkspaceSettingPB>();
|
||||
|
||||
assert!(workspace_settings.latest_view.is_some());
|
||||
dbg!(&workspace_settings);
|
||||
}
|
||||
}
|
@ -34,7 +34,6 @@ unicode-segmentation = "1.10"
|
||||
fancy-regex = "0.11.0"
|
||||
|
||||
[dev-dependencies]
|
||||
flowy-test = { path = "../flowy-test" }
|
||||
nanoid = "0.4.0"
|
||||
fake = "2.0.0"
|
||||
rand = "0.8.4"
|
||||
|
@ -16,6 +16,7 @@ pub async fn sign_in(
|
||||
) -> DataResult<UserProfilePB, FlowyError> {
|
||||
let params: SignInParams = data.into_inner().try_into()?;
|
||||
let auth_type = params.auth_type.clone();
|
||||
|
||||
let user_profile: UserProfilePB = session
|
||||
.sign_in(&auth_type, BoxAny::new(params))
|
||||
.await?
|
||||
|
@ -40,6 +40,7 @@ pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
/// The user cloud service provider.
|
||||
/// The provider can be supabase, firebase, aws, or any other cloud service.
|
||||
pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
||||
fn set_auth_type(&self, auth_type: AuthType);
|
||||
fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError>;
|
||||
}
|
||||
|
||||
@ -47,6 +48,10 @@ impl<T> UserCloudServiceProvider for Arc<T>
|
||||
where
|
||||
T: UserCloudServiceProvider,
|
||||
{
|
||||
fn set_auth_type(&self, auth_type: AuthType) {
|
||||
(**self).set_auth_type(auth_type)
|
||||
}
|
||||
|
||||
fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
|
||||
(**self).get_auth_service(auth_type)
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ impl UserSession {
|
||||
auth_type: &AuthType,
|
||||
params: BoxAny,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
self.cloud_services.set_auth_type(auth_type.clone());
|
||||
let resp = self
|
||||
.cloud_services
|
||||
.get_auth_service(auth_type)?
|
||||
@ -134,6 +135,7 @@ impl UserSession {
|
||||
auth_type: &AuthType,
|
||||
params: BoxAny,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
self.cloud_services.set_auth_type(auth_type.clone());
|
||||
let resp = self
|
||||
.cloud_services
|
||||
.get_auth_service(auth_type)?
|
||||
@ -163,6 +165,7 @@ impl UserSession {
|
||||
.execute(&*(self.db_connection()?))?;
|
||||
self.database.close_user_db(session.user_id)?;
|
||||
self.set_session(None)?;
|
||||
|
||||
let server = self.cloud_services.get_auth_service(auth_type)?;
|
||||
let token = session.token;
|
||||
let _ = tokio::spawn(async move {
|
||||
|
Loading…
Reference in New Issue
Block a user