finish service
This commit is contained in:
parent
83ad349f30
commit
6aac273b9a
10 changed files with 214 additions and 41 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
.git
|
23
.github/workflows/ci.yaml
vendored
Normal file
23
.github/workflows/ci.yaml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: "CI"
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: docker buildx create --use --driver=docker-container
|
||||
- run: |
|
||||
TAG_LATEST_IF_MASTER=$(if [ "$GITHUB_REF_NAME" = "main" ]; then echo "-t ghcr.io/${{ github.repository }}/agg-population:latest"; else echo ""; fi)
|
||||
docker buildx build . \
|
||||
-t ghcr.io/${{ github.repository }}/agg-population:${{ github.sha }} $TAG_LATEST_IF_MASTER \
|
||||
--push \
|
||||
--cache-to type=gha,scope=$GITHUB_REF_NAME-agg-population \
|
||||
--cache-from type=gha,scope=$GITHUB_REF_NAME-agg-population
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"editor.tabSize": 4
|
||||
}
|
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -102,6 +102,15 @@ version = "0.21.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -604,6 +613,7 @@ name = "metagame"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"openssl",
|
||||
|
|
|
@ -18,4 +18,5 @@ tracing = "0.1"
|
|||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
lazy_static = "1.4"
|
||||
serde-aux = "4"
|
||||
serde-aux = "4"
|
||||
bincode = "1.3"
|
||||
|
|
27
nomad/metagame.nomad.hcl
Normal file
27
nomad/metagame.nomad.hcl
Normal file
|
@ -0,0 +1,27 @@
|
|||
job "metagame" {
|
||||
type = "service"
|
||||
|
||||
update {
|
||||
max_parallel = 1
|
||||
stagger = "10s"
|
||||
}
|
||||
|
||||
group "api" {
|
||||
count = 1
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
static = 8067
|
||||
}
|
||||
}
|
||||
|
||||
task "api" {
|
||||
driver = "docker"
|
||||
|
||||
config {
|
||||
image = "ghcr.io/genudine/metagame/metagame:latest"
|
||||
ports = ["http"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,13 @@
|
|||
use crate::{alert_types::alert_type, misc};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
alert_types::alert_type,
|
||||
misc,
|
||||
types::{Alert, FactionPercents},
|
||||
};
|
||||
use chrono::{TimeZone, Utc};
|
||||
use serde::Deserialize;
|
||||
use serde_aux::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Alert {
|
||||
pub id: i32,
|
||||
pub zone: i32,
|
||||
pub end_time: Option<DateTime<Utc>>,
|
||||
pub start_time: Option<DateTime<Utc>>,
|
||||
pub alert_type: String,
|
||||
pub ps2alerts: String,
|
||||
}
|
||||
|
||||
pub async fn get_alerts(world_id: i32) -> Result<Vec<Alert>, ()> {
|
||||
let response = reqwest::get(format!(
|
||||
"https://census.daybreakgames.com/s:{}/get/{}/world_event/?world_id={}&type=METAGAME&c:limit=10",
|
||||
|
@ -42,6 +36,11 @@ pub async fn get_alerts(world_id: i32) -> Result<Vec<Alert>, ()> {
|
|||
"https://ps2alerts.com/alert/{}-{}",
|
||||
world_id, world_event.id
|
||||
),
|
||||
percentages: FactionPercents {
|
||||
nc: world_event.faction_nc,
|
||||
tr: world_event.faction_tr,
|
||||
vs: world_event.faction_vs,
|
||||
},
|
||||
});
|
||||
|
||||
if world_event.metagame_event_state_name == "started" {
|
||||
|
@ -81,4 +80,10 @@ struct WorldEvent {
|
|||
timestamp: i64,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
zone_id: i32,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
faction_nc: f32,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
faction_tr: f32,
|
||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||
faction_vs: f32,
|
||||
}
|
||||
|
|
103
src/main.rs
103
src/main.rs
|
@ -1,8 +1,16 @@
|
|||
#![feature(slice_group_by)]
|
||||
|
||||
use axum::{extract::Path, response::Html, routing::get, Json, Router};
|
||||
use alerts::get_alerts;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::Html,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use std::{env, net::SocketAddr};
|
||||
use types::{FactionPercents, World, Zone};
|
||||
use tokio::task::JoinSet;
|
||||
use tracing::Level;
|
||||
use types::{World, Zone};
|
||||
use zones::get_zone_states;
|
||||
|
||||
mod alert_types;
|
||||
|
@ -13,12 +21,16 @@ mod zones;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::DEBUG)
|
||||
.init();
|
||||
|
||||
let app = Router::new()
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.route("/:world", get(get_world))
|
||||
.route("/", get(root));
|
||||
.route("/:world", get(get_one_world))
|
||||
// .route("/all", get(get_all_worlds))
|
||||
.route("/", get(root))
|
||||
.with_state(sled::open("/tmp/metagame").expect("open"));
|
||||
|
||||
let addr = SocketAddr::from((
|
||||
[127, 0, 0, 1],
|
||||
|
@ -38,10 +50,79 @@ async fn root() -> Html<&'static str> {
|
|||
Html(include_str!("./html/index.html"))
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn get_world(Path(world): Path<i32>) -> Json<World> {
|
||||
Json(World {
|
||||
id: world,
|
||||
zones: get_zone_states(world).await.unwrap(),
|
||||
})
|
||||
pub async fn get_one_world(State(db): State<sled::Db>, Path(world): Path<i32>) -> Json<World> {
|
||||
Json(get_world(db, world).await)
|
||||
}
|
||||
|
||||
pub async fn get_all_worlds(State(db): State<sled::Db>) -> Json<Vec<World>> {
|
||||
let mut set = JoinSet::new();
|
||||
let mut worlds = vec![World::default(); 8];
|
||||
|
||||
for world in vec![1, 10, 13, 17, 19, 40, 1000, 2000] {
|
||||
set.spawn(get_world(db.clone(), world));
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
while let Some(response) = set.join_next().await {
|
||||
worlds[i] = response.unwrap_or_default();
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Json(worlds)
|
||||
}
|
||||
|
||||
pub async fn get_world(db: sled::Db, world: i32) -> World {
|
||||
match world_from_cache(db.clone(), world) {
|
||||
Ok(response) => return response,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let alerts = get_alerts(world).await.unwrap();
|
||||
let zones = get_zone_states(world).await.unwrap();
|
||||
|
||||
let converged_zones: Vec<Zone> = zones
|
||||
.into_iter()
|
||||
.map(|zone| {
|
||||
let mut zone = zone;
|
||||
let alert = alerts.iter().find(|alert| alert.zone == zone.id);
|
||||
|
||||
zone.alert = alert.cloned();
|
||||
|
||||
zone
|
||||
})
|
||||
.collect();
|
||||
|
||||
let response = World {
|
||||
id: world,
|
||||
zones: converged_zones,
|
||||
cached_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
world_to_cache(db, world, &response);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn world_from_cache(db: sled::Db, world: i32) -> Result<World, ()> {
|
||||
let key = format!("world:{}", world);
|
||||
let value = match db.get(key) {
|
||||
Ok(Some(value)) => value,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
match bincode::deserialize::<World>(&value) {
|
||||
Ok(response) => {
|
||||
if response.cached_at + chrono::Duration::minutes(3) < chrono::Utc::now() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn world_to_cache(db: sled::Db, world: i32, response: &World) {
|
||||
let key = format!("world:{}", world);
|
||||
let value = bincode::serialize(response).unwrap();
|
||||
db.insert(key, value).unwrap();
|
||||
}
|
||||
|
|
22
src/types.rs
22
src/types.rs
|
@ -1,31 +1,33 @@
|
|||
use serde::Serialize;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct World {
|
||||
pub id: i32,
|
||||
pub zones: Vec<Zone>,
|
||||
pub cached_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct Zone {
|
||||
pub id: i32,
|
||||
pub locked: bool,
|
||||
pub alert: Option<Alert>,
|
||||
pub faction_control: FactionPercents,
|
||||
pub territory: FactionPercents,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct Alert {
|
||||
pub id: i32,
|
||||
#[serde(rename = "type")]
|
||||
pub zone: i32,
|
||||
pub end_time: Option<DateTime<Utc>>,
|
||||
pub start_time: Option<DateTime<Utc>>,
|
||||
pub alert_type: String,
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
pub score: FactionPercents,
|
||||
pub ps2alerts: String,
|
||||
pub percentages: FactionPercents,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct FactionPercents {
|
||||
pub vs: f32,
|
||||
pub nc: f32,
|
||||
|
|
31
src/zones.rs
31
src/zones.rs
|
@ -3,7 +3,7 @@ use crate::{
|
|||
types::{FactionPercents, Zone},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde_aux::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -48,7 +48,7 @@ pub async fn get_zone_states(world_id: i32) -> Result<Vec<Zone>, ()> {
|
|||
id: map_zone.zone_id,
|
||||
locked: warpgate_factions[0] == warpgate_factions[1]
|
||||
&& warpgate_factions[1] == warpgate_factions[2],
|
||||
faction_control: calculate_faction_percents(&map_zone.regions.row),
|
||||
territory: calculate_faction_percents(&map_zone.regions.row),
|
||||
alert: None,
|
||||
};
|
||||
|
||||
|
@ -61,17 +61,36 @@ pub async fn get_zone_states(world_id: i32) -> Result<Vec<Zone>, ()> {
|
|||
fn calculate_faction_percents(regions: &Vec<MapRegionRowData>) -> FactionPercents {
|
||||
let groups = regions.group_by(|a, b| a.row_data.faction_id == b.row_data.faction_id);
|
||||
|
||||
let mut faction_percents = FactionPercents {
|
||||
struct FactionTotals {
|
||||
vs: f32,
|
||||
nc: f32,
|
||||
tr: f32,
|
||||
}
|
||||
|
||||
let mut faction_totals = FactionTotals {
|
||||
vs: 0.0,
|
||||
nc: 0.0,
|
||||
tr: 0.0,
|
||||
};
|
||||
|
||||
for faction in groups {
|
||||
faction.
|
||||
for row in groups {
|
||||
let faction_id = row[0].row_data.faction_id;
|
||||
|
||||
match faction_id {
|
||||
1 => faction_totals.vs += 1.0,
|
||||
2 => faction_totals.nc += 1.0,
|
||||
3 => faction_totals.tr += 1.0,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
faction_percents
|
||||
let total = faction_totals.vs + faction_totals.nc + faction_totals.tr;
|
||||
|
||||
FactionPercents {
|
||||
vs: faction_totals.vs / total * 100.0,
|
||||
nc: faction_totals.nc / total * 100.0,
|
||||
tr: faction_totals.tr / total * 100.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue