Merge branch 'develop'

This commit is contained in:
appflowy 2021-11-20 09:35:16 +08:00
commit 55c05364dd
1872 changed files with 15446 additions and 14955 deletions

View File

@ -1,26 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flowy_infra_ui_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const ExampleApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text && widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@ -1,8 +0,0 @@
// This file is provided as a convenience for running integration tests via the
// flutter drive command.
//
// flutter drive --driver integration_test/driver.dart --target integration_test/app_test.dart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();

View File

@ -1,29 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../lib/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate((Widget widget) {
if (widget is Text && widget.data != null) {
return widget.data.startsWith('Running on:');
}
return false;
}),
findsOneWidget,
);
});
}

View File

@ -1,3 +0,0 @@
// Auto-generated, do not edit
export './revision.pb.dart';
export './doc.pb.dart';

View File

@ -1,76 +0,0 @@
///
// Generated code. Do not modify.
// source: errors.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
import 'errors.pbenum.dart';
export 'errors.pbenum.dart';
class DocError extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocError', createEmptyInstance: create)
..e<ErrorCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: ErrorCode.DocIdInvalid, valueOf: ErrorCode.valueOf, enumValues: ErrorCode.values)
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
..hasRequiredFields = false
;
DocError._() : super();
factory DocError({
ErrorCode? code,
$core.String? msg,
}) {
final _result = create();
if (code != null) {
_result.code = code;
}
if (msg != null) {
_result.msg = msg;
}
return _result;
}
factory DocError.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DocError.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DocError clone() => DocError()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DocError copyWith(void Function(DocError) updates) => super.copyWith((message) => updates(message as DocError)) as DocError; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DocError create() => DocError._();
DocError createEmptyInstance() => create();
static $pb.PbList<DocError> createRepeated() => $pb.PbList<DocError>();
@$core.pragma('dart2js:noInline')
static DocError getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocError>(create);
static DocError? _defaultInstance;
@$pb.TagNumber(1)
ErrorCode get code => $_getN(0);
@$pb.TagNumber(1)
set code(ErrorCode v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasCode() => $_has(0);
@$pb.TagNumber(1)
void clearCode() => clearField(1);
@$pb.TagNumber(2)
$core.String get msg => $_getSZ(1);
@$pb.TagNumber(2)
set msg($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasMsg() => $_has(1);
@$pb.TagNumber(2)
void clearMsg() => clearField(2);
}

View File

@ -1,40 +0,0 @@
///
// Generated code. Do not modify.
// source: errors.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: UNDEFINED_SHOWN_NAME
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode DocIdInvalid = ErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocIdInvalid');
static const ErrorCode DocNotfound = ErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocNotfound');
static const ErrorCode WsConnectError = ErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WsConnectError');
static const ErrorCode UndoFail = ErrorCode._(200, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UndoFail');
static const ErrorCode RedoFail = ErrorCode._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RedoFail');
static const ErrorCode OutOfBound = ErrorCode._(202, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OutOfBound');
static const ErrorCode DuplicateRevision = ErrorCode._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateRevision');
static const ErrorCode UserUnauthorized = ErrorCode._(999, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
static const $core.List<ErrorCode> values = <ErrorCode> [
DocIdInvalid,
DocNotfound,
WsConnectError,
UndoFail,
RedoFail,
OutOfBound,
DuplicateRevision,
UserUnauthorized,
InternalError,
];
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
static ErrorCode? valueOf($core.int value) => _byValue[value];
const ErrorCode._($core.int v, $core.String n) : super(v, n);
}

View File

@ -1,39 +0,0 @@
///
// Generated code. Do not modify.
// source: errors.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
import 'dart:core' as $core;
import 'dart:convert' as $convert;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use errorCodeDescriptor instead')
const ErrorCode$json = const {
'1': 'ErrorCode',
'2': const [
const {'1': 'DocIdInvalid', '2': 0},
const {'1': 'DocNotfound', '2': 1},
const {'1': 'WsConnectError', '2': 10},
const {'1': 'UndoFail', '2': 200},
const {'1': 'RedoFail', '2': 201},
const {'1': 'OutOfBound', '2': 202},
const {'1': 'DuplicateRevision', '2': 400},
const {'1': 'UserUnauthorized', '2': 999},
const {'1': 'InternalError', '2': 1000},
],
};
/// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSEAoMRG9jSWRJbnZhbGlkEAASDwoLRG9jTm90Zm91bmQQARISCg5Xc0Nvbm5lY3RFcnJvchAKEg0KCFVuZG9GYWlsEMgBEg0KCFJlZG9GYWlsEMkBEg8KCk91dE9mQm91bmQQygESFgoRRHVwbGljYXRlUmV2aXNpb24QkAMSFQoQVXNlclVuYXV0aG9yaXplZBDnBxISCg1JbnRlcm5hbEVycm9yEOgH');
@$core.Deprecated('Use docErrorDescriptor instead')
const DocError$json = const {
'1': 'DocError',
'2': const [
const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.ErrorCode', '10': 'code'},
const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
],
};
/// Descriptor for `DocError`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List docErrorDescriptor = $convert.base64Decode('CghEb2NFcnJvchIeCgRjb2RlGAEgASgOMgouRXJyb3JDb2RlUgRjb2RlEhAKA21zZxgCIAEoCVIDbXNn');

View File

@ -1,4 +0,0 @@
// Auto-generated, do not edit
export './ws.pb.dart';
export './observable.pb.dart';
export './errors.pb.dart';

View File

@ -60,13 +60,12 @@ pin-project = "1.0.0"
byteorder = {version = "1.3.4"}
async-stream = "0.3.2"
flowy-user-infra = { path = "../rust-lib/flowy-user-infra" }
flowy-workspace-infra = { path = "../rust-lib/flowy-workspace-infra" }
flowy-document-infra = { path = "../rust-lib/flowy-document-infra" }
flowy-document = { path = "../rust-lib/flowy-document" }
flowy-ws = { path = "../rust-lib/flowy-ws" }
flowy-ot = { path = "../rust-lib/flowy-ot" }
flowy-net = { path = "../rust-lib/flowy-net", features = ["http_server"] }
flowy-user-infra = { path = "../frontend/rust-lib/flowy-user-infra" }
flowy-workspace-infra = { path = "../frontend/rust-lib/flowy-workspace-infra" }
flowy-document-infra = { path = "../frontend/rust-lib/flowy-document-infra" }
lib-ws = { path = "../frontend/rust-lib/lib-ws" }
lib-ot = { path = "../frontend/rust-lib/lib-ot" }
backend-service = { path = "../frontend/rust-lib/backend-service", features = ["http_server"] }
ormx = { version = "0.7", features = ["postgres"]}
[dependencies.sqlx]
@ -99,15 +98,14 @@ ignore_auth = []
parking_lot = "0.11"
once_cell = "1.7.2"
linkify = "0.5.0"
backend = { path = ".", features = ["flowy_test"]}
flowy-backend-api = { path = "../rust-lib/flowy-backend-api"}
flowy-sdk = { path = "../rust-lib/flowy-sdk", features = ["http_server"] }
flowy-user = { path = "../rust-lib/flowy-user", features = ["http_server"] }
flowy-document = { path = "../rust-lib/flowy-document", features = ["flowy_test", "http_server"] }
futures-util = "0.3.15"
flowy-ws = { path = "../rust-lib/flowy-ws" }
flowy-test = { path = "../rust-lib/flowy-test" }
flowy-infra = { path = "../rust-lib/flowy-infra" }
flowy-ot = { path = "../rust-lib/flowy-ot" }
flowy-sqlite = { path = "../rust-lib/flowy-sqlite" }
futures-util = "0.3.15"
backend = { path = ".", features = ["flowy_test"]}
flowy-sdk = { path = "../frontend/rust-lib/flowy-sdk", features = ["http_server"] }
flowy-user = { path = "../frontend/rust-lib/flowy-user", features = ["http_server"] }
flowy-document = { path = "../frontend/rust-lib/flowy-document", features = ["flowy_test", "http_server"] }
lib-ws = { path = "../frontend/rust-lib/lib-ws" }
flowy-test = { path = "../frontend/rust-lib/flowy-test" }
lib-infra = { path = "../frontend/rust-lib/lib-infra" }
lib-ot = { path = "../frontend/rust-lib/lib-ot" }
lib-sqlite = { path = "../frontend/rust-lib/lib-sqlite" }

View File

@ -4,7 +4,7 @@ use crate::service::{
};
use actix::Addr;
use actix_web::web::Data;
use flowy_ws::WsModule;
use lib_ws::WsModule;
use sqlx::PgPool;
use std::sync::Arc;

View File

@ -1,7 +1,7 @@
use crate::config::env::{domain, jwt_secret};
use backend_service::errors::ServerError;
use chrono::{Duration, Local};
use derive_more::{From, Into};
use flowy_net::errors::ServerError;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
@ -76,7 +76,7 @@ impl Token {
use crate::service::user::EXPIRED_DURATION_DAYS;
use actix_web::{dev::Payload, FromRequest, HttpRequest};
use flowy_net::config::HEADER_TOKEN;
use backend_service::config::HEADER_TOKEN;
use futures::future::{ready, Ready};
impl FromRequest for Token {

View File

@ -9,7 +9,7 @@ use actix_web::{
use crate::config::IGNORE_ROUTES;
use actix_web::{body::AnyBody, dev::MessageBody};
use flowy_net::{config::HEADER_TOKEN, errors::ServerError};
use backend_service::{config::HEADER_TOKEN, errors::ServerError};
use futures::future::{ok, LocalBoxFuture, Ready};
use std::{
convert::TryInto,

View File

@ -5,8 +5,8 @@ use crate::{
};
use crate::service::trash::read_trash_ids;
use backend_service::errors::{invalid_params, ServerError};
use chrono::Utc;
use flowy_net::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{
parser::{
app::{AppDesc, AppName},

View File

@ -2,7 +2,7 @@ use actix_web::{
web::{Data, Payload},
HttpResponse,
};
use flowy_net::errors::{invalid_params, ServerError};
use backend_service::errors::{invalid_params, ServerError};
use flowy_workspace_infra::protobuf::{AppIdentifier, CreateAppParams, UpdateAppParams};
use protobuf::Message;
use sqlx::PgPool;
@ -16,7 +16,7 @@ use crate::service::{
util::parse_from_payload,
};
use anyhow::Context;
use flowy_net::response::FlowyResponse;
use backend_service::response::FlowyResponse;
use flowy_workspace_infra::parser::app::{AppDesc, AppName};
pub async fn create_handler(

View File

@ -2,8 +2,8 @@ use crate::{
entities::workspace::{AppTable, APP_TABLE},
sqlx_ext::SqlBuilder,
};
use backend_service::errors::{invalid_params, ServerError};
use chrono::{DateTime, NaiveDateTime, Utc};
use flowy_net::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{
parser::app::AppId,
protobuf::{App, ColorStyle},

View File

@ -3,8 +3,8 @@ use crate::{
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use anyhow::Context;
use backend_service::errors::ServerError;
use flowy_document_infra::protobuf::{CreateDocParams, Doc, DocIdentifier, UpdateDocParams};
use flowy_net::errors::ServerError;
use sqlx::{postgres::PgArguments, PgPool, Postgres};
use uuid::Uuid;

View File

@ -7,9 +7,9 @@ use crate::service::{
ws::{WsBizHandler, WsClientData},
};
use actix_web::web::Data;
use backend_service::errors::{internal_error, ServerError};
use dashmap::DashMap;
use flowy_document_infra::protobuf::DocIdentifier;
use flowy_net::errors::{internal_error, ServerError};
use sqlx::PgPool;
use std::sync::Arc;
use tokio::{

View File

@ -4,8 +4,8 @@ use crate::service::{
};
use actix_web::web::Data;
use async_stream::stream;
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
use flowy_document_infra::protobuf::{Doc, Revision};
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
use futures::stream::StreamExt;
use sqlx::PgPool;
use std::sync::{atomic::Ordering::SeqCst, Arc};

View File

@ -1,27 +1,22 @@
use crate::service::{
doc::{edit::edit_actor::EditUser, update_doc},
util::md5,
ws::WsMessageAdaptor,
ws::{entities::Socket, WsMessageAdaptor},
};
use actix_web::web::Data;
use crate::service::ws::entities::Socket;
use bytes::Bytes;
use backend_service::errors::{internal_error, ServerError};
use dashmap::DashMap;
use flowy_document::{
use flowy_document_infra::{
core::Document,
entities::ws::{WsDataType, WsDocumentData},
services::doc::Document,
protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams},
};
use flowy_document_infra::protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams};
use flowy_net::errors::{internal_error, ServerError};
use flowy_ot::core::{Delta, OperationTransformable};
use flowy_ws::WsMessage;
use lib_ot::core::{Delta, OperationTransformable};
use parking_lot::RwLock;
use protobuf::Message;
use sqlx::PgPool;
use std::{
cmp::Ordering,
convert::TryInto,
sync::{
atomic::{AtomicI64, Ordering::SeqCst},
Arc,
@ -213,7 +208,7 @@ fn mk_push_message(doc_id: &str, revision: Revision) -> WsMessageAdaptor {
ty: WsDataType::PushRev,
data: bytes,
};
mk_ws_message(data)
data.into()
}
#[tracing::instrument(level = "debug", skip(socket, doc_id), err)]
@ -236,7 +231,7 @@ fn mk_pull_message(doc_id: &str, from_rev_id: i64, to_rev_id: i64) -> WsMessageA
ty: WsDataType::PullRev,
data: bytes,
};
mk_ws_message(data)
data.into()
}
#[tracing::instrument(level = "debug", skip(socket, revision), err)]
@ -248,7 +243,6 @@ fn send_acked_msg(socket: &Socket, revision: &Revision) -> Result<(), ServerErro
fn mk_acked_message(revision: &Revision) -> WsMessageAdaptor {
// let mut wtr = vec![];
// let _ = wtr.write_i64::<BigEndian>(revision.rev_id);
let mut rev_id = RevId::new();
rev_id.set_value(revision.rev_id);
let data = rev_id.write_to_bytes().unwrap();
@ -259,13 +253,7 @@ fn mk_acked_message(revision: &Revision) -> WsMessageAdaptor {
data,
};
mk_ws_message(data)
}
fn mk_ws_message<T: Into<WsMessage>>(data: T) -> WsMessageAdaptor {
let msg: WsMessage = data.into();
let bytes: Bytes = msg.try_into().unwrap();
WsMessageAdaptor(bytes)
data.into()
}
#[inline]

View File

@ -3,8 +3,8 @@ use crate::service::{
ws::{entities::Socket, WsUser},
};
use actix_web::web::Data;
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
use flowy_document_infra::protobuf::{Doc, Revision};
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
use sqlx::PgPool;
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot};

View File

@ -7,8 +7,8 @@ use actix_web::{
HttpResponse,
};
use anyhow::Context;
use backend_service::{errors::ServerError, response::FlowyResponse};
use flowy_document_infra::protobuf::{CreateDocParams, DocIdentifier, UpdateDocParams};
use flowy_net::{errors::ServerError, response::FlowyResponse};
use sqlx::PgPool;
pub async fn create_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {

View File

@ -6,9 +6,8 @@ use crate::service::{
use actix_rt::task::spawn_blocking;
use actix_web::web::Data;
use async_stream::stream;
use flowy_document::protobuf::{WsDataType, WsDocumentData};
use flowy_document_infra::protobuf::{NewDocUser, Revision};
use flowy_net::errors::{internal_error, Result as DocResult, ServerError};
use backend_service::errors::{internal_error, Result as DocResult, ServerError};
use flowy_document_infra::protobuf::{NewDocUser, Revision, WsDataType, WsDocumentData};
use futures::stream::StreamExt;
use sqlx::PgPool;
use std::sync::Arc;

View File

@ -9,7 +9,7 @@ use actix_web::{
HttpResponse,
};
use anyhow::Context;
use flowy_net::{
use backend_service::{
errors::{invalid_params, ServerError},
response::FlowyResponse,
};

View File

@ -8,7 +8,7 @@ use crate::{
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use ::protobuf::ProtobufEnum;
use flowy_net::errors::ServerError;
use backend_service::errors::ServerError;
use flowy_workspace_infra::protobuf::{RepeatedTrash, Trash, TrashType};
use sqlx::{postgres::PgArguments, Postgres, Row};
use uuid::Uuid;

View File

@ -4,11 +4,11 @@ use crate::{
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use anyhow::Context;
use chrono::Utc;
use flowy_net::{
use backend_service::{
errors::{invalid_params, ErrorCode, ServerError},
response::FlowyResponse,
};
use chrono::Utc;
use flowy_user_infra::{
parser::{UserEmail, UserName, UserPassword},
protobuf::{SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile},

View File

@ -1,8 +1,8 @@
use crate::entities::token::{Claim, Token};
use actix_web::http::HeaderValue;
use backend_service::errors::ServerError;
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use flowy_net::errors::ServerError;
use lazy_static::lazy_static;
lazy_static! {

View File

@ -6,7 +6,7 @@ use actix_web::{
};
use sqlx::PgPool;
use flowy_net::{errors::ServerError, response::FlowyResponse};
use backend_service::{errors::ServerError, response::FlowyResponse};
use flowy_user_infra::protobuf::{SignInParams, SignUpParams, UpdateUserParams};
use crate::{

View File

@ -7,9 +7,9 @@ use crate::{
};
use crate::service::view::{create_view_with_args, sql_builder::NewViewSqlBuilder};
use backend_service::errors::ServerError;
use chrono::Utc;
use flowy_document_infra::user_default::doc_initial_string;
use flowy_net::errors::ServerError;
use flowy_workspace_infra::protobuf::Workspace;
use std::convert::TryInto;

View File

@ -1,5 +1,5 @@
use backend_service::errors::{ErrorCode, ServerError};
use bcrypt::{hash, verify, DEFAULT_COST};
use flowy_net::errors::{ErrorCode, ServerError};
#[allow(dead_code)]
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }

View File

@ -1,6 +1,6 @@
use crate::config::MAX_PAYLOAD_SIZE;
use actix_web::web;
use flowy_net::errors::{ErrorCode, ServerError};
use backend_service::errors::{ErrorCode, ServerError};
use futures::StreamExt;
use protobuf::{Message, ProtobufResult};
@ -10,9 +10,7 @@ pub async fn parse_from_payload<T: Message>(payload: web::Payload) -> Result<T,
}
#[allow(dead_code)]
pub async fn parse_from_dev_payload<T: Message>(
payload: &mut actix_web::dev::Payload,
) -> Result<T, ServerError> {
pub async fn parse_from_dev_payload<T: Message>(payload: &mut actix_web::dev::Payload) -> Result<T, ServerError> {
let bytes = poll_payload(payload).await?;
parse_from_bytes(&bytes)
}
@ -31,9 +29,7 @@ pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, ServerError> {
}
}
pub async fn poll_payload(
payload: &mut actix_web::dev::Payload,
) -> Result<web::BytesMut, ServerError> {
pub async fn poll_payload(payload: &mut actix_web::dev::Payload) -> Result<web::BytesMut, ServerError> {
let mut body = web::BytesMut::new();
while let Some(chunk) = payload.next().await {
let chunk = chunk.map_err(|err| ServerError::internal().context(err))?;

View File

@ -9,7 +9,7 @@ use actix_web::{
HttpResponse,
};
use anyhow::Context;
use flowy_net::{
use backend_service::{
errors::{invalid_params, ServerError},
response::FlowyResponse,
};

View File

@ -2,8 +2,8 @@ use crate::{
entities::workspace::{ViewTable, VIEW_TABLE},
sqlx_ext::SqlBuilder,
};
use backend_service::errors::{invalid_params, ServerError};
use chrono::{DateTime, NaiveDateTime, Utc};
use flowy_net::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{
parser::view::ViewId,
protobuf::{View, ViewType},

View File

@ -8,9 +8,9 @@ use crate::{
},
sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use backend_service::errors::{invalid_params, ServerError};
use chrono::Utc;
use flowy_document_infra::protobuf::CreateDocParams;
use flowy_net::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{
parser::{
app::AppId,

View File

@ -14,7 +14,7 @@ use actix_web::{
HttpResponse,
};
use anyhow::Context;
use flowy_net::{
use backend_service::{
errors::{invalid_params, ServerError},
response::FlowyResponse,
};

View File

@ -2,8 +2,8 @@ use crate::{
entities::workspace::{WorkspaceTable, WORKSPACE_TABLE},
sqlx_ext::SqlBuilder,
};
use backend_service::errors::{invalid_params, ServerError};
use chrono::{DateTime, NaiveDateTime, Utc};
use flowy_net::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{parser::workspace::WorkspaceId, protobuf::Workspace};
use sqlx::postgres::PgArguments;
use uuid::Uuid;

View File

@ -5,7 +5,7 @@ use crate::{
sqlx_ext::*,
};
use anyhow::Context;
use flowy_net::errors::{invalid_params, ServerError};
use backend_service::errors::{invalid_params, ServerError};
use flowy_workspace_infra::{
parser::workspace::WorkspaceId,
protobuf::{RepeatedApp, RepeatedWorkspace, Workspace},

View File

@ -1,6 +1,6 @@
use crate::service::ws::WsClientData;
use flowy_ws::WsModule;
use lib_ws::WsModule;
use std::{collections::HashMap, sync::Arc};
pub trait WsBizHandler: Send + Sync {
@ -13,15 +13,9 @@ pub struct WsBizHandlers {
}
impl WsBizHandlers {
pub fn new() -> Self {
Self {
inner: HashMap::new(),
}
}
pub fn new() -> Self { Self { inner: HashMap::new() } }
pub fn register(&mut self, source: WsModule, handler: BizHandler) {
self.inner.insert(source, handler);
}
pub fn register(&mut self, source: WsModule, handler: BizHandler) { self.inner.insert(source, handler); }
pub fn get(&self, source: &WsModule) -> Option<BizHandler> {
match self.inner.get(source) {

View File

@ -1,6 +1,6 @@
use crate::service::ws::WsMessageAdaptor;
use actix::{Message, Recipient};
use flowy_net::errors::ServerError;
use backend_service::errors::ServerError;
use serde::{Deserialize, Serialize};
use std::fmt::Formatter;

View File

@ -1,5 +1,8 @@
use actix::Message;
use bytes::Bytes;
use flowy_document_infra::entities::ws::WsDocumentData;
use lib_ws::{WsMessage, WsModule};
use std::convert::TryInto;
#[derive(Debug, Message, Clone)]
#[rtype(result = "()")]
@ -10,3 +13,16 @@ impl std::ops::Deref for WsMessageAdaptor {
fn deref(&self) -> &Self::Target { &self.0 }
}
impl std::convert::From<WsDocumentData> for WsMessageAdaptor {
fn from(data: WsDocumentData) -> Self {
let bytes: Bytes = data.try_into().unwrap();
let msg = WsMessage {
module: WsModule::Doc,
data: bytes.to_vec(),
};
let bytes: Bytes = msg.try_into().unwrap();
WsMessageAdaptor(bytes)
}
}

View File

@ -14,7 +14,7 @@ use actix::*;
use actix_web::web::Data;
use actix_web_actors::{ws, ws::Message::Text};
use bytes::Bytes;
use flowy_ws::WsMessage;
use lib_ws::WsMessage;
use std::{convert::TryFrom, sync::Arc, time::Instant};
#[derive(Debug)]

View File

@ -3,8 +3,8 @@ use crate::service::ws::{
WsMessageAdaptor,
};
use actix::{Actor, Context, Handler};
use backend_service::errors::ServerError;
use dashmap::DashMap;
use flowy_net::errors::ServerError;
pub struct WsServer {
sessions: DashMap<SessionId, Session>,

View File

@ -1,4 +1,4 @@
use flowy_net::errors::ServerError;
use backend_service::errors::ServerError;
use sql_builder::SqlBuilder as InnerBuilder;
use sqlx::{postgres::PgArguments, Arguments, Encode, Postgres, Type};

View File

@ -1,4 +1,4 @@
use flowy_net::errors::{ErrorCode, ServerError};
use backend_service::errors::{ErrorCode, ServerError};
use sqlx::{Error, Postgres, Transaction};
pub type DBTransaction<'a> = Transaction<'a, Postgres>;

View File

@ -1,5 +1,5 @@
use crate::helper::{spawn_user_server, TestUserServer};
use flowy_net::errors::ErrorCode;
use crate::util::helper::{spawn_user_server, TestUserServer};
use backend_service::errors::ErrorCode;
use flowy_user_infra::entities::{SignInParams, SignUpParams, SignUpResponse, UpdateUserParams};
#[actix_rt::test]

View File

@ -1,4 +1,4 @@
use crate::helper::ViewTest;
use crate::util::helper::ViewTest;
use flowy_document_infra::entities::doc::DocIdentifier;
use flowy_workspace_infra::entities::view::ViewIdentifiers;

View File

@ -1,4 +1,4 @@
use crate::helper::*;
use crate::util::helper::*;
use flowy_workspace_infra::entities::{
app::{AppIdentifier, UpdateAppParams},
trash::{TrashIdentifier, TrashIdentifiers, TrashType},

View File

@ -1,6 +1,6 @@
use crate::document::helper::{DocScript, DocumentTest};
use flowy_document::services::doc::{Document, FlowyDoc};
use flowy_ot::core::{Attribute, Interval};
use flowy_document_infra::core::{Document, FlowyDoc};
use lib_ot::core::{Attribute, Interval};
#[rustfmt::skip]
// ┌─────────┐ ┌─────────┐
@ -20,10 +20,10 @@ use flowy_ot::core::{Attribute, Interval};
async fn delta_sync_while_editing() {
let test = DocumentTest::new().await;
test.run_scripts(vec![
DocScript::ConnectWs,
DocScript::OpenDoc,
DocScript::InsertText(0, "abc"),
DocScript::InsertText(3, "123"),
DocScript::ClientConnectWs,
DocScript::ClientOpenDoc,
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientInsertText(3, "123"),
DocScript::AssertClient(r#"[{"insert":"abc123\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"abc123\n"}]"#, 2),
])
@ -34,12 +34,12 @@ async fn delta_sync_while_editing() {
async fn delta_sync_multi_revs() {
let test = DocumentTest::new().await;
test.run_scripts(vec![
DocScript::ConnectWs,
DocScript::OpenDoc,
DocScript::InsertText(0, "abc"),
DocScript::InsertText(3, "123"),
DocScript::InsertText(6, "efg"),
DocScript::InsertText(9, "456"),
DocScript::ClientConnectWs,
DocScript::ClientOpenDoc,
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientInsertText(3, "123"),
DocScript::ClientInsertText(6, "efg"),
DocScript::ClientInsertText(9, "456"),
])
.await;
}
@ -48,14 +48,14 @@ async fn delta_sync_multi_revs() {
async fn delta_sync_while_editing_with_attribute() {
let test = DocumentTest::new().await;
test.run_scripts(vec![
DocScript::ConnectWs,
DocScript::OpenDoc,
DocScript::InsertText(0, "abc"),
DocScript::FormatText(Interval::new(0, 3), Attribute::Bold(true)),
DocScript::ClientConnectWs,
DocScript::ClientOpenDoc,
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientFormatText(Interval::new(0, 3), Attribute::Bold(true)),
DocScript::AssertClient(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"\n"}]"#, 2),
DocScript::InsertText(3, "efg"),
DocScript::FormatText(Interval::new(3, 5), Attribute::Italic(true)),
DocScript::ClientInsertText(3, "efg"),
DocScript::ClientFormatText(Interval::new(3, 5), Attribute::Italic(true)),
DocScript::AssertClient(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"ef","attributes":{"bold":true,"italic":true}},{"insert":"g","attributes":{"bold":true}},{"insert":"\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"abc","attributes":{"bold":true}},{"insert":"ef","attributes":{"bold":true,"italic":true}},{"insert":"g","attributes":{"bold":true}},{"insert":"\n"}]"#, 4),
])
@ -84,8 +84,8 @@ async fn delta_sync_with_http_request() {
let json = document.to_json();
test.run_scripts(vec![
DocScript::SetServerDocument(json, 3),
DocScript::OpenDoc,
DocScript::ServerSaveDocument(json, 3),
DocScript::ClientOpenDoc,
DocScript::AssertClient(r#"[{"insert":"123456\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"123456\n"}]"#, 3),
])
@ -100,9 +100,9 @@ async fn delta_sync_with_server_push_delta() {
let json = document.to_json();
test.run_scripts(vec![
DocScript::OpenDoc,
DocScript::SetServerDocument(json, 3),
DocScript::ConnectWs,
DocScript::ClientOpenDoc,
DocScript::ServerSaveDocument(json, 3),
DocScript::ClientConnectWs,
DocScript::AssertClient(r#"[{"insert":"\n123\n"}]"#),
])
.await;
@ -147,10 +147,10 @@ async fn delta_sync_while_local_rev_less_than_server_rev() {
let json = document.to_json();
test.run_scripts(vec![
DocScript::OpenDoc,
DocScript::SetServerDocument(json, 3),
DocScript::InsertText(0, "abc"),
DocScript::ConnectWs,
DocScript::ClientOpenDoc,
DocScript::ServerSaveDocument(json, 3),
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientConnectWs,
DocScript::AssertClient(r#"[{"insert":"abc\n123\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"abc\n123\n"}]"#, 4),
])
@ -190,14 +190,14 @@ async fn delta_sync_while_local_rev_greater_than_server_rev() {
let json = document.to_json();
test.run_scripts(vec![
DocScript::SetServerDocument(json, 1),
DocScript::OpenDoc,
DocScript::ServerSaveDocument(json, 1),
DocScript::ClientOpenDoc,
DocScript::AssertClient(r#"[{"insert":"123\n"}]"#),
DocScript::InsertText(3, "abc"),
DocScript::InsertText(6, "efg"),
DocScript::ConnectWs,
DocScript::ClientInsertText(3, "abc"),
DocScript::ClientInsertText(6, "efg"),
DocScript::ClientConnectWs,
DocScript::AssertClient(r#"[{"insert":"123abcefg\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"123abcefg\n"}]"#, 3),
// DocScript::AssertServer(r#"[{"insert":"123abcefg\n"}]"#, 3),
])
.await;
}

View File

@ -1,7 +1,7 @@
use actix_web::web::Data;
use backend::service::doc::{crud::update_doc, doc::DocManager};
use backend_service::config::ServerConfig;
use flowy_document::services::doc::ClientEditDoc as ClientEditDocContext;
use flowy_net::config::ServerConfig;
use flowy_test::{workspace::ViewTest, FlowyTest};
use flowy_user::services::user::UserSession;
use futures_util::{stream, stream::StreamExt};
@ -9,9 +9,9 @@ use sqlx::PgPool;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
// use crate::helper::*;
use crate::helper::{spawn_server, TestServer};
use crate::util::helper::{spawn_server, TestServer};
use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams};
use flowy_ot::core::{Attribute, Delta, Interval};
use lib_ot::core::{Attribute, Delta, Interval};
use parking_lot::RwLock;
pub struct DocumentTest {
@ -20,13 +20,13 @@ pub struct DocumentTest {
}
#[derive(Clone)]
pub enum DocScript {
ConnectWs,
InsertText(usize, &'static str),
FormatText(Interval, Attribute),
ClientConnectWs,
ClientInsertText(usize, &'static str),
ClientFormatText(Interval, Attribute),
ClientOpenDoc,
AssertClient(&'static str),
AssertServer(&'static str, i64),
SetServerDocument(String, i64), // delta_json, rev_id
OpenDoc,
ServerSaveDocument(String, i64), // delta_json, rev_id
}
impl DocumentTest {
@ -95,19 +95,19 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
let fut = async move {
let doc_id = context.read().doc_id.clone();
match script {
DocScript::ConnectWs => {
DocScript::ClientConnectWs => {
// sleep(Duration::from_millis(300)).await;
let user_session = context.read().client_user_session.clone();
let token = user_session.token().unwrap();
let _ = user_session.start_ws_connection(&token).await.unwrap();
},
DocScript::OpenDoc => {
DocScript::ClientOpenDoc => {
context.write().open_doc().await;
},
DocScript::InsertText(index, s) => {
DocScript::ClientInsertText(index, s) => {
context.read().client_edit_context().insert(index, s).await.unwrap();
},
DocScript::FormatText(interval, attribute) => {
DocScript::ClientFormatText(interval, attribute) => {
context
.read()
.client_edit_context()
@ -129,7 +129,7 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
let json = edit_doc.document_json().await.unwrap();
assert_eq(s, &json);
},
DocScript::SetServerDocument(json, rev_id) => {
DocScript::ServerSaveDocument(json, rev_id) => {
let pg_pool = context.read().server_pg_pool.clone();
save_doc(&doc_id, json, rev_id, pg_pool).await;
},

View File

@ -1,357 +0,0 @@
use backend::{
application::{get_connection_pool, init_app_context, Application},
config::{get_configuration, DatabaseSettings},
context::AppContext,
};
use flowy_backend_api::{user_request::*, workspace_request::*};
use flowy_document::services::server::read_doc_request;
use flowy_document_infra::entities::doc::{Doc, DocIdentifier};
use flowy_net::errors::ServerError;
use flowy_user_infra::entities::*;
use flowy_workspace_infra::entities::prelude::*;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use uuid::Uuid;
pub struct TestUserServer {
pub host: String,
pub port: u16,
pub pg_pool: PgPool,
pub user_token: Option<String>,
pub user_id: Option<String>,
}
impl TestUserServer {
pub async fn new() -> Self {
let mut server: TestUserServer = spawn_server().await.into();
let response = server.register_user().await;
server.user_token = Some(response.token);
server.user_id = Some(response.user_id);
server
}
pub async fn sign_in(&self, params: SignInParams) -> Result<SignInResponse, ServerError> {
let url = format!("{}/api/auth", self.http_addr());
let resp = user_sign_in_request(params, &url).await?;
Ok(resp)
}
pub async fn sign_out(&self) {
let url = format!("{}/api/auth", self.http_addr());
let _ = user_sign_out_request(self.user_token(), &url).await.unwrap();
}
pub fn user_token(&self) -> &str { self.user_token.as_ref().expect("must call register_user first ") }
pub fn user_id(&self) -> &str { self.user_id.as_ref().expect("must call register_user first ") }
pub async fn get_user_profile(&self) -> UserProfile {
let url = format!("{}/api/user", self.http_addr());
let user_profile = get_user_profile_request(self.user_token(), &url).await.unwrap();
user_profile
}
pub async fn update_user_profile(&self, params: UpdateUserParams) -> Result<(), ServerError> {
let url = format!("{}/api/user", self.http_addr());
let _ = update_user_profile_request(self.user_token(), params, &url).await?;
Ok(())
}
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> Workspace {
let url = format!("{}/api/workspace", self.http_addr());
let workspace = create_workspace_request(self.user_token(), params, &url).await.unwrap();
workspace
}
pub async fn read_workspaces(&self, params: WorkspaceIdentifier) -> RepeatedWorkspace {
let url = format!("{}/api/workspace", self.http_addr());
let workspaces = read_workspaces_request(self.user_token(), params, &url).await.unwrap();
workspaces
}
pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {
let url = format!("{}/api/workspace", self.http_addr());
update_workspace_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_workspace(&self, params: WorkspaceIdentifier) {
let url = format!("{}/api/workspace", self.http_addr());
delete_workspace_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_app(&self, params: CreateAppParams) -> App {
let url = format!("{}/api/app", self.http_addr());
let app = create_app_request(self.user_token(), params, &url).await.unwrap();
app
}
pub async fn read_app(&self, params: AppIdentifier) -> Option<App> {
let url = format!("{}/api/app", self.http_addr());
let app = read_app_request(self.user_token(), params, &url).await.unwrap();
app
}
pub async fn update_app(&self, params: UpdateAppParams) {
let url = format!("{}/api/app", self.http_addr());
update_app_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_app(&self, params: AppIdentifier) {
let url = format!("{}/api/app", self.http_addr());
delete_app_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_view(&self, params: CreateViewParams) -> View {
let url = format!("{}/api/view", self.http_addr());
let view = create_view_request(self.user_token(), params, &url).await.unwrap();
view
}
pub async fn read_view(&self, params: ViewIdentifier) -> Option<View> {
let url = format!("{}/api/view", self.http_addr());
let view = read_view_request(self.user_token(), params, &url).await.unwrap();
view
}
pub async fn update_view(&self, params: UpdateViewParams) {
let url = format!("{}/api/view", self.http_addr());
update_view_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_view(&self, params: ViewIdentifiers) {
let url = format!("{}/api/view", self.http_addr());
delete_view_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_view_trash(&self, view_id: &str) {
let identifier = TrashIdentifier {
id: view_id.to_string(),
ty: TrashType::View,
};
let url = format!("{}/api/trash", self.http_addr());
create_trash_request(self.user_token(), vec![identifier].into(), &url)
.await
.unwrap();
}
pub async fn delete_view_trash(&self, trash_identifiers: TrashIdentifiers) {
let url = format!("{}/api/trash", self.http_addr());
delete_trash_request(self.user_token(), trash_identifiers, &url)
.await
.unwrap();
}
pub async fn read_trash(&self) -> RepeatedTrash {
let url = format!("{}/api/trash", self.http_addr());
read_trash_request(self.user_token(), &url).await.unwrap()
}
pub async fn read_doc(&self, params: DocIdentifier) -> Option<Doc> {
let url = format!("{}/api/doc", self.http_addr());
let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
doc
}
pub async fn register_user(&self) -> SignUpResponse {
let params = SignUpParams {
email: "annie@appflowy.io".to_string(),
name: "annie".to_string(),
password: "HelloAppFlowy123!".to_string(),
};
self.register(params).await
}
pub async fn register(&self, params: SignUpParams) -> SignUpResponse {
let url = format!("{}/api/register", self.http_addr());
let response = user_sign_up_request(params, &url).await.unwrap();
response
}
pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
pub fn ws_addr(&self) -> String { format!("ws://{}/ws/{}", self.host, self.user_token.as_ref().unwrap()) }
}
impl std::convert::From<TestServer> for TestUserServer {
fn from(server: TestServer) -> Self {
TestUserServer {
host: server.host,
port: server.port,
pg_pool: server.pg_pool,
user_token: None,
user_id: None,
}
}
}
pub async fn spawn_user_server() -> TestUserServer {
let server: TestUserServer = spawn_server().await.into();
server
}
pub struct TestServer {
pub host: String,
pub port: u16,
pub pg_pool: PgPool,
pub app_ctx: AppContext,
}
pub async fn spawn_server() -> TestServer {
let database_name = format!("{}", Uuid::new_v4().to_string());
let configuration = {
let mut c = get_configuration().expect("Failed to read configuration.");
c.database.database_name = database_name.clone();
// Use a random OS port
c.application.port = 0;
c
};
let _ = configure_database(&configuration.database).await;
let app_ctx = init_app_context(&configuration).await;
let application = Application::build(configuration.clone(), app_ctx.clone())
.await
.expect("Failed to build application.");
let application_port = application.port();
let _ = tokio::spawn(async {
let _ = application.run_until_stopped();
// drop_test_database(database_name).await;
});
TestServer {
host: format!("localhost:{}", application_port),
port: application_port,
pg_pool: get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to the database"),
app_ctx,
}
}
async fn configure_database(config: &DatabaseSettings) -> PgPool {
// Create database
let mut connection = PgConnection::connect_with(&config.without_db())
.await
.expect("Failed to connect to Postgres");
connection
.execute(&*format!(r#"CREATE DATABASE "{}";"#, config.database_name))
.await
.expect("Failed to create database.");
// Migrate database
let connection_pool = PgPool::connect_with(config.with_db())
.await
.expect("Failed to connect to Postgres.");
sqlx::migrate!("./migrations")
.run(&connection_pool)
.await
.expect("Failed to migrate the database");
connection_pool
}
#[allow(dead_code)]
async fn drop_test_database(database_name: String) {
// https://stackoverflow.com/questions/36502401/postgres-drop-database-error-pq-cannot-drop-the-currently-open-database?rq=1
let configuration = {
let mut c = get_configuration().expect("Failed to read configuration.");
c.database.database_name = "flowy".to_owned();
c.application.port = 0;
c
};
let mut connection = PgConnection::connect_with(&configuration.database.without_db())
.await
.expect("Failed to connect to Postgres");
connection
.execute(&*format!(r#"Drop DATABASE "{}";"#, database_name))
.await
.expect("Failed to drop database.");
}
pub async fn create_test_workspace(server: &TestUserServer) -> Workspace {
let params = CreateWorkspaceParams {
name: "My first workspace".to_string(),
desc: "This is my first workspace".to_string(),
};
let workspace = server.create_workspace(params).await;
workspace
}
pub async fn create_test_app(server: &TestUserServer, workspace_id: &str) -> App {
let params = CreateAppParams {
workspace_id: workspace_id.to_owned(),
name: "My first app".to_string(),
desc: "This is my first app".to_string(),
color_style: ColorStyle::default(),
};
let app = server.create_app(params).await;
app
}
pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> View {
let name = "My first view".to_string();
let desc = "This is my first view".to_string();
let thumbnail = "http://1.png".to_string();
let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
let app = application.create_view(params).await;
app
}
pub struct WorkspaceTest {
pub server: TestUserServer,
pub workspace: Workspace,
}
impl WorkspaceTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
Self { server, workspace }
}
pub async fn create_app(&self) -> App { create_test_app(&self.server, &self.workspace.id).await }
}
pub struct AppTest {
pub server: TestUserServer,
pub workspace: Workspace,
pub app: App,
}
impl AppTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
let app = create_test_app(&server, &workspace.id).await;
Self { server, workspace, app }
}
}
pub struct ViewTest {
pub server: TestUserServer,
pub workspace: Workspace,
pub app: App,
pub view: View,
}
impl ViewTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
let app = create_test_app(&server, &workspace.id).await;
let view = create_test_view(&server, &app.id).await;
Self {
server,
workspace,
app,
view,
}
}
}

View File

@ -1,3 +1,3 @@
mod api;
mod document;
pub mod helper;
pub mod util;

View File

@ -0,0 +1,356 @@
use backend::{
application::{get_connection_pool, init_app_context, Application},
config::{get_configuration, DatabaseSettings},
context::AppContext,
};
use backend_service::{errors::ServerError, user_request::*, workspace_request::*};
use flowy_document::services::server::read_doc_request;
use flowy_document_infra::entities::doc::{Doc, DocIdentifier};
use flowy_user_infra::entities::*;
use flowy_workspace_infra::entities::prelude::*;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use uuid::Uuid;
pub struct TestUserServer {
pub host: String,
pub port: u16,
pub pg_pool: PgPool,
pub user_token: Option<String>,
pub user_id: Option<String>,
}
impl TestUserServer {
pub async fn new() -> Self {
let mut server: TestUserServer = spawn_server().await.into();
let response = server.register_user().await;
server.user_token = Some(response.token);
server.user_id = Some(response.user_id);
server
}
pub async fn sign_in(&self, params: SignInParams) -> Result<SignInResponse, ServerError> {
let url = format!("{}/api/auth", self.http_addr());
let resp = user_sign_in_request(params, &url).await?;
Ok(resp)
}
pub async fn sign_out(&self) {
let url = format!("{}/api/auth", self.http_addr());
let _ = user_sign_out_request(self.user_token(), &url).await.unwrap();
}
pub fn user_token(&self) -> &str { self.user_token.as_ref().expect("must call register_user first ") }
pub fn user_id(&self) -> &str { self.user_id.as_ref().expect("must call register_user first ") }
pub async fn get_user_profile(&self) -> UserProfile {
let url = format!("{}/api/user", self.http_addr());
let user_profile = get_user_profile_request(self.user_token(), &url).await.unwrap();
user_profile
}
pub async fn update_user_profile(&self, params: UpdateUserParams) -> Result<(), ServerError> {
let url = format!("{}/api/user", self.http_addr());
let _ = update_user_profile_request(self.user_token(), params, &url).await?;
Ok(())
}
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> Workspace {
let url = format!("{}/api/workspace", self.http_addr());
let workspace = create_workspace_request(self.user_token(), params, &url).await.unwrap();
workspace
}
pub async fn read_workspaces(&self, params: WorkspaceIdentifier) -> RepeatedWorkspace {
let url = format!("{}/api/workspace", self.http_addr());
let workspaces = read_workspaces_request(self.user_token(), params, &url).await.unwrap();
workspaces
}
pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {
let url = format!("{}/api/workspace", self.http_addr());
update_workspace_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_workspace(&self, params: WorkspaceIdentifier) {
let url = format!("{}/api/workspace", self.http_addr());
delete_workspace_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_app(&self, params: CreateAppParams) -> App {
let url = format!("{}/api/app", self.http_addr());
let app = create_app_request(self.user_token(), params, &url).await.unwrap();
app
}
pub async fn read_app(&self, params: AppIdentifier) -> Option<App> {
let url = format!("{}/api/app", self.http_addr());
let app = read_app_request(self.user_token(), params, &url).await.unwrap();
app
}
pub async fn update_app(&self, params: UpdateAppParams) {
let url = format!("{}/api/app", self.http_addr());
update_app_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_app(&self, params: AppIdentifier) {
let url = format!("{}/api/app", self.http_addr());
delete_app_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_view(&self, params: CreateViewParams) -> View {
let url = format!("{}/api/view", self.http_addr());
let view = create_view_request(self.user_token(), params, &url).await.unwrap();
view
}
pub async fn read_view(&self, params: ViewIdentifier) -> Option<View> {
let url = format!("{}/api/view", self.http_addr());
let view = read_view_request(self.user_token(), params, &url).await.unwrap();
view
}
pub async fn update_view(&self, params: UpdateViewParams) {
let url = format!("{}/api/view", self.http_addr());
update_view_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn delete_view(&self, params: ViewIdentifiers) {
let url = format!("{}/api/view", self.http_addr());
delete_view_request(self.user_token(), params, &url).await.unwrap();
}
pub async fn create_view_trash(&self, view_id: &str) {
let identifier = TrashIdentifier {
id: view_id.to_string(),
ty: TrashType::View,
};
let url = format!("{}/api/trash", self.http_addr());
create_trash_request(self.user_token(), vec![identifier].into(), &url)
.await
.unwrap();
}
pub async fn delete_view_trash(&self, trash_identifiers: TrashIdentifiers) {
let url = format!("{}/api/trash", self.http_addr());
delete_trash_request(self.user_token(), trash_identifiers, &url)
.await
.unwrap();
}
pub async fn read_trash(&self) -> RepeatedTrash {
let url = format!("{}/api/trash", self.http_addr());
read_trash_request(self.user_token(), &url).await.unwrap()
}
pub async fn read_doc(&self, params: DocIdentifier) -> Option<Doc> {
let url = format!("{}/api/doc", self.http_addr());
let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
doc
}
pub async fn register_user(&self) -> SignUpResponse {
let params = SignUpParams {
email: "annie@appflowy.io".to_string(),
name: "annie".to_string(),
password: "HelloAppFlowy123!".to_string(),
};
self.register(params).await
}
pub async fn register(&self, params: SignUpParams) -> SignUpResponse {
let url = format!("{}/api/register", self.http_addr());
let response = user_sign_up_request(params, &url).await.unwrap();
response
}
pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
pub fn ws_addr(&self) -> String { format!("ws://{}/ws/{}", self.host, self.user_token.as_ref().unwrap()) }
}
impl std::convert::From<TestServer> for TestUserServer {
fn from(server: TestServer) -> Self {
TestUserServer {
host: server.host,
port: server.port,
pg_pool: server.pg_pool,
user_token: None,
user_id: None,
}
}
}
pub async fn spawn_user_server() -> TestUserServer {
let server: TestUserServer = spawn_server().await.into();
server
}
pub struct TestServer {
pub host: String,
pub port: u16,
pub pg_pool: PgPool,
pub app_ctx: AppContext,
}
pub async fn spawn_server() -> TestServer {
let database_name = format!("{}", Uuid::new_v4().to_string());
let configuration = {
let mut c = get_configuration().expect("Failed to read configuration.");
c.database.database_name = database_name.clone();
// Use a random OS port
c.application.port = 0;
c
};
let _ = configure_database(&configuration.database).await;
let app_ctx = init_app_context(&configuration).await;
let application = Application::build(configuration.clone(), app_ctx.clone())
.await
.expect("Failed to build application.");
let application_port = application.port();
let _ = tokio::spawn(async {
let _ = application.run_until_stopped();
// drop_test_database(database_name).await;
});
TestServer {
host: format!("localhost:{}", application_port),
port: application_port,
pg_pool: get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to the database"),
app_ctx,
}
}
async fn configure_database(config: &DatabaseSettings) -> PgPool {
// Create database
let mut connection = PgConnection::connect_with(&config.without_db())
.await
.expect("Failed to connect to Postgres");
connection
.execute(&*format!(r#"CREATE DATABASE "{}";"#, config.database_name))
.await
.expect("Failed to create database.");
// Migrate database
let connection_pool = PgPool::connect_with(config.with_db())
.await
.expect("Failed to connect to Postgres.");
sqlx::migrate!("./migrations")
.run(&connection_pool)
.await
.expect("Failed to migrate the database");
connection_pool
}
#[allow(dead_code)]
async fn drop_test_database(database_name: String) {
// https://stackoverflow.com/questions/36502401/postgres-drop-database-error-pq-cannot-drop-the-currently-open-database?rq=1
let configuration = {
let mut c = get_configuration().expect("Failed to read configuration.");
c.database.database_name = "flowy".to_owned();
c.application.port = 0;
c
};
let mut connection = PgConnection::connect_with(&configuration.database.without_db())
.await
.expect("Failed to connect to Postgres");
connection
.execute(&*format!(r#"Drop DATABASE "{}";"#, database_name))
.await
.expect("Failed to drop database.");
}
pub async fn create_test_workspace(server: &TestUserServer) -> Workspace {
let params = CreateWorkspaceParams {
name: "My first workspace".to_string(),
desc: "This is my first workspace".to_string(),
};
let workspace = server.create_workspace(params).await;
workspace
}
pub async fn create_test_app(server: &TestUserServer, workspace_id: &str) -> App {
let params = CreateAppParams {
workspace_id: workspace_id.to_owned(),
name: "My first app".to_string(),
desc: "This is my first app".to_string(),
color_style: ColorStyle::default(),
};
let app = server.create_app(params).await;
app
}
pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> View {
let name = "My first view".to_string();
let desc = "This is my first view".to_string();
let thumbnail = "http://1.png".to_string();
let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
let app = application.create_view(params).await;
app
}
pub struct WorkspaceTest {
pub server: TestUserServer,
pub workspace: Workspace,
}
impl WorkspaceTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
Self { server, workspace }
}
pub async fn create_app(&self) -> App { create_test_app(&self.server, &self.workspace.id).await }
}
pub struct AppTest {
pub server: TestUserServer,
pub workspace: Workspace,
pub app: App,
}
impl AppTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
let app = create_test_app(&server, &workspace.id).await;
Self { server, workspace, app }
}
}
pub struct ViewTest {
pub server: TestUserServer,
pub workspace: Workspace,
pub app: App,
pub view: View,
}
impl ViewTest {
pub async fn new() -> Self {
let server = TestUserServer::new().await;
let workspace = create_test_workspace(&server).await;
let app = create_test_app(&server, &workspace.id).await;
let view = create_test_view(&server, &app.id).await;
Self {
server,
workspace,
app,
view,
}
}
}

View File

@ -0,0 +1 @@
pub mod helper;

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 268 B

View File

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View File

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View File

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View File

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 195 B

View File

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 194 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 641 B

View File

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 277 B

View File

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 202 B

View File

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 274 B

View File

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 336 B

View File

Before

Width:  |  Height:  |  Size: 148 B

After

Width:  |  Height:  |  Size: 148 B

Some files were not shown because too many files have changed in this diff Show More