diff --git a/package.json b/package.json index 317f3b6..803e0a9 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,6 @@ "startClient": "node out/clientMain.js", "startProcess": "node out/processMain.js", "documentation": "redocly build-docs .\\docs\\v1.yml --output=docs\\index.html", - "test": "jest" + "test": "jest --runInBand" } } \ No newline at end of file diff --git a/src/cache/redis.ts b/src/cache/redis.ts index 63c61cd..30e91df 100644 --- a/src/cache/redis.ts +++ b/src/cache/redis.ts @@ -1,5 +1,8 @@ import { createClient } from 'redis' +import * as dotenv from 'dotenv' +dotenv.config() + const client = createClient({ url: process.env.REDIS_URL, username: process.env.REDIS_USER, diff --git a/src/client/client.ts b/src/client/client.ts index b858835..90b722f 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -2,12 +2,7 @@ import { Router } from 'express' import { param, query } from 'express-validator' import { validateErrors } from '../common' import db from '../db/db' -import { - getWeaponList, - getWeaponReport, - getPlayerReport, - getPlayerList -} from '../cache/cacheUtils' +import client from '../cache/redis' const router = Router() //timeout middleware ? router.get('/*', (req, res, next) => { @@ -20,12 +15,12 @@ router.get( query(['player', 'server']).optional().toInt().isInt(), validateErrors, async (req, res) => { - const data = await getWeaponReport( - req.params.weaponId, - Number(req.query.server), - req.query.player?.toString() - ) - res.status(200).send(data) + let path = '' + if (req.query.server) path = path + `servers.${req.query.server}.` + if (req.query.player) path = path + `players.${req.query.player}.` + path = path + `weapons.${req.params.weaponId}` + if (await client.json.type('kills', path)) return res.status(200).send(await client.json.get('kills', { path })) + res.status(404).send() } ) @@ -34,11 +29,15 @@ router.get( query(['player', 'server']).optional().toInt().isInt(), validateErrors, async (req, res) => { - const data = await getWeaponList( - req.query.server?.toString(), - req.query.player?.toString() - ) - res.status(200).send(data) + let path = '' + if (req.query.server) path = path + `servers.${req.query.server}.` + if (req.query.player) path = path + `players.${req.query.player}.` + path = path + `weapons` + if (await client.json.type('kills', path)) { + const data = await client.json.get('kills', { path: `weapons` }) as { [key: number]: any } + return res.status(200).send(Object.fromEntries(Object.entries(data).map(([key, value]) => { return [key, { total_distance: value.total_distance, max_distance: value.max_distance, kills: value.kills, deaths: value.deaths }] }))) + } + res.status(404).send() } ) @@ -49,26 +48,12 @@ router.get( query('weapon').optional().isString(), validateErrors, async (req, res) => { - const data = await getPlayerReport( - req.params.playerId, - Number(req.query.server), - req.query.weapon?.toString() - ) - /* - if (!req.query.weapon) { - const weapons = await getWeaponList( - req.query.server?.toString(), - req.params.playerId - ) - data.weapons = weapons - } else { - data.weapons = (await getWeaponReport( - req.query.weapon.toString(), - Number(req.query.server) || undefined, - req.params.playerId - )) as any - }*/ - res.status(200).send(data) + let path = '' + if (req.query.server) path = path + `servers.${req.query.server}.` + if (req.query.weapon) path = path + `weapons.${req.query.weapon}.` + path = path + `players.${req.params.playerId}` + if (await client.json.type('kills', path)) return res.status(200).send(await client.json.get('kills', { path })) + res.status(404).send() } ) @@ -78,23 +63,23 @@ router.get( query('weapon').optional().isString(), validateErrors, async (req, res) => { - const data = await getPlayerList( - Number(req.query.server), - req.query.weapon?.toString() - ) - res.status(200).send(data) + let path = '' + if (req.query.server) path = path + `servers.${req.query.server}.` + if (req.query.weapon) path = path + `weapons.${req.query.weapon}.` + path = path + `players` + if (await client.json.type('kills', path)) { + const data = await client.json.get('kills', { path: `players` }) as { [key: number]: any } + return res.status(200).send(Object.fromEntries(Object.entries(data).map(([key, value]) => { return [key, { total_distance: value.total_distance, max_distance: value.max_distance, kills: value.kills, deaths: value.deaths }] }))) + } + res.status(404).send() } ) //router.get('/maps/', (req, res, next) => {}) router.get('/servers/', async (req, res) => { - const data = await db.selectFrom('server').selectAll().execute() - res.status(200).send( - data.map((e) => { - return { name: e.name, id: e.id, description: e.description } - }) - ) + const data = await client.json.get('kills', { path: `servers` }) as { [key: number]: any } + return res.status(200).send(Object.fromEntries(Object.entries(data).map(([key, value]) => { return [key, value.data] }))) }) //router.get('/servers/:serverId/', (req, res, next) => {}) diff --git a/src/clientMain.ts b/src/clientMain.ts index 580d2a1..f51f21f 100644 --- a/src/clientMain.ts +++ b/src/clientMain.ts @@ -3,8 +3,8 @@ dotenv.config() import express from 'express' import cors from 'cors' import client from './client/client' -import { dbReady } from './db/db' -import { cacheReady } from './cache/redis' +import db, { dbReady } from './db/db' +import cache, { cacheReady } from './cache/redis' const app = express() const port = 3000 @@ -28,4 +28,4 @@ export default }) }) }) - }) + }) \ No newline at end of file diff --git a/src/process/onKill.ts b/src/process/onKill.ts index d847b56..b6e7dea 100644 --- a/src/process/onKill.ts +++ b/src/process/onKill.ts @@ -1,5 +1,6 @@ import { Client } from 'pg' import client from '../cache/redis' +import { genPrefix } from './process' const pgClient = new Client({ host: process.env.POSTGRES_HOST, @@ -15,45 +16,72 @@ export default async function listenKills() { pgClient.on('notification', async (data) => { if (!data.payload) return const payload = JSON.parse(data.payload) - console.log(JSON.parse(data.payload)) - await updateGlobal(payload) - }) + return pgClient } + async function updateGlobal({ cause_of_death, distance, attacker_id, victim_id, server }: { cause_of_death: string, distance: number, attacker_id: string, victim_id: string, server: number }) { const promises: Promise[] = [] + if (!await client.json.type('kills')) await client.json.set('kills', '', { data: {}, weapons: {}, servers: {}, players: {} }) + + if (!await client.json.type('kills', genPrefix.weapon({ cause_of_death }))) await client.json.set('kills', genPrefix.weapon({ cause_of_death }), { players: {}, max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.player({ attacker_id }))) await client.json.set('kills', genPrefix.player({ attacker_id }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', `servers.${server}`)) await client.json.set('kills', `servers.${server}`, { data: {}, weapons: {}, players: {}, max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.weapon({ cause_of_death, server }))) await client.json.set('kills', genPrefix.weapon({ cause_of_death, server }), { players: {}, max_distance: 0, kills: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.player({ attacker_id, server }))) await client.json.set('kills', genPrefix.player({ attacker_id, server }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + + if (!await client.json.type('kills', genPrefix.player({ attacker_id: victim_id, server }))) await client.json.set('kills', genPrefix.player({ attacker_id: victim_id, server }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.player({ attacker_id: victim_id }))) await client.json.set('kills', genPrefix.player({ attacker_id: victim_id }), { weapons: {}, max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + + if (!await client.json.type('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death }))) await client.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death }), { max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death }))) await client.json.set('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death }), { max_distance: 0, kills: 0, total_distance: 0 }) + + if (!await client.json.type('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }))) await client.json.set('kills', genPrefix.weaponPlayers({ attacker_id: victim_id, cause_of_death, server }), { max_distance: 0, kills: 0, deaths: 0, total_distance: 0 }) + if (!await client.json.type('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server }))) await client.json.set('kills', genPrefix.playerWeapons({ attacker_id: victim_id, cause_of_death, server }), { max_distance: 0, kills: 0, total_distance: 0 }) + promises.push(updatePath(`data`, { distance })) //servers.1 promises.push(updatePath(`servers.${server}`, { distance }).then(() => Promise.all([ //servers.1.players.123456789 - updatePath(`servers.${server}.players.${attacker_id}`, { distance }).then(() => + updatePath(`servers.${server}.players.${attacker_id}`, { distance }).then(async () => { + if (!await client.json.type('kills', `servers.${server}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${server}.players.${victim_id}.deaths`, 0) + await client.json.numIncrBy('kills', `servers.${server}.players.${victim_id}.deaths`, 1) //servers.1.players.123456789.weapons.epg - updatePath(`servers.${server}.players.${attacker_id}.weapons.${cause_of_death}`, { distance }) + await updatePath(`servers.${server}.players.${attacker_id}.weapons.${cause_of_death}`, { distance }) + } + ), //servers.1.weapons.epg - updatePath(`servers.${server}.weapons.${cause_of_death}`, { distance }).then(() => + updatePath(`servers.${server}.weapons.${cause_of_death}`, { distance }).then(async () => { //servers.1.weapons.epg.players.123456789 - updatePath(`servers.${server}.weapons.${cause_of_death}.players.${attacker_id}`, { distance }) + await updatePath(`servers.${server}.weapons.${cause_of_death}.players.${attacker_id}`, { distance }) + if (!await client.json.type('kills', `servers.${server}.players.${victim_id}.deaths`)) await client.json.set('kills', `servers.${server}.weapons.${cause_of_death}.players.${victim_id}`, 0) + await client.json.numIncrBy('kills', `servers.${server}.weapons.${cause_of_death}.players.${victim_id}.deaths`, 1) + } + )]) )) //players.123456789 promises.push(updatePath(`players.${attacker_id}`, { distance }).then(async () => { + if (!await client.json.type('kills', `players.${victim_id}.deaths`)) await client.json.set('kills', `players.${victim_id}.deaths`, 0) await client.json.numIncrBy('kills', `players.${victim_id}.deaths`, 1) //players.123456789.weapons.epg await updatePath(`players.${attacker_id}.weapons.${cause_of_death}`, { distance }) - return await client.json.numIncrBy('kills', `server.${server}.players.${victim_id}.deaths`, 1) })) //weapons.epg - promises.push(updatePath(`weapons.${cause_of_death}`, { distance }).then(() => + promises.push(updatePath(`weapons.${cause_of_death}`, { distance }).then(async () => { //weapons.epg.players.123456789 - updatePath(`weapons.${cause_of_death}.players.${attacker_id}`, { distance }) + await updatePath(`weapons.${cause_of_death}.players.${attacker_id}`, { distance }) + if (!await client.json.type('kills', `weapons.${cause_of_death}.players.${victim_id}.deaths`)) await client.json.set('kills', `weapons.${cause_of_death}.players.${victim_id}.deaths`, 0) + await client.json.numIncrBy('kills', `weapons.${cause_of_death}.players.${victim_id}.deaths`, 1) + } )) @@ -61,11 +89,20 @@ async function updateGlobal({ cause_of_death, distance, attacker_id, victim_id, } async function updatePath(path: string, { distance }: { distance: number }) { + await setupPath(path) + console.log(path + '.max_distance', await client.json.type('kills', path + '.max_distance')) + console.log(path, await client.json.type('kills', path)) if (!await client.json.type('kills', path)) await client.json.set('kills', path, {}) let transaction = client.multi() transaction = transaction.json.numIncrBy('kills', `${path}.total_distance`, distance) let max_distance = Number(await client.json.get('kills', { path: `${path}.max_distance` })) if (isNaN(max_distance) || max_distance < distance) transaction = transaction.json.set('kills', `${path}.max_distance`, distance) transaction = transaction.json.numIncrBy('kills', `${path}.kills`, 1) - return transaction.exec() + await transaction.exec() +} + +async function setupPath(path: string) { + if (!await client.json.type('kills', path + '.kills')) await client.json.set('kills', path + '.kills', 0) + if (!await client.json.type('kills', path + '.total_distance')) await client.json.set('kills', path + '.total_distance', 0) + if (!await client.json.type('kills', path + '.max_distance')) await client.json.set('kills', path + '.max_distance', 0) } \ No newline at end of file diff --git a/src/process/process.ts b/src/process/process.ts index 316fac4..35c3ed3 100644 --- a/src/process/process.ts +++ b/src/process/process.ts @@ -2,7 +2,7 @@ import db from '../db/db' const { count, max, sum } = db.fn import client from '../cache/redis' -const genPrefix = { +export const genPrefix = { global: ({ server }: { server?: number }) => { return (server ? `servers.${server}.` : '') + 'data' }, @@ -59,6 +59,7 @@ async function processGlobalStats() { let transaction = client.multi() await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death }) => { const prefix = genPrefix.weapon({ cause_of_death }) + if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { players: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() @@ -70,6 +71,7 @@ async function processGlobalStats() { let transaction = client.multi() await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id }) => { const prefix = genPrefix.player({ attacker_id }) + if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { weapons: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() @@ -127,6 +129,7 @@ async function processServerStats() { let transaction = client.multi() await Promise.all(data.map(async ({ kills, max_distance, total_distance, cause_of_death, server }) => { const prefix = genPrefix.weapon({ cause_of_death, server }) + if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { players: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() @@ -137,6 +140,7 @@ async function processServerStats() { let transaction = client.multi() await Promise.all(data.map(async ({ kills, max_distance, total_distance, attacker_id, server }) => { const prefix = genPrefix.player({ attacker_id, server }) + if (!await client.json.type('kills', prefix)) await client.json.set('kills', prefix, { weapons: {} }) await processData(prefix, { kills, max_distance, total_distance }, transaction) })) return transaction.exec() diff --git a/src/serverMain.ts b/src/serverMain.ts index 9d861c2..0f26a0f 100644 --- a/src/serverMain.ts +++ b/src/serverMain.ts @@ -25,7 +25,3 @@ export default new Promise((resolve, reject) => { }) }) }) - -app.on('close', () => { - -}) diff --git a/tests/client.test.ts b/tests/client.test.ts index c5a0adf..51f6a27 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -1,6 +1,8 @@ import { afterAll, beforeAll, describe, expect, test } from '@jest/globals' import clientMain from '../src/clientMain' import * as dotenv from 'dotenv' +import db from '../src/db/db' +import cache from '../src/cache/redis' dotenv.config() let listenServer @@ -13,10 +15,10 @@ describe('client', () => { test('server list', async () => { const request = await fetch("http://127.0.0.1:3000/servers") const data = await request.json() - expect(data.length).toBeGreaterThan(0) - expect(data[0]).toHaveProperty('name') - expect(data[0]).toHaveProperty('id') - expect(data[0]).toHaveProperty('description') + const first = Object.entries(data)[0] + expect(first).toHaveProperty('name') + expect(first).toHaveProperty('id') + expect(first).toHaveProperty('description') }) test('player list', async () => { @@ -75,7 +77,9 @@ describe('client', () => { }) afterAll((done) => { - listenServer.close(() => { + listenServer.close(async () => { + await db.destroy() + await cache.quit() done() }) }) \ No newline at end of file diff --git a/tests/realtime.test.ts b/tests/realtime.test.ts index 179278b..8d71513 100644 --- a/tests/realtime.test.ts +++ b/tests/realtime.test.ts @@ -1,11 +1,15 @@ -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals' +import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals' import clientMain from '../src/clientMain' import serverMain from '../src/serverMain' import listenKills from "../src/process/onKill" import * as dotenv from 'dotenv' +import db from '../src/db/db' +import cache from '../src/cache/redis' dotenv.config() -let listenServerClient +let listenClient +let listenServer +let pgClient const data = { attacker_weapon_1_mods: 0, @@ -44,9 +48,20 @@ const data = { attacker_name: 'TestAttacker' } +function waitFor(time: number) { + return new Promise((resolve, reject) => { + setTimeout(resolve, time) + }) +} + +jest.setTimeout(15000) + beforeAll(async () => { - listenServerClient = await clientMain; - listenServerClient = await serverMain; + jest.setTimeout(15000) + listenClient = await clientMain; + listenServer = await serverMain; + pgClient = await listenKills() + const yea = waitFor(10000) const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, { method: "POST", // *GET, POST, PUT, DELETE, etc. credentials: "same-origin", // include, *same-origin, omit @@ -57,6 +72,7 @@ beforeAll(async () => { body: JSON.stringify(data), // body data type must match "Content-Type" header }); expect(response.status).toBe(201) + await yea }) describe('realtime', () => { @@ -85,12 +101,19 @@ describe('realtime', () => { }) test('update 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') + jest.setTimeout(15000) + const yea = waitFor(10000) + const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + credentials: "same-origin", // include, *same-origin, omit + headers: { + "Content-Type": "application/json", + 'Authorization': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + 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 }) test('check player update', async () => { @@ -108,3 +131,14 @@ describe('realtime', () => { }) }) + +afterAll((done) => { + listenClient.close(() => { + listenServer.close(async () => { + await pgClient.end() + await db.destroy() + await cache.quit() + done() + }) + }) +}) \ No newline at end of file diff --git a/tests/server.test.ts b/tests/server.test.ts index b5cbba3..e71e44e 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -124,9 +124,10 @@ describe('server', () => { }) afterAll((done) => { - listenServer.close(() => { - db.deleteFrom('kill').where('attacker_id', '=', '0').orWhere('attacker_id', '=', '1').execute().then(() => { - db.destroy().then(() => { done() }) + db.deleteFrom('kill').where('attacker_id', '=', '0').orWhere('attacker_id', '=', '1').execute().then(() => { + listenServer.close(async () => { + await db.destroy() + done() }) }) -}) \ No newline at end of file +})