use std::{collections::HashMap, vec}; use actix_web::{ delete, get, post, put, web::{self, Json}, HttpResponse, Responder, }; use crate::{db, AppState}; pub(crate) mod schemas; fn verify_auth(token: &str, data: &AppState) -> schemas::AuthReturn { let mut auth_return = schemas::AuthReturn { moderator: false, admin: false, }; 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; } #[utoipa::path( params( schemas::AuthParams ), responses( (status = 200, description = "OK", body = AuthReturn), ), security( ("api_key" = []) ), )] #[get("/api/v3/auth")] pub(crate) async fn auth( data: web::Data<AppState>, params: web::Query<schemas::AuthParamsOptional>, ) -> impl Responder { let params: schemas::AuthParamsOptional = params.into_inner(); let mut auth_return = schemas::AuthReturn { moderator: false, admin: false, }; if let Some(auth_token) = params.token.clone() { if auth_token == data.auth_tokens.admin { auth_return.admin = true; auth_return.moderator = true; } else if auth_token == data.auth_tokens.moderator { auth_return.moderator = true; } } Json(auth_return) } #[utoipa::path( params( ), 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 { let start = chrono::Local::now(); // DEBUG // 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"); 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); }); db_portraits.iter().for_each(|portrait| { let new_data = schemas::Portrait { id: portrait.id, hires: portrait.hires.clone(), lores: portrait.lores.clone(), }; parsed_data .species .get_mut(&portrait.group_id) .unwrap() .portraits .entry(portrait.id) .and_modify(|d| *d = new_data.clone()) .or_insert(new_data); }); // Games Vector and Children db_games.iter().for_each(|game| { let new_data = schemas::ChellarisGame { 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); }); db_groups.iter().for_each(|group| { let new_data = schemas::ChellarisGameGroup { 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); }); db_empires.iter().for_each(|empire| { let new_data = schemas::ChellarisEmpire { id: empire.id, gestalt: empire.gestalt.unwrap_or(false), machine: false, group: empire.group_id, empire_portrait: empire.empire_portrait_id, empire_portrait_group: empire.empire_portrait_group_id, discord_user: empire.discord_user.clone(), 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); }); // 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); }); 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(), 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); }); println!("{:?} ms", (chrono::Local::now() - start).to_std().unwrap()); // DEBUG Json(parsed_data) } // Data Fetching Endpoints for Admin/Moderator Menu #[utoipa::path( params(), responses( (status = 200, description = "OK", body = HashMap<i32, ChellarisGameLite>), ), security( ("api_key" = []) ), )] #[get("/api/v3/list_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"); let mut parsed_data: HashMap<i32, schemas::ChellarisGameLite> = HashMap::new(); // Data processing // Games Vector db_games.iter().for_each(|game| { let new_data = schemas::ChellarisGameLite { 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( (status = 200, description = "OK", body = HashMap<i32, ChellarisGameLite>), ), security( ("api_key" = []) ), )] #[get("/api/v3/game")] pub(crate) async fn get_game_data( data: web::Data<AppState>, params: web::Query<schemas::GetGameParam>, ) -> impl Responder { let params: schemas::GetGameParam = params.into_inner(); // SQL Queries // TODO: Optimize by utilizing some SQL magic, for now this has to do let db_games: Vec<db::schemas::Game>; if let Some(game_id) = params.game_id { db_games = match sqlx::query_as!( db::schemas::Game, "SELECT * FROM public.games WHERE id = $1 ORDER BY id", game_id as i32 ) .fetch_one(&data.db) .await { Ok(data) => vec![data], Err(_) => vec![], }; } else { db_games = sqlx::query_as("SELECT * FROM public.games ORDER BY id") .fetch_all(&data.db) .await .expect("Error Fetching Data from DB"); } let mut parsed_data: HashMap<i32, schemas::ChellarisGameLite> = HashMap::new(); // Data processing // Games Vector db_games.iter().for_each(|game| { let new_data = schemas::ChellarisGameLite { 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( 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>, ) -> impl Responder { let params = params.into_inner(); let user_auth: schemas::AuthReturn = verify_auth(¶ms.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 { 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(), }; let parsed_game: schemas::ChellarisGameLite = schemas::ChellarisGameLite { 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>, auth_params: web::Json<schemas::AuthParams>, param: web::Query<schemas::DeleteGameParam>, ) -> impl Responder { let auth_params = auth_params.into_inner(); let param = param.into_inner(); let user_auth: schemas::AuthReturn = verify_auth(&auth_params.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.games WHERE id = $1", param.game_id as i32 ) .execute(&data.db) .await { Ok(_) => {}, Err(_) => return HttpResponse::UnprocessableEntity().finish(), }; return HttpResponse::Ok().into(); } else { return HttpResponse::Unauthorized().finish(); } } // Data Manipulation Endpoints // Moderator & Admin // Add/Update/Remove Empire // Add/Update/Remove Group // Add/Update/Remove Game // Admin // Add/Update/Remove Portrait // Add/Update/Remove Species // Add/Update/Remove Ethics