Merge pull request #17 from Legonzaur/v2-alternate

V2 alternate
This commit is contained in:
Legonzaur
2023-04-20 11:52:25 +02:00
committed by GitHub
22 changed files with 645 additions and 746 deletions
+1 -2
View File
@@ -33,8 +33,7 @@
"build": "npx tsc",
"startServer": "node out/serverMain.js",
"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"
}
}
-97
View File
@@ -1,97 +0,0 @@
import cache from './redis'
/**
* Shows the data for one weapon
* @param weapon
* @param server
* @param player
* @returns
*/
export async function getWeaponReport(
weapon: string,
server?: number,
player?: string
) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const playerPrefix = player ? `players:${player}:` : ''
const cacheLocation = serverPrefix + playerPrefix + `weapons:` + weapon
const data = await cache.HGETALL(cacheLocation)
delete data.last_entry
return data
}
export function getWeaponSet(server?: number, player?: string) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const playerPrefix = player ? `players:${player}:` : ''
const cacheLocation = serverPrefix + playerPrefix + `weapons`
return cache.SMEMBERS(cacheLocation)
}
/**
* Show the processed list of weapons
* @param server
* @param player
* @returns string
*/
export async function getWeaponList(server?: string, player?: string) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const playerPrefix = player ? `players:${player}:` : ''
return JSON.parse(
(await cache.GET(serverPrefix + playerPrefix + `weapons:processedList`)) ||
'{}'
)
}
/**
* Show processed data for a player
* @param player
* @param server
* @param weapon
* @returns
*/
export async function getPlayerReport(
player: string,
server?: number,
weapon?: string
) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const weaponPrefix = weapon ? `weapons:${weapon}:` : ''
const cacheLocation = serverPrefix + weaponPrefix + `players:` + player
const data = await cache.HGETALL(cacheLocation)
delete data.last_entry
return data
}
/**
* Shows processed list of players
* @param server
* @param weapon
* @returns string
*/
export async function getPlayerList(server?: number, weapon?: string) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const weaponPrefix = weapon ? `weapons:${weapon}:` : ''
return JSON.parse(
(await cache.GET(serverPrefix + weaponPrefix + `players:processedList`)) ||
'{}'
)
}
export function getPlayerSet(server?: number, weapon?: string) {
const serverPrefix = !isNaN(Number(server?.toString()))
? `servers:${server}:`
: ''
const weaponPrefix = weapon ? `weapons:${weapon}:` : ''
const cacheLocation = serverPrefix + weaponPrefix + `players`
return cache.SMEMBERS(cacheLocation)
}
-18
View File
@@ -1,18 +0,0 @@
import { createClient } from 'redis'
const client = createClient({
url: process.env.REDIS_URL,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD
})
client.on('error', (err) => console.log('Redis Client Error', err))
export function cacheReady() {
return client.connect()
}
export default client
//await client.set('key', 'value');
//const value = await client.get('key');
//await client.disconnect();
+82 -89
View File
@@ -1,101 +1,94 @@
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 { allData } from '../process/process'
import { getHostList } from '../db/db'
const router = Router()
//timeout middleware ?
router.get('/*', (req, res, next) => {
next()
})
router.get(
'/weapons/:weaponId',
param(['weaponId']).exists().isString(),
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)
}
)
router.get(
'/weapons/',
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)
}
)
router.get(
'/players/:playerId',
param(['playerId']).exists().toInt().isInt(),
query(['server']).optional().toInt().isInt(),
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)
}
)
router.get(
'/players/',
query(['server']).optional().toInt().isInt(),
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)
}
)
//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 }
})
function filters(e: any, query: any) {
return (
(query.player ? query.player == e.attacker_id : true) &&
(query.server ? query.server == e.servername : true) &&
(query.host ? Number(query.host) == e.host : true) &&
(query.map ? query.map == e.map : true) &&
(query.weapon ? query.weapon == e.cause_of_death : true) &&
(query.gamemode ? query.gamemode == e.game_mode : true)
)
})
//router.get('/servers/:serverId/', (req, res, next) => {})
}
router.get('/hosts',
async (req, res) => {
const result = (await getHostList())
const data: { [key: number]: string } = {}
result.forEach(e => data[Number(e.id)] = e.name)
res.status(200).send(data)
})
router.get(
'/:dataType',
param('dataType')
.custom(
(e) => e == 'weapons' || e == 'players' || e == 'maps' || e == 'servers'
)
.withMessage('Only weapons, players, maps or servers are valid paths'),
query(['player', 'host']).optional().toInt().isInt(),
query(['server', 'map', 'weapon', 'gamemode']).optional().isString(),
validateErrors,
(req, res) => {
const data: {
[key: string]: {
deaths: number
kills: number
max_distance: number
total_distance: number
username?: string
host?: number
}
} = {}
let index: 'cause_of_death' | 'attacker_id' | 'map' | 'servername'
switch (req.params.dataType) {
case 'weapons':
index = 'cause_of_death'
break
case 'players':
index = 'attacker_id'
break
case 'maps':
index = 'map'
break
case 'servers':
index = 'servername'
break
default:
return res.status(400).send()
}
allData
.filter((e) => filters(e, req.query))
.forEach((e) => {
if (!data[e[index]])
data[e[index]] = {
deaths: 0,
kills: 0,
max_distance: 0,
total_distance: 0
}
if (index === 'attacker_id') data[e[index]].username = e.attacker_name
if (index === 'servername') data[e[index]].host = e.host
data[e[index]].deaths += Number(e.deaths)
data[e[index]].kills += Number(e.kills)
data[e[index]].total_distance += Number(e.total_distance)
data[e[index]].max_distance = Math.max(
data[e[index]].max_distance,
Number(e.max_distance)
)
})
res.status(200).send(data)
}
)
export default router
-227
View File
@@ -1,227 +0,0 @@
import db from '../db/db'
const { count, max, sum } = db.fn
import client from '../cache/redis'
import { createWeaponJson } from './process/processWeapon'
import { createPlayerJson } from './process/processPlayer'
/**
* Starts the process of populating the REDIS database globally
* @returns
*/
async function processAll() {
console.log('Starting data calculation...')
const timeStart = new Date()
await Promise.all([processGlobalStats(), processServerStats()])
console.log(
'Data calculation finished. Took + ' +
Math.abs(new Date().getTime() - timeStart.getTime()) / 1000 +
' seconds'
)
if (process.env.ENVIRONMENT == 'production') {
return
}
setTimeout(processAll, 3600000)
}
async function processGlobalStats() {
let promises: Promise<any>[] = []
//Global kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance')])
.execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance }) => {
transaction = processGlobalData({ kills, max_distance, total_distance }, transaction)
})
return transaction.exec()
}))
//Global weapon kill
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death'])
.groupBy('cause_of_death').execute().then(async (data) => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, cause_of_death }) => {
transaction = processWeaponData({ kills, max_distance, total_distance, cause_of_death }, transaction)
})
await transaction.exec()
}))
//Player weapon kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death', 'attacker_id'])
.groupBy(['cause_of_death', 'attacker_id']).execute().then(async (data) => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, cause_of_death, attacker_id }) => {
transaction = processWeaponData({ kills, max_distance, total_distance, cause_of_death, attacker_id }, transaction)
})
await transaction.exec()
}))
//Player weapon deaths
promises.push(db.selectFrom('kill').select([count('id').as('deaths'), 'victim_id', 'cause_of_death'])
.groupBy(['victim_id', 'cause_of_death']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ deaths, victim_id, cause_of_death }) => {
transaction = transaction.HSET(`weapons:${cause_of_death}:players:${victim_id}`, "deaths", deaths.toString())
})
return transaction.exec()
}))
//Global player kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id'])
.groupBy('attacker_id').execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, attacker_id }) => {
processPlayerData({ kills, max_distance, total_distance, attacker_id }, transaction)
})
return transaction.exec()
})
)
//Global player deaths
promises.push(db.selectFrom('kill').select([count('id').as('deaths'), 'victim_id'])
.groupBy('victim_id').execute().then(data => {
let transaction = client.multi()
data.forEach(({ deaths, victim_id }) => {
transaction = transaction.HSET('players:' + victim_id, "deaths", deaths.toString())
})
return transaction.exec()
}))
await Promise.all(promises)
promises = [];
promises.push(createWeaponJson());
promises.push(createPlayerJson());
(await client.sMembers('players')).forEach((player: string) => {
promises.push(createWeaponJson(undefined, player))
});
(await client.sMembers('weapons')).forEach((weapon: string) => {
promises.push(createPlayerJson(undefined, weapon))
});
await Promise.all(promises)
}
async function processServerStats() {
let promises: Promise<any>[] = []
//Server global kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'server'])
.groupBy(['server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, server }) => {
transaction = processGlobalData({ kills, max_distance, total_distance, server }, transaction)
})
return transaction.exec()
}))
//Server weapon kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death', 'server'])
.groupBy(['cause_of_death', 'server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, cause_of_death, server }) => {
transaction = processWeaponData({ kills, max_distance, total_distance, cause_of_death, server }, transaction)
})
return transaction.exec()
}))
//Server Player weapon kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'cause_of_death', 'attacker_id', 'server'])
.groupBy(['cause_of_death', 'attacker_id', 'server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, cause_of_death, attacker_id, server }) => {
transaction = processWeaponData({ kills, max_distance, total_distance, cause_of_death, attacker_id, server }, transaction)
})
return transaction.exec()
}))
//Server Player weapon deaths
promises.push(db.selectFrom('kill').select([count('id').as('deaths'), 'victim_id', 'cause_of_death', 'server'])
.groupBy(['victim_id', 'cause_of_death', 'server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ deaths, victim_id, cause_of_death, server }) => {
transaction = transaction.HSET(`servers:${server}:weapons:${cause_of_death}:players:${victim_id}`, "deaths", deaths.toString())
})
return transaction.exec()
}))
//server player kills
promises.push(db.selectFrom('kill')
.select([count('id').as('kills'), max('distance').as('max_distance'), sum('distance').as('total_distance'), 'attacker_id', 'server'])
.groupBy(['attacker_id', 'server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ kills, max_distance, total_distance, attacker_id, server }) => {
transaction = processPlayerData({ kills, max_distance, total_distance, attacker_id, server }, transaction)
})
return transaction.exec()
})
)
//server player deaths
promises.push(db.selectFrom('kill').select([count('id').as('deaths'), 'victim_id', 'server'])
.groupBy(['victim_id', 'server']).execute().then(data => {
let transaction = client.multi()
data.forEach(({ deaths, victim_id, server }) => {
transaction = transaction.HSET('servers:' + server + ':players:' + victim_id, "deaths", deaths.toString())
})
return transaction.exec()
}))
await Promise.all(promises)
promises = [];
((await client.sMembers('servers')).forEach((server: string) => {
if (isNaN(Number(server))) return
promises.push(createWeaponJson(Number(server)));
promises.push(createPlayerJson(Number(server)));
promises.push(client.sMembers('servers:' + server + ':players').then(data => {
data.forEach((player: string) => {
promises.push(createWeaponJson(Number(server), player))
})
}))
promises.push(client.sMembers('servers:' + server + 'weapons').then(data => {
data.forEach((weapon: string) => {
promises.push(createPlayerJson(Number(server), weapon))
})
}))
}));
await Promise.all(promises)
}
function processGlobalData({ kills, max_distance, total_distance, server }: { kills: string | number | bigint, max_distance: number, total_distance: string | number | bigint, server?: number }, transaction: any) {
const serverPrefix = server ? `servers:${server}:` : ''
const cacheLocation = serverPrefix + 'global'
transaction = transaction.HSET(cacheLocation, "total_distance", total_distance.toString())
transaction = transaction.HSET(cacheLocation, "max_distance", max_distance.toString())
transaction = transaction.HSET(cacheLocation, "kills", kills.toString())
if (server) {
transaction = transaction.SADD('servers', server.toString())
}
return transaction
}
function processWeaponData({ kills, max_distance, total_distance, cause_of_death, attacker_id, server }: { kills: string | number | bigint, max_distance: number, total_distance: string | number | bigint, cause_of_death: string, attacker_id?: string, server?: number }, transaction: any) {
const serverPrefix = server ? `servers:${server}:` : ''
const playerPrefix = attacker_id ? `players:${attacker_id}:` : ''
const cacheLocation = serverPrefix + playerPrefix + `weapons:` + cause_of_death
transaction = transaction.HSET(cacheLocation, "total_distance", total_distance.toString())
transaction = transaction.HSET(cacheLocation, "max_distance", max_distance.toString())
transaction = transaction.HSET(cacheLocation, "kills", kills.toString())
transaction = transaction.SADD(serverPrefix + playerPrefix + `weapons`, cause_of_death.toString())
return transaction
}
function processPlayerData({ kills, max_distance, total_distance, attacker_id, cause_of_death, server }: { kills: string | number | bigint, max_distance: number, total_distance: string | number | bigint, attacker_id: string, server?: number, cause_of_death?: string }, transaction: any) {
const serverPrefix = server ? `servers:${server}:` : ''
const weaponPrefix = cause_of_death ? `weapons:${cause_of_death}:` : ''
const cacheLocation = serverPrefix + weaponPrefix + `players:` + attacker_id
transaction = transaction.HSET(cacheLocation, "total_distance", total_distance.toString())
transaction = transaction.HSET(cacheLocation, "max_distance", max_distance.toString())
transaction = transaction.HSET(cacheLocation, "kills", kills.toString())
transaction = transaction.SADD(serverPrefix + weaponPrefix + `players`, attacker_id.toString())
return transaction
}
export default processAll
-42
View File
@@ -1,42 +0,0 @@
/**
* This file contains all functions related to population of the REDIS database for players.
*/
import cache from '../../cache/redis'
import { getPlayerReport, getPlayerSet } from '../../cache/cacheUtils'
/**
* Combines player reports in a readable json string.
*
* ProcessPlayerReport must be called manually before this function !
*
* Inputs `SET` `[servers:{serverId}:][weapons:{weaponId}:]players`
*
* Outputs `STRING` `[servers:{serverId}:][weapons:{weaponId}:]players:processedList`
* @param server optional server filter
* @param weapon optional player filter
* @returns
*/
export async function createPlayerJson(server?: number, weapon?: string) {
const serverPrefix = server ? `servers:${server}:` : ''
const weaponPrefix = weapon ? `weapons:${weapon}:` : ''
const cacheLocation = serverPrefix + weaponPrefix + `players`
const players = await getPlayerSet(server, weapon)
const promises: Promise<any>[] = []
const data: {
[playerId: string]: {
[playerData: string]: string
}
} = {}
players.forEach((plr) => {
promises.push(
(async () => {
const playerData = await getPlayerReport(plr, server, weapon)
if (Object.keys(playerData).length > 0) {
data[plr] = playerData
}
})()
)
})
await Promise.all(promises)
return await cache.SET(cacheLocation + `:processedList`, JSON.stringify(data))
}
-42
View File
@@ -1,42 +0,0 @@
/**
* This file contains all functions related to population of the REDIS database for weapons.
*/
import cache from '../../cache/redis'
import { getWeaponReport } from '../../cache/cacheUtils'
/**
* Combines weapons reports in a readable json string.
*
* ProcessWeaponReports must be called manually before this function !
*
* Inputs `SET` `[servers:{serverId}:][players:{playerId}:]weapons`
*
* Outputs `STRING` `[servers:{serverId}:][players:{playerId}:]weapons:processedList`
* @param server optional server filter
* @param player optional player filter
* @returns
*/
export async function createWeaponJson(server?: number, player?: string) {
const serverPrefix = server ? `servers:${server}:` : ''
const playerPrefix = player ? `players:${player}:` : ''
const cacheLocation = serverPrefix + playerPrefix + `weapons`
const weapons = await cache.SMEMBERS(cacheLocation)
const promises: Promise<any>[] = []
const data: {
[weaponId: string]: {
[weaponData: string]: string
}
} = {}
weapons.forEach((wpn) => {
promises.push(
(async () => {
const weaponData = await getWeaponReport(wpn, server, player)
if (Object.keys(weaponData).length > 0) {
data[wpn] = weaponData
}
})()
)
})
await Promise.all(promises)
return await cache.SET(cacheLocation + `:processedList`, JSON.stringify(data))
}
+7 -9
View File
@@ -4,7 +4,7 @@ import express from 'express'
import cors from 'cors'
import client from './client/client'
import { dbReady } from './db/db'
import { cacheReady } from './cache/redis'
import processAll from './process/process'
const app = express()
const port = 3000
@@ -19,13 +19,11 @@ app.get('/', (req, res) => {
app.use('/', client)
export default
new Promise((resolve, reject) => {
dbReady().then((e) => {
cacheReady().then((e) => {
const listenServer = app.listen(port, '0.0.0.0', () => {
console.log(`Tone client api listening on port ${port}`)
resolve(listenServer)
})
})
new Promise(async (resolve, reject) => {
await dbReady()
await processAll()
const listenServer = app.listen(port, '0.0.0.0', () => {
console.log(`Tone client api listening on port ${port}`)
resolve(listenServer)
})
})
+6 -58
View File
@@ -34,77 +34,25 @@ interface RemoveFromKill {
type KillRecord = Omit<KillTable, keyof RemoveFromKill>
export async function CreateKillRecord(data: KillRecord) {
//TODO : Handle server foreign key crashes
await db
.insertInto('kill')
.values({ ...data })
.execute()
}
export async function FindServer({ name }: { name: string }) {
return await db
.selectFrom('server')
.select(['server.name', 'server.description'])
.where('server.name', '=', name)
.executeTakeFirst()
export async function getHostList() {
return await db.selectFrom("host").select(['id', 'host.name']).execute()
}
export async function CreateServer({
name,
description
}: {
name: string
description: string
}) {
return await db
.insertInto('server')
.values({ name, description })
.returning(['id', 'token'])
.executeTakeFirstOrThrow()
}
//tokens are stored in raw... maybe we should use something better in the future
//Using callback for express-basic-auth
export function CheckServerToken(
this: { params: any, body: any },
name: string,
password: string,
cb: (error: Error | null, success: boolean) => void
) {
db.selectFrom('server')
.where('id', '=', Number(name))
.where('token', '=', password)
export function CheckServerToken(token: string) {
return db.selectFrom('host').select('id')
.where('token', '=', Buffer.from(token, 'base64').toString())
.executeTakeFirst()
.then((result) => {
if (name != this.params.serverId) return cb(null, false)
return cb(null, !!result)
return result
})
}
/*
async function demo() {
const { id } = await db
.insertInto("person")
.values({ first_name: "Jennifer", gender: "female" })
.returning("id")
.executeTakeFirstOrThrow();
await db
.insertInto("pet")
.values({ name: "Catto", species: "cat", owner_id: id })
.execute();
const person = await db
.selectFrom("person")
.innerJoin("pet", "pet.owner_id", "person.id")
.select(["first_name", "pet.name as pet_name"])
.where("person.id", "=", id)
.executeTakeFirst();
if (person) {
person.pet_name;
}
}
*/
export async function dbReady(): Promise<Kysely<Database>> {
await migration
return db
@@ -0,0 +1,34 @@
import { Kysely, sql } from 'kysely'
import { Client } from 'pg'
const notify_newkill = `
CREATE OR REPLACE FUNCTION notify_new_kill()
RETURNS trigger
AS
$$
BEGIN
PERFORM pg_notify('new_kill', row_to_json(NEW)::text);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
`
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 async function up(db: Kysely<any>): Promise<void> {
await pgClient.connect()
await pgClient.query(notify_newkill)
await pgClient.query(`CREATE TRIGGER insert_kills_notify AFTER INSERT ON kill FOR EACH ROW EXECUTE PROCEDURE notify_new_kill();`)
}
export async function down(db: Kysely<any>): Promise<void> {
await pgClient.query(`DROP FUNCTION IF EXISTS notify_new_kill;`)
await pgClient.query(`DROP TRIGGER IF EXISTS insert_kills_notify;`)
await pgClient.end()
}
@@ -0,0 +1,40 @@
import { Kysely, sql } from 'kysely'
import { Client } from 'pg'
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 async function up(db: Kysely<any>): Promise<void> {
await pgClient.connect()
await pgClient.query('DROP TABLE IF EXISTS player')
await pgClient.query('ALTER TABLE kill ADD COLUMN IF NOT EXISTS servername character varying NULL;')
await pgClient.query('ALTER TABLE kill ADD COLUMN IF NOT EXISTS host integer NULL;')
await pgClient.query('UPDATE kill SET servername = server.name FROM server WHERE server.id = kill.server;')
await pgClient.query('CREATE TABLE host (id SERIAL PRIMARY KEY, name character varying, token character varying)')
await pgClient.query('INSERT INTO host (token) SELECT DISTINCT token FROM server')
await pgClient.query('UPDATE kill SET host = host.id FROM host FULL JOIN server ON server.token = host.token WHERE host.token = server.token AND server.id = kill.server;')
await pgClient.query('ALTER TABLE kill ALTER COLUMN servername SET NOT NULL;')
await pgClient.query('ALTER TABLE kill DROP COLUMN server;')
await pgClient.query('DROP TABLE IF EXISTS server')
//Update the hosts column once the hoster table is manually updated
await pgClient.end()
}
export async function down(db: Kysely<any>): Promise<void> {
await pgClient.connect()
await pgClient.query('ALTER TABLE kill ADD server integer NULL;')
await pgClient.query(`ALTER TABLE kill ADD server integer NULL;`)
await pgClient.query('UPDATE kill SET server = server.id FROM server where server.name = kill.servername;')
await pgClient.query('ALTER TABLE kill ALTER COLUMN server SET NOT NULL;')
await pgClient.query('ALTER TABLE kill DROP COLUMN servername;')
await pgClient.query('ALTER TABLE kill DROP COLUMN host;')
await pgClient.query('DROP TABLE IF EXISTS host')
await pgClient.end()
}
@@ -0,0 +1,46 @@
import { Kysely, sql } from 'kysely'
import { Client } from 'pg'
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 async function up(db: Kysely<any>): Promise<void> {
await pgClient.connect()
await pgClient.query(`CREATE OR REPLACE FUNCTION public.first_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1';`)
await pgClient.query(`CREATE AGGREGATE public.first (anyelement) (
SFUNC = public.first_agg
, STYPE = anyelement
, PARALLEL = safe
);`)
await pgClient.query(`CREATE OR REPLACE FUNCTION public.last_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $2';`)
await pgClient.query(`CREATE AGGREGATE public.last (anyelement) (
SFUNC = public.last_agg
, STYPE = anyelement
, PARALLEL = safe
);`)
//Update the hosts column once the hoster table is manually updated
await pgClient.end()
}
export async function down(db: Kysely<any>): Promise<void> {
await pgClient.connect()
pgClient.query('DROP AGGREGATE public.last')
pgClient.query('DROP FUNCTION public.last_agg')
pgClient.query('DROP AGGREGATE public.first')
pgClient.query('DROP AGGREGATE public.first_agg')
await pgClient.end()
}
+8 -6
View File
@@ -2,7 +2,8 @@ import { ColumnType, Generated } from 'kysely'
export interface KillTable {
id: Generated<number>
server: number
servername: string
host: number
killstat_version: string
match_id: string
game_mode: string
@@ -37,13 +38,15 @@ export interface KillTable {
cause_of_death: string
distance: number
}
/*
interface PlayerTable {
id: number
name: string
'opt-out': boolean
hide_TOS: boolean
}
*/
interface WeaponTable {
id: string
name: string
@@ -56,18 +59,17 @@ interface MapTable {
description: string
image: string
}
interface ServerTable {
interface HosterTable {
id: Generated<number>
name: string
description: string
token: Generated<string>
}
interface Database {
kill: KillTable
player: PlayerTable
//player: PlayerTable
weapon: WeaponTable
maps: MapTable
server: ServerTable
host: HosterTable
}
export default Database
+76
View File
@@ -0,0 +1,76 @@
import { Client } from 'pg'
import { allData } from './process'
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.find(
(e) =>
e.attacker_id == payload.attacker_id &&
e.cause_of_death == payload.cause_of_death &&
e.game_mode == payload.game_mode &&
e.host == payload.host &&
e.map == payload.map &&
e.servername == payload.servername
)
let deathEntry = allData.find(
(e) =>
e.attacker_id == payload.victim_id &&
e.cause_of_death == payload.cause_of_death &&
e.game_mode == payload.game_mode &&
e.host == payload.host &&
e.map == payload.map &&
e.servername == payload.servername
)
if (!killEntry) {
killEntry = {
kills: 0,
deaths: 0,
max_distance: 0,
total_distance: 0,
attacker_name: payload.attacker_name,
attacker_id: payload.attacker_id,
cause_of_death: payload.cause_of_death,
game_mode: payload.game_mode,
host: payload.host,
map: payload.map,
servername: payload.servername
}
allData.push(killEntry)
}
if (!deathEntry) {
deathEntry = {
kills: 0,
deaths: 0,
max_distance: 0,
total_distance: 0,
attacker_name: payload.victim_name,
attacker_id: payload.victim_id,
cause_of_death: payload.victim_current_weapon,
game_mode: payload.game_mode,
host: payload.host,
map: payload.map,
servername: payload.servername
}
allData.push(deathEntry)
}
killEntry.kills++
killEntry.total_distance += payload.distance
killEntry.max_distance = Math.max(killEntry.max_distance, payload.distance)
deathEntry.deaths++
})
return pgClient
}
+94
View File
@@ -0,0 +1,94 @@
import db from '../db/db'
const { count, max, sum, coalesce } = db.fn
import { sql } from 'kysely'
export let allData: Awaited<ReturnType<typeof processGlobalStats>>
/**
* populates allData
* @returns
*/
async function processAll() {
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'
)
if (process.env.ENVIRONMENT == 'production') {
return
}
}
async function processGlobalStats() {
return await db
.with('kills', (db) =>
db
.selectFrom('kill')
.select([
count<number>('id').as('kills'),
coalesce(
max<number | null, 'distance'>('distance'),
sql<number>`0`
).as('max_distance'),
coalesce(sum<number | null>('distance'), sql<number>`0`).as(
'total_distance'
),
'attacker_id',
sql<string>`last(attacker_name)`.as('attacker_name'),
'cause_of_death',
'map',
'game_mode',
'servername',
'host'
])
.whereRef('attacker_id', '!=', 'victim_id')
.groupBy([
'attacker_id',
'cause_of_death',
'map',
'servername',
'host',
'game_mode'
])
)
.with('deaths', (db) =>
db
.selectFrom('kill')
.select([
count<number>('id').as('deaths'),
'victim_id',
'victim_current_weapon',
sql<string>`last(attacker_name)`.as('attacker_name'),
'map',
'game_mode',
'servername',
'host'
])
.groupBy([
'victim_id',
'victim_current_weapon',
'map',
'servername',
'host',
'game_mode'
])
)
.selectFrom('kills')
.leftJoin('deaths', (join) =>
join
.onRef('deaths.victim_id', '=', 'kills.attacker_id')
.onRef('deaths.victim_current_weapon', '=', 'kills.cause_of_death')
.onRef('deaths.servername', '=', 'kills.servername')
.onRef('deaths.host', '=', 'kills.host')
.onRef('deaths.game_mode', '=', 'kills.game_mode')
)
.selectAll('kills')
.select(sql<number>`COALESCE(deaths.deaths, 0)`.as('deaths'))
.execute()
}
export default processAll
-12
View File
@@ -1,12 +0,0 @@
import * as dotenv from 'dotenv'
dotenv.config()
import { dbReady } from './db/db'
import { cacheReady } from './cache/redis'
import processAll from './client/process'
async function startup() {
await dbReady()
await cacheReady()
await processAll()
}
export default startup()
-90
View File
@@ -1,90 +0,0 @@
import { Router } from 'express'
import { GetRequest, validateErrors } from '../common'
import { FindServer, CreateServer } from '../db/db'
import { body, validationResult } from 'express-validator'
const verificationString = 'I am a northstar server!'
const masterServerURL = 'https://northstar.tf'
const router = Router()
const hostsCount: { [id: string]: number } = {}
const hostsTimeout: { [id: string]: NodeJS.Timeout } = {}
//Very simple rate limiting. max 2 registers per IP every 5 mins. Maybe 2 is a bit few ?
router.post('/register', (req, res, next) => {
let ip =
req.header('x-forwarded-for') || req.socket.remoteAddress || 'undefined'
if (hostsCount[ip] > 2) {
return res.status(429).json({
error:
'too many requests. Please wait 5 minutes before requesting a register again.'
})
}
clearTimeout(hostsTimeout[ip])
hostsCount[ip] = hostsCount[ip] ?? (hostsCount[ip] + 1) | 1
hostsTimeout[ip] = setTimeout(() => {
hostsCount[ip] = 0
}, 300000)
next()
})
router.post(
'/register',
body(['name', 'description']).isString().withMessage('must be strings'),
body('auth_port')
.toInt()
.isInt({ min: 1, max: 65535 })
.withMessage('must be between 1 and 65535'),
validateErrors,
async (req, res) => {
try {
//Check if server name isn't already in database
if (!!(await FindServer({ name: req.body.name }))) {
return res
.status(403)
.json({ error: 'Server already exists in the database' })
}
//Check if server is in masterserver's list
const masterServerList = JSON.parse(
await GetRequest(masterServerURL + '/client/servers')
) as Array<any>
if (!masterServerList.find((e) => e.name == req.body.name)) {
return res
.status(403)
.json({ error: 'Server not listed in masterserver' })
}
//Send request to verify server. Not very useful for now, but maybe a future method for auth ?
//Maybe should set a blacklist here for local domain ?
const endpoint =
'http://' +
(req.header('x-forwarded-for') || req.socket.remoteAddress) +
':' +
req.body.auth_port +
'/verify'
if ((await GetRequest(endpoint)) != verificationString) {
return res.status(400).json({
error: "Couldn't reach gameserver at " + endpoint
})
}
//send token here
res.status(201).json(
await CreateServer({
name: req.body.name,
description: req.body.description
})
)
} catch (e) {
console.log(e)
return res.status(400).json({
error: "Server encountered an error, Couldn't register gameserver."
})
}
}
)
export default router
+31 -33
View File
@@ -1,37 +1,31 @@
import { NextFunction, Router } from 'express'
import expressBasicAuth from 'express-basic-auth'
import { body, header, param } from 'express-validator'
import register from './register'
import { Router } from 'express'
import { body, header } from 'express-validator'
import { CreateKillRecord, CheckServerToken } from '../db/db'
import { validateErrors } from '../common'
const router = Router()
router.use('/', register)
//auth middleware
router.post(
'/:serverId*',
'/*',
header('authorization')
.exists({ checkFalsy: true })
.withMessage('Missing Authorization Header')
.bail()
.contains('Basic')
.withMessage('Authorization Token is not Basic'),
.custom(e => e.split(' ')[0].toLowerCase() == 'bearer')
.withMessage('Authorization Token is not Bearer'),
validateErrors,
//Huge mess to retrieve server id from expressBasicAuth. We probably should fix it.
(req, res, next) => {
if (!req) res.sendStatus(500)
return expressBasicAuth({
authorizeAsync: true,
authorizer: CheckServerToken.bind(req),
unauthorizedResponse: { error: 'invalid credentials' }
})(req as any, res, next)
async (req, res, next) => {
if (!req) return res.sendStatus(500)
if (!req.headers.authorization) return res.sendStatus(403)
const query = await CheckServerToken(req.headers.authorization.split(' ')[1])
if (!query || !query.id) return res.sendStatus(403)
next()
}
)
//Route to check auth
router.post('/:serverId', (req, res) => {
router.post('/', (req, res) => {
res.sendStatus(200)
})
@@ -39,8 +33,8 @@ const serversCount: { [id: string]: number } = {}
const serversTimeout: { [id: string]: NodeJS.Timeout } = {}
//same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
router.post('/:serverId/kill', (req, res, next) => {
let serverId = Number(req.query.serverId)
/*router.post('/kill', (req, res, next) => {
let host = Number(req.query.serverId)
if (serversCount[serverId] > 2) {
return res.status(429).json({
error: 'too many requests. Are players really making that much kills ?'
@@ -53,11 +47,10 @@ router.post('/:serverId/kill', (req, res, next) => {
serversCount[serverId] = 0
}, 1000)
next()
})
})*/
router.post(
'/:serverId/kill',
param('serverId').exists().toInt().isInt(),
'/kill',
body([
'attacker_current_weapon_mods',
'attacker_weapon_1_mods',
@@ -104,6 +97,7 @@ router.post(
.withMessage('must be a valid float'),
body(
[
'servername',
'attacker_id',
'victim_id',
'killstat_version',
@@ -131,9 +125,17 @@ router.post(
min: 0
}),
body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(),
body('servername').customSanitizer(e => e.replace(/[^a-z0-9]/gi, '')),
validateErrors,
(req, res) => {
async (req, res) => {
if (!req.headers.authorization) return res.sendStatus(403)
const headers = req.headers.authorization.split(' ')
if (headers[0].toLowerCase() != "bearer") return res.status(403).send("authorization must be token bearer")
const query = (await CheckServerToken(headers[1]))
if (!query) return res.sendStatus(403)
const host = query.id
const {
servername,
killstat_version,
match_id,
game_mode,
@@ -167,13 +169,10 @@ router.post(
cause_of_death,
distance
} = req.body
if (!req.params.serverId) {
res.status(500).send('serverId cannot be undefined')
return
}
CreateKillRecord({
killstat_version,
server: Number(req.params.serverId),
servername,
host,
match_id,
game_mode,
map,
@@ -209,16 +208,15 @@ router.post(
.then((e) => {
res.sendStatus(201)
console.log(
`[${Date.now().toLocaleString()}] Kill submitted for server ${
req.params.serverId
}, ${attacker_name} killed ${victim_name}`
`[${Date.now().toLocaleString()}] Kill submitted for server ${servername}, ${attacker_name} killed ${victim_name}`
)
})
.catch((e) => {
res.sendStatus(500)
console.log({
killstat_version,
server: Number(req.params.serverId),
servername,
host,
match_id,
game_mode,
map,
-4
View File
@@ -25,7 +25,3 @@ export default new Promise((resolve, reject) => {
})
})
})
app.on('close', () => {
})
+25 -5
View File
@@ -1,6 +1,7 @@
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'
dotenv.config()
let listenServer
@@ -13,10 +14,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[1]).toHaveProperty('max_distance')
expect(first[1]).toHaveProperty('total_distance')
expect(first[1]).toHaveProperty('kills')
})
test('player list', async () => {
@@ -28,6 +29,15 @@ describe('client', () => {
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()
@@ -36,10 +46,20 @@ describe('client', () => {
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(() => {
listenServer.close(async () => {
await db.destroy()
done()
})
})
+143
View File
@@ -0,0 +1,143 @@
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'
dotenv.config()
let listenClient
let listenServer
let pgClient
const data = {
attacker_weapon_1_mods: 0,
victim_id: '0',
servername: 'testServer',
victim_name: 'TestVictim',
victim_offhand_weapon_2: 0,
attacker_offhand_weapon_3: '0',
victim_weapon_3_mods: 0,
attacker_weapon_2_mods: 0,
attacker_offhand_weapon_1: 0,
attacker_weapon_3_mods: 0,
attacker_offhand_weapon_2: 0,
victim_offhand_weapon_3: '0',
victim_offhand_weapon_1: 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: 'smr',
victim_weapon_2: 'autopistol',
victim_weapon_1_mods: 0,
victim_weapon_3: 'defender',
attacker_name: 'TestAttacker'
}
function waitFor(time: number) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time)
})
}
jest.setTimeout(15000)
beforeAll(async () => {
jest.setTimeout(15000)
listenClient = await clientMain;
listenServer = await serverMain;
pgClient = await listenKills()
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
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('update player', async () => {
jest.setTimeout(15000)
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
})
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)
})
})
afterAll((done) => {
listenClient.close(() => {
listenServer.close(async () => {
await pgClient.end()
await db.destroy()
done()
})
})
})
+50 -10
View File
@@ -11,13 +11,49 @@ beforeAll(async () => {
})
describe('server', () => {
test('server auth prefetch', async () => {
const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}`, {
test('bad auth prefetch', async () => {
const response = await fetch(`http://127.0.0.1:3001/`, {
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')}`
'Authorization': `Bearere ${Buffer.from('' + process.env.SERVERAUTH_TOKEN).toString('base64')}`
}
});
expect(response.status).toBe(400)
const response2 = await fetch(`http://127.0.0.1:3001/`, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${Buffer.from('badtoken').toString('base64')}`
}
});
expect(response2.status).toBe(403)
})
test('bad kill token', async () => {
const response2 = 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('badtoken').toString('base64')}`
}
});
expect(response2.status).toBe(403)
})
test('good auth prefetch', async () => {
const response = await fetch(`http://127.0.0.1:3001/`, {
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')}`
}
});
expect(response.status).toBe(200)
@@ -25,6 +61,7 @@ describe('server', () => {
test('register a kill', async () => {
const data = {
servername: 'testserver',
attacker_weapon_1_mods: 0,
victim_id: '0',
victim_name: 'TestVictim',
@@ -60,21 +97,23 @@ describe('server', () => {
victim_weapon_3: 'defender',
attacker_name: 'TestAttacker'
}
const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, {
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': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}`
'Authorization': `Bearer ${Buffer.from(process.env.SERVERAUTH_TOKEN + '').toString('base64')}`
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
console.log(await response.text())
expect(response.status).toBe(201)
})
test('register a kill with missing data', async () => {
const data = {
servername: 'testserver',
attacker_weapon_1_mods: NaN,
victim_id: '0',
victim_name: 'TestVictim',
@@ -110,12 +149,12 @@ describe('server', () => {
victim_weapon_3: 'arc_launcher',
attacker_name: 'TestAttacker'
}
const response = await fetch(`http://127.0.0.1:3001/${process.env.SERVERAUTH_ID}/kill`, {
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': `Basic ${Buffer.from(process.env.SERVERAUTH_ID + ':' + process.env.SERVERAUTH_TOKEN).toString('base64')}`
'Authorization': `Bearer ${Buffer.from(process.env.SERVERAUTH_TOKEN + '').toString('base64')}`
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
@@ -124,9 +163,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()
})
})
})