From fecbb809759fbc76bbd1a4d87d9e4c3c732e8099 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Wed, 9 Aug 2023 15:51:37 +0200 Subject: [PATCH] remove client and websocket --- src/client/client.ts | 134 ---------------------------------- src/clientMain.ts | 51 ------------- src/process/onKill.ts | 56 -------------- src/process/process.ts | 43 ----------- src/websocket.ts | 78 -------------------- tests/client.test.ts | 68 ----------------- tests/realtime.test.ts | 161 ----------------------------------------- 7 files changed, 591 deletions(-) delete mode 100644 src/client/client.ts delete mode 100644 src/clientMain.ts delete mode 100644 src/process/onKill.ts delete mode 100644 src/process/process.ts delete mode 100644 src/websocket.ts delete mode 100644 tests/client.test.ts delete mode 100644 tests/realtime.test.ts diff --git a/src/client/client.ts b/src/client/client.ts deleted file mode 100644 index 90e0422..0000000 --- a/src/client/client.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Router } from 'express' -import { param } from 'express-validator' -import { validateErrors } from '../common' -import db, { getHostList } from '../db/db' -import { sql } from 'kysely' -import { allData } from '../process/process' - -const { max, sum } = db.fn -const router = Router() -// timeout middleware ? -router.get('/*', (req, res, next) => { - next() -}) - -const queryFilters = { - player: 'attacker_id', - server: 'servername', - map: 'map', - weapon: 'cause_of_death', - gamemode: 'game_mode', - host: 'host' -} as const - -const path = { - player: 'attacker_id', - servers: 'servername', - maps: 'map', - weapons: 'cause_of_death', - gamemodes: 'game_mode', - hosts: 'host' -} as const - -interface KillRecord { - kills: number - deaths: number - deaths_while_equipped: number - username?: string - max_distance: number - total_distance: number -} - -router.get('/hosts', (_req, res) => { - void (async () => { - const result = await getHostList() - const data: Record = {} - result.forEach((e) => (data[Number(e.host_id)] = e.host_name)) - - const dataString = JSON.stringify(data) - const buffer = Buffer.from(dataString) - const size = buffer.length - res.status(200).setHeader('X-File-Size', size).setHeader('Content-Type', 'application/json').send(buffer) - })() -}) - -function processQueryArgs (data: ReturnType>, queryArgs: string | string[]): ReturnType> { - Object.entries(queryArgs).forEach(([one, two]) => { - if (!two) return - if (!(one in queryFilters)) return - const key = one as keyof typeof queryFilters - if (Array.isArray(two)) { - data = data.where((qb) => { - (two as string[]).forEach((e) => { qb = qb.orWhere(queryFilters[key], '=', e) }) - return qb - }) - } else { - data = data.where(queryFilters[key], '=', two) - } - }) - return data -} - -router.get('/players', - (req, res) => { - void (async () => { - const result = db.selectFrom('kill_view') - let data - const resultFiltered = processQueryArgs(result, req.query as unknown as string | string[]) - // Cache stuff when no route is present so request doesn't takes 4secs to complete - if (result !== resultFiltered) { - const selection = resultFiltered.select([sum('kills').as('kills'), sum('deaths').as('deaths'), sum('deaths_with_weapon').as('deaths_while_equipped'), 'attacker_id', sql`last(attacker_name)`.as('username'), sum('total_distance').as('total_distance'), max('max_distance').as('max_distance')]).groupBy('attacker_id') - data = (await selection.execute()).reduce>((acc, curr) => { - acc[curr.attacker_id] = { - kills: Number(curr.kills), - deaths: Number(curr.deaths), - max_distance: Number(curr.max_distance), - total_distance: Number(curr.total_distance), - deaths_while_equipped: Number(curr.deaths_while_equipped), - username: curr.username - } - return acc - }, {}) - } else { - data = allData - } - - const dataString = JSON.stringify(data) - const buffer = Buffer.from(dataString) - const size = buffer.length - res.status(200).setHeader('X-File-Size', size).setHeader('Content-Type', 'application/json').send(buffer) - })() - }) - -router.get('/:dataType', - param('dataType') - .custom((e) => e in path) - .withMessage('Only weapons, players, maps, gamemodes or servers are valid paths'), - validateErrors, - (req, res) => { - void (async () => { - let result = db.selectFrom('kill_view') - if (req.query) { - result = processQueryArgs(result, req.query as unknown as string | string[]) - } - const dataType = req.params.dataType as keyof typeof path - - const selection = result.select([sum('kills').as('kills'), sum('deaths').as('deaths'), sum('deaths_with_weapon').as('deaths_while_equipped'), path[dataType], sum('total_distance').as('total_distance'), max('max_distance').as('max_distance')]).groupBy(path[dataType]) - const data = (await selection.execute()).reduce>((acc, curr) => { - acc[curr[path[dataType]]] = { - kills: Number(curr.kills), - deaths: Number(curr.deaths), - max_distance: Number(curr.max_distance), - total_distance: Number(curr.total_distance), - deaths_while_equipped: Number(curr.deaths_while_equipped) - } - return acc - }, {}) - const dataString = JSON.stringify(data) - const buffer = Buffer.from(dataString) - const size = buffer.length - res.status(200).setHeader('X-File-Size', size).setHeader('Content-Type', 'application/json').send(buffer) - })() - }) - -export default router diff --git a/src/clientMain.ts b/src/clientMain.ts deleted file mode 100644 index 4b62501..0000000 --- a/src/clientMain.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as dotenv from 'dotenv' -import cluster from 'cluster' -import os from 'os' -import express from 'express' -import cors from 'cors' -import client from './client/client' -import { dbReady } from './db/db' -import processAll from './process/process' -import listenKills from './process/onKill' -dotenv.config() - -const cCPUs = os.cpus().length - -const port = 3000 - -if (cluster.isPrimary && process.env.ENVIRONMENT !== 'dev') { - // Create a worker for each CPU - for (let i = 0; i < cCPUs; i++) { - cluster.fork() - } - cluster.on('online', function (worker) { - console.log(`Worker ${worker.process.pid ?? ''} is online`) - }) - cluster.on('exit', function (worker, code, signal) { - console.log(`Worker ${worker.process.pid ?? ''} died`) - }) -} - -export default -new Promise((resolve, reject) => { - void (async () => { - if (!cluster.isPrimary || process.env.ENVIRONMENT === 'dev') { - const app = express() - app.use(express.json()) - app.use(cors({ exposedHeaders: ['X-File-Size', 'Content-Encoding'] })) - app.get('/', (req, res) => { - res.send('Tone client API online') - }) - - app.use('/', client) - - await dbReady() - await processAll() - await listenKills() - const listenServer = app.listen(port, '0.0.0.0', () => { - console.log(`Tone client api listening on port ${port}`) - resolve(listenServer) - }) - } - })() -}) diff --git a/src/process/onKill.ts b/src/process/onKill.ts deleted file mode 100644 index e1b0ff0..0000000 --- a/src/process/onKill.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Client } from 'pg' -import { allData } from './process' - -export const pgClient = new Client({ - host: process.env.POSTGRES_HOST, - database: process.env.POSTGRES_DATABASE, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD -}) - -export default async function listenKills () { - await pgClient.connect() - await pgClient.query('LISTEN new_kill') - - pgClient.on('notification', (data) => { - if (!data.payload) return - const payload = JSON.parse(data.payload) - let killEntry = allData[payload.attacker_id] - let deathEntry = allData[payload.victim_id] - - if (!killEntry) { - killEntry = { - kills: 0, - deaths: 0, - max_distance: 0, - total_distance: 0, - deaths_while_equipped: 0, - username: payload.attacker_name - } - allData[payload.attacker_id] = killEntry - } - - if (!deathEntry) { - deathEntry = { - kills: 0, - deaths: 0, - max_distance: 0, - total_distance: 0, - deaths_while_equipped: 0, - username: payload.attacker_name - } - allData[payload.victim_id] = deathEntry - } - - if (payload.victim_id !== payload.attacker_id) { - killEntry.kills++ - killEntry.total_distance = Number(killEntry.total_distance) + Number(payload.distance) - killEntry.max_distance = Math.max(killEntry.max_distance || 0, payload.distance) - } - deathEntry.deaths++ - deathEntry.deaths_while_equipped++ - deathEntry.username = payload.victim_name - killEntry.username = payload.attacker_name - }) - return pgClient -} diff --git a/src/process/process.ts b/src/process/process.ts deleted file mode 100644 index 1a945f8..0000000 --- a/src/process/process.ts +++ /dev/null @@ -1,43 +0,0 @@ -import db from '../db/db' -import { sql } from 'kysely' -const { max, sum } = db.fn - -export let allData: Awaited> - -/** - * populates allData - * @returns - */ -async function processAll (): Promise { - console.log('Starting data calculation...') - const timeStart = new Date() - - allData = await processGlobalStats() - - console.log(`Data calculation finished. Took + ${Math.abs(new Date().getTime() - timeStart.getTime()) / 1000} seconds`) -} - -interface KillRecord { - kills: number - deaths: number - deaths_while_equipped: number - username?: string - max_distance: number - total_distance: number -} - -async function processGlobalStats () { - const data = await db.selectFrom('kill_view').select(['attacker_id', sql`last(attacker_name)`.as('username'), sum('kills').as('kills'), sum('deaths').as('deaths'), sum('deaths_with_weapon').as('deaths_while_equipped'), sum('total_distance').as('total_distance'), max('max_distance').as('max_distance')]).groupBy('attacker_id').execute() - return data.reduce>((acc, curr) => { - acc[curr.attacker_id] = { - kills: Number(curr.kills), - deaths: Number(curr.deaths), - max_distance: Number(curr.max_distance), - total_distance: Number(curr.total_distance), - deaths_while_equipped: Number(curr.deaths_while_equipped), - username: curr.username - } - return acc - }, {}) -} -export default processAll diff --git a/src/websocket.ts b/src/websocket.ts deleted file mode 100644 index 25f641e..0000000 --- a/src/websocket.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as dotenv from 'dotenv' -import { Client } from 'pg' -import { type WebSocket, WebSocketServer } from 'ws' -import { type KillTable } from './db/model' -dotenv.config() - -const port = 3002 -const wss = new WebSocketServer({ port }) - -export const pgClient = new Client({ - host: process.env.POSTGRES_HOST, - database: process.env.POSTGRES_DATABASE, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD -}) - -wss.on('connection', function connection (ws: WebSocket & { isAlive: boolean }, req) { - const ip = req?.headers['x-forwarded-for']?.toString().split(',')[0].trim() ?? - req.socket.remoteAddress ?? 'ip_unknown' - console.log(`${new Date().toLocaleString()},${ip},connect,${wss.clients.size}`) - ws.onclose = function () { - console.log(`${new Date().toLocaleString()},${ip},connect,${wss.clients.size}`) - } - ws.onmessage = function (msg) { - if (msg.data === 'pong') { - ws.isAlive = true - return - } - if (msg.data === 'ping') { - ws.send('pong') - } - } - ws.send('ping') -}) - -const interval = setInterval(function ping () { - wss.clients.forEach(function each (ws) { - if (!(ws as WebSocket & { isAlive: boolean }).isAlive) { ws.terminate(); return } - - (ws as WebSocket & { isAlive: boolean }).isAlive = false - ws.send('ping') - }) -}, 30000) - -wss.on('close', function close () { - clearInterval(interval) -}) - -export default async () => { - await pgClient.connect() - await pgClient.query('LISTEN new_kill') - - pgClient.on('notification', (data) => { - if (!data.payload) return - const payload = JSON.parse(data.payload) as KillTable - wss.clients.forEach(function (client) { - if (client.readyState === client.OPEN) { - client.send( - JSON.stringify({ - match_id: payload.match_id, - attacker_id: payload.attacker_id - // attacker_name: payload.attacker_name, - // cause_of_death: payload.cause_of_death, - // victim_id: payload.victim_id, - // victim_name: payload.victim_name, - // attacker_current_weapon: payload.attacker_current_weapon, - // victim_current_weapon: payload.victim_current_weapon, - // distance: payload.distance, - // game_mode: payload.game_mode, - // servername: payload.servername, - // map: payload.map, - // host: payload.host - }) - ) - } - }) - }) -} diff --git a/tests/client.test.ts b/tests/client.test.ts deleted file mode 100644 index 4fc66b7..0000000 --- a/tests/client.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals' -import clientMain from '../src/clientMain' -import { pgClient } from '../src/process/onKill' -import * as dotenv from 'dotenv' -import db from '../src/db/db' -dotenv.config() - -let listenServer - -jest.setTimeout(30000) -beforeAll(async () => { - listenServer = await clientMain; -}) - -describe('client', () => { - test('server list', async () => { - const request = await fetch("http://127.0.0.1:3000/servers") - const data = await request.json() - const first = Object.entries(data)[0] - expect(first[1]).toHaveProperty('max_distance') - expect(first[1]).toHaveProperty('total_distance') - expect(first[1]).toHaveProperty('kills') - }) - - test('player list', async () => { - const request = await fetch("http://127.0.0.1:3000/players") - const data = await request.json() - const first = Object.entries(data)[0] - expect(first[1]).toHaveProperty('max_distance') - expect(first[1]).toHaveProperty('total_distance') - expect(first[1]).toHaveProperty('kills') - }) - - test('player list with weapon filter', async () => { - const request = await fetch("http://127.0.0.1:3000/players?weapons=sniper") - const data = await request.json() - const first = Object.entries(data)[0] - expect(first[1]).toHaveProperty('max_distance') - expect(first[1]).toHaveProperty('total_distance') - expect(first[1]).toHaveProperty('kills') - }) - - test('weapon list', async () => { - const request = await fetch("http://127.0.0.1:3000/weapons") - const data = await request.json() - const first = Object.entries(data)[0] - expect(first[1]).toHaveProperty('max_distance') - expect(first[1]).toHaveProperty('total_distance') - expect(first[1]).toHaveProperty('kills') - }) - - test('weapon list with player filter', async () => { - const request = await fetch("http://127.0.0.1:3000/weapons?players=1005930844007") - const data = await request.json() - const first = Object.entries(data)[0] - expect(first[1]).toHaveProperty('max_distance') - expect(first[1]).toHaveProperty('total_distance') - expect(first[1]).toHaveProperty('kills') - }) -}) - -afterAll((done) => { - listenServer.close(async () => { - await pgClient.end() - await db.destroy() - done() - }) -}) \ No newline at end of file diff --git a/tests/realtime.test.ts b/tests/realtime.test.ts deleted file mode 100644 index 8a79e45..0000000 --- a/tests/realtime.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals' -import clientMain from '../src/clientMain' -import serverMain from '../src/serverMain' -import { pgClient } from '../src/process/onKill' -import * as dotenv from 'dotenv' -import db from '../src/db/db' -dotenv.config() - -let listenClient -let listenServer - -const data = { - servername: 'testserver', - attacker_weapon_1_mods: 0, - victim_id: '0', - victim_name: 'TestVictim', - victim_offhand_weapon_2: 'null', - victim_offhand_weapon_2_mods: 0, - victim_weapon_3_mods: 0, - attacker_weapon_2_mods: NaN, - attacker_offhand_weapon_1: 'null', - attacker_offhand_weapon_1_mods: 0, - attacker_weapon_3_mods: NaN, - attacker_offhand_weapon_2: 'null', - attacker_offhand_weapon_2_mods: NaN, - victim_offhand_weapon_1: 'offhand_weapon_test', - victim_offhand_weapon_1_mods: 0, - attacker_weapon_3: 'defender', - killstat_version: 'ks_3.0.0', - attacker_weapon_1: 'smr', - match_id: '31b1f34d', - distance: 0, - victim_current_weapon: 'smr', - cause_of_death: 'smr', - victim_weapon_2_mods: 0, - victim_current_weapon_mods: 0, - attacker_current_weapon_mods: 0, - game_time: 377.799, - player_count: 1, - attacker_current_weapon: 'smr', - attacker_id: '1', - game_mode: 'tdm', - map: 'thaw', - attacker_weapon_2: 'autopistol', - victim_weapon_1: 'null', - victim_weapon_2: 'autopistol', - victim_weapon_1_mods: 0, - victim_weapon_3: 'defender', - attacker_name: 'TestAttacker', - victim_titan: 'null', - attacker_titan: 'null' -} - -function waitFor(time: number) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time) - }) -} - -jest.setTimeout(30000) - -beforeAll(async () => { - listenClient = await clientMain; - listenServer = await serverMain; - const yea = waitFor(1000) - const response = await fetch(`http://127.0.0.1:3001/kill`, { - method: "POST", // *GET, POST, PUT, DELETE, etc. - credentials: "same-origin", // include, *same-origin, omit - headers: { - "Content-Type": "application/json", - 'Authorization': `Bearer ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}` - }, - body: JSON.stringify(data), // body data type must match "Content-Type" header - }); - expect(response.status).toBe(201) - await yea -}) - -describe('realtime', () => { - let playerKills - let weaponKills - let playerDeaths - let serverKills - test('fetch player', async () => { - const request = await fetch("http://127.0.0.1:3000/players") - const data = await request.json() - const player = data["1"] - expect(player).toHaveProperty('max_distance') - expect(player).toHaveProperty('total_distance') - expect(player).toHaveProperty('kills') - playerKills = player.kills - playerDeaths = player.deaths - }) - - test('fetch weapon', async () => { - const request = await fetch("http://127.0.0.1:3000/weapons") - const data = await request.json() - const weapon = data.smr - expect(weapon).toHaveProperty('max_distance') - expect(weapon).toHaveProperty('total_distance') - expect(weapon).toHaveProperty('kills') - weaponKills = weapon.kills - }) - - test('fetch server', async () => { - const request = await fetch("http://127.0.0.1:3000/servers") - const data = await request.json() - const server = data.testserver - expect(server).toHaveProperty('kills') - serverKills = server.kills - }) - - test('update player', async () => { - jest.setTimeout(15000) - const response = await fetch(`http://127.0.0.1:3001/kill`, { - method: "POST", // *GET, POST, PUT, DELETE, etc. - credentials: "same-origin", // include, *same-origin, omit - headers: { - "Content-Type": "application/json", - 'Authorization': `Bearer ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}` - }, - body: JSON.stringify(data), // body data type must match "Content-Type" header - }); - expect(response.status).toBe(201) - const yea = waitFor(1000) - await yea - }) - - test('check player update', async () => { - - const request = await fetch("http://127.0.0.1:3000/players") - const data = await request.json() - const player = data["1"] - expect(player.kills).toBe(playerKills + 1) - }) - - test('check weapon update', async () => { - const request = await fetch("http://127.0.0.1:3000/weapons") - const data = await request.json() - const weapon = data.smr - expect(weapon.kills).toBe(weaponKills + 1) - }) - - test('check server update', async () => { - const request = await fetch("http://127.0.0.1:3000/servers") - const data = await request.json() - const server = data.testserver - expect(server.kills).toBe(serverKills + 1) - }) - -}) - -afterAll((done) => { - listenClient.close(() => { - listenServer.close(async () => { - await pgClient.end() - await db.destroy() - done() - }) - }) -}) \ No newline at end of file