refactor api
This commit is contained in:
parent
a8c4bc5756
commit
01471342b0
18 changed files with 873 additions and 1595 deletions
1343
Cargo.lock
generated
1343
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,17 @@
|
|||
// GENERATED CODE -- Do not edit. Run `cargo run --bin codegen` to regenerate.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
static VEHICLE_TO_NAME: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
lazy_static! {
|
||||
static ref VEHICLE_TO_NAME: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
{% for vehicle in vehicles %}("{{ vehicle.vehicle_id }}", "{{ vehicle.name.en }}"),{% endfor %}
|
||||
])
|
||||
});
|
||||
]);
|
||||
|
||||
static ref LOADOUT_TO_CLASS: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
{% for class in classes %}("{{ class.loadout_id }}", "{{ class.code_name }}"),{% endfor %}
|
||||
]);
|
||||
}
|
||||
|
||||
pub fn vehicle_to_name(vehicle_id: &str) -> String {
|
||||
match VEHICLE_TO_NAME.get(&vehicle_id) {
|
||||
|
@ -16,12 +20,6 @@ pub fn vehicle_to_name(vehicle_id: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
static LOADOUT_TO_CLASS: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
{% for class in classes %}("{{ class.loadout_id }}", "{{ class.code_name }}"),{% endfor %}
|
||||
])
|
||||
});
|
||||
|
||||
pub fn loadout_to_class(loadout_id: &str) -> String {
|
||||
match LOADOUT_TO_CLASS.get(&loadout_id) {
|
||||
Some(name) => name.to_string(),
|
||||
|
|
|
@ -6,10 +6,12 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
rocket_db_pools = { version = "0.1.0-rc.2", features = [ "deadpool_redis" ] }
|
||||
redis = { version = "0.22.1", features = ["aio", "r2d2", "tokio-comp"] }
|
||||
serde_json = "1.0.88"
|
||||
serde = "1.0.147"
|
||||
juniper = "0.15.10"
|
||||
juniper_rocket = "0.8.2"
|
||||
once_cell = "1.16.0"
|
||||
async-graphql = { version = "4.0.16", features = ["apollo_tracing"] }
|
||||
async-graphql-axum = "4.0.16"
|
||||
axum = "0.6.0"
|
||||
tokio = { version = "1.22.0", features = ["full"] }
|
||||
tower-http = { version = "0.3.4", features = ["cors"] }
|
||||
lazy_static = "1.4.0"
|
39
services/api/src/classes.rs
Normal file
39
services/api/src/classes.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use crate::util::zcount;
|
||||
use async_graphql::{Context, Object};
|
||||
use redis::aio::MultiplexedConnection;
|
||||
|
||||
pub struct Classes {
|
||||
world_id: String,
|
||||
}
|
||||
|
||||
impl Classes {
|
||||
pub fn new(world_id: String) -> Self {
|
||||
Self { world_id }
|
||||
}
|
||||
async fn by_class<'ctx>(&self, ctx: &Context<'ctx>, class_name: &str) -> u32 {
|
||||
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
|
||||
zcount(con, format!("c:{}/{}", self.world_id, class_name)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Classes {
|
||||
async fn infiltrator<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "infiltrator").await
|
||||
}
|
||||
async fn light_assault<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "light_assault").await
|
||||
}
|
||||
async fn combat_medic<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "combat_medic").await
|
||||
}
|
||||
async fn engineer<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "engineer").await
|
||||
}
|
||||
async fn heavy_assault<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "heavy_assault").await
|
||||
}
|
||||
async fn max<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "max").await
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::http::Header;
|
||||
use rocket::{Request, Response};
|
||||
|
||||
pub struct CORS;
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Fairing for CORS {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Add CORS headers to responses",
|
||||
kind: Kind::Response,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Methods", "GET, POST"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Credentials", "false"));
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
use crate::redispool::RedisPool;
|
||||
|
||||
use self::types::{Health, World};
|
||||
use juniper::{graphql_object, FieldResult};
|
||||
use rocket::response::content::RawHtml;
|
||||
|
||||
pub mod types;
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[graphql_object(context = Context)]
|
||||
impl Query {
|
||||
fn world(id: String) -> FieldResult<World> {
|
||||
Ok(World {
|
||||
world_id: id.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn allWorlds() -> FieldResult<Vec<World>> {
|
||||
Ok(vec![
|
||||
World {
|
||||
world_id: "1".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "10".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "13".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "17".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "19".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "40".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "1000".to_string(),
|
||||
},
|
||||
World {
|
||||
world_id: "2000".to_string(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
fn worldByName(name: String) -> FieldResult<World> {
|
||||
let id = match name.to_lowercase().as_str() {
|
||||
"connery" => "1",
|
||||
"miller" => "10",
|
||||
"cobalt" => "13",
|
||||
"emerald" => "17",
|
||||
"jaeger" => "19",
|
||||
"soltech" => "40",
|
||||
"genudine" => "1000",
|
||||
"ceres" => "2000",
|
||||
_ => "-1",
|
||||
};
|
||||
|
||||
Ok(World {
|
||||
world_id: id.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn health() -> FieldResult<Health> {
|
||||
Ok(Health {})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
con: RedisPool,
|
||||
}
|
||||
|
||||
impl juniper::Context for Context {}
|
||||
|
||||
#[get("/graphiql")]
|
||||
pub fn graphiql() -> RawHtml<String> {
|
||||
juniper_rocket::graphiql_source("/graphql", None)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub fn playground() -> RawHtml<String> {
|
||||
juniper_rocket::playground_source("/graphql", None)
|
||||
}
|
||||
|
||||
#[get("/playground")]
|
||||
pub fn playground2() -> RawHtml<String> {
|
||||
juniper_rocket::playground_source("/graphql", None)
|
||||
}
|
||||
|
||||
#[post("/", data = "<query>")]
|
||||
pub async fn post_graphql(
|
||||
query: juniper_rocket::GraphQLRequest,
|
||||
schema: &rocket::State<Schema>,
|
||||
con: &RedisPool,
|
||||
) -> juniper_rocket::GraphQLResponse {
|
||||
query.execute(&*schema, &Context { con: con.clone() }).await
|
||||
}
|
||||
|
||||
#[get("/?<query..>")]
|
||||
pub async fn get_graphql(
|
||||
query: juniper_rocket::GraphQLRequest,
|
||||
schema: &rocket::State<Schema>,
|
||||
con: &RedisPool,
|
||||
) -> juniper_rocket::GraphQLResponse {
|
||||
query.execute(&*schema, &Context { con: con.clone() }).await
|
||||
}
|
||||
|
||||
pub type Schema = juniper::RootNode<
|
||||
'static,
|
||||
Query,
|
||||
juniper::EmptyMutation<Context>,
|
||||
juniper::EmptySubscription<Context>,
|
||||
>;
|
||||
|
||||
pub fn schema() -> Schema {
|
||||
Schema::new(
|
||||
Query,
|
||||
juniper::EmptyMutation::<Context>::new(),
|
||||
juniper::EmptySubscription::<Context>::new(),
|
||||
)
|
||||
}
|
|
@ -1,361 +0,0 @@
|
|||
use juniper::graphql_object;
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket_db_pools::deadpool_redis::redis::cmd;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Sub,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
static WORLD_ID_TO_NAME: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
("1", "Connery"),
|
||||
("10", "Miller"),
|
||||
("13", "Cobalt"),
|
||||
("17", "Emerald"),
|
||||
("19", "Jaeger"),
|
||||
("40", "SolTech"),
|
||||
("1000", "Genudine"),
|
||||
("2000", "Ceres"),
|
||||
])
|
||||
});
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct World {
|
||||
pub world_id: String,
|
||||
}
|
||||
|
||||
#[graphql_object(context = super::Context)]
|
||||
impl World {
|
||||
pub fn id(&self) -> juniper::ID {
|
||||
juniper::ID::from(self.world_id.clone())
|
||||
}
|
||||
pub fn name(&self) -> String {
|
||||
WORLD_ID_TO_NAME
|
||||
.get(&self.world_id.to_string().as_str())
|
||||
.unwrap_or(&"Unknown")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub async fn population(&self, context: &mut super::Context) -> i32 {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
let id = self.world_id.to_string();
|
||||
|
||||
let filter_timestamp = SystemTime::now()
|
||||
.sub(Duration::from_secs(60 * 15))
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let pop: u32 = cmd("ZCOUNT")
|
||||
.arg(format!("wp:{}", id))
|
||||
.arg(filter_timestamp)
|
||||
.arg("+inf")
|
||||
.query_async(&mut con)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
pop as i32
|
||||
}
|
||||
|
||||
pub async fn faction_population(&self) -> FactionPopulation {
|
||||
FactionPopulation {
|
||||
world_id: juniper::ID::from(self.world_id.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn vehicles(&self) -> Vehicles {
|
||||
Vehicles {
|
||||
world_id: juniper::ID::from(self.world_id.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn classes(&self) -> Classes {
|
||||
Classes {
|
||||
world_id: juniper::ID::from(self.world_id.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FactionPopulation {
|
||||
world_id: juniper::ID,
|
||||
}
|
||||
|
||||
impl FactionPopulation {
|
||||
async fn by_faction(&self, context: &super::Context, world_id: String, faction: i32) -> i32 {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
|
||||
let filter_timestamp = SystemTime::now()
|
||||
.sub(Duration::from_secs(60 * 15))
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
cmd("ZCOUNT")
|
||||
.arg(format!("wp:{}/{}", world_id, faction))
|
||||
.arg(filter_timestamp)
|
||||
.arg("+inf")
|
||||
.query_async(&mut con)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_object(context = super::Context)]
|
||||
#[graphql(description = "The population of each faction on a world")]
|
||||
impl FactionPopulation {
|
||||
async fn vs(&self, context: &super::Context) -> i32 {
|
||||
self.by_faction(context, self.world_id.to_string(), 1).await
|
||||
}
|
||||
async fn nc(&self, context: &super::Context) -> i32 {
|
||||
self.by_faction(context, self.world_id.to_string(), 2).await
|
||||
}
|
||||
async fn tr(&self, context: &super::Context) -> i32 {
|
||||
self.by_faction(context, self.world_id.to_string(), 3).await
|
||||
}
|
||||
async fn ns(&self, context: &super::Context) -> i32 {
|
||||
self.by_faction(context, self.world_id.to_string(), 4).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Vehicles {
|
||||
world_id: juniper::ID,
|
||||
}
|
||||
|
||||
impl Vehicles {
|
||||
async fn get_vehicle(
|
||||
&self,
|
||||
context: &super::Context,
|
||||
world_id: String,
|
||||
vehicle_name: &str,
|
||||
) -> i32 {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
|
||||
let filter_timestamp = SystemTime::now()
|
||||
.sub(Duration::from_secs(60 * 15))
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
cmd("ZCOUNT")
|
||||
.arg(format!("v:{}/{}", world_id, vehicle_name))
|
||||
.arg(filter_timestamp)
|
||||
.arg("+inf")
|
||||
.query_async(&mut con)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_object(context = super::Context)]
|
||||
#[graphql(description = "The count of active vehicles on a world")]
|
||||
impl Vehicles {
|
||||
// Transporters
|
||||
async fn flash(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "flash")
|
||||
.await
|
||||
}
|
||||
async fn sunderer(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "sunderer")
|
||||
.await
|
||||
}
|
||||
async fn ant(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "ant")
|
||||
.await
|
||||
}
|
||||
async fn harasser(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "harasser")
|
||||
.await
|
||||
}
|
||||
async fn javelin(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "javelin")
|
||||
.await
|
||||
}
|
||||
|
||||
// Tanks
|
||||
async fn lightning(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "lightning")
|
||||
.await
|
||||
}
|
||||
async fn prowler(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "prowler")
|
||||
.await
|
||||
}
|
||||
async fn vanguard(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "vanguard")
|
||||
.await
|
||||
}
|
||||
async fn magrider(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "magrider")
|
||||
.await
|
||||
}
|
||||
async fn chimera(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "chimera")
|
||||
.await
|
||||
}
|
||||
|
||||
// Air
|
||||
async fn mosquito(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "mosquito")
|
||||
.await
|
||||
}
|
||||
async fn liberator(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "liberator")
|
||||
.await
|
||||
}
|
||||
async fn galaxy(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "galaxy")
|
||||
.await
|
||||
}
|
||||
async fn valkyrie(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "valkyrie")
|
||||
.await
|
||||
}
|
||||
async fn reaver(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "reaver")
|
||||
.await
|
||||
}
|
||||
async fn scythe(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "scythe")
|
||||
.await
|
||||
}
|
||||
async fn dervish(&self, context: &super::Context) -> i32 {
|
||||
self.get_vehicle(context, self.world_id.to_string(), "dervish")
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Classes {
|
||||
pub world_id: juniper::ID,
|
||||
}
|
||||
|
||||
impl Classes {
|
||||
async fn get_class(&self, context: &super::Context, world_id: String, class_name: &str) -> i32 {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
|
||||
let filter_timestamp = SystemTime::now()
|
||||
.sub(Duration::from_secs(60 * 15))
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
cmd("ZCOUNT")
|
||||
.arg(format!("c:{}/{}", world_id, class_name))
|
||||
.arg(filter_timestamp)
|
||||
.arg("+inf")
|
||||
.query_async(&mut con)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_object(context = super::Context)]
|
||||
#[graphql(description = "The count of active classes on a world")]
|
||||
impl Classes {
|
||||
async fn infiltrator(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "infiltrator")
|
||||
.await
|
||||
}
|
||||
async fn light_assault(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "light_assault")
|
||||
.await
|
||||
}
|
||||
async fn combat_medic(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "combat_medic")
|
||||
.await
|
||||
}
|
||||
async fn engineer(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "engineer")
|
||||
.await
|
||||
}
|
||||
async fn heavy_assault(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "heavy_assault")
|
||||
.await
|
||||
}
|
||||
async fn max(&self, context: &super::Context) -> i32 {
|
||||
self.get_class(context, self.world_id.to_string(), "max")
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLEnum)]
|
||||
enum WebsocketState {
|
||||
#[graphql(
|
||||
description = "Using Nanite Systems manifold. This is the best possible running state."
|
||||
)]
|
||||
Primary,
|
||||
|
||||
#[graphql(
|
||||
description = "Using backup Daybreak Games manifold. This means the primary socket hasn't recieved events for at least 60 seconds."
|
||||
)]
|
||||
Backup,
|
||||
|
||||
#[graphql(description = "Both event processors are down. This is bad.")]
|
||||
Down,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLEnum)]
|
||||
enum UpDown {
|
||||
#[graphql(description = "Checks have passed.")]
|
||||
Up,
|
||||
|
||||
#[graphql(description = "Checks have failed. This is bad.")]
|
||||
Down,
|
||||
}
|
||||
|
||||
pub struct Health {}
|
||||
|
||||
impl Health {
|
||||
async fn get_heartbeat(context: &super::Context, pair: &str) -> WebsocketState {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
|
||||
let res: Result<i32, _> = cmd("GET")
|
||||
.arg(format!("heartbeat:{}:primary", pair))
|
||||
.query_async(&mut *con)
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => WebsocketState::Primary,
|
||||
Err(_) => {
|
||||
let res: Result<i32, _> = cmd("GET")
|
||||
.arg(format!("heartbeat:{}:backup", pair))
|
||||
.query_async(&mut con)
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => WebsocketState::Backup,
|
||||
Err(_) => WebsocketState::Down,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_object(context = super::Context)]
|
||||
#[graphql(description = "Saerro's self-checks. Down is universally bad.")]
|
||||
impl Health {
|
||||
#[graphql(description = "Checks PC event processors for its running state.")]
|
||||
async fn pc(context: &super::Context) -> WebsocketState {
|
||||
Health::get_heartbeat(context, "pc").await
|
||||
}
|
||||
|
||||
#[graphql(description = "Checks PS4 US event processors for its running state.")]
|
||||
async fn ps4us(context: &super::Context) -> WebsocketState {
|
||||
Health::get_heartbeat(context, "ps4us").await
|
||||
}
|
||||
|
||||
#[graphql(description = "Checks PS4 EU event processors for its running state.")]
|
||||
async fn ps4eu(context: &super::Context) -> WebsocketState {
|
||||
Health::get_heartbeat(context, "ps4eu").await
|
||||
}
|
||||
|
||||
#[graphql(description = "Is our datastore working?")]
|
||||
async fn redis(context: &super::Context) -> UpDown {
|
||||
let mut con = (*context).con.get().await.unwrap();
|
||||
|
||||
let res: Result<String, _> = cmd("PING").query_async(&mut con).await;
|
||||
|
||||
match res {
|
||||
Ok(_) => UpDown::Up,
|
||||
Err(_) => UpDown::Down,
|
||||
}
|
||||
}
|
||||
}
|
74
services/api/src/health.rs
Normal file
74
services/api/src/health.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use async_graphql::{Enum, Object};
|
||||
use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
|
||||
use redis::pipe;
|
||||
|
||||
pub async fn get_health(
|
||||
Extension(mut redis): Extension<redis::aio::MultiplexedConnection>,
|
||||
) -> impl IntoResponse {
|
||||
let (ping, pc, ps4us, ps4eu): (String, bool, bool, bool) = pipe()
|
||||
.cmd("PING")
|
||||
.get("heartbeat:pc")
|
||||
.get("heartbeat:ps4us")
|
||||
.get("heartbeat:ps4eu")
|
||||
.query_async(&mut redis)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if ping != "PONG" {
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(json!({
|
||||
"status": "error",
|
||||
"message": "Redis is not responding",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(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" },
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
|
||||
enum UpDown {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
|
||||
enum WebsocketState {
|
||||
Primary,
|
||||
Backup,
|
||||
Down,
|
||||
}
|
||||
|
||||
pub struct Health {}
|
||||
|
||||
#[Object]
|
||||
impl Health {
|
||||
async fn redis(&self) -> UpDown {
|
||||
UpDown::Up
|
||||
}
|
||||
|
||||
#[graphql(name = "pc")]
|
||||
async fn pc(&self) -> WebsocketState {
|
||||
WebsocketState::Primary
|
||||
}
|
||||
|
||||
#[graphql(name = "ps4us")]
|
||||
async fn ps4us(&self) -> WebsocketState {
|
||||
WebsocketState::Primary
|
||||
}
|
||||
|
||||
#[graphql(name = "ps4eu")]
|
||||
async fn ps4eu(&self) -> WebsocketState {
|
||||
WebsocketState::Primary
|
||||
}
|
||||
}
|
22
services/api/src/html/404.html
Normal file
22
services/api/src/html/404.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<title>404 - Saerro Listening Post</title>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
background-color: #010101;
|
||||
color: #e0e0e0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #cead42;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>404 Not Found</h1>
|
||||
<p>
|
||||
[<a href="/">home</a>] [<a href="/graphql/playground">graphql playground</a>]
|
||||
</p>
|
|
@ -1,91 +1,102 @@
|
|||
pub mod cors;
|
||||
pub mod graphql;
|
||||
pub mod redispool;
|
||||
mod classes;
|
||||
mod health;
|
||||
mod query;
|
||||
mod util;
|
||||
mod vehicles;
|
||||
mod world;
|
||||
|
||||
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};
|
||||
use async_graphql::{
|
||||
extensions::ApolloTracing,
|
||||
http::{playground_source, GraphQLPlaygroundConfig},
|
||||
EmptyMutation, EmptySubscription, Request, Response, Schema,
|
||||
};
|
||||
use axum::{
|
||||
extract::Query,
|
||||
http::Method,
|
||||
response::{Html, IntoResponse, Redirect},
|
||||
routing::{get, post},
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> RawHtml<String> {
|
||||
RawHtml(include_str!("html/index.html").to_string())
|
||||
async fn index() -> Html<&'static str> {
|
||||
Html(include_str!("html/index.html"))
|
||||
}
|
||||
|
||||
#[get("/health")]
|
||||
async fn health(
|
||||
mut con: Connection<RedisPool>,
|
||||
) -> Result<serde_json::Value, status::Custom<serde_json::Value>> {
|
||||
let (ping, pc, ps4us, ps4eu): (String, bool, bool, bool) = pipe()
|
||||
.cmd("PING")
|
||||
.get("heartbeat:pc")
|
||||
.get("heartbeat:ps4us")
|
||||
.get("heartbeat:ps4eu")
|
||||
.query_async(&mut *con)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
async fn handle_404() -> Html<&'static str> {
|
||||
Html(include_str!("html/404.html"))
|
||||
}
|
||||
|
||||
if ping != "PONG" {
|
||||
return Err(status::Custom(
|
||||
rocket::http::Status::ServiceUnavailable,
|
||||
json!({
|
||||
"status": "error",
|
||||
"message": "Redis is not responding",
|
||||
}),
|
||||
));
|
||||
async fn graphql_handler_post(
|
||||
Extension(schema): Extension<Schema<query::Query, EmptyMutation, EmptySubscription>>,
|
||||
Json(query): Json<Request>,
|
||||
) -> Json<Response> {
|
||||
Json(schema.execute(query).await)
|
||||
}
|
||||
|
||||
async fn graphql_handler_get(
|
||||
Extension(schema): Extension<Schema<query::Query, EmptyMutation, EmptySubscription>>,
|
||||
query: Query<Request>,
|
||||
) -> axum::response::Response {
|
||||
match query.operation_name {
|
||||
Some(_) => Json(schema.execute(query.0).await).into_response(),
|
||||
None => Redirect::to("/graphql/playground").into_response(),
|
||||
}
|
||||
|
||||
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" },
|
||||
}))
|
||||
}
|
||||
async fn graphql_playground() -> impl IntoResponse {
|
||||
Html(playground_source(GraphQLPlaygroundConfig::new("/graphql")))
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> Rocket<Build> {
|
||||
let figment = rocket::Config::figment().merge((
|
||||
"databases.redis.url",
|
||||
format!(
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let redis_url = format!(
|
||||
"redis://{}:{}",
|
||||
std::env::var("REDIS_HOST").unwrap_or("localhost".to_string()),
|
||||
std::env::var("REDIS_PORT").unwrap_or("6379".to_string()),
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
rocket::build()
|
||||
.configure(figment)
|
||||
.attach(cors::CORS)
|
||||
.attach(RedisPool::init())
|
||||
.attach(AdHoc::on_ignite("Redis Check", |rocket| async move {
|
||||
if let Some(pool) = RedisPool::fetch(&rocket) {
|
||||
let mut con = pool.get().await.unwrap();
|
||||
let _: () = cmd("PING").query_async(&mut con).await.unwrap();
|
||||
} else {
|
||||
error!("Redis connection failed");
|
||||
}
|
||||
rocket
|
||||
}))
|
||||
.manage(graphql::schema())
|
||||
.mount("/", routes![index, health,])
|
||||
.mount(
|
||||
let redis = redis::Client::open(redis_url)
|
||||
.unwrap()
|
||||
.get_multiplexed_tokio_connection()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let schema = Schema::build(query::Query, EmptyMutation, EmptySubscription)
|
||||
.data(redis.clone())
|
||||
.extension(ApolloTracing)
|
||||
.finish();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/health", get(health::get_health))
|
||||
.route(
|
||||
"/graphql",
|
||||
routes![
|
||||
graphql::graphiql,
|
||||
graphql::playground,
|
||||
graphql::playground2,
|
||||
graphql::get_graphql,
|
||||
graphql::post_graphql
|
||||
],
|
||||
post(graphql_handler_post).get(graphql_handler_get),
|
||||
)
|
||||
.route("/graphql/playground", get(graphql_playground))
|
||||
.fallback(handle_404)
|
||||
.layer(Extension(redis))
|
||||
.layer(Extension(schema))
|
||||
.layer(CorsLayer::new().allow_origin(Any).allow_methods([
|
||||
Method::GET,
|
||||
Method::POST,
|
||||
Method::OPTIONS,
|
||||
]));
|
||||
|
||||
let port: u16 = std::env::var("PORT")
|
||||
.unwrap_or("8000".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
|
24
services/api/src/query.rs
Normal file
24
services/api/src/query.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use crate::health::Health;
|
||||
use crate::world::World;
|
||||
use async_graphql::Object;
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn world(&self, id: String) -> World {
|
||||
World { id: id.clone() }
|
||||
}
|
||||
|
||||
async fn world_by_name(&self, name: String) -> World {
|
||||
World::from_name(name)
|
||||
}
|
||||
|
||||
async fn all_worlds(&self) -> Vec<World> {
|
||||
World::all_worlds()
|
||||
}
|
||||
|
||||
async fn health(&self) -> Health {
|
||||
Health {}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
use rocket_db_pools::{deadpool_redis, Database};
|
||||
|
||||
#[derive(Database, Clone)]
|
||||
#[database("redis")]
|
||||
pub struct RedisPool(deadpool_redis::Pool);
|
17
services/api/src/util.rs
Normal file
17
services/api/src/util.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use redis::{aio::MultiplexedConnection, AsyncCommands, FromRedisValue};
|
||||
use std::{
|
||||
ops::Sub,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
pub async fn zcount<RV: FromRedisValue>(mut con: MultiplexedConnection, key: String) -> RV {
|
||||
let filter_timestamp = SystemTime::now()
|
||||
.sub(Duration::from_secs(60 * 15))
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
con.zcount::<String, u64, &'static str, RV>(key, filter_timestamp, "+inf")
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
76
services/api/src/vehicles.rs
Normal file
76
services/api/src/vehicles.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use crate::util::zcount;
|
||||
use async_graphql::{Context, Object};
|
||||
use redis::aio::MultiplexedConnection;
|
||||
|
||||
pub struct Vehicles {
|
||||
world_id: String,
|
||||
}
|
||||
|
||||
impl Vehicles {
|
||||
pub fn new(world_id: String) -> Self {
|
||||
Self { world_id }
|
||||
}
|
||||
async fn by_vehicle<'ctx>(&self, ctx: &Context<'ctx>, vehicle_name: &str) -> u32 {
|
||||
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
|
||||
zcount(con, format!("v:{}/{}", self.world_id, vehicle_name)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Vehicles {
|
||||
async fn flash<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "flash").await
|
||||
}
|
||||
async fn sunderer<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "sunderer").await
|
||||
}
|
||||
async fn ant<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "ant").await
|
||||
}
|
||||
async fn harasser<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "harasser").await
|
||||
}
|
||||
async fn javelin<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "javelin").await
|
||||
}
|
||||
|
||||
// Tanks
|
||||
async fn lightning<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "lightning").await
|
||||
}
|
||||
async fn prowler<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "prowler").await
|
||||
}
|
||||
async fn vanguard<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "vanguard").await
|
||||
}
|
||||
async fn magrider<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "magrider").await
|
||||
}
|
||||
async fn chimera<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "chimera").await
|
||||
}
|
||||
|
||||
// Air
|
||||
async fn mosquito<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "mosquito").await
|
||||
}
|
||||
async fn liberator<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "liberator").await
|
||||
}
|
||||
async fn galaxy<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "galaxy").await
|
||||
}
|
||||
async fn valkyrie<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "valkyrie").await
|
||||
}
|
||||
async fn reaver<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "reaver").await
|
||||
}
|
||||
async fn scythe<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "scythe").await
|
||||
}
|
||||
async fn dervish<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "dervish").await
|
||||
}
|
||||
}
|
112
services/api/src/world.rs
Normal file
112
services/api/src/world.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use crate::{classes::Classes, util::zcount, vehicles::Vehicles};
|
||||
use async_graphql::{Context, Object};
|
||||
use lazy_static::lazy_static;
|
||||
use redis::aio::MultiplexedConnection;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static! {
|
||||
static ref WORLD_NAME_TO_ID: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
("connery", "1"),
|
||||
("miller", "10"),
|
||||
("cobalt", "13"),
|
||||
("emerald", "17"),
|
||||
("jaeger", "19"),
|
||||
("soltech", "40"),
|
||||
("genudine", "1000"),
|
||||
("ceres", "2000"),
|
||||
]);
|
||||
static ref WORLD_ID_TO_NAME: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
("1", "Connery"),
|
||||
("10", "Miller"),
|
||||
("13", "Cobalt"),
|
||||
("17", "Emerald"),
|
||||
("19", "Jaeger"),
|
||||
("40", "SolTech"),
|
||||
("1000", "Genudine"),
|
||||
("2000", "Ceres"),
|
||||
]);
|
||||
}
|
||||
|
||||
pub struct World {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn from_name(name: String) -> World {
|
||||
let id = WORLD_NAME_TO_ID
|
||||
.get(name.to_lowercase().as_str())
|
||||
.unwrap_or(&"-1");
|
||||
|
||||
World { id: id.to_string() }
|
||||
}
|
||||
|
||||
pub fn all_worlds() -> Vec<World> {
|
||||
WORLD_ID_TO_NAME
|
||||
.keys()
|
||||
.map(|id| World { id: id.to_string() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl World {
|
||||
async fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
async fn name(&self) -> String {
|
||||
WORLD_ID_TO_NAME
|
||||
.get(self.id.as_str())
|
||||
.unwrap_or(&"Unknown")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
async fn population<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
|
||||
zcount(con, format!("wp:{}", self.id)).await
|
||||
}
|
||||
|
||||
async fn faction_population(&self) -> FactionPopulation {
|
||||
FactionPopulation {
|
||||
world_id: self.id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn vehicles(&self) -> Vehicles {
|
||||
Vehicles::new(self.id.clone())
|
||||
}
|
||||
|
||||
async fn classes(&self) -> Classes {
|
||||
Classes::new(self.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct FactionPopulation {
|
||||
world_id: String,
|
||||
}
|
||||
|
||||
impl FactionPopulation {
|
||||
async fn by_faction<'ctx>(&self, ctx: &Context<'ctx>, faction: u8) -> u32 {
|
||||
let con = ctx.data::<MultiplexedConnection>().unwrap().to_owned();
|
||||
zcount(con, format!("wp:{}/{}", self.world_id, faction)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl FactionPopulation {
|
||||
async fn vs<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 1).await
|
||||
}
|
||||
|
||||
async fn nc<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 2).await
|
||||
}
|
||||
|
||||
async fn tr<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 3).await
|
||||
}
|
||||
|
||||
async fn ns<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 4).await
|
||||
}
|
||||
}
|
|
@ -7,8 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
redis = { version = "0.22.1", default_features = false, features = ["r2d2"] }
|
||||
redis_ts = "0.4.2"
|
||||
once_cell = "1.16.0"
|
||||
lazy_static = "1.4.0"
|
||||
tokio-tungstenite = { version = "0.17.2", features=["native-tls"] }
|
||||
serde_json = "1.0.88"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use futures::{pin_mut, FutureExt};
|
||||
use futures_util::StreamExt;
|
||||
use once_cell::sync::Lazy;
|
||||
use lazy_static::lazy_static;
|
||||
use redis::Commands;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
@ -9,14 +9,17 @@ use tokio_tungstenite::{connect_async, tungstenite::Message};
|
|||
|
||||
mod translators;
|
||||
|
||||
pub static REDIS_CLIENT: Lazy<redis::Client> = Lazy::new(|| {
|
||||
redis::Client::open(std::env::var("REDIS_ADDR").unwrap_or("redis://localhost:6379".to_string()))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
static PAIR: Lazy<String> = Lazy::new(|| env::var("PAIR").unwrap_or_default());
|
||||
static ROLE: Lazy<String> = Lazy::new(|| env::var("ROLE").unwrap_or("primary".to_string()));
|
||||
static WS_ADDR: Lazy<String> = Lazy::new(|| env::var("WS_ADDR").unwrap_or_default());
|
||||
lazy_static! {
|
||||
static ref REDIS_CLIENT: redis::Client = redis::Client::open(format!(
|
||||
"redis://{}:{}",
|
||||
std::env::var("REDIS_HOST").unwrap_or("localhost".to_string()),
|
||||
std::env::var("REDIS_PORT").unwrap_or("6379".to_string()),
|
||||
))
|
||||
.unwrap();
|
||||
static ref PAIR: String = env::var("PAIR").unwrap_or_default();
|
||||
static ref ROLE: String = env::var("ROLE").unwrap_or("primary".to_string());
|
||||
static ref WS_ADDR: String = env::var("WS_ADDR").unwrap_or_default();
|
||||
}
|
||||
|
||||
async fn send_init(tx: futures::channel::mpsc::UnboundedSender<Message>) {
|
||||
let worlds_raw = env::var("WORLDS").unwrap_or_default();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// GENERATED CODE -- Do not edit. Run `cargo run --bin codegen` to regenerate.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
static VEHICLE_TO_NAME: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
lazy_static! {
|
||||
static ref VEHICLE_TO_NAME: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
("1", "flash"),
|
||||
("2", "sunderer"),
|
||||
("3", "lightning"),
|
||||
|
@ -48,18 +48,8 @@ static VEHICLE_TO_NAME: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
|||
("2136", "dervish"),
|
||||
("2137", "chimera"),
|
||||
("2142", "corsair"),
|
||||
])
|
||||
});
|
||||
|
||||
pub fn vehicle_to_name(vehicle_id: &str) -> String {
|
||||
match VEHICLE_TO_NAME.get(&vehicle_id) {
|
||||
Some(name) => name.to_string(),
|
||||
None => "unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
static LOADOUT_TO_CLASS: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
]);
|
||||
static ref LOADOUT_TO_CLASS: HashMap<&'static str, &'static str> = HashMap::from([
|
||||
("1", "infiltrator"),
|
||||
("3", "light_assault"),
|
||||
("4", "combat_medic"),
|
||||
|
@ -84,8 +74,15 @@ static LOADOUT_TO_CLASS: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
|
|||
("31", "engineer"),
|
||||
("32", "heavy_assault"),
|
||||
("45", "max"),
|
||||
])
|
||||
});
|
||||
]);
|
||||
}
|
||||
|
||||
pub fn vehicle_to_name(vehicle_id: &str) -> String {
|
||||
match VEHICLE_TO_NAME.get(&vehicle_id) {
|
||||
Some(name) => name.to_string(),
|
||||
None => "unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loadout_to_class(loadout_id: &str) -> String {
|
||||
match LOADOUT_TO_CLASS.get(&loadout_id) {
|
||||
|
|
Loading…
Add table
Reference in a new issue