use std::{collections::HashMap, vec}; use actix_web::{ delete, get, post, put, web::{self, Json}, HttpRequest, HttpResponse, Responder, }; use sqlx::QueryBuilder; use dotenvy; use crate::{db, AppState}; pub(crate) mod schemas; fn verify_auth(token: Option<&str>, data: &AppState) -> schemas::AuthReturn { let mut auth_return = schemas::AuthReturn { moderator: false, admin: false, }; 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; } fn get_auth_header(req: &HttpRequest) -> Option<&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>, req: HttpRequest) -> impl Responder { let auth_token = get_auth_header(&req); let auth_return = verify_auth(auth_token, &data); Json(auth_return) } #[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 { // 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 empire_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(), phenotypes: HashMap::new(), }; // Data processing // Species Vector db_portrait_groups.iter().for_each(|species| { let new_data = schemas::Phenotype { id: species.id, display: species.name.clone(), species: HashMap::new(), }; parsed_data .phenotypes .entry(species.id) .and_modify(|d| *d = new_data.clone()) .or_insert(new_data); }); db_portraits.iter().for_each(|portrait| { let new_data = schemas::Species { id: portrait.id, hires: portrait.hires.clone(), lores: portrait.lores.clone(), }; parsed_data .phenotypes .get_mut(&portrait.group_id) .unwrap() .species .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::ChellarisGameLegacy { 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::ChellarisGameGroupLegacy { 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::ChellarisEmpireLegacy { id: empire.id, gestalt: empire.gestalt, machine: empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()), group: empire.group_id, portrait_id: empire.portrait_id, portrait_group_id: empire.portrait_group_id, discord_user: None, ethics: HashMap::new(), }; parsed_data .games .get_mut(&empire.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, display: ethic.name.clone(), gestalt: ethic.gestalt_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.empire_game_id; let id = empire_ethic.empire_id; let new_data = schemas::EmpireEthicLegacy { ethic_id: empire_ethic.ethics_id, display: parsed_data.ethics[&empire_ethic.ethics_id].display.clone(), gestalt: parsed_data.ethics[&empire_ethic.ethics_id].gestalt, fanatic: empire_ethic.fanatic, }; if new_data.gestalt { parsed_data .games .get_mut(&game_id) .unwrap() .empires .get_mut(&id) .unwrap() .gestalt = 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); }); Json(parsed_data) } // Data Fetching Endpoints for Admin/Moderator Menu // Game Endpoints #[utoipa::path( params(), responses( (status = 200, description = "OK", body = HashMap<i32, ChellarisGameFlat>), ), security( ("api_key" = []) ), )] #[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"); let mut parsed_data: HashMap<i32, schemas::ChellarisGameFlat> = HashMap::new(); // Data processing // Games Vector db_games.iter().for_each(|game| { 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( (status = 200, description = "OK", body = ChellarisGame), ), security( ("api_key" = []) ), )] #[get("/api/v3/game")] pub(crate) async fn get_game_data( data: web::Data<AppState>, path_param: web::Query<schemas::GetGameParam>, req: HttpRequest, ) -> impl Responder { 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 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 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, game: empire.game_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 { Some("deprecated".to_string()) } else { None }, gestalt: empire.gestalt, portrait_id: empire.portrait_id, portrait_group_id: empire.portrait_group_id, }; parsed_data .empires .entry(empire.id) .and_modify(|d| *d = new_empire.clone()) .or_insert(new_empire); }); db_groups.iter().for_each(|group| { let new_group = schemas::ChellarisGroupFlat { id: group.id, name: group.name.clone(), }; parsed_data .groups .entry(group.id) .and_modify(|d| *d = new_group.clone()) .or_insert(new_group); }); return HttpResponse::Ok().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>, 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, "INSERT INTO public.games(name) VALUES ($1) RETURNING * ", params.game_name ) .fetch_one(&data.db) .await { Ok(data) => data, Err(_) => return HttpResponse::UnprocessableEntity().finish(), }; 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(); } } #[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, ), 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>, req: HttpRequest, param: web::Query<schemas::DeleteGameParam>, ) -> 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.games WHERE id = $1", param.game_id) .execute(&data.db) .await { 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, ), 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(); } } // Empire Endpoints #[utoipa::path( params( schemas::GetEmpireParams ), responses( (status = 200, description = "OK", body = ChellarisEmpire), ), security( ("api_key" = []) ), )] #[get("/api/v3/empire")] pub(crate) async fn get_empire( data: web::Data<AppState>, params: web::Query<schemas::GetEmpireParams>, 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 { let db_empire: db::schemas::Empire; let mut db_empire_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empires WHERE"); db_empire_query.push(" id = ").push_bind(params.empire_id); db_empire_query .push(" AND game_id = ") .push_bind(params.game_id); db_empire = match db_empire_query .build_query_as::<db::schemas::Empire>() .fetch_one(&data.db) .await { Ok(data) => data, Err(e) => { return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())) } }; let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new(); let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empire_ethics WHERE"); db_ethic_query .push(" empire_id = ") .push_bind(params.empire_id); db_ethic_query .push(" AND empire_game_id = ") .push_bind(params.game_id); match db_ethic_query .build_query_as::<db::schemas::EmpireEthic>() .fetch_all(&data.db) .await { Ok(data) => { for ethic in data { db_ethics.insert( ethic.ethics_id, schemas::EmpireEthic { ethic_id: ethic.ethics_id, fanatic: ethic.fanatic, }, ); } } Err(e) => { return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())) } }; // Empire Ethic Creation let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire { id: db_empire.id, group: db_empire.group_id, game: db_empire.game_id, name: db_empire.name, discord_user: Some("deprecated".to_string()), machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()), gestalt: db_empire.gestalt, portrait_id: db_empire.portrait_id, portrait_group_id: db_empire.portrait_group_id, ethics: db_ethics, }; return HttpResponse::Ok().json(parsed_empire); } else { return HttpResponse::Unauthorized().finish(); } } #[utoipa::path( request_body = PostEmpireParams, responses( (status = 200, description = "OK", body = ChellarisEmpireFlat), (status = 422, description = "Missing Game Name"), (status = 401, description = "Auth Token Invalid"), ), security( ("api_key" = []) ), )] #[post("/api/v3/empire")] pub(crate) async fn create_empire( data: web::Data<AppState>, params: web::Json<schemas::PostEmpireParams>, 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 { let mut all_params_present = true; if params.empire_name == "" { all_params_present = false; } if all_params_present { // Basic Empire Creation let db_empire: db::schemas::Empire; let mut db_empire_query = QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empires("); db_empire_query.push("group_id"); db_empire_query.push(", game_id"); db_empire_query.push(", name"); db_empire_query.push(", gestalt"); db_empire_query.push(", portrait_id"); db_empire_query.push(", portrait_group_id"); db_empire_query.push(", discord_user"); db_empire_query.push(") VALUES ("); db_empire_query.push("").push_bind(params.group_id); db_empire_query.push(", ").push_bind(params.game_id); db_empire_query.push(", ").push_bind(params.empire_name); db_empire_query.push(", ").push_bind(params.gestalt); db_empire_query.push(", ").push_bind(params.portrait_id); db_empire_query .push(", ") .push_bind(params.portrait_group_id); if let Some(discord_user) = params.discord_user { db_empire_query.push(", ").push_bind(discord_user); } else { let val: Option<String> = None; db_empire_query.push(", ").push_bind(val); } db_empire_query.push(") RETURNING * "); db_empire = match db_empire_query .build_query_as::<db::schemas::Empire>() .fetch_one(&data.db) .await { Ok(data) => data, Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new(); for ethic in params.ethics { let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empire_ethics("); db_ethic_query.push("empire_id").push(", empire_game_id"); db_ethic_query.push(", ethics_id").push(", fanatic"); db_ethic_query.push(") VALUES ("); db_ethic_query.push("").push_bind(db_empire.id); db_ethic_query.push(", ").push_bind(db_empire.game_id); db_ethic_query.push(", ").push_bind(ethic.ethic_id); db_ethic_query.push(", ").push_bind(ethic.fanatic); db_ethic_query.push(") RETURNING * "); match db_ethic_query .build_query_as::<db::schemas::EmpireEthic>() .fetch_one(&data.db) .await { Ok(data) => db_ethics.insert( data.ethics_id, schemas::EmpireEthic { ethic_id: data.ethics_id, fanatic: data.fanatic, }, ), Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; } // Empire Ethic Creation let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire { id: db_empire.id, group: db_empire.group_id, game: db_empire.game_id, name: db_empire.name, discord_user: Some("deprecated".to_string()), machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()), gestalt: db_empire.gestalt, portrait_id: db_empire.portrait_id, portrait_group_id: db_empire.portrait_group_id, ethics: db_ethics, }; return HttpResponse::Ok().json(parsed_empire); } else { return HttpResponse::UnprocessableEntity().finish(); } } else { return HttpResponse::Unauthorized().finish(); } } #[utoipa::path( request_body = UpdateEmpireParams, responses( (status = 200, description = "OK", body = ChellarisEmpireFlat), (status = 422, description = "Missing Game Name"), (status = 401, description = "Auth Token Invalid"), ), security( ("api_key" = []) ), )] #[put("/api/v3/empire")] pub(crate) async fn edit_empire( data: web::Data<AppState>, params: web::Json<schemas::UpdateEmpireParams>, 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 { let mut any_param_present = false; let mut db_empire_query = QueryBuilder::<sqlx::Postgres>::new("UPDATE public.empires SET "); let mut db_empire_separated = db_empire_query.separated(", "); // More often than not does nothing but is required to ensure data is handled properly db_empire_separated .push(" game_id = ") .push_bind_unseparated(params.game_id); if let Some(new_group) = params.group_id { any_param_present = true; db_empire_separated .push(" group_id = ") .push_bind_unseparated(new_group); } if let Some(new_name) = params.empire_name { if new_name != "" { any_param_present = true; db_empire_separated .push(" name = ") .push_bind_unseparated(new_name); } } if let Some(new_gestalt) = params.gestalt { any_param_present = true; db_empire_separated .push(" gestalt = ") .push_bind_unseparated(new_gestalt); } if let Some(new_portrait) = params.portrait_id { any_param_present = true; db_empire_separated .push(" portrait_id = ") .push_bind_unseparated(new_portrait); } if let Some(new_portrait_group) = params.portrait_group_id { any_param_present = true; db_empire_separated .push(" portrait_group_id = ") .push_bind_unseparated(new_portrait_group); } if let Some(new_discord_user) = params.discord_user { if new_discord_user != "" { any_param_present = true; db_empire_separated .push(" discord_user = ") .push_bind_unseparated(new_discord_user); } else { any_param_present = true; let val: Option<String> = None; db_empire_separated .push(" discord_user = ").push_bind_unseparated(val); } } let db_empire: db::schemas::Empire; if any_param_present { db_empire_separated .push_unseparated(" WHERE id = ") .push_bind_unseparated(params.empire_id); db_empire_separated .push_unseparated(" AND game_id = ") .push_bind_unseparated(params.game_id); db_empire_separated.push_unseparated(" RETURNING * "); db_empire = match db_empire_query .build_query_as::<db::schemas::Empire>() .fetch_one(&data.db) .await { Ok(data) => data, Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; } else { let mut db_empire_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empires"); db_empire_separated .push_unseparated(" WHERE id = ") .push_bind_unseparated(params.empire_id); db_empire_separated .push_unseparated(" AND game_id = ") .push_bind_unseparated(params.game_id); db_empire = match db_empire_query .build_query_as::<db::schemas::Empire>() .fetch_one(&data.db) .await { Ok(data) => data, Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; } let mut ethics_changed = false; let mut db_ethics: HashMap<i32, schemas::EmpireEthic> = HashMap::new(); if let Some(new_ethics) = params.ethics { if new_ethics.len() != 0 { ethics_changed = true; let mut db_ethic_wipe = QueryBuilder::<sqlx::Postgres>::new("DELETE FROM public.empire_ethics"); db_ethic_wipe .push(" WHERE empire_id = ") .push_bind(params.empire_id); db_ethic_wipe .push(" AND empire_game_id = ") .push_bind(params.game_id); match db_ethic_wipe.build().execute(&data.db).await { Ok(_) => {} Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; for ethic in new_ethics { let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("INSERT INTO public.empire_ethics("); db_ethic_query.push("empire_id").push(", empire_game_id"); db_ethic_query.push(", ethics_id").push(", fanatic"); db_ethic_query.push(") VALUES ("); db_ethic_query.push("").push_bind(db_empire.id); db_ethic_query.push(", ").push_bind(db_empire.game_id); db_ethic_query.push(", ").push_bind(ethic.ethic_id); db_ethic_query.push(", ").push_bind(ethic.fanatic); db_ethic_query.push(") RETURNING * "); match db_ethic_query.build().execute(&data.db).await { Ok(_) => {} Err(e) => { return HttpResponse::UnprocessableEntity() .body(format!("{:#?}", e.to_string())) } }; } } } let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.empire_ethics"); db_ethic_query .push(" WHERE empire_id = ") .push_bind(params.empire_id); db_ethic_query .push(" AND empire_game_id = ") .push_bind(params.game_id); match db_ethic_query .build_query_as::<db::schemas::EmpireEthic>() .fetch_all(&data.db) .await { Ok(data) => data.iter().for_each(|db_ethic| { db_ethics.insert( db_ethic.ethics_id, schemas::EmpireEthic { ethic_id: db_ethic.ethics_id, fanatic: db_ethic.fanatic, }, ); }), Err(e) => { return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())) } }; if any_param_present || ethics_changed { let parsed_empire: schemas::ChellarisEmpire = schemas::ChellarisEmpire { id: db_empire.id, group: db_empire.group_id, game: db_empire.game_id, name: db_empire.name, discord_user: Some("deprecated".to_string()), machine: db_empire.portrait_group_id.to_string() == dotenvy::var("MACHINE_GROUP_ID").unwrap_or("12".to_string()), gestalt: db_empire.gestalt, portrait_id: db_empire.portrait_id, portrait_group_id: db_empire.portrait_group_id, ethics: db_ethics, }; return HttpResponse::Ok().json(parsed_empire); } else { return HttpResponse::UnprocessableEntity().finish(); } } else { return HttpResponse::Unauthorized().finish(); } } #[utoipa::path( params( schemas::DeleteEmpireParams, ), responses( (status = 200, description = "OK"), (status = 422, description = "Missing Game ID"), (status = 401, description = "Auth Token Invalid"), ), security( ("api_key" = []) ), )] #[delete("/api/v3/empire")] pub(crate) async fn delete_empire( data: web::Data<AppState>, req: HttpRequest, param: web::Query<schemas::DeleteEmpireParams>, ) -> 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.empires WHERE id = $1 AND game_id = $2", param.empire_id, param.game_id ) .execute(&data.db) .await { Ok(_) => {} Err(_) => return HttpResponse::UnprocessableEntity().finish(), }; return HttpResponse::Ok().into(); } else { return HttpResponse::Unauthorized().finish(); } } // Ethics Endpoints // Data Manipulation Admin Only #[utoipa::path( responses( (status = 200, description = "OK", body = ChellarisEthics), ), )] #[get("/api/v3/ethics")] pub(crate) async fn get_ethics(data: web::Data<AppState>) -> impl Responder { // SQL Queries // TODO: Optimize by utilizing some SQL magic, for now this has to do let mut db_ethics: HashMap<i32, schemas::Ethic> = HashMap::new(); let mut db_ethic_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.ethics"); match db_ethic_query .build_query_as::<db::schemas::Ethic>() .fetch_all(&data.db) .await { Ok(data) => { for ethic in data { db_ethics.insert( ethic.id, schemas::Ethic { id: ethic.id, gestalt: ethic.gestalt_ethic, display: ethic.name, }, ); } } Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())), }; // Empire Ethic Creation let parsed_data: schemas::ChellarisEthics = schemas::ChellarisEthics { ethics: db_ethics }; return HttpResponse::Ok().json(parsed_data); } // Species & Portrait Endpoints // Data Manipulation Admin Only #[utoipa::path( responses( (status = 200, description = "OK", body = ChellarisPhenotypes), ), )] #[get("/api/v3/phenotypes")] pub(crate) async fn get_phenotypes(data: web::Data<AppState>) -> impl Responder { // SQL Queries // TODO: Optimize by utilizing some SQL magic, for now this has to do let mut db_phenotypes: HashMap<i32, schemas::Phenotype> = HashMap::new(); let mut db_phenotype_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.portrait_groups"); match db_phenotype_query .build_query_as::<db::schemas::PortraitGroup>() .fetch_all(&data.db) .await { Ok(data) => { for phenotype in data { db_phenotypes.insert( phenotype.id, schemas::Phenotype { id: phenotype.id, display: phenotype.name, species: HashMap::new(), }, ); } } Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())), }; let mut db_species_query = QueryBuilder::<sqlx::Postgres>::new("SELECT * FROM public.portraits"); match db_species_query .build_query_as::<db::schemas::Portrait>() .fetch_all(&data.db) .await { Ok(data) => { for species in data { if let Some(phenotype) = db_phenotypes.get_mut(&species.group_id) { phenotype.species.insert( species.id, schemas::Species { id: species.id, hires: species.hires, lores: species.lores } ); } } } Err(e) => return HttpResponse::UnprocessableEntity().body(format!("{:#?}", e.to_string())), }; // Empire Ethic Creation let parsed_data: schemas::ChellarisPhenotypes = schemas::ChellarisPhenotypes { phenotypes: db_phenotypes }; return HttpResponse::Ok().json(parsed_data); }