369 lines
12 KiB
Rust
369 lines
12 KiB
Rust
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;
|
|
|
|
fn get_auth_header(req: &HttpRequest) -> Option<&str> {
|
|
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 = GetUserParams,
|
|
responses(
|
|
(status = 200, description = "OK", body = User),
|
|
(status = 403, description = "Unauthorized"),
|
|
(status = 500, description = "Internal Server Error")
|
|
),
|
|
security(
|
|
("api_key" = [])
|
|
),
|
|
)]
|
|
#[post("/api/v1/user")]
|
|
pub(crate) 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, ¶ms.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(),
|
|
};
|
|
|
|
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);
|
|
|
|
let return_data = schemas::User {
|
|
user_token: user.token,
|
|
discord_handle: user.discord_id,
|
|
profile_picture: user.picture_url,
|
|
permissions: user_permissions
|
|
};
|
|
|
|
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/create")]
|
|
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 user_permissions: HashMap<String, bool> = HashMap::new();
|
|
match params.permissions {
|
|
Some(data) => {user_permissions = data.clone()},
|
|
None => {},
|
|
}
|
|
|
|
let mut elevated_auth = false;
|
|
if user_permissions.len() != 0 {
|
|
if user_permissions["game_permissions"] || user_permissions["empire_permissions"] || user_permissions["data_permissions"] || user_permissions["user_permissions"] {
|
|
elevated_auth = true;
|
|
}
|
|
}
|
|
|
|
let auth = verify_user_auth(&data, &auth_token, ¶ms.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;
|
|
}
|
|
|
|
if user_permissions.len() != 0 {
|
|
for (entry, value) in user_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(),
|
|
};
|
|
}
|
|
|
|
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);
|
|
|
|
let return_data = schemas::User {
|
|
user_token: user.token,
|
|
discord_handle: user.discord_id,
|
|
profile_picture: user.picture_url,
|
|
permissions: user_permissions
|
|
};
|
|
return HttpResponse::Ok().json(return_data);
|
|
} else {
|
|
return HttpResponse::Unauthorized().finish();
|
|
}
|
|
}
|
|
|
|
#[utoipa::path(
|
|
request_body = 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, ¶ms.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(_) => {}
|
|
Err(_) => {
|
|
return HttpResponse::InternalServerError().finish();
|
|
}
|
|
};
|
|
|
|
return HttpResponse::Ok().into();
|
|
} else {
|
|
return HttpResponse::Unauthorized().finish();
|
|
}
|
|
}
|