diff --git a/services/api/src/classes.rs b/services/api/src/classes.rs deleted file mode 100644 index a48e7d7..0000000 --- a/services/api/src/classes.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::redispool::RedisPool; -use core::time; -use rocket_db_pools::deadpool_redis::redis::{pipe, AsyncCommands}; -use rocket_db_pools::Connection; -use serde::{Deserialize, Serialize}; -use std::ops::Sub; -use std::time::SystemTime; - -#[derive(Serialize, Deserialize, Debug)] -struct ClassCounts { - world_id: String, - classes: Classes, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Classes { - light_assault: i32, - engineer: i32, - combat_medic: i32, - heavy_assault: i32, - infiltrator: i32, - max: i32, -} - -#[get("/w//classes")] -pub async fn get_classes(world_id: String, mut con: Connection) -> serde_json::Value { - let cache_key = format!("cache:classes:{}", world_id); - - match con.get::(cache_key.clone()).await { - Ok(cached) => { - return serde_json::from_str(&cached).unwrap(); - } - Err(_) => {} - } - - let classes = fetch_classes(world_id.clone(), &mut con).await; - - // I hate this but it's fast??? - // The type only allows 12 at a time. - - let response = ClassCounts { world_id, classes }; - - let out = json!(response); - - con.set_ex::(cache_key, out.to_string(), 5) - .await - .unwrap(); - - out -} - -pub async fn fetch_classes(world_id: String, con: &mut Connection) -> Classes { - let filter_timestamp = SystemTime::now() - .sub(time::Duration::from_secs(60 * 15)) - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - let (light_assault, engineer, combat_medic, heavy_assault, infiltrator, max): ( - i32, - i32, - i32, - i32, - i32, - i32, - ) = pipe() - .zcount( - format!("c:{}/{}", world_id, "light_assault"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("c:{}/{}", world_id, "engineer"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("c:{}/{}", world_id, "combat_medic"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("c:{}/{}", world_id, "heavy_assault"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("c:{}/{}", world_id, "infiltrator"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("c:{}/{}", world_id, "max"), - filter_timestamp, - "+inf", - ) - .query_async(&mut **con) - .await - .unwrap(); - - Classes { - light_assault, - engineer, - combat_medic, - heavy_assault, - infiltrator, - max, - } -} diff --git a/services/api/src/graphql/mod.rs b/services/api/src/graphql/mod.rs index 7a291b8..b068b30 100644 --- a/services/api/src/graphql/mod.rs +++ b/services/api/src/graphql/mod.rs @@ -84,6 +84,11 @@ pub fn playground() -> RawHtml { juniper_rocket::playground_source("/graphql", None) } +#[get("/playground")] +pub fn playground2() -> RawHtml { + juniper_rocket::playground_source("/graphql", None) +} + #[post("/", data = "")] pub async fn post_graphql( query: juniper_rocket::GraphQLRequest, diff --git a/services/api/src/html/index.html b/services/api/src/html/index.html new file mode 100644 index 0000000..c664406 --- /dev/null +++ b/services/api/src/html/index.html @@ -0,0 +1,55 @@ + +Saerro Listening Post + + + +

Saerro Listening Post

+

Live Population Stats API for PlanetSide 2

+ +

+ All data is an aggregate of the last 15 minutes of Death and VehicleDestroy + events, including both attacker and victim. +

+ +

+ This API is provided by Genudine Dynamics.
As always, we take no + responsibility for your use of this data... or our weapons. :) +

+

For help, please contact us in #api-dev on the PlanetSide 2 Discord.

+

[github]

diff --git a/services/api/src/main.rs b/services/api/src/main.rs index cd93aec..b18452c 100644 --- a/services/api/src/main.rs +++ b/services/api/src/main.rs @@ -1,12 +1,11 @@ -pub mod classes; pub mod cors; pub mod graphql; -pub mod population; pub mod redispool; -pub mod vehicles; use redispool::RedisPool; use rocket::fairing::AdHoc; +use rocket::response::content::RawHtml; +use rocket::response::status; use rocket::{error, Build, Rocket}; use rocket_db_pools::deadpool_redis::redis::{cmd, pipe}; use rocket_db_pools::{Connection, Database}; @@ -16,42 +15,15 @@ extern crate rocket; #[macro_use] extern crate serde_json; -fn hello_world(host: String, world_id: &str) -> serde_json::Value { - json!({ - "population": format!("https://{}/w/{}", host, world_id), - "vehicles": format!("https://{}/w/{}/vehicles", host, world_id), - "classes": format!("https://{}/w/{}/classes", host, world_id), - }) -} - -fn hello(host: String) -> serde_json::Value { - json!({ - "@": "Saerro Listening Post - PlanetSide 2 Live Population API", - "@GitHub": "https://github.com/genudine/saerro", - "@Disclaimer": "Genudine Dynamics is not responsible for any damages caused by this software. Use at your own risk.", - "@Support": "#api-dev in https://discord.com/servers/planetside-2-community-251073753759481856", - "GraphQL": format!("https://{}/graphql", host), - "Worlds": { - "Connery": hello_world(host.clone(), "1"), - "Miller": hello_world(host.clone(), "10"), - "Cobalt": hello_world(host.clone(), "13"), - "Emerald": hello_world(host.clone(), "17"), - "Jaeger": hello_world(host.clone(), "19"), - "SolTech": hello_world(host.clone(), "40"), - "Genudine": hello_world(host.clone(), "1000"), - "Ceres": hello_world(host.clone(), "2000"), - }, - // "All World Population": format!("https://{}/m/?ids=1,10,13,17,19,40,1000,2000", host), - }) -} - #[get("/")] -fn index() -> serde_json::Value { - hello("saerro.harasse.rs".to_string()) +async fn index() -> RawHtml { + RawHtml(include_str!("html/index.html").to_string()) } #[get("/health")] -async fn health(mut con: Connection) -> serde_json::Value { +async fn health( + mut con: Connection, +) -> Result> { let (ping, pc, ps4us, ps4eu): (String, bool, bool, bool) = pipe() .cmd("PING") .get("heartbeat:pc") @@ -61,13 +33,23 @@ async fn health(mut con: Connection) -> serde_json::Value { .await .unwrap_or_default(); - json!({ + if ping != "PONG" { + return Err(status::Custom( + rocket::http::Status::ServiceUnavailable, + json!({ + "status": "error", + "message": "Redis is not responding", + }), + )); + } + + Ok(json!({ "status": if ping == "PONG" && pc && ps4us && ps4eu { "ok" } else { "degraded" }, "redis": ping == "PONG", "pc": if pc { "primary" } else { "backup/down" }, "ps4us": if ps4us { "primary" } else { "backup/down" }, "ps4eu": if ps4eu { "primary" } else { "backup/down" }, - }) + })) } #[launch] @@ -95,21 +77,13 @@ fn rocket() -> Rocket { rocket })) .manage(graphql::schema()) - .mount( - "/", - routes![ - index, - health, - population::get_world_pop, - vehicles::get_vehicles, - classes::get_classes, - ], - ) + .mount("/", routes![index, health,]) .mount( "/graphql", routes![ graphql::graphiql, graphql::playground, + graphql::playground2, graphql::get_graphql, graphql::post_graphql ], diff --git a/services/api/src/population.rs b/services/api/src/population.rs deleted file mode 100644 index b5a8fa7..0000000 --- a/services/api/src/population.rs +++ /dev/null @@ -1,57 +0,0 @@ -use core::time; -use std::{ops::Sub, time::SystemTime}; - -use rocket_db_pools::{deadpool_redis::redis::pipe, Connection}; -use serde::{Deserialize, Serialize}; - -use crate::redispool::RedisPool; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Factions { - pub tr: i32, - pub nc: i32, - pub vs: i32, - pub ns: i32, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct WorldPopulation { - world_id: i32, - pub total: i32, - pub factions: Factions, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct MultipleWorldPopulation { - worlds: Vec, -} - -#[get("/w/")] -pub async fn get_world_pop(world_id: String, mut con: Connection) -> serde_json::Value { - json!(fetch_world_pop(world_id, &mut con).await) -} - -pub async fn fetch_world_pop(world_id: String, con: &mut Connection) -> WorldPopulation { - let filter_timestamp = SystemTime::now() - .sub(time::Duration::from_secs(60 * 15)) - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - let (vs, nc, tr, ns): (i32, i32, i32, i32) = pipe() - .zcount(format!("wp:{}/{}", world_id, 1), filter_timestamp, "+inf") - .zcount(format!("wp:{}/{}", world_id, 2), filter_timestamp, "+inf") - .zcount(format!("wp:{}/{}", world_id, 3), filter_timestamp, "+inf") - .zcount(format!("wp:{}/{}", world_id, 4), filter_timestamp, "+inf") - .query_async(&mut **con) - .await - .unwrap(); - - let total = tr + vs + nc; - - WorldPopulation { - world_id: world_id.parse().unwrap(), - total, - factions: Factions { tr, nc, vs, ns }, - } -} diff --git a/services/api/src/vehicles.rs b/services/api/src/vehicles.rs deleted file mode 100644 index 9465ed5..0000000 --- a/services/api/src/vehicles.rs +++ /dev/null @@ -1,222 +0,0 @@ -use core::time; -use rocket_db_pools::deadpool_redis::redis::{pipe, AsyncCommands}; -use rocket_db_pools::Connection; -use serde::{Deserialize, Serialize}; -use std::ops::Sub; -use std::time::SystemTime; - -use crate::redispool::RedisPool; - -#[derive(Serialize, Deserialize, Debug)] -struct VehiclesCounts { - world_id: String, - total: u32, - vehicles: Vehicles, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Vehicles { - flash: u32, - sunderer: u32, - lightning: u32, - scythe: u32, - vanguard: u32, - prowler: u32, - reaver: u32, - mosquito: u32, - galaxy: u32, - valkyrie: u32, - liberator: u32, - ant: u32, - harasser: u32, - dervish: u32, - chimera: u32, - javelin: u32, - corsair: u32, - magrider: u32, -} - -#[get("/w//vehicles")] -pub async fn get_vehicles(world_id: String, mut con: Connection) -> serde_json::Value { - let cache_key = format!("cache:vehicles:{}", world_id); - - match con.get::(cache_key.clone()).await { - Ok(cached) => { - return serde_json::from_str(&cached).unwrap(); - } - Err(_) => {} - } - - let filter_timestamp = SystemTime::now() - .sub(time::Duration::from_secs(60 * 15)) - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - // I hate this but it's fast??? - // The type only allows 12 at a time. - let ( - flash, - sunderer, - lightning, - scythe, - vanguard, - prowler, - reaver, - mosquito, - galaxy, - valkyrie, - liberator, - ant - ): (u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32) = pipe() - .zcount( - format!("v:{}/{}", world_id, "flash"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "sunderer"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "lightning"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "scythe"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "vanguard"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "prowler"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "reaver"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "mosquito"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "galaxy"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "valkyrie"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "liberator"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "ant"), - filter_timestamp, - "+inf", - ) - .query_async(&mut *con) - .await - .unwrap(); - - let (harasser, dervish, chimera, javelin, corsair, magrider): (u32, u32, u32, u32, u32, u32) = pipe() - .zcount( - format!("v:{}/{}", world_id, "harasser"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "dervish"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "chimera"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "javelin"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "corsair"), - filter_timestamp, - "+inf", - ) - .zcount( - format!("v:{}/{}", world_id, "magrider"), - filter_timestamp, - "+inf", - ) - .query_async(&mut *con) - .await - .unwrap(); - - let total = flash - + sunderer - + lightning - + scythe - + vanguard - + prowler - + reaver - + mosquito - + galaxy - + valkyrie - + liberator - + ant - + harasser - + dervish - + chimera - + javelin - + corsair - + magrider; - - let response = VehiclesCounts { - world_id, - total, - vehicles: Vehicles { - flash, - sunderer, - lightning, - scythe, - vanguard, - prowler, - reaver, - mosquito, - galaxy, - valkyrie, - liberator, - ant, - harasser, - dervish, - chimera, - javelin, - corsair, - magrider - }, - }; - - let out = json!(response); - - con.set_ex::(cache_key, out.to_string(), 5) - .await - .unwrap(); - - out -}