[client]: fix trash notification issue

This commit is contained in:
appflowy 2021-10-16 21:22:59 +08:00
parent 560ee376f9
commit 2513853ea4
14 changed files with 95 additions and 451 deletions

View File

@ -40,7 +40,7 @@ class ApplicationBlocObserver extends BlocObserver {
@override
// ignore: unnecessary_overrides
void onTransition(Bloc bloc, Transition transition) {
// Log.debug("[current]: ${transition.currentState} \n[next]: ${transition.nextState}");
Log.debug("[current]: ${transition.currentState} \n[next]: ${transition.nextState}");
super.onTransition(bloc, transition);
}

View File

@ -28,7 +28,10 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
},
putback: (e) async* {
final result = await iTrash.putback(e.trashId);
result.fold((l) {}, (error) {});
yield result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)),
);
},
delete: (e) async* {
final result = await iTrash.delete(e.trashId);

View File

@ -32,7 +32,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
(error) => state.copyWith(successOrFailure: right(error)),
);
}, delete: (e) async* {
final result = await iViewImpl.pushIntoTrash();
final result = await iViewImpl.delete();
yield result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)),

View File

@ -1,43 +0,0 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/domain/i_view.dart';
part 'view_edit_bloc.freezed.dart';
class ViewEditBloc extends Bloc<ViewEditEvent, ViewEditState> {
final IView iViewImpl;
ViewEditBloc({
required this.iViewImpl,
}) : super(ViewEditState.initial());
@override
Stream<ViewEditState> mapEventToState(ViewEditEvent event) async* {
yield* event.map(initial: (_) async* {
yield state;
});
}
}
@freezed
class ViewEditEvent with _$ViewEditEvent {
const factory ViewEditEvent.initial() = Initial;
}
@freezed
class ViewEditState with _$ViewEditState {
const factory ViewEditState({
required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure,
}) = _ViewState;
factory ViewEditState.initial() => ViewEditState(
isLoading: false,
view: none(),
successOrFailure: left(unit),
);
}

View File

@ -1,332 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'view_edit_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
class _$ViewEditEventTearOff {
const _$ViewEditEventTearOff();
Initial initial() {
return const Initial();
}
}
/// @nodoc
const $ViewEditEvent = _$ViewEditEventTearOff();
/// @nodoc
mixin _$ViewEditEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ViewEditEventCopyWith<$Res> {
factory $ViewEditEventCopyWith(
ViewEditEvent value, $Res Function(ViewEditEvent) then) =
_$ViewEditEventCopyWithImpl<$Res>;
}
/// @nodoc
class _$ViewEditEventCopyWithImpl<$Res>
implements $ViewEditEventCopyWith<$Res> {
_$ViewEditEventCopyWithImpl(this._value, this._then);
final ViewEditEvent _value;
// ignore: unused_field
final $Res Function(ViewEditEvent) _then;
}
/// @nodoc
abstract class $InitialCopyWith<$Res> {
factory $InitialCopyWith(Initial value, $Res Function(Initial) then) =
_$InitialCopyWithImpl<$Res>;
}
/// @nodoc
class _$InitialCopyWithImpl<$Res> extends _$ViewEditEventCopyWithImpl<$Res>
implements $InitialCopyWith<$Res> {
_$InitialCopyWithImpl(Initial _value, $Res Function(Initial) _then)
: super(_value, (v) => _then(v as Initial));
@override
Initial get _value => super._value as Initial;
}
/// @nodoc
class _$Initial implements Initial {
const _$Initial();
@override
String toString() {
return 'ViewEditEvent.initial()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) || (other is Initial);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class Initial implements ViewEditEvent {
const factory Initial() = _$Initial;
}
/// @nodoc
class _$ViewEditStateTearOff {
const _$ViewEditStateTearOff();
_ViewState call(
{required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure}) {
return _ViewState(
isLoading: isLoading,
view: view,
successOrFailure: successOrFailure,
);
}
}
/// @nodoc
const $ViewEditState = _$ViewEditStateTearOff();
/// @nodoc
mixin _$ViewEditState {
bool get isLoading => throw _privateConstructorUsedError;
Option<View> get view => throw _privateConstructorUsedError;
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ViewEditStateCopyWith<ViewEditState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ViewEditStateCopyWith<$Res> {
factory $ViewEditStateCopyWith(
ViewEditState value, $Res Function(ViewEditState) then) =
_$ViewEditStateCopyWithImpl<$Res>;
$Res call(
{bool isLoading,
Option<View> view,
Either<Unit, WorkspaceError> successOrFailure});
}
/// @nodoc
class _$ViewEditStateCopyWithImpl<$Res>
implements $ViewEditStateCopyWith<$Res> {
_$ViewEditStateCopyWithImpl(this._value, this._then);
final ViewEditState _value;
// ignore: unused_field
final $Res Function(ViewEditState) _then;
@override
$Res call({
Object? isLoading = freezed,
Object? view = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_value.copyWith(
isLoading: isLoading == freezed
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
view: view == freezed
? _value.view
: view // ignore: cast_nullable_to_non_nullable
as Option<View>,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
: successOrFailure // ignore: cast_nullable_to_non_nullable
as Either<Unit, WorkspaceError>,
));
}
}
/// @nodoc
abstract class _$ViewStateCopyWith<$Res>
implements $ViewEditStateCopyWith<$Res> {
factory _$ViewStateCopyWith(
_ViewState value, $Res Function(_ViewState) then) =
__$ViewStateCopyWithImpl<$Res>;
@override
$Res call(
{bool isLoading,
Option<View> view,
Either<Unit, WorkspaceError> successOrFailure});
}
/// @nodoc
class __$ViewStateCopyWithImpl<$Res> extends _$ViewEditStateCopyWithImpl<$Res>
implements _$ViewStateCopyWith<$Res> {
__$ViewStateCopyWithImpl(_ViewState _value, $Res Function(_ViewState) _then)
: super(_value, (v) => _then(v as _ViewState));
@override
_ViewState get _value => super._value as _ViewState;
@override
$Res call({
Object? isLoading = freezed,
Object? view = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_ViewState(
isLoading: isLoading == freezed
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
view: view == freezed
? _value.view
: view // ignore: cast_nullable_to_non_nullable
as Option<View>,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
: successOrFailure // ignore: cast_nullable_to_non_nullable
as Either<Unit, WorkspaceError>,
));
}
}
/// @nodoc
class _$_ViewState implements _ViewState {
const _$_ViewState(
{required this.isLoading,
required this.view,
required this.successOrFailure});
@override
final bool isLoading;
@override
final Option<View> view;
@override
final Either<Unit, WorkspaceError> successOrFailure;
@override
String toString() {
return 'ViewEditState(isLoading: $isLoading, view: $view, successOrFailure: $successOrFailure)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is _ViewState &&
(identical(other.isLoading, isLoading) ||
const DeepCollectionEquality()
.equals(other.isLoading, isLoading)) &&
(identical(other.view, view) ||
const DeepCollectionEquality().equals(other.view, view)) &&
(identical(other.successOrFailure, successOrFailure) ||
const DeepCollectionEquality()
.equals(other.successOrFailure, successOrFailure)));
}
@override
int get hashCode =>
runtimeType.hashCode ^
const DeepCollectionEquality().hash(isLoading) ^
const DeepCollectionEquality().hash(view) ^
const DeepCollectionEquality().hash(successOrFailure);
@JsonKey(ignore: true)
@override
_$ViewStateCopyWith<_ViewState> get copyWith =>
__$ViewStateCopyWithImpl<_ViewState>(this, _$identity);
}
abstract class _ViewState implements ViewEditState {
const factory _ViewState(
{required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure}) = _$_ViewState;
@override
bool get isLoading => throw _privateConstructorUsedError;
@override
Option<View> get view => throw _privateConstructorUsedError;
@override
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;
@override
@JsonKey(ignore: true)
_$ViewStateCopyWith<_ViewState> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -7,7 +7,7 @@ typedef ViewUpdatedCallback = void Function(Either<View, WorkspaceError>);
abstract class IView {
View get view;
Future<Either<Unit, WorkspaceError>> pushIntoTrash();
Future<Either<Unit, WorkspaceError>> delete();
Future<Either<View, WorkspaceError>> rename(String newName);
}

View File

@ -13,8 +13,8 @@ class IViewImpl extends IView {
View get view => repo.view;
@override
Future<Either<Unit, WorkspaceError>> pushIntoTrash() {
return repo.updateView(isTrash: true).then((result) {
Future<Either<Unit, WorkspaceError>> delete() {
return repo.delete().then((result) {
return result.fold(
(_) => left(unit),
(error) => right(error),

View File

@ -21,9 +21,7 @@ class AppRepository {
});
Future<Either<App, WorkspaceError>> getAppDesc() {
final request = QueryAppRequest.create()
..appId = appId
..readBelongings = false;
final request = QueryAppRequest.create()..appId = appId;
return WorkspaceEventReadApp(request).send();
}
@ -39,9 +37,7 @@ class AppRepository {
}
Future<Either<List<View>, WorkspaceError>> getViews() {
final request = QueryAppRequest.create()
..appId = appId
..readBelongings = true;
final request = QueryAppRequest.create()..appId = appId;
return WorkspaceEventReadApp(request).send().then((result) {
return result.fold(

View File

@ -26,7 +26,7 @@ class ViewRepository {
return WorkspaceEventReadView(request).send();
}
Future<Either<View, WorkspaceError>> updateView({String? name, String? desc, bool? isTrash}) {
Future<Either<View, WorkspaceError>> updateView({String? name, String? desc}) {
final request = UpdateViewRequest.create()..viewId = view.id;
if (name != null) {
@ -37,15 +37,11 @@ class ViewRepository {
request.desc = desc;
}
if (isTrash != null) {
request.isTrash = isTrash;
}
return WorkspaceEventUpdateView(request).send();
}
Future<Either<Unit, WorkspaceError>> delete() {
final request = DeleteViewRequest.create()..viewId = view.id;
final request = DeleteViewRequest.create()..viewIds.add(view.id);
return WorkspaceEventDeleteView(request).send();
}
}

View File

@ -149,7 +149,9 @@ class _TrashStackPageState extends State<TrashStackPage> {
height: 42,
child: TrashCell(
object: object,
onRestore: () => context.read<TrashBloc>().add(TrashEvent.putback(object.id)),
onRestore: () {
context.read<TrashBloc>().add(TrashEvent.putback(object.id));
},
onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
),
);

View File

@ -1,16 +1,15 @@
use crate::{
entities::{
trash::{RepeatedTrash, TrashIdentifier},
},
entities::trash::{RepeatedTrash, TrashIdentifier},
errors::WorkspaceError,
services::TrashCan,
};
use flowy_dispatch::prelude::{data_result, Data, DataResult, Unit};
use std::{sync::Arc};
use std::sync::Arc;
#[tracing::instrument(skip(controller), err)]
pub(crate) async fn read_trash_handler(controller: Unit<Arc<TrashCan>>) -> DataResult<RepeatedTrash, WorkspaceError> {
let repeated_trash = controller.read_trash()?;
let conn = controller.database.db_connection()?;
let repeated_trash = controller.read_trash(&conn)?;
data_result(repeated_trash)
}

View File

@ -41,7 +41,7 @@ impl TrashEvent {
}
pub struct TrashCan {
database: Arc<dyn WorkspaceDatabase>,
pub database: Arc<dyn WorkspaceDatabase>,
notify: broadcast::Sender<TrashEvent>,
}
@ -51,16 +51,36 @@ impl TrashCan {
Self { database, notify: tx }
}
pub fn read_trash(&self) -> Result<RepeatedTrash, WorkspaceError> {
let conn = self.database.db_connection()?;
pub fn read_trash(&self, conn: &SqliteConnection) -> Result<RepeatedTrash, WorkspaceError> {
let repeated_trash = TrashTableSql::read_all(&*conn)?;
Ok(repeated_trash)
}
pub fn trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, WorkspaceError> {
let ids = TrashTableSql::read_all(&*conn)?
.take_items()
.into_iter()
.map(|item| item.id)
.collect::<Vec<String>>();
Ok(ids)
}
#[tracing::instrument(level = "debug", skip(self), fields(putback) err)]
pub async fn putback(&self, trash_id: &str) -> WorkspaceResult<()> {
let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?;
let _ = thread::scope(|_s| {
let conn = self.database.db_connection()?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(())
})?;
Ok::<(), WorkspaceError>(())
})
.unwrap()?;
tracing::Span::current().record(
"putback",
&format!("{:?}: {}", &trash_table.ty, trash_table.id).as_str(),
@ -70,12 +90,6 @@ impl TrashCan {
.send(TrashEvent::Putback(trash_table.ty.into(), vec![trash_table.id], tx));
let _ = rx.recv().await.unwrap()?;
let conn = self.database.db_connection()?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(())
})?;
Ok(())
}
@ -122,10 +136,12 @@ impl TrashCan {
if trash_type.as_ref().unwrap() != &t.ty {
return Err(WorkspaceError::internal());
}
ids.push(t.id.clone());
let trash_id = t.id.clone();
log::debug!("create trash: {:?}", t);
let _ = TrashTableSql::create_trash(t.into(), &*conn)?;
ids.push(trash_id);
}
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(())
})?;
Ok::<(), WorkspaceError>(())

View File

@ -59,12 +59,12 @@ impl ViewController {
pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
let view = self.create_view_on_server(params.clone()).await?;
let conn = &*self.database.db_connection()?;
let trash_can = self.trash_can.clone();
// TODO: rollback anything created before if failed?
conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = self.save_view(view.clone(), conn)?;
self.document.create(CreateDocParams::new(&view.id, params.data))?;
let repeated_view = ViewTableSql::read_views(&view.belong_to_id, conn)?;
let repeated_view = read_belonging_view(&view.belong_to_id, trash_can, &conn)?;
send_dart_notification(&view.belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view)
.send();
@ -112,7 +112,7 @@ impl ViewController {
pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, WorkspaceError> {
// TODO: read from server
let conn = self.database.db_connection()?;
let repeated_view = ViewTableSql::read_views(belong_to_id, &*conn)?;
let repeated_view = read_belonging_view(belong_to_id, self.trash_can.clone(), &conn)?;
Ok(repeated_view)
}
@ -165,22 +165,22 @@ impl ViewController {
Ok(())
}
#[tracing::instrument(skip(self), err)]
fn delete_view_on_server(&self, view_ids: Vec<String>) -> Result<(), WorkspaceError> {
let token = self.user.token()?;
let server = self.server.clone();
let params = DeleteViewParams { view_ids };
spawn(async move {
match server.delete_view(&token, params).await {
Ok(_) => {},
Err(e) => {
// TODO: retry?
log::error!("Delete view failed: {:?}", e);
},
}
});
Ok(())
}
// #[tracing::instrument(skip(self), err)]
// fn delete_view_on_server(&self, view_ids: Vec<String>) -> Result<(),
// WorkspaceError> { let token = self.user.token()?;
// let server = self.server.clone();
// let params = DeleteViewParams { view_ids };
// spawn(async move {
// match server.delete_view(&token, params).await {
// Ok(_) => {},
// Err(e) => {
// // TODO: retry?
// log::error!("Delete view failed: {:?}", e);
// },
// }
// });
// Ok(())
// }
#[tracing::instrument(skip(self), err)]
fn read_view_on_server(&self, params: ViewIdentifier) -> Result<(), WorkspaceError> {
@ -202,6 +202,7 @@ impl ViewController {
let mut rx = self.trash_can.subscribe();
let database = self.database.clone();
let document = self.document.clone();
let trash_can = self.trash_can.clone();
let _ = tokio::spawn(async move {
loop {
let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move {
@ -212,7 +213,9 @@ impl ViewController {
}));
let event: Option<TrashEvent> = stream.next().await;
match event {
Some(event) => handle_trash_event(database.clone(), document.clone(), event),
Some(event) => {
handle_trash_event(database.clone(), document.clone(), trash_can.clone(), event).await
},
None => {},
}
}
@ -220,7 +223,12 @@ impl ViewController {
}
}
fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyDocument>, event: TrashEvent) {
async fn handle_trash_event(
database: Arc<dyn WorkspaceDatabase>,
document: Arc<FlowyDocument>,
trash_can: Arc<TrashCan>,
event: TrashEvent,
) {
let db_result = database.db_connection();
match event {
@ -229,13 +237,13 @@ fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyD
let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
for view_id in view_ids {
let _ = notify_view_num_did_change(&view_id, conn)?;
let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
}
Ok(())
})?;
Ok::<(), WorkspaceError>(())
};
let _ = ret.send(result());
let _ = ret.send(result()).await;
},
TrashEvent::Delete(_, delete_ids, ret) => {
let result = || {
@ -244,22 +252,34 @@ fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyD
for view_id in delete_ids {
let _ = ViewTableSql::delete_view(&view_id, conn)?;
let _ = document.delete(view_id.clone().into())?;
let _ = notify_view_num_did_change(&view_id, conn)?;
let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
}
Ok(())
})?;
Ok::<(), WorkspaceError>(())
};
let _ = ret.send(result());
let _ = ret.send(result()).await;
},
}
}
fn notify_view_num_did_change(view_id: &str, conn: &SqliteConnection) -> WorkspaceResult<()> {
#[tracing::instrument(skip(conn, trash_can), err)]
fn notify_view_num_did_change(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> {
let view_table = ViewTableSql::read_view(view_id, conn)?;
let repeated_view = ViewTableSql::read_views(&view_table.belong_to_id, conn)?;
let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can, conn)?;
send_dart_notification(&view_table.belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view)
.send();
Ok(())
}
fn read_belonging_view(
belong_to_id: &str,
trash_can: Arc<TrashCan>,
conn: &SqliteConnection,
) -> WorkspaceResult<RepeatedView> {
let mut repeated_view = ViewTableSql::read_views(belong_to_id, conn)?;
let trash_ids = trash_can.trash_ids(conn)?;
repeated_view.retain(|view| !trash_ids.contains(&view.id));
Ok(repeated_view)
}

View File

@ -35,29 +35,16 @@ impl ViewTableSql {
.filter(view_table::id.eq(view_id))
.first::<ViewTable>(conn)?;
let repeated_trash: Vec<String> = trash_table::dsl::trash_table.select(trash_table::dsl::id).load(conn)?;
if repeated_trash.contains(&view_table.id) {
return Err(WorkspaceError::not_found());
}
Ok(view_table)
}
// belong_to_id will be the app_id or view_id.
pub(crate) fn read_views(belong_to_id: &str, conn: &SqliteConnection) -> Result<RepeatedView, WorkspaceError> {
let mut view_tables = dsl::view_table
let view_tables = dsl::view_table
.filter(view_table::belong_to_id.eq(belong_to_id))
.into_boxed()
.load::<ViewTable>(conn)?;
let repeated_trash: Vec<String> = trash_table::dsl::trash_table.select(trash_table::dsl::id).load(conn)?;
view_tables = view_tables
.into_iter()
.filter(|table| !repeated_trash.contains(&table.id))
.collect::<Vec<ViewTable>>();
let views = view_tables
.into_iter()
.map(|view_table| view_table.into())