mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: skip create sync plugin when the authenticator for given user … (#4252)
* chore: skip create sync plugin when the authenticator for given user is local * chore: bump collab
This commit is contained in:
parent
50694bb589
commit
7ba3020ab3
@ -9,8 +9,6 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'auth/auth_service.dart';
|
||||
|
||||
/// A service to manage realtime interactions with Supabase.
|
||||
///
|
||||
/// `SupbaseRealtimeService` handles subscribing to table changes in Supabase
|
||||
@ -37,10 +35,10 @@ class SupabaseRealtimeService {
|
||||
},
|
||||
onInvalidAuth: (message) async {
|
||||
Log.error(message);
|
||||
await getIt<AuthService>().signOut();
|
||||
channel?.unsubscribe();
|
||||
channel = null;
|
||||
if (!isLoggingOut) {
|
||||
isLoggingOut = true;
|
||||
await runAppFlowy();
|
||||
}
|
||||
},
|
||||
|
18
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
18
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -883,7 +883,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -902,7 +902,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -932,7 +932,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -944,7 +944,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -963,7 +963,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -977,7 +977,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -1019,7 +1019,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1040,7 +1040,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1066,7 +1066,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
|
@ -67,14 +67,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a45
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
|
||||
|
||||
|
||||
|
18
frontend/rust-lib/Cargo.lock
generated
18
frontend/rust-lib/Cargo.lock
generated
@ -733,7 +733,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -752,7 +752,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -782,7 +782,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -794,7 +794,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -813,7 +813,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -827,7 +827,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -869,7 +869,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -890,7 +890,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -916,7 +916,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e7d77a4b233886c4e9b7e03934c03d3e4489ec86#e7d77a4b233886c4e9b7e03934c03d3e4489ec86"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f#cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
|
@ -109,11 +109,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a45
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e7d77a4b233886c4e9b7e03934c03d3e4489ec86" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cf6aae1fbeb97d7b2f475fcdfc73217942ed8c8f" }
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use anyhow::Error;
|
||||
@ -16,13 +16,13 @@ use tracing::trace;
|
||||
use lib_infra::future::Fut;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CollabDataSource {
|
||||
pub enum CollabPluginProviderType {
|
||||
Local,
|
||||
AppFlowyCloud,
|
||||
Supabase,
|
||||
}
|
||||
|
||||
pub enum CollabStorageProviderContext {
|
||||
pub enum CollabPluginProviderContext {
|
||||
Local,
|
||||
AppFlowyCloud {
|
||||
uid: i64,
|
||||
@ -37,23 +37,43 @@ pub enum CollabStorageProviderContext {
|
||||
},
|
||||
}
|
||||
|
||||
pub trait CollabStorageProvider: Send + Sync + 'static {
|
||||
fn storage_source(&self) -> CollabDataSource;
|
||||
impl Display for CollabPluginProviderContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
CollabPluginProviderContext::Local => "Local".to_string(),
|
||||
CollabPluginProviderContext::AppFlowyCloud {
|
||||
uid: _,
|
||||
collab_object,
|
||||
local_collab: _,
|
||||
} => collab_object.to_string(),
|
||||
CollabPluginProviderContext::Supabase {
|
||||
uid: _,
|
||||
collab_object,
|
||||
local_collab: _,
|
||||
local_collab_db: _,
|
||||
} => collab_object.to_string(),
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_plugins(&self, context: CollabStorageProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>>;
|
||||
pub trait CollabCloudPluginProvider: Send + Sync + 'static {
|
||||
fn provider_type(&self) -> CollabPluginProviderType;
|
||||
|
||||
fn get_plugins(&self, context: CollabPluginProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>>;
|
||||
|
||||
fn is_sync_enabled(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> CollabStorageProvider for Arc<T>
|
||||
impl<T> CollabCloudPluginProvider for Arc<T>
|
||||
where
|
||||
T: CollabStorageProvider,
|
||||
T: CollabCloudPluginProvider,
|
||||
{
|
||||
fn storage_source(&self) -> CollabDataSource {
|
||||
(**self).storage_source()
|
||||
fn provider_type(&self) -> CollabPluginProviderType {
|
||||
(**self).provider_type()
|
||||
}
|
||||
|
||||
fn get_plugins(&self, context: CollabStorageProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
fn get_plugins(&self, context: CollabPluginProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
(**self).get_plugins(context)
|
||||
}
|
||||
|
||||
@ -65,7 +85,7 @@ where
|
||||
pub struct AppFlowyCollabBuilder {
|
||||
network_reachability: CollabNetworkReachability,
|
||||
workspace_id: RwLock<Option<String>>,
|
||||
cloud_storage: tokio::sync::RwLock<Arc<dyn CollabStorageProvider>>,
|
||||
plugin_provider: tokio::sync::RwLock<Arc<dyn CollabCloudPluginProvider>>,
|
||||
snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>,
|
||||
rocksdb_backup: Mutex<Option<Arc<dyn RocksdbBackup>>>,
|
||||
device_id: String,
|
||||
@ -89,11 +109,11 @@ impl CollabBuilderConfig {
|
||||
}
|
||||
|
||||
impl AppFlowyCollabBuilder {
|
||||
pub fn new<T: CollabStorageProvider>(storage_provider: T, device_id: String) -> Self {
|
||||
pub fn new<T: CollabCloudPluginProvider>(storage_provider: T, device_id: String) -> Self {
|
||||
Self {
|
||||
network_reachability: CollabNetworkReachability::new(),
|
||||
workspace_id: Default::default(),
|
||||
cloud_storage: tokio::sync::RwLock::new(Arc::new(storage_provider)),
|
||||
plugin_provider: tokio::sync::RwLock::new(Arc::new(storage_provider)),
|
||||
snapshot_persistence: Default::default(),
|
||||
rocksdb_backup: Default::default(),
|
||||
device_id,
|
||||
@ -219,20 +239,20 @@ impl AppFlowyCollabBuilder {
|
||||
{
|
||||
let collab_object = self.collab_object(uid, object_id, object_type)?;
|
||||
if build_config.sync_enable {
|
||||
let cloud_storage_type = self.cloud_storage.read().await.storage_source();
|
||||
let provider_type = self.plugin_provider.read().await.provider_type();
|
||||
let span = tracing::span!(tracing::Level::TRACE, "collab_builder", object_id = %object_id);
|
||||
let _enter = span.enter();
|
||||
match cloud_storage_type {
|
||||
CollabDataSource::AppFlowyCloud => {
|
||||
match provider_type {
|
||||
CollabPluginProviderType::AppFlowyCloud => {
|
||||
#[cfg(feature = "appflowy_cloud_integrate")]
|
||||
{
|
||||
trace!("init appflowy cloud collab plugins");
|
||||
let local_collab = Arc::downgrade(&collab);
|
||||
let plugins = self
|
||||
.cloud_storage
|
||||
.plugin_provider
|
||||
.read()
|
||||
.await
|
||||
.get_plugins(CollabStorageProviderContext::AppFlowyCloud {
|
||||
.get_plugins(CollabPluginProviderContext::AppFlowyCloud {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
@ -245,17 +265,17 @@ impl AppFlowyCollabBuilder {
|
||||
}
|
||||
}
|
||||
},
|
||||
CollabDataSource::Supabase => {
|
||||
CollabPluginProviderType::Supabase => {
|
||||
#[cfg(feature = "supabase_integrate")]
|
||||
{
|
||||
trace!("init supabase collab plugins");
|
||||
let local_collab = Arc::downgrade(&collab);
|
||||
let local_collab_db = collab_db.clone();
|
||||
let plugins = self
|
||||
.cloud_storage
|
||||
.plugin_provider
|
||||
.read()
|
||||
.await
|
||||
.get_plugins(CollabStorageProviderContext::Supabase {
|
||||
.get_plugins(CollabPluginProviderContext::Supabase {
|
||||
uid,
|
||||
collab_object: collab_object.clone(),
|
||||
local_collab,
|
||||
@ -267,7 +287,7 @@ impl AppFlowyCollabBuilder {
|
||||
}
|
||||
}
|
||||
},
|
||||
CollabDataSource::Local => {},
|
||||
CollabPluginProviderType::Local => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,10 @@ pub struct ServerProvider {
|
||||
pub(crate) encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) store_preferences: Weak<StorePreferences>,
|
||||
pub(crate) enable_sync: RwLock<bool>,
|
||||
pub(crate) user_enable_sync: RwLock<bool>,
|
||||
|
||||
/// The authenticator type of the user.
|
||||
pub(crate) user_authenticator: RwLock<Authenticator>,
|
||||
pub(crate) uid: Arc<RwLock<Option<i64>>>,
|
||||
}
|
||||
|
||||
@ -69,7 +72,8 @@ impl ServerProvider {
|
||||
config,
|
||||
server: RwLock::new(server),
|
||||
providers: RwLock::new(HashMap::new()),
|
||||
enable_sync: RwLock::new(true),
|
||||
user_enable_sync: RwLock::new(true),
|
||||
user_authenticator: RwLock::new(Authenticator::Local),
|
||||
encryption: RwLock::new(Arc::new(encryption)),
|
||||
store_preferences,
|
||||
uid: Default::default(),
|
||||
@ -112,7 +116,7 @@ impl ServerProvider {
|
||||
let config = AFCloudConfiguration::from_env()?;
|
||||
let server = Arc::new(AppFlowyCloudServer::new(
|
||||
config,
|
||||
*self.enable_sync.read(),
|
||||
*self.user_enable_sync.read(),
|
||||
self.config.device_id.clone(),
|
||||
));
|
||||
|
||||
@ -126,7 +130,7 @@ impl ServerProvider {
|
||||
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(Arc::new(SupabaseServer::new(
|
||||
uid,
|
||||
config,
|
||||
*self.enable_sync.read(),
|
||||
*self.user_enable_sync.read(),
|
||||
self.config.device_id.clone(),
|
||||
encryption,
|
||||
)))
|
||||
|
@ -8,10 +8,10 @@ use collab::core::origin::{CollabClient, CollabOrigin};
|
||||
use collab::preclude::CollabPlugin;
|
||||
use collab_entity::CollabType;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tracing::instrument;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use collab_integrate::collab_builder::{
|
||||
CollabDataSource, CollabStorageProvider, CollabStorageProviderContext,
|
||||
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
|
||||
};
|
||||
use collab_integrate::postgres::SupabaseDBPlugin;
|
||||
use flowy_database_deps::cloud::{CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot};
|
||||
@ -71,20 +71,14 @@ impl UserCloudServiceProvider for ServerProvider {
|
||||
fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
|
||||
if let Ok(server) = self.get_server(&self.get_server_type()) {
|
||||
server.set_enable_sync(uid, enable_sync);
|
||||
*self.enable_sync.write() = enable_sync;
|
||||
*self.user_enable_sync.write() = enable_sync;
|
||||
*self.uid.write() = Some(uid);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_network_reachable(&self, reachable: bool) {
|
||||
if let Ok(server) = self.get_server(&self.get_server_type()) {
|
||||
server.set_network_reachable(reachable);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_encrypt_secret(&self, secret: String) {
|
||||
tracing::info!("🔑Set encrypt secret");
|
||||
self.encryption.write().set_secret(secret);
|
||||
fn set_user_authenticator(&self, authenticator: &Authenticator) {
|
||||
debug!("set user authenticator: {:?}", authenticator);
|
||||
*self.user_authenticator.write() = authenticator.clone();
|
||||
}
|
||||
|
||||
/// When user login, the provider type is set by the [Authenticator] and save to disk for next use.
|
||||
@ -98,6 +92,17 @@ impl UserCloudServiceProvider for ServerProvider {
|
||||
self.set_server_type(server_type.clone());
|
||||
}
|
||||
|
||||
fn set_network_reachable(&self, reachable: bool) {
|
||||
if let Ok(server) = self.get_server(&self.get_server_type()) {
|
||||
server.set_network_reachable(reachable);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_encrypt_secret(&self, secret: String) {
|
||||
tracing::info!("🔑Set encrypt secret");
|
||||
self.encryption.write().set_secret(secret);
|
||||
}
|
||||
|
||||
fn get_authenticator(&self) -> Authenticator {
|
||||
let server_type = self.get_server_type();
|
||||
Authenticator::from(server_type)
|
||||
@ -315,16 +320,25 @@ impl DocumentCloudService for ServerProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl CollabStorageProvider for ServerProvider {
|
||||
fn storage_source(&self) -> CollabDataSource {
|
||||
impl CollabCloudPluginProvider for ServerProvider {
|
||||
fn provider_type(&self) -> CollabPluginProviderType {
|
||||
self.get_server_type().into()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, context), fields(server_type = %self.get_server_type()))]
|
||||
fn get_plugins(&self, context: CollabStorageProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
fn get_plugins(&self, context: CollabPluginProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
// If the user is local, we don't need to create a sync plugin.
|
||||
if self.user_authenticator.read().is_local() {
|
||||
debug!(
|
||||
"User authenticator is local, skip create sync plugin for: {}",
|
||||
context
|
||||
);
|
||||
return to_fut(async move { vec![] });
|
||||
}
|
||||
|
||||
match context {
|
||||
CollabStorageProviderContext::Local => to_fut(async move { vec![] }),
|
||||
CollabStorageProviderContext::AppFlowyCloud {
|
||||
CollabPluginProviderContext::Local => to_fut(async move { vec![] }),
|
||||
CollabPluginProviderContext::AppFlowyCloud {
|
||||
uid: _,
|
||||
collab_object,
|
||||
local_collab,
|
||||
@ -332,6 +346,9 @@ impl CollabStorageProvider for ServerProvider {
|
||||
if let Ok(server) = self.get_server(&Server::AppFlowyCloud) {
|
||||
to_fut(async move {
|
||||
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
|
||||
|
||||
// If the user is local, we don't need to create a sync plugin.
|
||||
|
||||
match server.collab_ws_channel(&collab_object.object_id).await {
|
||||
Ok(Some((channel, ws_connect_state, is_connected))) => {
|
||||
let origin = CollabOrigin::Client(CollabClient::new(
|
||||
@ -369,7 +386,7 @@ impl CollabStorageProvider for ServerProvider {
|
||||
to_fut(async move { vec![] })
|
||||
}
|
||||
},
|
||||
CollabStorageProviderContext::Supabase {
|
||||
CollabPluginProviderContext::Supabase {
|
||||
uid,
|
||||
collab_object,
|
||||
local_collab,
|
||||
@ -397,7 +414,7 @@ impl CollabStorageProvider for ServerProvider {
|
||||
}
|
||||
|
||||
fn is_sync_enabled(&self) -> bool {
|
||||
*self.enable_sync.read()
|
||||
*self.user_enable_sync.read()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,10 @@ pub(crate) struct UserStatusCallbackImpl {
|
||||
}
|
||||
|
||||
impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
fn authenticator_did_changed(&self, _auth_type: Authenticator) {}
|
||||
|
||||
fn did_init(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_authenticator: &Authenticator,
|
||||
cloud_config: &Option<UserCloudConfig>,
|
||||
user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
@ -44,6 +43,10 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
let database_manager = self.database_manager.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
|
||||
self
|
||||
.server_provider
|
||||
.set_user_authenticator(user_authenticator);
|
||||
|
||||
if let Some(cloud_config) = cloud_config {
|
||||
self
|
||||
.server_provider
|
||||
@ -131,6 +134,9 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
let database_manager = self.database_manager.clone();
|
||||
let user_workspace = user_workspace.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
self
|
||||
.server_provider
|
||||
.set_user_authenticator(&user_profile.authenticator);
|
||||
let server_type = self.server_provider.get_server_type();
|
||||
|
||||
to_fut(async move {
|
||||
|
@ -7,7 +7,7 @@ use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, event, info, instrument};
|
||||
|
||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabDataSource};
|
||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProviderType};
|
||||
use flowy_database2::DatabaseManager;
|
||||
use flowy_document2::manager::DocumentManager;
|
||||
use flowy_folder2::manager::FolderManager;
|
||||
@ -238,12 +238,12 @@ fn init_user_manager(
|
||||
)
|
||||
}
|
||||
|
||||
impl From<Server> for CollabDataSource {
|
||||
impl From<Server> for CollabPluginProviderType {
|
||||
fn from(server_type: Server) -> Self {
|
||||
match server_type {
|
||||
Server::Local => CollabDataSource::Local,
|
||||
Server::AppFlowyCloud => CollabDataSource::AppFlowyCloud,
|
||||
Server::Supabase => CollabDataSource::Supabase,
|
||||
Server::Local => CollabPluginProviderType::Local,
|
||||
Server::AppFlowyCloud => CollabPluginProviderType::AppFlowyCloud,
|
||||
Server::Supabase => CollabPluginProviderType::Supabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ use tracing_subscriber::{fmt::Subscriber, util::SubscriberInitExt, EnvFilter};
|
||||
use uuid::Uuid;
|
||||
|
||||
use collab_integrate::collab_builder::{
|
||||
AppFlowyCollabBuilder, CollabDataSource, CollabStorageProvider, CollabStorageProviderContext,
|
||||
AppFlowyCollabBuilder, CollabCloudPluginProvider, CollabPluginProviderContext,
|
||||
CollabPluginProviderType,
|
||||
};
|
||||
use collab_integrate::RocksCollabDB;
|
||||
use flowy_document2::document::MutexDocument;
|
||||
@ -176,12 +177,12 @@ impl FileStorageService for DocumentTestFileStorageService {
|
||||
struct DefaultCollabStorageProvider();
|
||||
|
||||
#[async_trait]
|
||||
impl CollabStorageProvider for DefaultCollabStorageProvider {
|
||||
fn storage_source(&self) -> CollabDataSource {
|
||||
CollabDataSource::Local
|
||||
impl CollabCloudPluginProvider for DefaultCollabStorageProvider {
|
||||
fn provider_type(&self) -> CollabPluginProviderType {
|
||||
CollabPluginProviderType::Local
|
||||
}
|
||||
|
||||
fn get_plugins(&self, _context: CollabStorageProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
fn get_plugins(&self, _context: CollabPluginProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
|
||||
to_fut(async move { vec![] })
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,15 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
||||
/// * `enable_sync`: A boolean indicating whether synchronization should be enabled or disabled.
|
||||
fn set_enable_sync(&self, uid: i64, enable_sync: bool);
|
||||
|
||||
/// Sets the authentication type for a user. The authentication type is the type when user sign in or sign up.
|
||||
fn set_user_authenticator(&self, authenticator: &Authenticator);
|
||||
|
||||
/// Sets the authenticator when user sign in or sign up.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `authenticator`: An `Authenticator` object.
|
||||
fn set_authenticator(&self, authenticator: Authenticator);
|
||||
|
||||
/// Sets the network reachability status.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -97,12 +106,6 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
||||
/// * `secret`: A `String` representing the encryption secret.
|
||||
fn set_encrypt_secret(&self, secret: String);
|
||||
|
||||
/// Sets the authenticator used for authentication processes.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `authenticator`: An `Authenticator` object.
|
||||
fn set_authenticator(&self, authenticator: Authenticator);
|
||||
|
||||
/// Retrieves the current authenticator.
|
||||
///
|
||||
/// # Returns
|
||||
|
@ -198,6 +198,7 @@ pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
fn did_init(
|
||||
&self,
|
||||
user_id: i64,
|
||||
user_authenticator: &Authenticator,
|
||||
cloud_config: &Option<UserCloudConfig>,
|
||||
user_workspace: &UserWorkspace,
|
||||
device_id: &str,
|
||||
@ -229,6 +230,7 @@ impl UserStatusCallback for DefaultUserStatusCallback {
|
||||
fn did_init(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
_authenticator: &Authenticator,
|
||||
_cloud_config: &Option<UserCloudConfig>,
|
||||
_user_workspace: &UserWorkspace,
|
||||
_device_id: &str,
|
||||
|
@ -112,6 +112,7 @@ impl UserManager {
|
||||
|
||||
pub fn close_db(&self) {
|
||||
if let Ok(session) = self.get_session() {
|
||||
info!("Close db for user: {}", session.user_id);
|
||||
if let Err(err) = self.database.close(session.user_id) {
|
||||
error!("Close db failed: {:?}", err);
|
||||
}
|
||||
@ -227,15 +228,12 @@ impl UserManager {
|
||||
},
|
||||
_ => error!("Failed to get collab db or sqlite pool"),
|
||||
}
|
||||
// Init the user awareness
|
||||
self
|
||||
.initialize_user_awareness(&session, UserAwarenessDataSource::Local)
|
||||
.await;
|
||||
|
||||
let cloud_config = get_cloud_config(session.user_id, &self.store_preferences);
|
||||
if let Err(e) = user_status_callback
|
||||
.did_init(
|
||||
session.user_id,
|
||||
user.uid,
|
||||
&user.authenticator,
|
||||
&cloud_config,
|
||||
&session.user_workspace,
|
||||
&self.user_config.device_id,
|
||||
@ -244,6 +242,10 @@ impl UserManager {
|
||||
{
|
||||
error!("Failed to call did_init callback: {:?}", e);
|
||||
}
|
||||
// Init the user awareness
|
||||
self
|
||||
.initialize_user_awareness(&session, UserAwarenessDataSource::Local)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -312,17 +314,11 @@ impl UserManager {
|
||||
send_auth_state_notification(AuthStateChangedPB {
|
||||
state: AuthStatePB::AuthStateSignIn,
|
||||
message: "Sign in success".to_string(),
|
||||
})
|
||||
.send();
|
||||
});
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_authenticator(&self, authenticator: &Authenticator) {
|
||||
self
|
||||
.user_status_callback
|
||||
.read()
|
||||
.await
|
||||
.authenticator_did_changed(authenticator.clone());
|
||||
self.cloud_services.set_authenticator(authenticator.clone());
|
||||
}
|
||||
|
||||
@ -422,9 +418,6 @@ impl UserManager {
|
||||
let _ = self.database.close(old_user.session.user_id);
|
||||
}
|
||||
}
|
||||
self
|
||||
.initialize_user_awareness(&new_session, user_awareness_source)
|
||||
.await;
|
||||
|
||||
self
|
||||
.save_auth_data(&response, authenticator, &new_session)
|
||||
@ -442,11 +435,14 @@ impl UserManager {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self
|
||||
.initialize_user_awareness(&new_session, user_awareness_source)
|
||||
.await;
|
||||
|
||||
send_auth_state_notification(AuthStateChangedPB {
|
||||
state: AuthStatePB::AuthStateSignIn,
|
||||
message: "Sign up success".to_string(),
|
||||
})
|
||||
.send();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -575,8 +571,7 @@ impl UserManager {
|
||||
send_auth_state_notification(AuthStateChangedPB {
|
||||
state: AuthStatePB::InvalidAuth,
|
||||
message: "User is not found on the server".to_string(),
|
||||
})
|
||||
.send();
|
||||
});
|
||||
}
|
||||
Err(err)
|
||||
},
|
||||
@ -676,13 +671,14 @@ impl UserManager {
|
||||
}
|
||||
|
||||
pub(crate) fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||
debug!("Set current user: {:?}", session);
|
||||
debug!("Set current user session: {:?}", session);
|
||||
match &session {
|
||||
None => {
|
||||
self.current_session.write().take();
|
||||
self
|
||||
.store_preferences
|
||||
.remove(&self.user_config.session_cache_key)
|
||||
.remove(self.user_config.session_cache_key.as_ref());
|
||||
Ok(())
|
||||
},
|
||||
Some(session) => {
|
||||
self.current_session.write().replace(session.clone());
|
||||
@ -690,9 +686,9 @@ impl UserManager {
|
||||
.store_preferences
|
||||
.set_object(&self.user_config.session_cache_key, session.clone())
|
||||
.map_err(internal_error)?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_sign_in_url_with_email(
|
||||
@ -740,10 +736,10 @@ impl UserManager {
|
||||
|
||||
save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?;
|
||||
event!(tracing::Level::INFO, "Save new user profile to disk");
|
||||
self.set_session(Some(session.clone()))?;
|
||||
self
|
||||
.save_user(uid, (user_profile, authenticator.clone()).into())
|
||||
.await?;
|
||||
self.set_session(Some(session.clone()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,12 @@ pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationB
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub(crate) fn send_auth_state_notification(payload: AuthStateChangedPB) -> NotificationBuilder {
|
||||
pub(crate) fn send_auth_state_notification(payload: AuthStateChangedPB) {
|
||||
NotificationBuilder::new(
|
||||
"auth_state_change_notification",
|
||||
UserNotification::UserAuthStateChanged,
|
||||
USER_OBSERVABLE_SOURCE,
|
||||
)
|
||||
.payload(payload)
|
||||
.send()
|
||||
}
|
||||
|
@ -73,8 +73,7 @@ pub(crate) fn validate_encryption_sign(user_profile: &UserProfile, encryption_si
|
||||
send_auth_state_notification(AuthStateChangedPB {
|
||||
state: AuthStatePB::InvalidAuth,
|
||||
message: "Encryption configuration was changed".to_string(),
|
||||
})
|
||||
.send();
|
||||
});
|
||||
}
|
||||
is_valid
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user