From 56cdd18ff289f9f730465cbd50a0bc9f3c6a0bc6 Mon Sep 17 00:00:00 2001 From: schneefux Date: Tue, 21 Mar 2017 16:24:23 +0100 Subject: first implementation of updater --- .gitignore | 59 +++++++++++++++++++ api.js | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 30 ++++++++++ package.json | 18 ++++++ 4 files changed, 288 insertions(+) create mode 100644 .gitignore create mode 100644 api.js create mode 100644 index.html create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00cbbdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + diff --git a/api.js b/api.js new file mode 100644 index 0000000..5bd341e --- /dev/null +++ b/api.js @@ -0,0 +1,181 @@ +#!/usr/bin/env node +/* jshint esnext: true */ + +var request = require("request-promise"); + +var pg = require("pg"); +var Pool = pg.Pool; +var db_config_raw = { + user: "vainraw", + password: "vainraw", + host: "localhost", + database: "vainsocial-raw", + port: 5433, + max: 10 +}; +var db_config_web = { + user: "vainweb", + password: "vainweb", + host: "localhost", + database: "vainsocial-web", + port: 5432, + max: 10 +}; +var pool_raw = new Pool(db_config_raw); +var pool_web = new Pool(db_config_web); + +var app = require("express")(); +var http = require("http").Server(app); +var io = require("socket.io")(http); + +var APITOKEN = process.env.VAINSOCIAL_APITOKEN; +if (APITOKEN == undefined) throw "Need a valid API token!"; + +http.listen(8080); + +/* API helper */ +/* searches for player name across all shards */ +async function findPlayer(name) { + var regions = ["na", "eu", "sg"], + finds = []; + + for (let region of regions) { + var options = { + uri: "https://api.dc01.gamelockerapp.com/shards/" + region + "/players", + headers: { + "X-Title-Id": "semc-vainglory", + "Authorization": APITOKEN + }, + qs: { + "filter[playerNames]": name + }, + json: true, + gzip: true + }; + try { + res = await request(options); + finds.push({ + "region": res.data[0].attributes.shardId, + "id": res.data[0].id, + "last_update": res.data[0].attributes.createdAt + }); + } catch (err) { + // TODO + } + } + + if (finds.length == 0) + return undefined; + + // due to an API bug, many players are also present in NA + // TODO: get history for all regions in case of region transfer + finds.sort((a, b) => { return a.last_update < b.last_update; }); + return finds[0]; +} + +/* routes */ +app.get("/api/player/:name", async (req, res) => { + var name = req.params.name; + + var raw = await pool_raw.connect(), + web = await pool_web.connect(); + + /* search for the player in our db */ + var player = await web.query(` + SELECT api_id, shard_id, last_match_created_date + FROM player WHERE name=$1 + `, [name]); + var player_id, player_region, grab_start; + + /* found */ + if (player.rows.length > 0) { + console.log("player '" + name + "' was found in db"); + player_id = player.rows[0].api_id; + player_region = player.rows[0].shard_id; + grab_start = player.rows[0].last_match_created_date; + // TODO db does not save time zone offset @stormcaller remove this + grab_start.setMinutes(grab_start.getMinutes() - (new Date().getTimezoneOffset())); + } + /* not found */ + if (player.rows.length == 0) { + console.log("player '" + name + "' not found in db"); + /* search in all regions */ + player = await findPlayer(name); + if (player == undefined) { + console.log("player '" + name + "' not found in API"); + /* give up */ + res.sendStatus(404); + return; + } + + player_id = player.id; + player_region = player.region; + grab_start = new Date("2017-01-01T00:00:00Z"); + } + + /* request update job */ + // createdAt-start <= x <= createdAt-end + grab_start.setSeconds(grab_start.getSeconds() + 1); + payload = { + "region": player_region, + "params": { + "filter[playerIds]": player_id, + "filter[playerNames]": name, // TODO remove in 2.0 - backwards compat + "filter[createdAt-start]": grab_start.toISOString(), + "filter[gameMode]": "casual,ranked" + } + }; + var jobq = await raw.query(` + INSERT INTO jobs(type, payload, priority) + VALUES('grab', $1, 0) + RETURNING id + `, [payload]); + console.log("player '" + name + "' updating after " + grab_start.toISOString()); + + /* wake apigrabber up */ + await raw.query(`NOTIFY grab_open`, []); + jobid = jobq.rows[0].id; + + /* clean up */ + raw.release(); + web.release(); + + res.json({ + "job_id": jobid, + "player_id": player_id, + "player_region": player_region + }); +}); + +/* internal monitoring */ +app.get("/", async (req, res) => { + res.sendFile(__dirname + "/index.html"); +}); + +/* notifications from database */ +function listen() { + var client = new pg.Client(db_config_raw); + client.connect(); + client.on('notification', (msg) => { + io.emit("job update", msg.channel); + }); + client.query("LISTEN grab_open"); + client.query("LISTEN process_open"); + client.query("LISTEN compile_open"); + client.query("LISTEN analyze_open"); + client.query("LISTEN grab_running"); + client.query("LISTEN process_running"); + client.query("LISTEN compile_running"); + client.query("LISTEN analyze_running"); + client.query("LISTEN grab_finished"); + client.query("LISTEN process_finished"); + client.query("LISTEN compile_finished"); + client.query("LISTEN analyze_finished"); + client.query("LISTEN grab_failed"); + client.query("LISTEN process_failed"); + client.query("LISTEN compile_failed"); + client.query("LISTEN analyze_failed"); + // keep open forever +} + +listen(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..b31ad37 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + Vainsocial backend status + + +
+ + +
+ + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..07b7c83 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "vainsocial-updater", + "version": "2.0.0", + "description": "Vainsocial frontend to backend communication service", + "main": "api.js", + "dependencies": { + "express": "^4.15.2", + "pg": "^6.1.4", + "request-promise": "^4.2.0", + "socketio": "^1.0.0" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "" +} -- cgit v1.3.1