2023-12-12 20:39:15 +01:00
use crate ::{ db , AppState } ;
use actix_web ::{ delete , get , post , put , web , HttpRequest , HttpResponse , Responder } ;
use std ::collections ::HashMap ;
use std ::ops ::Deref ;
use rand ::distributions ::Alphanumeric ;
use rand ::{ Rng , thread_rng } ;
use sqlx ::QueryBuilder ;
pub ( crate ) mod schemas ;
2023-12-12 20:49:39 +01:00
fn get_auth_header ( req : & HttpRequest ) -> Option < & str > {
2023-12-12 20:39:15 +01:00
req . headers ( ) . get ( " x-api-key " ) ? . to_str ( ) . ok ( )
}
async fn verify_user_auth ( data : & web ::Data < AppState > , auth_token : & str , user_token : & str , table : schemas ::TablePermission , permissions_only : bool ) -> bool {
let user : db ::schemas ::User = match sqlx ::query_as! (
db ::schemas ::User ,
" SELECT * FROM public.users WHERE token = $1 " ,
auth_token
)
. fetch_one ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return false ,
} ;
if user . token = = user_token & & ! permissions_only {
return true ;
}
else {
match table {
schemas ::TablePermission ::Game = > {
return user . data_permissions ;
} ,
schemas ::TablePermission ::Empire = > {
return user . empire_permissions ;
} ,
schemas ::TablePermission ::Data = > {
return user . data_permissions ;
} ,
schemas ::TablePermission ::User = > {
return user . user_permissions ;
} ,
}
}
}
// User Endpoints
#[ utoipa::path(
request_body = schemas ::GetUserParams ,
responses (
( status = 200 , description = " OK " , body = User ) ,
( status = 403 , description = " Unauthorized " ) ,
( status = 500 , description = " Internal Server Error " )
) ,
security (
( " api_key " = [ ] )
) ,
) ]
#[ get( " /api/v1/user " ) ]
async fn get_user (
data : web ::Data < AppState > ,
params : web ::Json < schemas ::GetUserParams > ,
req : HttpRequest ,
) -> impl Responder {
let auth_header = get_auth_header ( & req ) ;
let params = params . into_inner ( ) ;
let auth_token : String ;
match auth_header {
Some ( token ) = > auth_token = token . to_string ( ) ,
None = > return HttpResponse ::Unauthorized ( ) . finish ( ) ,
} ;
let auth = verify_user_auth ( & data , & auth_token , & params . user_token , schemas ::TablePermission ::User , false ) . await ;
if auth {
let user : db ::schemas ::User = match sqlx ::query_as! (
db ::schemas ::User ,
" SELECT * FROM public.users WHERE token = $1 " ,
params . user_token
)
. fetch_one ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return HttpResponse ::InternalServerError ( ) . finish ( ) ,
} ;
2023-12-12 20:49:39 +01:00
let mut user_permissions : HashMap < String , bool > = HashMap ::new ( ) ;
2023-12-12 20:39:15 +01:00
2023-12-12 20:49:39 +01:00
user_permissions . insert ( " game_permissions " . to_string ( ) , user . game_permissions ) ;
user_permissions . insert ( " empire_permissions " . to_string ( ) , user . empire_permissions ) ;
user_permissions . insert ( " data_permissions " . to_string ( ) , user . data_permissions ) ;
user_permissions . insert ( " user_permissions " . to_string ( ) , user . user_permissions ) ;
2023-12-12 20:39:15 +01:00
let return_data = schemas ::User {
user_token : user . token ,
discord_handle : user . discord_id ,
profile_picture : user . picture_url ,
2023-12-12 20:49:39 +01:00
permissions : user_permissions
2023-12-12 20:39:15 +01:00
} ;
return HttpResponse ::Ok ( ) . json ( return_data ) ;
}
else {
return HttpResponse ::Unauthorized ( ) . finish ( ) ;
}
}
#[ utoipa::path(
responses (
( status = 200 , description = " OK " , body = User ) ,
( status = 500 , description = " Internal Server Error " )
) ,
) ]
#[ post( " /api/v1/user " ) ]
pub ( crate ) async fn create_user (
data : web ::Data < AppState > ,
) -> impl Responder {
let user : db ::schemas ::User ;
let mut rng = thread_rng ( ) ;
let user_tokens = match sqlx ::query_scalar! (
" SELECT token FROM public.users "
)
. fetch_all ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return HttpResponse ::InternalServerError ( ) . finish ( ) ,
} ;
let new_token : String ;
loop {
let mut chars : String = ( 0 .. 6 ) . map ( | _ | rng . sample ( Alphanumeric ) as char ) . collect ( ) ;
chars = chars . to_uppercase ( ) ;
if ! user_tokens . contains ( & chars ) {
new_token = chars ;
break ;
}
else {
println! ( " looping " ) ;
}
}
user = match sqlx ::query_as! (
db ::schemas ::User ,
" INSERT INTO public.users(token, game_permissions, empire_permissions, data_permissions, user_permissions) VALUES ($1, $2, $3, $4, $5) RETURNING * " ,
new_token ,
false ,
false ,
false ,
false
)
. fetch_one ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return HttpResponse ::InternalServerError ( ) . finish ( ) ,
} ;
return HttpResponse ::Ok ( ) . json ( user )
}
#[ utoipa::path(
request_body = UpdateUserParams ,
responses (
( status = 200 , description = " OK " ) ,
( status = 403 , description = " Unauthorized " ) ,
( status = 500 , description = " Internal Server Error " )
) ,
security (
( " api_key " = [ ] )
) ,
) ]
#[ put( " /api/v1/user " ) ]
pub ( crate ) async fn update_user (
data : web ::Data < AppState > ,
params : web ::Json < schemas ::UpdateUserParams > ,
req : HttpRequest ,
) -> impl Responder {
let auth_header = get_auth_header ( & req ) ;
let params = params . into_inner ( ) ;
let auth_token : String ;
match auth_header {
Some ( token ) = > auth_token = token . to_string ( ) ,
None = > return HttpResponse ::Unauthorized ( ) . finish ( ) ,
} ;
let mut elevated_auth = false ;
if params . permissions [ " game_permissions " ] | | params . permissions [ " empire_permissions " ] | | params . permissions [ " data_permissions " ] | | params . permissions [ " user_permissions " ] {
elevated_auth = true ;
}
let auth = verify_user_auth ( & data , & auth_token , & params . user_token , schemas ::TablePermission ::User , elevated_auth ) . await ;
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if auth {
let user : db ::schemas ::User ;
let mut user_query = QueryBuilder ::< sqlx ::Postgres > ::new ( " UPDATE public.users SET " ) ;
let mut user_query_separated = user_query . separated ( " , " ) ;
let mut any_param_present = false ;
if let Some ( discord_handle ) = params . discord_handle {
user_query_separated . push ( " discord_id = " ) . push_bind_unseparated ( discord_handle ) ;
any_param_present = true ;
}
if let Some ( profile_picture ) = params . profile_picture {
user_query_separated . push ( " picture_url = " ) ;
match any_param_present {
true = > user_query_separated . push_bind ( profile_picture ) ,
false = > user_query_separated . push_bind_unseparated ( profile_picture )
} ;
any_param_present = true ;
}
for ( entry , value ) in params . permissions . iter ( ) {
match entry . deref ( ) {
" game_permissions " = > {
user_query_separated . push ( " game_permissions = " ) ;
match any_param_present {
true = > user_query_separated . push_bind ( value ) ,
false = > user_query_separated . push_bind_unseparated ( value )
} ;
any_param_present = true ;
} ,
" empire_permissions " = > {
user_query_separated . push ( " empire_permissions = " ) ;
match any_param_present {
true = > user_query_separated . push_bind ( value ) ,
false = > user_query_separated . push_bind_unseparated ( value )
} ;
any_param_present = true ;
} ,
" data_permissions " = > {
user_query_separated . push ( " data_permissions = " ) ;
match any_param_present {
true = > user_query_separated . push_bind ( value ) ,
false = > user_query_separated . push_bind_unseparated ( value )
} ;
any_param_present = true ;
} ,
" user_permissions " = > {
user_query_separated . push ( " user_permissions = " ) ;
match any_param_present {
true = > user_query_separated . push_bind ( value ) ,
false = > user_query_separated . push_bind_unseparated ( value )
} ;
any_param_present = true ;
} ,
_ = > { }
}
}
if any_param_present {
user_query_separated . push_unseparated ( " WHERE token = " ) . push_bind_unseparated ( params . user_token ) ;
user_query_separated . push_unseparated ( " RETURNING * " ) ;
user = match user_query
. build_query_as ::< db ::schemas ::User > ( )
. fetch_one ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return HttpResponse ::InternalServerError ( ) . finish ( ) ,
} ;
} else {
user = match sqlx ::query_as! (
db ::schemas ::User ,
" SELECT * FROM public.users WHERE token = $1 " ,
params . user_token
)
. fetch_one ( & data . db )
. await
{
Ok ( data ) = > data ,
Err ( _ ) = > return HttpResponse ::InternalServerError ( ) . finish ( ) ,
} ;
}
2023-12-12 20:49:39 +01:00
let mut user_permissions : HashMap < String , bool > = HashMap ::new ( ) ;
user_permissions . insert ( " game_permissions " . to_string ( ) , user . game_permissions ) ;
user_permissions . insert ( " empire_permissions " . to_string ( ) , user . empire_permissions ) ;
user_permissions . insert ( " data_permissions " . to_string ( ) , user . data_permissions ) ;
user_permissions . insert ( " user_permissions " . to_string ( ) , user . user_permissions ) ;
2023-12-12 20:39:15 +01:00
let return_data = schemas ::User {
user_token : user . token ,
discord_handle : user . discord_id ,
profile_picture : user . picture_url ,
2023-12-12 20:49:39 +01:00
permissions : user_permissions
2023-12-12 20:39:15 +01:00
} ;
return HttpResponse ::Ok ( ) . json ( return_data ) ;
} else {
return HttpResponse ::Unauthorized ( ) . finish ( ) ;
}
}
#[ utoipa::path(
request_body = schemas ::DeleteUserParams ,
responses (
( status = 200 , description = " OK " ) ,
( status = 403 , description = " Unauthorized " ) ,
( status = 500 , description = " Internal Server Error " )
) ,
security (
( " api_key " = [ ] )
) ,
) ]
#[ delete( " /api/v1/user " ) ]
pub ( crate ) async fn delete_user (
data : web ::Data < AppState > ,
params : web ::Query < schemas ::DeleteUserParams > ,
req : HttpRequest ,
) -> impl Responder {
let auth_header = get_auth_header ( & req ) ;
let params = params . into_inner ( ) ;
let auth_token : String ;
match auth_header {
Some ( token ) = > auth_token = token . to_string ( ) ,
None = > return HttpResponse ::Unauthorized ( ) . finish ( ) ,
} ;
let auth = verify_user_auth ( & data , & auth_token , & params . user_token , schemas ::TablePermission ::User , false ) . await ;
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if auth {
match sqlx ::query! (
" DELETE FROM public.users WHERE token = $1 " ,
params . user_token
)
. execute ( & data . db )
. await
{
Ok ( _ ) = > { }
2023-12-12 20:49:39 +01:00
Err ( _ ) = > {
2023-12-12 20:39:15 +01:00
return HttpResponse ::InternalServerError ( ) . finish ( ) ;
}
} ;
return HttpResponse ::Ok ( ) . into ( ) ;
} else {
return HttpResponse ::Unauthorized ( ) . finish ( ) ;
}
}