2023-08-12 17:36:31 +08:00
use std ::collections ::HashMap ;
2023-08-17 23:46:39 +08:00
use std ::sync ::{ Arc , Weak } ;
2023-05-21 18:53:59 +08:00
2023-08-12 17:36:31 +08:00
use collab_plugins ::cloud_storage ::{ CollabObject , RemoteCollabStorage , RemoteUpdateSender } ;
use parking_lot ::{ Mutex , RwLock } ;
use serde_json ::Value ;
2023-05-21 18:53:59 +08:00
2023-07-29 09:46:24 +08:00
use flowy_database_deps ::cloud ::DatabaseCloudService ;
use flowy_document_deps ::cloud ::DocumentCloudService ;
2023-08-18 15:13:34 +08:00
use flowy_encrypt ::decrypt_bytes ;
2023-07-29 09:46:24 +08:00
use flowy_folder_deps ::cloud ::FolderCloudService ;
use flowy_server_config ::supabase_config ::SupabaseConfiguration ;
use flowy_user_deps ::cloud ::UserService ;
2023-05-21 18:53:59 +08:00
2023-07-29 09:46:24 +08:00
use crate ::supabase ::api ::{
2023-08-14 12:57:59 +08:00
RESTfulPostgresServer , SupabaseCollabStorageImpl , SupabaseDatabaseServiceImpl ,
SupabaseDocumentServiceImpl , SupabaseFolderServiceImpl , SupabaseServerServiceImpl ,
SupabaseUserServiceImpl ,
2023-07-05 20:57:09 +08:00
} ;
2023-08-12 17:36:31 +08:00
use crate ::supabase ::entities ::RealtimeCollabUpdateEvent ;
2023-08-17 23:46:39 +08:00
use crate ::{ AppFlowyEncryption , AppFlowyServer } ;
2023-05-21 18:53:59 +08:00
2023-07-29 09:46:24 +08:00
/// https://www.pgbouncer.org/features.html
/// Only support session mode.
///
/// Session mode:
/// When a new client connects, a connection is assigned to the client until it disconnects. Afterward,
/// the connection is returned back to the pool. All PostgreSQL features can be used with this option.
/// For the moment, the default pool size of pgbouncer in supabase is 15 in session mode. Which means
/// that we can have 15 concurrent connections to the database.
///
/// Transaction mode:
/// This is the suggested option for serverless functions. With this, the connection is only assigned
/// to the client for the duration of a transaction. Once done, the connection is returned to the pool.
/// Two consecutive transactions from the same client could be done over two, different connections.
/// Some session-based PostgreSQL features such as prepared statements are not available with this option.
/// A more comprehensive list of incompatible features can be found here.
///
/// Most of the case, Session mode is faster than Transaction mode(no statement cache(https://github.com/supabase/supavisor/issues/69) and queue transaction).
/// But Transaction mode is more suitable for serverless functions. It can reduce the number of concurrent
/// connections to the database.
/// TODO(nathan): fix prepared statement error when using transaction mode. https://github.com/prisma/prisma/issues/11643
///
#[ derive(Clone, Debug, Default) ]
pub enum PgPoolMode {
#[ default ]
Session ,
Transaction ,
}
impl PgPoolMode {
pub fn support_prepare_cached ( & self ) -> bool {
matches! ( self , PgPoolMode ::Session )
}
}
2023-07-05 20:57:09 +08:00
/// Supabase server is used to provide the implementation of the [AppFlowyServer] trait.
/// It contains the configuration of the supabase server and the postgres server.
pub struct SupabaseServer {
#[ allow(dead_code) ]
config : SupabaseConfiguration ,
2023-08-18 15:13:34 +08:00
/// did represents as the device id is used to identify the device that is currently using the app.
did : Mutex < String > ,
2023-08-12 17:36:31 +08:00
update_tx : RwLock < HashMap < String , RemoteUpdateSender > > ,
2023-07-29 09:46:24 +08:00
restful_postgres : Arc < RwLock < Option < Arc < RESTfulPostgresServer > > > > ,
2023-08-17 23:46:39 +08:00
encryption : Weak < dyn AppFlowyEncryption > ,
2023-07-05 20:57:09 +08:00
}
impl SupabaseServer {
2023-08-17 23:46:39 +08:00
pub fn new (
config : SupabaseConfiguration ,
enable_sync : bool ,
encryption : Weak < dyn AppFlowyEncryption > ,
) -> Self {
2023-08-12 17:36:31 +08:00
let update_tx = RwLock ::new ( HashMap ::new ( ) ) ;
2023-08-17 23:46:39 +08:00
let restful_postgres = if enable_sync {
Some ( Arc ::new ( RESTfulPostgresServer ::new (
config . clone ( ) ,
encryption . clone ( ) ,
) ) )
2023-07-14 13:37:13 +08:00
} else {
None
} ;
2023-07-05 20:57:09 +08:00
Self {
config ,
2023-08-18 15:13:34 +08:00
did : Default ::default ( ) ,
2023-08-12 17:36:31 +08:00
update_tx ,
2023-07-29 09:46:24 +08:00
restful_postgres : Arc ::new ( RwLock ::new ( restful_postgres ) ) ,
2023-08-17 23:46:39 +08:00
encryption ,
2023-07-14 13:37:13 +08:00
}
}
pub fn set_enable_sync ( & self , enable : bool ) {
if enable {
2023-07-29 09:46:24 +08:00
if self . restful_postgres . read ( ) . is_some ( ) {
2023-07-14 13:37:13 +08:00
return ;
}
2023-08-17 23:46:39 +08:00
let postgres = RESTfulPostgresServer ::new ( self . config . clone ( ) , self . encryption . clone ( ) ) ;
* self . restful_postgres . write ( ) = Some ( Arc ::new ( postgres ) ) ;
2023-07-14 13:37:13 +08:00
} else {
2023-07-29 09:46:24 +08:00
* self . restful_postgres . write ( ) = None ;
2023-07-05 20:57:09 +08:00
}
}
2023-05-21 18:53:59 +08:00
}
2023-07-05 20:57:09 +08:00
impl AppFlowyServer for SupabaseServer {
2023-08-17 23:46:39 +08:00
fn set_enable_sync ( & self , enable : bool ) {
2023-07-14 13:37:13 +08:00
tracing ::info! ( " supabase sync: {} " , enable ) ;
self . set_enable_sync ( enable ) ;
}
2023-08-12 17:36:31 +08:00
fn set_sync_device_id ( & self , device_id : & str ) {
2023-08-18 15:13:34 +08:00
* self . did . lock ( ) = device_id . to_string ( ) ;
2023-08-12 17:36:31 +08:00
}
2023-07-29 09:46:24 +08:00
fn user_service ( & self ) -> Arc < dyn UserService > {
2023-08-14 12:57:59 +08:00
Arc ::new ( SupabaseUserServiceImpl ::new ( SupabaseServerServiceImpl (
self . restful_postgres . clone ( ) ,
) ) )
2023-07-05 20:57:09 +08:00
}
fn folder_service ( & self ) -> Arc < dyn FolderCloudService > {
2023-08-05 15:02:05 +08:00
Arc ::new ( SupabaseFolderServiceImpl ::new ( SupabaseServerServiceImpl (
self . restful_postgres . clone ( ) ,
) ) )
2023-07-05 20:57:09 +08:00
}
fn database_service ( & self ) -> Arc < dyn DatabaseCloudService > {
2023-08-05 15:02:05 +08:00
Arc ::new ( SupabaseDatabaseServiceImpl ::new ( SupabaseServerServiceImpl (
self . restful_postgres . clone ( ) ,
) ) )
2023-07-05 20:57:09 +08:00
}
fn document_service ( & self ) -> Arc < dyn DocumentCloudService > {
2023-08-05 15:02:05 +08:00
Arc ::new ( SupabaseDocumentServiceImpl ::new ( SupabaseServerServiceImpl (
self . restful_postgres . clone ( ) ,
) ) )
2023-05-21 18:53:59 +08:00
}
2023-05-23 23:55:21 +08:00
2023-08-12 17:36:31 +08:00
fn collab_storage ( & self , collab_object : & CollabObject ) -> Option < Arc < dyn RemoteCollabStorage > > {
let ( tx , rx ) = tokio ::sync ::mpsc ::unbounded_channel ( ) ;
self
. update_tx
. write ( )
. insert ( collab_object . object_id . clone ( ) , tx ) ;
2023-08-05 15:02:05 +08:00
Some ( Arc ::new ( SupabaseCollabStorageImpl ::new (
2023-07-29 09:46:24 +08:00
SupabaseServerServiceImpl ( self . restful_postgres . clone ( ) ) ,
2023-08-12 17:36:31 +08:00
Some ( rx ) ,
2023-08-17 23:46:39 +08:00
self . encryption . clone ( ) ,
2023-07-14 13:37:13 +08:00
) ) )
}
2023-08-12 17:36:31 +08:00
fn handle_realtime_event ( & self , json : Value ) {
match serde_json ::from_value ::< RealtimeCollabUpdateEvent > ( json ) {
Ok ( event ) = > {
2023-08-18 15:13:34 +08:00
if let ( Some ( tx ) , Some ( secret ) ) = (
self . update_tx . read ( ) . get ( event . payload . oid . as_str ( ) ) ,
self
. encryption
. upgrade ( )
. and_then ( | encryption | encryption . get_secret ( ) ) ,
) {
if self . did . lock ( ) . as_str ( ) ! = event . payload . did . as_str ( ) {
tracing ::trace! ( " Did receive realtime event: {} " , event ) ;
let value = if event . payload . encrypt = = 1 {
decrypt_bytes ( event . payload . value , & secret ) . unwrap_or_default ( )
} else {
event . payload . value
} ;
if ! value . is_empty ( ) {
tracing ::trace! ( " Parse payload with len: {} success " , value . len ( ) ) ;
if let Err ( e ) = tx . send ( value ) {
tracing ::trace! ( " send realtime update error: {} " , e ) ;
}
2023-08-12 17:36:31 +08:00
}
}
}
} ,
Err ( e ) = > {
tracing ::error! ( " parser realtime event error: {} " , e ) ;
} ,
}
}
2023-07-14 13:37:13 +08:00
}