2021-07-21 10:23:14 +02:00
use juniper ::{
EmptySubscription ,
ID ,
RootNode ,
graphql_object ,
GraphQLObject ,
2021-10-17 01:52:58 +02:00
GraphQLInputObject ,
2021-07-21 10:23:14 +02:00
GraphQLEnum ,
2021-07-27 21:11:40 +02:00
FieldResult ,
FieldError ,
2021-07-21 10:23:14 +02:00
} ;
use sqlx ::{
types ::chrono ::{
DateTime ,
Utc ,
} ,
2021-07-27 21:11:40 +02:00
FromRow ,
Encode ,
Decode ,
Type ,
Row ,
Result as SqlxResult ,
Error as SqlxError ,
} ;
#[ cfg(feature = " sqlite " ) ]
use sqlx ::{
SqlitePool ,
sqlite ::SqliteRow ,
2021-07-21 10:23:14 +02:00
} ;
use url ::Url ;
2021-10-17 01:52:58 +02:00
use uuid ::Uuid ;
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
fn into_user_limit (
#[ cfg(feature = " sqlite " ) ]
row : & SqliteRow ,
index : & str
2021-10-17 01:52:58 +02:00
) -> SqlxResult < Vec < String > > {
let row_value : & str = row . try_get ( index ) ? ;
into_toml_iter ( row_value , index ) . map ( | i | i . collect ( ) )
2021-07-27 21:11:40 +02:00
}
fn into_server_limit (
#[ cfg(feature = " sqlite " ) ]
row : & SqliteRow ,
index : & str
2021-10-17 01:52:58 +02:00
) -> SqlxResult < Vec < Url > > {
let row_value : & str = row . try_get ( index ) ? ;
let mut url_vector = Vec ::new ( ) ;
for s in into_toml_iter ( row_value , index ) ? {
url_vector . push ( Url ::parse ( s ) . map_err ( into_column_decode_err ( index ) ) ? ) ;
2021-07-27 21:11:40 +02:00
}
2021-10-17 01:52:58 +02:00
Ok ( url_vector )
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[ inline ]
fn into_column_decode_err < E > ( index : & str ) -> impl FnOnce ( E ) -> SqlxError + '_
where
E : Into < Box < dyn std ::error ::Error + Send + Sync > >
{
move | err : E | SqlxError ::ColumnDecode {
index : index . to_owned ( ) ,
source : err . into ( ) ,
}
}
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
#[ inline ]
fn into_toml_iter < ' d , T > ( raw : & ' d str , index : & str ) -> SqlxResult < impl Iterator < Item = T > >
where
T : serde ::de ::Deserialize < ' d >
{
Ok ( toml ::from_str ::< Vec < T > > ( raw )
. map_err ( into_column_decode_err ( index ) ) ?
. into_iter ( )
)
}
#[ inline ]
fn into_toml_iter_from_row < ' d , T > (
#[ cfg(feature = " sqlite " ) ]
row : & ' d SqliteRow ,
index : & str
) -> SqlxResult < impl Iterator < Item = T > >
where
T : serde ::de ::Deserialize < ' d >
{
into_toml_iter ( row . try_get ( index ) ? , index )
}
2021-10-17 01:52:58 +02:00
fn id_to_uuid ( id : & ID ) -> Result < Uuid , uuid ::Error > {
use std ::str ::FromStr ;
Uuid ::from_str ( & id )
}
2021-07-27 21:11:40 +02:00
#[ derive(Clone, Debug) ]
pub struct Context {
#[ cfg(feature = " sqlite " ) ]
2021-09-26 16:59:56 +02:00
pub db : SqlitePool ,
2021-07-27 21:11:40 +02:00
}
impl juniper ::Context for Context { }
#[ derive(Clone, Debug, Decode, Type) ]
2021-07-21 10:23:14 +02:00
pub struct User {
2021-10-17 01:52:58 +02:00
// can we change this to Uuid?
pub id : ID ,
pub user_name : String ,
pub display_name : Option < String > ,
pub activated : bool ,
pub created : DateTime < Utc > ,
pub last_online : Option < DateTime < Utc > > ,
pub preferences : UserPreferences ,
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[ graphql_object(context = Context) ]
impl User {
pub const fn id ( & self ) -> & ID {
& self . id
}
pub const fn user_name ( & self ) -> & String {
& self . user_name
}
pub const fn display_name ( & self ) -> Option < & String > {
self . display_name . as_ref ( )
}
pub const fn activated ( & self ) -> bool {
self . activated
}
pub const fn created ( & self ) -> & DateTime < Utc > {
& self . created
}
pub const fn last_online ( & self ) -> Option < & DateTime < Utc > > {
self . last_online . as_ref ( )
}
pub async fn preferences ( & self , context : & Context ) -> FieldResult < UserPreferences > {
UserPreferences ::try_get ( & self . id , & context . db ) . await . map_err ( FieldError ::from )
}
}
impl User {
async fn from_row_with_db (
#[ cfg(feature = " sqlite " ) ]
row : & SqliteRow ,
#[ cfg(feature = " sqlite " ) ]
db : & SqlitePool ,
) -> Result < Self , SqlxError > {
let id = row . try_get ::< String , _ > ( " id " ) ? . into ( ) ;
Ok ( User {
user_name : row . try_get ( " user_name " ) ? ,
display_name : row . try_get ( " display_name " ) ? ,
activated : row . try_get ( " activated " ) ? ,
created : row . try_get ( " created " ) ? ,
last_online : row . try_get ( " last_online " ) ? ,
preferences : UserPreferences ::try_get ( & id , db ) . await ? ,
id ,
} )
}
}
2021-10-17 01:52:58 +02:00
#[ derive(Clone, Debug, GraphQLInputObject) ]
pub struct NewUser {
pub ( crate ) user_name : String ,
pub ( crate ) display_name : Option < String > ,
pub ( crate ) password_hash : String ,
}
impl From < NewUser > for User {
fn from ( new_user : NewUser ) -> Self {
User {
// TODO: how do we generate uuids?
id : ID ::new ( Uuid ::new_v4 ( ) . to_string ( ) ) ,
user_name : new_user . user_name ,
display_name : new_user . display_name ,
activated : false ,
created : Utc ::now ( ) ,
last_online : None ,
preferences : UserPreferences {
privacy_preferences : Default ::default ( ) ,
security_preferences : SecurityPreferences {
account_tokens : Vec ::new ( ) ,
password_hash : new_user . password_hash ,
} ,
notification_preferences : Default ::default ( ) ,
external_servers_preferences : Default ::default ( ) ,
} ,
}
}
}
2021-07-27 21:11:40 +02:00
#[ derive(Clone, Debug, GraphQLObject, Decode, Encode, FromRow) ]
2021-07-21 10:23:14 +02:00
pub struct UserPreferences {
privacy_preferences : PrivacyPreferences ,
notification_preferences : NotificationPreferences ,
security_preferences : SecurityPreferences ,
external_servers_preferences : ExternalServersPreferences ,
}
2021-07-27 21:11:40 +02:00
impl UserPreferences {
async fn try_get ( id : & ID , db : & SqlitePool ) -> SqlxResult < Self > {
let privacy_preferences : PrivacyPreferences =
sqlx ::query_as ( " SELECT * FROM privacy_preferences WHERE users.id = privacy_preferences.id " )
. fetch_one ( db ) . await ? ;
let notification_preferences : NotificationPreferences =
sqlx ::query_as ( " SELECT * FROM notification_preferences WHERE users.id = privacy_preferences.id " )
. fetch_one ( db ) . await ? ;
let security_preferences : SecurityPreferences =
sqlx ::query_as ( " SELECT * FROM security_preferences WHERE users.id = privacy_preferences.id " )
. fetch_one ( db ) . await ? ;
let external_servers_preferences = ExternalServersPreferences ::from_row_with_db (
sqlx ::query ( & * format! ( " SELECT * FROM external_server_preferences WHERE id = {} " , id ) )
. fetch_one ( db ) . await ? ,
db ,
) . await ? ;
Ok ( UserPreferences {
privacy_preferences ,
notification_preferences ,
security_preferences ,
external_servers_preferences ,
} )
}
}
2021-07-21 10:23:14 +02:00
#[ derive(Clone, Debug, GraphQLObject) ]
pub struct PrivacyPreferences {
discovery : RestrictionPolicy ,
2021-10-17 01:52:58 +02:00
discovery_user_limit : Vec < String > ,
discovery_server_limit : Vec < Url > ,
2021-07-21 10:23:14 +02:00
last_seen : RestrictionPolicy ,
2021-10-17 01:52:58 +02:00
last_seen_user_limit : Vec < String > ,
last_seen_server_limit : Vec < Url > ,
2021-07-21 10:23:14 +02:00
last_seen_course : bool ,
info : RestrictionPolicy ,
2021-10-17 01:52:58 +02:00
info_user_limit : Vec < String > ,
info_server_limit : Vec < Url > ,
}
impl Default for PrivacyPreferences {
fn default ( ) -> Self {
PrivacyPreferences {
discovery : Default ::default ( ) ,
discovery_user_limit : Default ::default ( ) ,
discovery_server_limit : Default ::default ( ) ,
last_seen : Default ::default ( ) ,
last_seen_user_limit : Default ::default ( ) ,
last_seen_server_limit : Default ::default ( ) ,
last_seen_course : true ,
info : Default ::default ( ) ,
info_user_limit : Default ::default ( ) ,
info_server_limit : Default ::default ( ) ,
}
}
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
impl PrivacyPreferences {
fn from_row_cfg (
#[ cfg(feature = " sqlite " ) ]
row : & SqliteRow ,
) -> SqlxResult < Self > {
Ok ( PrivacyPreferences {
discovery : row . try_get ( " discovery " ) ? ,
discovery_user_limit : into_user_limit ( row , " discovery_user_limit " ) ? ,
discovery_server_limit : into_server_limit ( row , " discovery_server_limit " ) ? ,
last_seen : row . try_get ( " last_seen " ) ? ,
last_seen_user_limit : into_user_limit ( row , " last_seen_user_limit " ) ? ,
last_seen_server_limit : into_server_limit ( row , " last_seen_server_limit " ) ? ,
last_seen_course : row . try_get ( " last_seen_course " ) ? ,
info : row . try_get ( " info " ) ? ,
info_user_limit : into_user_limit ( row , " info_user_limit " ) ? ,
info_server_limit : into_server_limit ( row , " info_server_limit " ) ? ,
} )
}
}
#[ cfg(feature = " sqlite " ) ]
impl FromRow < '_ , SqliteRow > for PrivacyPreferences {
fn from_row ( row : & SqliteRow ) -> SqlxResult < Self > {
Self ::from_row_cfg ( row )
}
}
2021-10-17 01:52:58 +02:00
#[ derive(Clone, Copy, Debug, Default, GraphQLObject, Decode, Encode, FromRow) ]
2021-07-21 10:23:14 +02:00
pub struct NotificationPreferences {
lock_details : bool ,
do_not_disturb : bool ,
}
2021-07-27 21:11:40 +02:00
#[ derive(Clone, Debug, GraphQLObject, Decode, Encode) ]
2021-07-21 10:23:14 +02:00
pub struct SecurityPreferences {
account_tokens : Vec < ID > ,
password_hash : String ,
}
2021-07-27 21:11:40 +02:00
impl SecurityPreferences {
fn from_row_cfg (
#[ cfg(feature = " sqlite " ) ]
row : & SqliteRow ,
) -> SqlxResult < Self > {
Ok ( SecurityPreferences {
account_tokens : into_toml_iter_from_row ::< & str > ( row , " account_tokens " ) ?
. map ( ID ::new )
. collect ( ) ,
password_hash : row . try_get ( " password_hash " ) ? ,
} )
}
}
#[ cfg(feature = " sqlite " ) ]
impl FromRow < '_ , SqliteRow > for SecurityPreferences {
fn from_row ( row : & SqliteRow ) -> SqlxResult < Self > {
Self ::from_row_cfg ( row )
}
}
2021-10-17 01:52:58 +02:00
#[ derive(Clone, Debug, Default, GraphQLObject, Decode, Encode, FromRow) ]
2021-07-21 10:23:14 +02:00
pub struct ExternalServersPreferences {
privacy_preferences : PrivacyPreferences ,
external_servers : RestrictionPolicy ,
2021-10-17 01:52:58 +02:00
external_servers_limit : Vec < Url > ,
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
impl ExternalServersPreferences {
async fn from_row_with_db (
#[ cfg(feature = " sqlite " ) ]
row : SqliteRow ,
#[ cfg(feature = " sqlite " ) ]
db : & SqlitePool ,
) -> SqlxResult < Self > {
Ok ( Self {
privacy_preferences :
sqlx ::query_as ( & * format! ( " SELECT * FROM external_server_privacy_preferences WHERE external_servers_preferences.id = external_servers_privacy_preferences.id && id = {} " , row . try_get ::< String , _ > ( " id " ) ? ) )
. fetch_one ( db ) . await ? ,
external_servers : row . try_get ( " external_servers " ) ? ,
external_servers_limit : into_server_limit ( & row , " external_server_limit " ) ? ,
} )
}
}
#[ derive(Clone, Copy, Debug, GraphQLEnum, Type) ]
2021-07-21 10:23:14 +02:00
pub enum RestrictionPolicy {
2021-07-27 21:11:40 +02:00
Everyone = 0 ,
Excluding = 1 ,
Including = 2 ,
None = 3 ,
2021-07-21 10:23:14 +02:00
}
2021-10-17 01:52:58 +02:00
impl Default for RestrictionPolicy {
fn default ( ) -> Self {
Self ::Everyone
}
}
2021-07-21 10:23:14 +02:00
#[ derive(Clone, Copy, Debug) ]
2021-07-27 21:11:40 +02:00
pub struct Query ;
impl Query {
async fn users_sqlx_result ( context : & Context ) -> SqlxResult < Vec < User > > {
let rows = sqlx ::query ( " SELECT * FROM users " )
. fetch_all ( & context . db ) . await ? ;
let mut users = Vec ::with_capacity ( rows . capacity ( ) ) ;
for row in rows {
users . push ( User ::from_row_with_db ( & row , & context . db ) . await ? ) ;
}
Ok ( users )
}
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[ graphql_object(context = Context) ]
impl Query {
2021-10-17 18:28:50 +02:00
async fn get_user_id ( context : & Context , username : String ) -> FieldResult < ID > {
sqlx ::query ( format! (
r # "SELECT id FROM users WHERE user_name="{}""# ,
username
) . as_str ( ) ) . fetch_one ( & context . db ) . await ?
. try_get ::< String , _ > ( " id " )
. map ( ID ::new )
. map_err ( FieldError ::from )
}
2021-07-27 21:11:40 +02:00
async fn users ( context : & Context ) -> FieldResult < Vec < User > > {
Self ::users_sqlx_result ( context ) . await . map_err ( FieldError ::from )
}
async fn user_preferences ( context : & Context , id : ID ) -> FieldResult < UserPreferences > {
UserPreferences ::try_get ( & id , & context . db ) . await . map_err ( FieldError ::from )
2021-07-21 10:23:14 +02:00
}
}
2021-10-17 01:52:58 +02:00
#[ derive(Clone, Copy, Debug) ]
pub struct Mutation ;
#[ graphql_object(context = Context) ]
impl Mutation {
2021-10-17 18:58:04 +02:00
async fn createUser ( context : & Context , new_user : NewUser ) -> FieldResult < ID > {
2021-10-17 01:52:58 +02:00
let user : User = new_user . into ( ) ;
if sqlx ::query (
format! ( r # "SELECT id FROM users WHERE user_name="{}""# , user . user_name ) . as_str ( )
) . fetch_all ( & context . db ) . await ? . len ( ) > 0 {
return Err ( " username is already in use " . into ( ) ) ;
}
sqlx ::query ( format! (
r # "INSERT INTO users VALUES ("{}", "{}", {}, {}, {}, {})"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
user . user_name ,
user . display_name . map ( | x | format! ( r # ""{}""# , x ) ) . unwrap_or ( " NULL " . to_string ( ) ) ,
user . activated as u8 ,
user . created . timestamp ( ) ,
match user . last_online {
None = > " NULL " . to_string ( ) ,
Some ( d ) = > format! ( " {} " , d . timestamp ( ) ) ,
} ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
let privacy_preferences = user . preferences . privacy_preferences ;
sqlx ::query ( format! (
r # "INSERT INTO privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
privacy_preferences . discovery as u8 ,
privacy_preferences . discovery_user_limit . join ( " , " ) ,
privacy_preferences . discovery_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
privacy_preferences . last_seen as u8 ,
privacy_preferences . last_seen_user_limit . join ( " , " ) ,
privacy_preferences . last_seen_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
privacy_preferences . last_seen_course as u8 ,
privacy_preferences . info as u8 ,
privacy_preferences . info_user_limit . join ( " , " ) ,
privacy_preferences . info_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
let notification_preferences = user . preferences . notification_preferences ;
sqlx ::query ( format! (
r # "INSERT INTO notification_preferences VALUES ("{}", {}, {})"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
notification_preferences . lock_details as u8 ,
notification_preferences . do_not_disturb as u8 ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
let security_preferences = user . preferences . security_preferences ;
sqlx ::query ( format! (
r # "INSERT INTO security_preferences VALUES ("{}", "{}", "{}")"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
security_preferences . account_tokens . iter ( ) . map ( | id | id . as_ref ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
security_preferences . password_hash ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
let external_servers_preferences = user . preferences . external_servers_preferences ;
sqlx ::query ( format! (
r # "INSERT INTO external_servers_preferences VALUES ("{}", {}, "{}")"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
external_servers_preferences . external_servers as u8 ,
external_servers_preferences . external_servers_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
let external_servers_privacy_preferences = external_servers_preferences . privacy_preferences ;
sqlx ::query ( format! (
r # "INSERT INTO external_servers_privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"# ,
id_to_uuid ( & user . id ) ? . to_simple ( ) ,
external_servers_privacy_preferences . discovery as u8 ,
external_servers_privacy_preferences . discovery_user_limit . join ( " , " ) ,
external_servers_privacy_preferences . discovery_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
external_servers_privacy_preferences . last_seen as u8 ,
external_servers_privacy_preferences . last_seen_user_limit . join ( " , " ) ,
external_servers_privacy_preferences . last_seen_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
external_servers_privacy_preferences . last_seen_course as u8 ,
external_servers_privacy_preferences . info as u8 ,
external_servers_privacy_preferences . info_user_limit . join ( " , " ) ,
external_servers_privacy_preferences . info_server_limit . iter ( ) . map ( | u | u . to_string ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ,
) . as_str ( ) ) . execute ( & context . db ) . await ? ;
2021-10-17 18:58:04 +02:00
Ok ( ID ::new ( id_to_uuid ( & user . id ) ? . to_simple ( ) . to_string ( ) ) )
2021-10-17 01:52:58 +02:00
}
}
pub type Schema < ' root_node > = RootNode < ' root_node , Query , Mutation , EmptySubscription < Context > > ;
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
pub fn schema < ' root_node > ( ) -> Schema < ' root_node > {
2021-10-17 01:52:58 +02:00
Schema ::new ( Query , Mutation , EmptySubscription ::new ( ) )
2021-07-21 10:23:14 +02:00
}