From 8340465ed20da76b0967900117e015dbf4a3053a Mon Sep 17 00:00:00 2001 From: legonzaur Date: Mon, 13 Mar 2023 12:19:05 +0100 Subject: [PATCH] add player weapon route --- .vscode/launch.json | 4 +- src/client/client.ts | 5 +- src/client/player.ts | 113 +++++++++++++++++++++++++++++++ src/client/weapons.ts | 33 ++++----- src/{client.ts => clientMain.ts} | 0 src/common.ts | 11 +++ src/server/register.ts | 10 +-- src/server/server.ts | 16 +---- src/{server.ts => serverMain.ts} | 0 9 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 src/client/player.ts rename src/{client.ts => clientMain.ts} (100%) rename src/{server.ts => serverMain.ts} (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2f3c791..2da8654 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "request": "launch", "name": "Launch Server API", "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/server.ts", + "program": "${workspaceFolder}/src/serverMain.ts", "preLaunchTask": "tsc: build - tsconfig.json", "outFiles": ["${workspaceFolder}/out/**/*.js"] }, @@ -18,7 +18,7 @@ "request": "launch", "name": "Launch Client API", "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/client.ts", + "program": "${workspaceFolder}/src/clientMain.ts", "preLaunchTask": "tsc: build - tsconfig.json", "outFiles": ["${workspaceFolder}/out/**/*.js"] } diff --git a/src/client/client.ts b/src/client/client.ts index a7ecd85..3923176 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,5 +1,6 @@ import { Router } from 'express' import weapons from './weapons' +import player from './player' const router = Router() //timeout middleware ? router.get('/*', (req, res, next) => { @@ -16,7 +17,7 @@ router.get('/players/:playerId', (req, res, next) => {}) router.get('/servers/', (req, res, next) => {}) router.get('/servers/:serverId/', (req, res, next) => {}) router.get('/servers/:serverId/players', (req, res, next) => {}) -router.get('/servers/:serverId/players/:playerId', (req, res, next) => {}) +router.get('/servers/:serverId/player/:playerId', player) -router.use('/', weapons) +router.use('/servers/:serverId/weapons', weapons) export default router diff --git a/src/client/player.ts b/src/client/player.ts new file mode 100644 index 0000000..dd9cb5c --- /dev/null +++ b/src/client/player.ts @@ -0,0 +1,113 @@ +import { RequestHandler } from 'express' +import { param } from 'express-validator' +import { validateErrors } from '../common' +import cache from '../cache/redis' +import { sql } from 'kysely' +import db from '../db/db' + +const { count, max } = db.fn + +const middlewares: RequestHandler[] = [ + param(['serverId', 'playerId']).exists().toInt().isInt(), + validateErrors, + async (req, res) => { + const server = Number(req.params.serverId) + const player = Number(req.params.playerId) + await processPlayerWeapons(server, player) + const killsData = await cache.HGETALL( + `servers:${server}:players:${player}:weapons:kills` + ) + const deathData = await cache.HGETALL( + `servers:${server}:players:${player}:weapons:deaths` + ) + const data: { [x: string]: { kills: string; deaths: string } } = {} + Object.keys(killsData).forEach((key) => { + data[key] = { kills: killsData[key], deaths: deathData[key] } + }) + + res.status(200).send(data) + } +] + +async function processPlayerWeapons(server: number, player: number) { + let last_entry = + Number( + await cache.HGET( + `servers:${server}:players:${player}:weapons`, + 'last_entry' + ) + ) || 0 + const newData = await db + .with('killdata', () => + db + .selectFrom('kill') + .select([count('kill.id').as('num_kills'), 'kill.cause_of_death']) + .where('attacker_id', '=', player.toString()) + .where('kill.id', '>', last_entry) + .where('kill.server', '=', server) + .whereRef('attacker_id', '!=', 'victim_id') + .groupBy('cause_of_death') + ) + .with('deathdata', () => + db + .selectFrom('kill') + .select([count('kill.id').as('num_deaths'), 'kill.cause_of_death']) + .where('victim_id', '=', player.toString()) + .where('kill.id', '>', last_entry) + .where('kill.server', '=', server) + .groupBy('cause_of_death') + ) + .selectFrom('killdata') + .select([ + sql`coalesce(killdata.num_kills, 0)`.as('kills'), + sql`coalesce(deathdata.num_deaths, 0)`.as('deaths'), + sql`COALESCE(killdata.cause_of_death, deathdata.cause_of_death)`.as( + 'weapon' + ), + db + .selectFrom('kill') + .select(max('kill.id').as('last_entry')) + .as('last_entry') + ]) + .fullJoin( + 'deathdata', + 'deathdata.cause_of_death', + 'killdata.cause_of_death' + ) + .orderBy('kills', 'desc') + .execute() + //if no new kills + if (newData.length == 0) { + return + } + last_entry = newData.reduce( + (acc, current) => (current.last_entry > acc ? current.last_entry : acc), + 0 + ) + console.log(newData) + const promises: Promise[] = [] + newData.forEach(({ weapon, kills, deaths }) => { + promises.push( + cache.HINCRBY( + `servers:${server}:players:${player}:weapons:deaths`, + weapon, + deaths + ), + cache.HINCRBY( + `servers:${server}:players:${player}:weapons:kills`, + weapon, + kills + ) + ) + }) + promises.push( + cache.HSET( + `servers:${server}:players:${player}:weapons`, + 'last_entry', + newData[0].last_entry + ) + ) + await Promise.all(promises) +} + +export default middlewares diff --git a/src/client/weapons.ts b/src/client/weapons.ts index 6dab271..a7c2691 100644 --- a/src/client/weapons.ts +++ b/src/client/weapons.ts @@ -1,32 +1,24 @@ -import { Router } from 'express' -import { param, validationResult } from 'express-validator' +import { RequestHandler } from 'express' +import { param } from 'express-validator' +import { validateErrors } from '../common' import cache from '../cache/redis' import db from '../db/db' -const router = Router() + const { count, max } = db.fn -router.get( - '/servers/:serverId/weapons', +const middlewares: RequestHandler[] = [ param('serverId').exists().toInt().isInt(), + validateErrors, async (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - console.error(JSON.stringify(errors)) - return res.status(400).json({ errors: errors.array() }) - } - if (!req.params) { - res.sendStatus(500) - return - } const server = Number(req.params.serverId) - await processTopWeapons(server) + await processWeapons(server) const data = await cache.HGETALL(`servers:${server}:weapons`) delete data.last_entry res.status(200).send(data) } -) +] -async function processTopWeapons(server: number) { +async function processWeapons(server: number) { let last_entry = Number(await cache.HGET(`servers:${server}:weapons`, 'last_entry')) || 0 const newData = await db @@ -34,7 +26,10 @@ async function processTopWeapons(server: number) { .select([ count('kill.id').as('num_kills'), 'kill.cause_of_death', - max('kill.id').as('last_entry') + db + .selectFrom('kill') + .select(max('kill.id').as('last_entry')) + .as('last_entry') ]) .where('server', '=', server) .where('kill.id', '>', last_entry) @@ -60,4 +55,4 @@ async function processTopWeapons(server: number) { await Promise.all(promises) } -export default router +export default middlewares diff --git a/src/client.ts b/src/clientMain.ts similarity index 100% rename from src/client.ts rename to src/clientMain.ts diff --git a/src/common.ts b/src/common.ts index 15f0f01..a6c4834 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,3 +1,5 @@ +import { RequestHandler } from 'express' +import { Result, validationResult } from 'express-validator' import http from 'http' import https from 'https' @@ -29,3 +31,12 @@ export function GetRequest(url: string) { .end() }) } + +export const validateErrors: RequestHandler = (req, res, next) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + console.error(JSON.stringify(errors)) + return res.status(400).json({ errors: errors.array() }) + } + next() +} diff --git a/src/server/register.ts b/src/server/register.ts index 05c7c60..d57f994 100644 --- a/src/server/register.ts +++ b/src/server/register.ts @@ -1,5 +1,5 @@ import { Router } from 'express' -import { GetRequest } from '../common' +import { GetRequest, validateErrors } from '../common' import { FindServer, CreateServer } from '../db/db' import { body, validationResult } from 'express-validator' @@ -37,12 +37,8 @@ router.post( .toInt() .isInt({ min: 1, max: 65535 }) .withMessage('must be between 1 and 65535'), - async (req, res, next) => { - const errors = validationResult(req) - console.log(req.body) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } + validateErrors, + async (req, res) => { try { //Check if server name isn't already in database if (!!(await FindServer({ name: req.body.name }))) { diff --git a/src/server/server.ts b/src/server/server.ts index 93508ea..4667123 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -3,6 +3,7 @@ import expressBasicAuth from 'express-basic-auth' import { body, header, validationResult } from 'express-validator' import register from './register' import { CreateKillRecord, CheckServerToken } from '../db/db' +import { validateErrors } from '../common' const router = Router() @@ -17,14 +18,7 @@ router.post( .bail() .contains('Basic') .withMessage('Authorization Token is not Basic'), - (req, res, next) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - console.log(JSON.stringify(errors)) - return res.status(403).json({ errors: errors.array() }) - } - next() - }, + validateErrors, //Huge mess to retrieve server id from expressBasicAuth. We probably should fix it. (req, res, next) => { if (!req) res.sendStatus(500) @@ -118,12 +112,8 @@ router.post( min: 0 }), body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(), + validateErrors, (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - console.error(JSON.stringify(errors)) - return res.status(400).json({ errors: errors.array() }) - } const { killstat_version, match_id, diff --git a/src/server.ts b/src/serverMain.ts similarity index 100% rename from src/server.ts rename to src/serverMain.ts