api rebuild
This commit is contained in:
parent
50c4ac387a
commit
cecaecb92e
11 changed files with 782 additions and 175 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -49,6 +49,7 @@ dependencies = [
|
|||
"async-graphql-axum",
|
||||
"axum",
|
||||
"lazy_static",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
|
@ -64,9 +65,9 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
|||
|
||||
[[package]]
|
||||
name = "async-graphql"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5005cfd364b44d9cb55486b44184fe41a57b97339e17ce10db05f6b6093571d9"
|
||||
checksum = "42bb92ffef089e5b61e90bcc004c9689554dfb5a150d88e81c7f6fef9e76eeae"
|
||||
dependencies = [
|
||||
"async-graphql-derive",
|
||||
"async-graphql-parser",
|
||||
|
@ -96,9 +97,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-graphql-axum"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5554f6f1578ba1c65942ff7103c4c5421a47890a64666e1863d38bb9194ab091"
|
||||
checksum = "077bf197d54397cff2324b79d86a7a21b2a83260e62e33eccae33009427897c9"
|
||||
dependencies = [
|
||||
"async-graphql",
|
||||
"async-trait",
|
||||
|
@ -113,9 +114,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-graphql-derive"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ff995b9d89198740d3701f1e5f7101e2822d5f4f1f4db19e9a1a9314cb14364"
|
||||
checksum = "4fa579c7cea32030600994d579554b257e10d5ad87705f3d150b49ee08bd629d"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"async-graphql-parser",
|
||||
|
@ -129,9 +130,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-graphql-parser"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e73570e2270b9921a183df47760bea67a65afb145eaaa1b82a9c34b0c6209ff"
|
||||
checksum = "3b67a5bea60997ca72908854655ae87f7970dc7d786d9a42fd1d17069fa42ebc"
|
||||
dependencies = [
|
||||
"async-graphql-value",
|
||||
"pest",
|
||||
|
@ -141,9 +142,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-graphql-value"
|
||||
version = "5.0.2"
|
||||
version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97013c726c11f29262f52e9487025a72bae58262bad3c26389936ce3bf143b11"
|
||||
checksum = "79c2721eb88245ca055e148a3f03cb11a88535c206ac5a7c59e9edb22816320a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"indexmap",
|
||||
|
|
|
@ -8,10 +8,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
serde_json = "1.0.89"
|
||||
serde = "1.0.149"
|
||||
async-graphql = { version = "5.0.2" }
|
||||
async-graphql-axum = "5.0.2"
|
||||
async-graphql = { version = "5.0.3" }
|
||||
async-graphql-axum = "5.0.3"
|
||||
axum = "0.6.1"
|
||||
sqlx = { version = "0.6.2", features = [ "runtime-tokio-native-tls", "postgres" ] }
|
||||
tokio = { version = "1.23.0", features = [ "full" ] }
|
||||
tower-http = { version = "0.3.5", features = ["cors"] }
|
||||
lazy_static = "1.4.0"
|
||||
lazy_static = "1.4.0"
|
||||
reqwest = "0.11.13"
|
|
@ -1,36 +1,140 @@
|
|||
use crate::utils::{Filters, IdOrNameBy};
|
||||
use async_graphql::{Context, Object};
|
||||
use sqlx::{Pool, Postgres, Row};
|
||||
|
||||
/// A specific with optional faction filter.
|
||||
pub struct Class {
|
||||
filters: Filters,
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
impl Class {
|
||||
async fn fetch<'ctx>(&self, ctx: &Context<'ctx>, filters: Filters) -> i64 {
|
||||
let pool = ctx.data::<Pool<Postgres>>().unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT count(distinct character_id) FROM classes WHERE time > now() - interval '15 minutes' AND class_id = $1 {};",
|
||||
filters.sql(),
|
||||
);
|
||||
|
||||
println!("{}", sql);
|
||||
|
||||
let query: i64 = sqlx::query(sql.as_str())
|
||||
.bind(self.class_name.as_str())
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.get(0);
|
||||
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Class {
|
||||
async fn total<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(ctx, self.filters.clone()).await
|
||||
}
|
||||
async fn nc<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(1)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn tr<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(2)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn vs<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(3)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Super-struct of each class.
|
||||
pub struct Classes {
|
||||
world_id: String,
|
||||
filters: Filters,
|
||||
}
|
||||
|
||||
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 {
|
||||
0
|
||||
pub fn new(filters: Option<Filters>) -> Self {
|
||||
Self {
|
||||
filters: filters.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Classes {
|
||||
async fn infiltrator<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "infiltrator").await
|
||||
async fn infiltrator(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "infiltrator".to_string(),
|
||||
}
|
||||
}
|
||||
async fn light_assault<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "light_assault").await
|
||||
async fn light_assault(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "light_assault".to_string(),
|
||||
}
|
||||
}
|
||||
async fn combat_medic<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "combat_medic").await
|
||||
async fn combat_medic(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "combat_medic".to_string(),
|
||||
}
|
||||
}
|
||||
async fn engineer<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "engineer").await
|
||||
async fn engineer(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "engineer".to_string(),
|
||||
}
|
||||
}
|
||||
async fn heavy_assault<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "heavy_assault").await
|
||||
async fn heavy_assault(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "heavy_assault".to_string(),
|
||||
}
|
||||
}
|
||||
async fn max<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_class(ctx, "max").await
|
||||
async fn max(&self) -> Class {
|
||||
Class {
|
||||
filters: self.filters.clone(),
|
||||
class_name: "max".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClassesQuery;
|
||||
|
||||
#[Object]
|
||||
impl ClassesQuery {
|
||||
/// Get all classes
|
||||
pub async fn classes(&self, filter: Option<Filters>) -> Classes {
|
||||
Classes::new(filter)
|
||||
}
|
||||
|
||||
/// Get a specific class
|
||||
pub async fn class(&self, filter: Option<Filters>, class_name: String) -> Class {
|
||||
Class {
|
||||
filters: filter.unwrap_or_default(),
|
||||
class_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,3 +91,14 @@ impl Health {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HealthQuery;
|
||||
|
||||
#[Object]
|
||||
impl HealthQuery {
|
||||
/// Reports on the health of Saerro Listening Post
|
||||
pub async fn health(&self) -> Health {
|
||||
Health {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
mod classes;
|
||||
mod health;
|
||||
mod population;
|
||||
mod query;
|
||||
mod utils;
|
||||
mod vehicles;
|
||||
mod world;
|
||||
mod zone;
|
||||
|
||||
use async_graphql::{
|
||||
http::GraphiQLSource, EmptyMutation, EmptySubscription, Request, Response, Schema,
|
||||
|
@ -61,7 +64,7 @@ async fn main() {
|
|||
.unwrap_or("postgres://saerrouser:saerro321@localhost:5432/data".to_string());
|
||||
let db = sqlx::PgPool::connect(&db_url).await.unwrap();
|
||||
|
||||
let schema = Schema::build(query::Query, EmptyMutation, EmptySubscription)
|
||||
let schema = Schema::build(query::Query::default(), EmptyMutation, EmptySubscription)
|
||||
.data(db.clone())
|
||||
.finish();
|
||||
|
||||
|
|
85
services/api/src/population.rs
Normal file
85
services/api/src/population.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::utils::Filters;
|
||||
use async_graphql::{Context, Object};
|
||||
use sqlx::{Pool, Postgres, Row};
|
||||
|
||||
/// A filterable list of currently active players.
|
||||
pub struct Population {
|
||||
filters: Filters,
|
||||
}
|
||||
|
||||
impl Population {
|
||||
pub fn new(filters: Option<Filters>) -> Self {
|
||||
Self {
|
||||
filters: filters.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Population {
|
||||
async fn by_faction<'ctx>(&self, ctx: &Context<'ctx>, faction: i32) -> i64 {
|
||||
let pool = ctx.data::<Pool<Postgres>>().unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT count(distinct character_id) FROM players WHERE time > now() - interval '15 minutes' AND faction_id = $1 {};",
|
||||
self.filters.sql(),
|
||||
);
|
||||
|
||||
println!("{}", sql);
|
||||
|
||||
let query: i64 = sqlx::query(sql.as_str())
|
||||
.bind(faction)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.get(0);
|
||||
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Population {
|
||||
async fn total<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
let pool = ctx.data::<Pool<Postgres>>().unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT count(distinct character_id) FROM players WHERE time > now() - interval '15 minutes' {};",
|
||||
self.filters.sql(),
|
||||
);
|
||||
|
||||
println!("{}", sql);
|
||||
|
||||
let query: i64 = sqlx::query(sql.as_str())
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.get(0);
|
||||
|
||||
query
|
||||
}
|
||||
async fn nc<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.by_faction(ctx, 1).await
|
||||
}
|
||||
async fn vs<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.by_faction(ctx, 2).await
|
||||
}
|
||||
async fn tr<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.by_faction(ctx, 3).await
|
||||
}
|
||||
async fn ns<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.by_faction(ctx, 4).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PopulationQuery;
|
||||
|
||||
#[Object]
|
||||
impl PopulationQuery {
|
||||
/// A filterable list of currently active players.
|
||||
/// This is a core query that others will use to filter by,
|
||||
/// i.e. `emerald { population { total } }` is equivalent to `population(filter: { world: { name: "emerald" } }) { total }`
|
||||
pub async fn population(&self, filter: Option<Filters>) -> Population {
|
||||
Population::new(filter)
|
||||
}
|
||||
}
|
|
@ -1,30 +1,15 @@
|
|||
use crate::health::Health;
|
||||
use crate::world::World;
|
||||
use async_graphql::Object;
|
||||
use crate::{
|
||||
classes::ClassesQuery, health::HealthQuery, population::PopulationQuery,
|
||||
vehicles::VehicleQuery, world::WorldQuery, zone::ZoneQuery,
|
||||
};
|
||||
use async_graphql::MergedObject;
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
/// Returns a graph for the world with the given ID.
|
||||
/// If the world does not exist, this will not fail.
|
||||
async fn world(&self, id: String) -> World {
|
||||
World { id: id.clone() }
|
||||
}
|
||||
|
||||
/// Returns a graph for the world specified by it's human name.
|
||||
/// This is case-insensitive; but will not fail.
|
||||
async fn world_by_name(&self, name: String) -> World {
|
||||
World::from_name(name)
|
||||
}
|
||||
|
||||
/// Returns a graph of all known live play worlds.
|
||||
async fn all_worlds(&self) -> Vec<World> {
|
||||
World::all_worlds()
|
||||
}
|
||||
|
||||
/// Reports on the health of Saerro Listening Post
|
||||
async fn health(&self) -> Health {
|
||||
Health {}
|
||||
}
|
||||
}
|
||||
#[derive(MergedObject, Default)]
|
||||
pub struct Query(
|
||||
PopulationQuery,
|
||||
VehicleQuery,
|
||||
ClassesQuery,
|
||||
WorldQuery,
|
||||
ZoneQuery,
|
||||
HealthQuery,
|
||||
);
|
||||
|
|
100
services/api/src/utils.rs
Normal file
100
services/api/src/utils.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use async_graphql::{InputObject, OneofObject};
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref WORLD_IDS: HashMap<String, i32> = HashMap::from([
|
||||
("connery".to_string(), 1),
|
||||
("miller".to_string(), 10),
|
||||
("cobalt".to_string(), 13),
|
||||
("emerald".to_string(), 17),
|
||||
("jaeger".to_string(), 19),
|
||||
("soltech".to_string(), 40),
|
||||
("genudine".to_string(), 1000),
|
||||
("ceres".to_string(), 2000),
|
||||
]);
|
||||
pub static ref ID_TO_WORLD: HashMap<i32, String> = WORLD_IDS
|
||||
.iter()
|
||||
.map(|(name, id)| (id.to_owned(), name.to_owned()))
|
||||
.collect();
|
||||
pub static ref FACTION_IDS: HashMap<String, i32> = HashMap::from([
|
||||
("vs".to_string(), 1),
|
||||
("nc".to_string(), 2),
|
||||
("tr".to_string(), 3),
|
||||
("ns".to_string(), 4),
|
||||
]);
|
||||
pub static ref ID_TO_FACTION: HashMap<i32, String> = FACTION_IDS
|
||||
.iter()
|
||||
.map(|(name, id)| (id.to_owned(), name.to_owned()))
|
||||
.collect();
|
||||
pub static ref ZONE_IDS: HashMap<String, i32> = HashMap::from([
|
||||
("indar".to_string(), 2),
|
||||
("hossin".to_string(), 4),
|
||||
("amerish".to_string(), 6),
|
||||
("esamir".to_string(), 8),
|
||||
("oshur".to_string(), 344),
|
||||
]);
|
||||
pub static ref ID_TO_ZONE: HashMap<i32, String> = ZONE_IDS
|
||||
.iter()
|
||||
.map(|(name, id)| (id.to_owned(), name.to_owned()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Allows for one of the following:
|
||||
/// - By ID, example: `{ id: 1 }`
|
||||
/// - By name (case-insensitive), example: `{ name: "Connery" }`
|
||||
#[derive(OneofObject, Clone)]
|
||||
pub enum IdOrNameBy {
|
||||
Id(i32),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
pub fn id_or_name_to_id(map: &HashMap<String, i32>, by: &IdOrNameBy) -> Option<i32> {
|
||||
match by {
|
||||
IdOrNameBy::Id(id) => Some(*id),
|
||||
IdOrNameBy::Name(name) => map.get(&name.to_lowercase()).map(|id| *id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id_or_name_to_name(map: &HashMap<i32, String>, id: &IdOrNameBy) -> Option<String> {
|
||||
match id {
|
||||
IdOrNameBy::Id(id) => map.get(id).map(|name| name.to_owned()),
|
||||
IdOrNameBy::Name(name) => Some(name.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter for core queries, allows for filtering by world, faction, and zone.
|
||||
/// Omitting a field will not filter by that field, so for example:
|
||||
/// `{ world: { id: 1 }, faction: { name: "VS" } }`
|
||||
/// will filter by world ID 1 and faction name "VS", but also search in every continent.
|
||||
#[derive(InputObject, Default, Clone)]
|
||||
pub struct Filters {
|
||||
/// The world to filter by, like Connery, Emerald, etc.
|
||||
pub world: Option<IdOrNameBy>,
|
||||
/// The faction to filter by, like VS, NC, TR, or NS
|
||||
pub faction: Option<IdOrNameBy>,
|
||||
/// The zone or continent to filter by, like Indar, Amerish, etc.
|
||||
pub zone: Option<IdOrNameBy>,
|
||||
}
|
||||
|
||||
impl Filters {
|
||||
pub fn sql(&self) -> String {
|
||||
let mut sql = String::new();
|
||||
if let Some(world) = &self.world {
|
||||
if let Some(world_id) = id_or_name_to_id(&WORLD_IDS, world) {
|
||||
sql.push_str(&format!(" AND world_id = {}", world_id));
|
||||
}
|
||||
}
|
||||
if let Some(faction) = &self.faction {
|
||||
if let Some(faction_id) = id_or_name_to_id(&FACTION_IDS, faction) {
|
||||
sql.push_str(&format!(" AND faction_id = {}", faction_id));
|
||||
}
|
||||
}
|
||||
if let Some(zone) = &self.zone {
|
||||
if let Some(zone_id) = id_or_name_to_id(&ZONE_IDS, zone) {
|
||||
sql.push_str(&format!(" AND zone_id = {}", zone_id));
|
||||
}
|
||||
}
|
||||
sql
|
||||
}
|
||||
}
|
|
@ -1,73 +1,221 @@
|
|||
use crate::utils::{Filters, IdOrNameBy};
|
||||
use async_graphql::{Context, Object};
|
||||
use sqlx::{Pool, Postgres, Row};
|
||||
|
||||
/// A specific vehicle
|
||||
pub struct Vehicle {
|
||||
filters: Filters,
|
||||
vehicle_name: String,
|
||||
}
|
||||
|
||||
impl Vehicle {
|
||||
async fn fetch<'ctx>(&self, ctx: &Context<'ctx>, filters: Filters) -> i64 {
|
||||
let pool = ctx.data::<Pool<Postgres>>().unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT count(distinct character_id) FROM vehicles WHERE time > now() - interval '15 minutes' AND vehicle_id = $1 {};",
|
||||
filters.sql(),
|
||||
);
|
||||
|
||||
println!("{}", sql);
|
||||
|
||||
let query: i64 = sqlx::query(sql.as_str())
|
||||
.bind(self.vehicle_name.as_str())
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.get(0);
|
||||
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Vehicle {
|
||||
async fn total<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(ctx, self.filters.clone()).await
|
||||
}
|
||||
async fn nc<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(1)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn tr<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(2)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn vs<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
self.fetch(
|
||||
ctx,
|
||||
Filters {
|
||||
faction: Some(IdOrNameBy::Id(3)),
|
||||
..self.filters.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Super-struct for all vehicles.
|
||||
pub struct Vehicles {
|
||||
world_id: String,
|
||||
filters: Filters,
|
||||
}
|
||||
|
||||
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 {
|
||||
0
|
||||
pub fn new(filters: Option<Filters>) -> Self {
|
||||
Self {
|
||||
filters: filters.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Vehicles {
|
||||
async fn flash<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "flash").await
|
||||
async fn total<'ctx>(&self, ctx: &Context<'ctx>) -> i64 {
|
||||
let pool = ctx.data::<Pool<Postgres>>().unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT count(distinct character_id) FROM vehicles WHERE time > now() - interval '15 minutes' {};",
|
||||
self.filters.sql(),
|
||||
);
|
||||
|
||||
println!("{}", sql);
|
||||
|
||||
let query: i64 = sqlx::query(sql.as_str())
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.get(0);
|
||||
|
||||
query
|
||||
}
|
||||
async fn sunderer<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "sunderer").await
|
||||
|
||||
// Transport
|
||||
async fn flash(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "flash".to_string(),
|
||||
}
|
||||
}
|
||||
async fn ant<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "ant").await
|
||||
async fn sunderer(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "sunderer".to_string(),
|
||||
}
|
||||
}
|
||||
async fn harasser<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "harasser").await
|
||||
async fn ant(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "ant".to_string(),
|
||||
}
|
||||
}
|
||||
async fn javelin<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "javelin").await
|
||||
async fn harasser(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "harasser".to_string(),
|
||||
}
|
||||
}
|
||||
async fn javelin(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "javelin".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Tanks
|
||||
async fn lightning<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "lightning").await
|
||||
async fn lightning(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "javelin".to_string(),
|
||||
}
|
||||
}
|
||||
async fn prowler<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "prowler").await
|
||||
async fn prowler(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "prowler".to_string(),
|
||||
}
|
||||
}
|
||||
async fn vanguard<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "vanguard").await
|
||||
async fn vanguard(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "vanguard".to_string(),
|
||||
}
|
||||
}
|
||||
async fn magrider<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "magrider").await
|
||||
async fn magrider(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "magrider".to_string(),
|
||||
}
|
||||
}
|
||||
async fn chimera<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "chimera").await
|
||||
async fn chimera(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "chimera".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Air
|
||||
async fn mosquito<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "mosquito").await
|
||||
async fn mosquito(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "mosquito".to_string(),
|
||||
}
|
||||
}
|
||||
async fn liberator<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "liberator").await
|
||||
async fn liberator(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "liberator".to_string(),
|
||||
}
|
||||
}
|
||||
async fn galaxy<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "galaxy").await
|
||||
async fn galaxy(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "galaxy".to_string(),
|
||||
}
|
||||
}
|
||||
async fn valkyrie<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "valkyrie").await
|
||||
async fn valkyrie(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "valkyrie".to_string(),
|
||||
}
|
||||
}
|
||||
async fn reaver<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "reaver").await
|
||||
async fn reaver(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "reaver".to_string(),
|
||||
}
|
||||
}
|
||||
async fn scythe<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "scythe").await
|
||||
async fn scythe(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "scythe".to_string(),
|
||||
}
|
||||
}
|
||||
async fn dervish<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_vehicle(ctx, "dervish").await
|
||||
async fn dervish(&self) -> Vehicle {
|
||||
Vehicle {
|
||||
filters: self.filters.clone(),
|
||||
vehicle_name: "dervish".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VehicleQuery;
|
||||
|
||||
#[Object]
|
||||
impl VehicleQuery {
|
||||
pub async fn vehicles(&self, filter: Option<Filters>) -> Vehicles {
|
||||
Vehicles::new(filter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,25 @@
|
|||
use crate::{classes::Classes, vehicles::Vehicles};
|
||||
use async_graphql::{Context, Object};
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
use crate::{
|
||||
classes::Classes,
|
||||
population::Population,
|
||||
utils::{id_or_name_to_id, id_or_name_to_name, Filters, IdOrNameBy, ID_TO_WORLD, WORLD_IDS},
|
||||
vehicles::Vehicles,
|
||||
zone::Zones,
|
||||
};
|
||||
use async_graphql::Object;
|
||||
|
||||
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,
|
||||
filter: Filters,
|
||||
}
|
||||
|
||||
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()
|
||||
pub fn new(filter: IdOrNameBy) -> Self {
|
||||
Self {
|
||||
filter: Filters {
|
||||
world: Some(filter),
|
||||
faction: None,
|
||||
zone: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,61 +31,121 @@ impl World {
|
|||
/// If World.id is not valid or known to the API, World.name will return "Unknown".
|
||||
#[Object]
|
||||
impl World {
|
||||
async fn id(&self) -> &str {
|
||||
&self.id
|
||||
/// The ID of the world.
|
||||
async fn id(&self) -> i32 {
|
||||
id_or_name_to_id(&WORLD_IDS, self.filter.world.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
/// The name of the world, in official game capitalization.
|
||||
async fn name(&self) -> String {
|
||||
WORLD_ID_TO_NAME
|
||||
.get(self.id.as_str())
|
||||
.unwrap_or(&"Unknown")
|
||||
.to_string()
|
||||
}
|
||||
let name = id_or_name_to_name(&ID_TO_WORLD, self.filter.world.as_ref().unwrap()).unwrap();
|
||||
|
||||
async fn population<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
async fn faction_population(&self) -> FactionPopulation {
|
||||
FactionPopulation {
|
||||
world_id: self.id.clone(),
|
||||
// Special case for SolTech, lol.
|
||||
if name == "soltech" {
|
||||
return "SolTech".to_string();
|
||||
}
|
||||
|
||||
// Capitalize the first letter
|
||||
name[0..1].to_uppercase() + &name[1..]
|
||||
}
|
||||
|
||||
/// Population filtered to this world.
|
||||
async fn population(&self) -> Population {
|
||||
Population::new(Some(Filters {
|
||||
world: self.filter.world.clone(),
|
||||
faction: None,
|
||||
zone: None,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Vehicles filtered to this world.
|
||||
async fn vehicles(&self) -> Vehicles {
|
||||
Vehicles::new(self.id.clone())
|
||||
Vehicles::new(Some(Filters {
|
||||
world: self.filter.world.clone(),
|
||||
faction: None,
|
||||
zone: None,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Classes filtered to this world.
|
||||
async fn classes(&self) -> Classes {
|
||||
Classes::new(self.id.clone())
|
||||
Classes::new(Some(Filters {
|
||||
world: self.filter.world.clone(),
|
||||
faction: None,
|
||||
zone: None,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get a specific zone/continent on this world.
|
||||
async fn zones(&self) -> Zones {
|
||||
Zones::new(Some(self.filter.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
struct FactionPopulation {
|
||||
world_id: String,
|
||||
}
|
||||
|
||||
impl FactionPopulation {
|
||||
async fn by_faction<'ctx>(&self, ctx: &Context<'ctx>, faction: u8) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct WorldQuery;
|
||||
|
||||
#[Object]
|
||||
impl FactionPopulation {
|
||||
async fn vs<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 1).await
|
||||
impl WorldQuery {
|
||||
/// A world by ID or name.
|
||||
pub async fn world(&self, by: IdOrNameBy) -> World {
|
||||
World::new(by)
|
||||
}
|
||||
|
||||
async fn nc<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 2).await
|
||||
/// All worlds. This is a convenience method for getting all worlds in one query.
|
||||
/// If you want all of them as aggregate instead of as individual units, use `population`, `vehicles`, `classes` directly instead.
|
||||
pub async fn all_worlds(&self) -> Vec<World> {
|
||||
ID_TO_WORLD
|
||||
.iter()
|
||||
.map(|(id, _)| World::new(IdOrNameBy::Id(*id)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn tr<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 3).await
|
||||
/// The Connery world in US West on PC
|
||||
/// Shorthand for `world(by: { id: 1 }})`
|
||||
pub async fn connery(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(1))
|
||||
}
|
||||
|
||||
async fn ns<'ctx>(&self, ctx: &Context<'ctx>) -> u32 {
|
||||
self.by_faction(ctx, 4).await
|
||||
/// The Miller world in EU on PC
|
||||
/// Shorthand for `world(by: { id: 10 }})`
|
||||
pub async fn miller(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(10))
|
||||
}
|
||||
|
||||
/// The Cobalt world in EU on PC
|
||||
/// Shorthand for `world(by: { id: 13 }})`
|
||||
pub async fn cobalt(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(13))
|
||||
}
|
||||
|
||||
/// The Emerald world in US East on PC
|
||||
/// Shorthand for `world(by: { id: 17 }})`
|
||||
pub async fn emerald(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(17))
|
||||
}
|
||||
|
||||
/// The Jaeger world in US East on PC
|
||||
/// Shorthand for `world(by: { id: 19 }})`
|
||||
pub async fn jaeger(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(19))
|
||||
}
|
||||
|
||||
/// The SolTech world in Japan on PC
|
||||
/// Shorthand for `world(by: { id: 40 }})`
|
||||
pub async fn soltech(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(40))
|
||||
}
|
||||
|
||||
/// The Genudine world in US East on PS4
|
||||
/// Shorthand for `world(by: { id: 1000 }})`
|
||||
pub async fn genudine(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(1000))
|
||||
}
|
||||
|
||||
/// The Ceres world in EU on PS4
|
||||
/// Shorthand for `world(by: { id: 2000 }})`
|
||||
pub async fn ceres(&self) -> World {
|
||||
World::new(IdOrNameBy::Id(2000))
|
||||
}
|
||||
}
|
||||
|
|
132
services/api/src/zone.rs
Normal file
132
services/api/src/zone.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use crate::{
|
||||
classes::Classes,
|
||||
population::Population,
|
||||
utils::{id_or_name_to_id, id_or_name_to_name, Filters, IdOrNameBy, ID_TO_ZONE, ZONE_IDS},
|
||||
vehicles::Vehicles,
|
||||
};
|
||||
use async_graphql::Object;
|
||||
|
||||
/// An individual zone/continent.
|
||||
pub struct Zone {
|
||||
filters: Filters,
|
||||
}
|
||||
|
||||
impl Zone {
|
||||
pub fn new(filters: Option<Filters>) -> Self {
|
||||
Self {
|
||||
filters: filters.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Zone {
|
||||
/// The ID of the zone/continent.
|
||||
async fn id(&self) -> i32 {
|
||||
id_or_name_to_id(&ZONE_IDS, self.filters.zone.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
/// The name of the continent, in official game capitalization.
|
||||
async fn name(&self) -> String {
|
||||
let name = id_or_name_to_name(&ID_TO_ZONE, self.filters.zone.as_ref().unwrap()).unwrap();
|
||||
|
||||
// Capitalize the first letter
|
||||
name[0..1].to_uppercase() + &name[1..]
|
||||
}
|
||||
|
||||
async fn population(&self) -> Population {
|
||||
Population::new(Some(self.filters.clone()))
|
||||
}
|
||||
|
||||
async fn vehicles(&self) -> Vehicles {
|
||||
Vehicles::new(Some(self.filters.clone()))
|
||||
}
|
||||
|
||||
async fn classes(&self) -> Classes {
|
||||
Classes::new(Some(self.filters.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Super-struct for querying zones/continents.
|
||||
pub struct Zones {
|
||||
filters: Filters,
|
||||
}
|
||||
|
||||
impl Zones {
|
||||
pub fn new(filters: Option<Filters>) -> Self {
|
||||
Self {
|
||||
filters: filters.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl Zones {
|
||||
/// Every zone/continent individually.
|
||||
async fn all(&self) -> Vec<Zone> {
|
||||
ID_TO_ZONE
|
||||
.iter()
|
||||
.map(|(id, _)| {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(*id)),
|
||||
}))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn indar(&self) -> Zone {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(2)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn hossin(&self) -> Zone {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(4)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn amerish(&self) -> Zone {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(6)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn esamir(&self) -> Zone {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(8)),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn oshur(&self) -> Zone {
|
||||
Zone::new(Some(Filters {
|
||||
world: self.filters.world.clone(),
|
||||
faction: self.filters.faction.clone(),
|
||||
zone: Some(IdOrNameBy::Id(344)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ZoneQuery;
|
||||
|
||||
#[Object]
|
||||
impl ZoneQuery {
|
||||
pub async fn zone(&self, filter: Option<Filters>) -> Zone {
|
||||
Zone::new(filter)
|
||||
}
|
||||
|
||||
pub async fn zones(&self, filter: Option<Filters>) -> Zones {
|
||||
Zones::new(filter)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue