This repository has been archived on 2024-08-06. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
chellaris-rust-api/src/v3/mod.rs

726 lines
21 KiB
Rust
Raw Normal View History

use std::{collections::HashMap, vec};
2023-08-26 06:36:12 +02:00
use actix_web::{
delete, get, post, put,
web::{self, Json},
2023-09-02 20:23:34 +02:00
HttpResponse, Responder, HttpRequest,
};
2023-08-26 06:36:12 +02:00
use crate::{db, AppState};
2023-08-26 06:36:12 +02:00
pub(crate) mod schemas;
2023-09-02 20:23:34 +02:00
fn verify_auth(token: Option<&str>, data: &AppState) -> schemas::AuthReturn {
let mut auth_return = schemas::AuthReturn {
moderator: false,
admin: false,
};
2023-09-02 20:23:34 +02:00
if let Some(token) = token {
if token == data.auth_tokens.admin {
auth_return.admin = true;
auth_return.moderator = true;
} else if token == data.auth_tokens.moderator {
auth_return.moderator = true;
}
}
return auth_return;
}
2023-09-02 20:23:34 +02:00
fn get_auth_header<'a>(req: &'a HttpRequest) -> Option<&'a str> {
req.headers().get("x-api-key")?.to_str().ok()
}
#[utoipa::path(
responses(
(status = 200, description = "OK", body = AuthReturn),
),
security(
("api_key" = [])
),
)]
#[get("/api/v3/auth")]
pub(crate) async fn auth(
data: web::Data<AppState>,
2023-09-02 20:23:34 +02:00
req: HttpRequest,
) -> impl Responder {
2023-09-02 20:23:34 +02:00
let auth_token = get_auth_header(&req);
2023-09-02 20:23:34 +02:00
let auth_return = verify_auth(auth_token, &data);
Json(auth_return)
}
2023-08-26 06:36:12 +02:00
#[utoipa::path(
responses(
(status = 200, description = "OK", body = FullViewData),
),
security(
("api_key" = [])
),
)]
#[get("/api/v3/full_view_data")]
pub(crate) async fn full_view_data(data: web::Data<AppState>) -> impl Responder {
2023-08-26 06:36:12 +02:00
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
let db_games: Vec<db::schemas::Game> = sqlx::query_as("SELECT * FROM public.games ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_groups: Vec<db::schemas::GameGroup> =
sqlx::query_as("SELECT * FROM public.game_groups ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_empires: Vec<db::schemas::Empire> =
sqlx::query_as("SELECT * FROM public.empires ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_ethics: Vec<db::schemas::Ethic> =
sqlx::query_as("SELECT * FROM public.ethics ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_empire_ethics: Vec<db::schemas::EmpireEthic> =
sqlx::query_as("SELECT * FROM public.empire_ethics ORDER BY empires_id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_portrait_groups: Vec<db::schemas::PortraitGroup> =
sqlx::query_as("SELECT * FROM public.portrait_groups ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
let db_portraits: Vec<db::schemas::Portrait> =
sqlx::query_as("SELECT * FROM public.portraits ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
2023-08-26 06:36:12 +02:00
let mut parsed_data: schemas::FullViewData = schemas::FullViewData {
games: HashMap::new(),
ethics: HashMap::new(),
species: HashMap::new(),
};
// Data processing
// Species Vector
db_portrait_groups.iter().for_each(|species| {
let new_data = schemas::Species {
id: species.id,
displayName: species.name.clone(),
portraits: HashMap::new(),
};
parsed_data
.species
.entry(species.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
db_portraits.iter().for_each(|portrait| {
let new_data = schemas::Portrait {
id: portrait.id,
hires: portrait.hires.clone(),
2023-09-02 20:23:34 +02:00
lores: Some(portrait.lores.clone()),
2023-08-26 06:36:12 +02:00
};
parsed_data
.species
.get_mut(&portrait.group_id)
.unwrap()
.portraits
.entry(portrait.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
// Games Vector and Children
db_games.iter().for_each(|game| {
2023-09-02 20:23:34 +02:00
let new_data = schemas::ChellarisGameLegacy {
2023-08-26 06:36:12 +02:00
id: game.id,
name: game.name.clone(),
empires: HashMap::new(),
groups: HashMap::new(),
};
parsed_data
.games
.entry(game.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
db_groups.iter().for_each(|group| {
2023-09-02 20:23:34 +02:00
let new_data = schemas::ChellarisGameGroupLegacy {
2023-08-26 06:36:12 +02:00
id: group.id,
name: group.name.clone(),
};
parsed_data
.games
.get_mut(&group.game_id)
.unwrap()
.groups
.entry(group.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
db_empires.iter().for_each(|empire| {
let new_data = schemas::ChellarisEmpire {
id: empire.id,
2023-09-02 20:23:34 +02:00
gestalt: empire.gestalt,
machine: false,
2023-08-26 06:36:12 +02:00
group: empire.group_id,
empire_portrait: empire.empire_portrait_id,
empire_portrait_group: empire.empire_portrait_group_id,
2023-09-02 20:23:34 +02:00
discord_user: None,
2023-08-26 06:36:12 +02:00
ethics: HashMap::new(),
};
parsed_data
.games
.get_mut(&empire.group_game_id)
.unwrap()
.empires
.entry(empire.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
// Ethics Vector
db_ethics.iter().for_each(|ethic| {
let new_data = schemas::Ethic {
id: ethic.id,
displayName: ethic.name.clone(),
machine: ethic.machine_ethic,
};
parsed_data
.ethics
.entry(ethic.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
db_empire_ethics.iter().for_each(|empire_ethic| {
let game_id = empire_ethic.empires_group_game_id;
let id = empire_ethic.empires_id;
let new_data = schemas::EmpireEthic {
id: empire_ethic.ethics_id,
displayName: parsed_data.ethics[&empire_ethic.ethics_id]
.displayName
.clone(),
2023-08-26 06:36:12 +02:00
machine: parsed_data.ethics[&empire_ethic.ethics_id].machine,
fanatic: empire_ethic.ethics_fanatic,
};
if new_data.machine {
parsed_data
.games
.get_mut(&game_id)
.unwrap()
.empires
.get_mut(&id)
.unwrap()
.machine = true;
}
parsed_data
.games
.get_mut(&game_id)
.unwrap()
.empires
.get_mut(&id)
.unwrap()
.ethics
.entry(empire_ethic.ethics_id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
2023-08-26 06:36:12 +02:00
});
Json(parsed_data)
}
// Data Fetching Endpoints for Admin/Moderator Menu
2023-09-02 20:23:34 +02:00
// Game Endpoints
#[utoipa::path(
params(),
responses(
(status = 200, description = "OK", body = HashMap<i32, ChellarisGameLite>),
),
security(
("api_key" = [])
),
)]
2023-09-02 20:23:34 +02:00
#[get("/api/v3/games")]
pub(crate) async fn list_games(data: web::Data<AppState>) -> impl Responder {
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
let db_games: Vec<db::schemas::Game> = sqlx::query_as("SELECT * FROM public.games ORDER BY id")
.fetch_all(&data.db)
.await
.expect("Error Fetching Data from DB");
2023-09-02 20:23:34 +02:00
let mut parsed_data: HashMap<i32, schemas::ChellarisGameFlat> = HashMap::new();
// Data processing
// Games Vector
db_games.iter().for_each(|game| {
2023-09-02 20:23:34 +02:00
let new_data = schemas::ChellarisGameFlat {
id: game.id,
name: game.name.clone(),
};
parsed_data
.entry(game.id)
.and_modify(|d| *d = new_data.clone())
.or_insert(new_data);
});
Json(parsed_data)
}
#[utoipa::path(
params(
schemas::GetGameParam
),
responses(
2023-09-02 20:23:34 +02:00
(status = 200, description = "OK", body = ChellarisGame),
),
security(
("api_key" = [])
),
)]
#[get("/api/v3/game")]
pub(crate) async fn get_game_data(
data: web::Data<AppState>,
2023-09-02 20:23:34 +02:00
path_param: web::Query<schemas::GetGameParam>,
req: HttpRequest,
) -> impl Responder {
2023-09-02 20:23:34 +02:00
let auth_token = get_auth_header(&req);
let param = path_param.into_inner();
let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
2023-09-02 20:23:34 +02:00
let db_game: db::schemas::Game = match sqlx::query_as!(
db::schemas::Game,
"SELECT * FROM public.games WHERE id = $1",
param.game_id
)
.fetch_one(&data.db)
.await
{
Ok(data) => data,
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
let db_empires: Vec<db::schemas::Empire> = sqlx::query_as!(
db::schemas::Empire,
"SELECT * FROM public.empires WHERE group_game_id = $1",
param.game_id
)
.fetch_all(&data.db)
.await
.unwrap_or(vec![]);
let db_groups: Vec<db::schemas::GameGroup> = sqlx::query_as!(
db::schemas::GameGroup,
"SELECT * FROM public.game_groups WHERE game_id = $1",
param.game_id
)
.fetch_all(&data.db)
.await
.unwrap_or(vec![]);
// Data Processing
let mut parsed_data: schemas::ChellarisGame = schemas::ChellarisGame {
id: db_game.id,
name: db_game.name,
empires: HashMap::new(),
groups: HashMap::new(),
};
db_empires.iter().for_each(|empire| {
let new_empire = schemas::ChellarisEmpireFlat {
id: empire.id,
group: empire.group_id,
name: if user_auth.moderator || user_auth.admin {empire.name.clone()} else {"[REDACTED]".to_string()},
discord_user: if user_auth.moderator || user_auth.admin {empire.discord_user.clone()} else {None},
gestalt: empire.gestalt,
empire_portrait: empire.empire_portrait_id,
empire_portrait_group: empire.empire_portrait_group_id,
};
2023-09-02 20:23:34 +02:00
parsed_data
.empires
.entry(empire.id)
.and_modify(|d| *d = new_empire.clone())
.or_insert(new_empire);
});
2023-09-02 20:23:34 +02:00
db_groups.iter().for_each(|group| {
let new_group = schemas::ChellarisGroupFlat {
id: group.id,
name: group.name.clone(),
};
parsed_data
2023-09-02 20:23:34 +02:00
.groups
.entry(group.id)
.and_modify(|d| *d = new_group.clone())
.or_insert(new_group);
});
2023-09-02 20:23:34 +02:00
return HttpResponse::Ok().json(parsed_data);
2023-08-26 06:36:12 +02:00
}
#[utoipa::path(
request_body = PostGameParams,
responses(
(status = 200, description = "OK", body = ChellarisGameLite),
(status = 422, description = "Missing Game Name"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[post("/api/v3/game")]
pub(crate) async fn create_game(
data: web::Data<AppState>,
params: web::Json<schemas::PostGameParams>,
2023-09-02 20:23:34 +02:00
req: HttpRequest,
) -> impl Responder {
2023-09-02 20:23:34 +02:00
let auth_token = get_auth_header(&req);
let params = params.into_inner();
2023-09-02 20:23:34 +02:00
let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
if params.game_name != "" {
let db_game: db::schemas::Game;
db_game = match sqlx::query_as!(
db::schemas::Game,
"INSERT INTO public.games(name) VALUES ($1) RETURNING * ",
params.game_name
)
.fetch_one(&data.db)
.await
{
Ok(data) => data,
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
2023-09-02 20:23:34 +02:00
match sqlx::query!(
"INSERT INTO public.game_groups(name, game_id) VALUES ($1, $2)",
"N/A", db_game.id
)
.execute(&data.db)
.await
{
Ok(_) => {},
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
let parsed_game: schemas::ChellarisGameFlat = schemas::ChellarisGameFlat {
id: db_game.id,
name: db_game.name,
};
return HttpResponse::Ok().json(parsed_game);
} else {
return HttpResponse::UnprocessableEntity().finish();
}
} else {
return HttpResponse::Unauthorized().finish();
}
}
2023-09-02 20:23:34 +02:00
#[utoipa::path(
request_body = UpdateGameParams,
responses(
(status = 200, description = "OK", body = ChellarisGameLite),
(status = 422, description = "Missing Game Name"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[put("/api/v3/game")]
pub(crate) async fn edit_game(
data: web::Data<AppState>,
params: web::Json<schemas::UpdateGameParams>,
req: HttpRequest,
) -> impl Responder {
let auth_token = get_auth_header(&req);
let params = params.into_inner();
let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
if params.game_name != "" {
let db_game: db::schemas::Game;
db_game = match sqlx::query_as!(
db::schemas::Game,
"UPDATE public.games SET name = $1 WHERE id = $2 RETURNING * ;",
params.game_name, params.game_id
)
.fetch_one(&data.db)
.await
{
Ok(data) => data,
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
let parsed_game: schemas::ChellarisGameFlat = schemas::ChellarisGameFlat {
id: db_game.id,
name: db_game.name,
};
return HttpResponse::Ok().json(parsed_game);
} else {
return HttpResponse::UnprocessableEntity().finish();
}
} else {
return HttpResponse::Unauthorized().finish();
}
}
#[utoipa::path(
params(
schemas::DeleteGameParam,
),
request_body = AuthParams,
responses(
(status = 200, description = "OK"),
(status = 422, description = "Missing Game ID"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[delete("/api/v3/game")]
pub(crate) async fn delete_game(
data: web::Data<AppState>,
2023-09-02 20:23:34 +02:00
req: HttpRequest,
param: web::Query<schemas::DeleteGameParam>,
) -> impl Responder {
2023-09-02 20:23:34 +02:00
let auth_token = get_auth_header(&req);
let param = param.into_inner();
2023-09-02 20:23:34 +02:00
let user_auth: schemas::AuthReturn = verify_auth(auth_token, &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
2023-09-02 20:23:34 +02:00
match sqlx::query!("DELETE FROM public.games WHERE id = $1", param.game_id)
.execute(&data.db)
.await
{
2023-09-02 20:23:34 +02:00
Ok(_) => {}
Err(e) => {
println!("{:#?}", e);
return HttpResponse::UnprocessableEntity().finish();
},
};
return HttpResponse::Ok().into();
} else {
return HttpResponse::Unauthorized().finish();
}
}
// Group Endpoints
#[utoipa::path(
request_body = PostGroupParams,
responses(
(status = 200, description = "OK", body = ChellarisGroupFlat),
(status = 422, description = "Missing Game Name"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[post("/api/v3/group")]
pub(crate) async fn create_group(
data: web::Data<AppState>,
params: web::Json<schemas::PostGroupParams>,
req: HttpRequest,
) -> impl Responder {
let auth_token = get_auth_header(&req);
let params = params.into_inner();
let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
if params.group_name != "" {
let db_group: db::schemas::GameGroup;
db_group = match sqlx::query_as!(
db::schemas::GameGroup,
"INSERT INTO public.game_groups(name, game_id) VALUES ($1, $2) RETURNING * ",
params.group_name, params.game_id
)
.fetch_one(&data.db)
.await
{
Ok(data) => data,
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
let parsed_group: schemas::ChellarisGroupFlat = schemas::ChellarisGroupFlat {
id: db_group.id,
name: db_group.name,
};
return HttpResponse::Ok().json(parsed_group);
} else {
return HttpResponse::UnprocessableEntity().finish();
}
} else {
return HttpResponse::Unauthorized().finish();
}
}
#[utoipa::path(
request_body = UpdateGroupParams,
responses(
(status = 200, description = "OK", body = ChellarisGroupFlat),
(status = 422, description = "Missing Game Name"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[put("/api/v3/group")]
pub(crate) async fn edit_group(
data: web::Data<AppState>,
params: web::Json<schemas::UpdateGroupParams>,
req: HttpRequest,
) -> impl Responder {
let auth_token = get_auth_header(&req);
let params = params.into_inner();
let user_auth: schemas::AuthReturn = verify_auth(auth_token.as_deref(), &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
if params.group_name != "" {
let db_group: db::schemas::GameGroup;
db_group = match sqlx::query_as!(
db::schemas::GameGroup,
"UPDATE public.game_groups SET name = $1 WHERE id = $2 AND game_id = $3 RETURNING * ;",
params.group_name, params.group_id, params.game_id
)
.fetch_one(&data.db)
.await
{
Ok(data) => data,
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
let parsed_group: schemas::ChellarisGroupFlat = schemas::ChellarisGroupFlat {
id: db_group.id,
name: db_group.name,
};
return HttpResponse::Ok().json(parsed_group);
} else {
return HttpResponse::UnprocessableEntity().finish();
}
} else {
return HttpResponse::Unauthorized().finish();
}
}
#[utoipa::path(
params(
schemas::DeleteGroupParams,
),
request_body = AuthParams,
responses(
(status = 200, description = "OK"),
(status = 422, description = "Missing Game ID"),
(status = 401, description = "Auth Token Invalid"),
),
security(
("api_key" = [])
),
)]
#[delete("/api/v3/group")]
pub(crate) async fn delete_group(
data: web::Data<AppState>,
req: HttpRequest,
param: web::Query<schemas::DeleteGroupParams>,
) -> impl Responder {
let auth_token = get_auth_header(&req);
let param = param.into_inner();
let user_auth: schemas::AuthReturn = verify_auth(auth_token, &data);
// SQL Queries
// TODO: Optimize by utilizing some SQL magic, for now this has to do
if user_auth.admin || user_auth.moderator {
match sqlx::query!("DELETE FROM public.game_groups WHERE id = $1 AND game_id = $2 AND name != 'N/A'", param.group_id, param.game_id)
.execute(&data.db)
.await
{
Ok(_) => {}
Err(_) => return HttpResponse::UnprocessableEntity().finish(),
};
return HttpResponse::Ok().into();
} else {
return HttpResponse::Unauthorized().finish();
}
}
2023-09-02 20:23:34 +02:00
// Empire Endpoints
// Data Manipulation Endpoints
// Moderator & Admin
// Add/Update/Remove Empire
2023-09-02 20:23:34 +02:00
// Update Group
// Update Game
// Admin
// Add/Update/Remove Portrait
// Add/Update/Remove Species
// Add/Update/Remove Ethics