+1
-2
@@ -33,8 +33,7 @@
|
|||||||
"build": "npx tsc",
|
"build": "npx tsc",
|
||||||
"startServer": "node out/serverMain.js",
|
"startServer": "node out/serverMain.js",
|
||||||
"startClient": "node out/clientMain.js",
|
"startClient": "node out/clientMain.js",
|
||||||
"startProcess": "node out/processMain.js",
|
|
||||||
"documentation": "redocly build-docs .\\docs\\v1.yml --output=docs\\index.html",
|
"documentation": "redocly build-docs .\\docs\\v1.yml --output=docs\\index.html",
|
||||||
"test": "jest"
|
"test": "jest --runInBand"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
-97
@@ -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)
|
|
||||||
}
|
|
||||||
Vendored
-18
@@ -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
@@ -1,101 +1,94 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import { param, query } from 'express-validator'
|
import { param, query } from 'express-validator'
|
||||||
import { validateErrors } from '../common'
|
import { validateErrors } from '../common'
|
||||||
import db from '../db/db'
|
import { allData } from '../process/process'
|
||||||
import {
|
import { getHostList } from '../db/db'
|
||||||
getWeaponList,
|
|
||||||
getWeaponReport,
|
|
||||||
getPlayerReport,
|
|
||||||
getPlayerList
|
|
||||||
} from '../cache/cacheUtils'
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
//timeout middleware ?
|
//timeout middleware ?
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get(
|
function filters(e: any, query: any) {
|
||||||
'/weapons/:weaponId',
|
return (
|
||||||
param(['weaponId']).exists().isString(),
|
(query.player ? query.player == e.attacker_id : true) &&
|
||||||
query(['player', 'server']).optional().toInt().isInt(),
|
(query.server ? query.server == e.servername : true) &&
|
||||||
validateErrors,
|
(query.host ? Number(query.host) == e.host : true) &&
|
||||||
async (req, res) => {
|
(query.map ? query.map == e.map : true) &&
|
||||||
const data = await getWeaponReport(
|
(query.weapon ? query.weapon == e.cause_of_death : true) &&
|
||||||
req.params.weaponId,
|
(query.gamemode ? query.gamemode == e.game_mode : true)
|
||||||
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 }
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
//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
|
export default router
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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
@@ -4,7 +4,7 @@ import express from 'express'
|
|||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import client from './client/client'
|
import client from './client/client'
|
||||||
import { dbReady } from './db/db'
|
import { dbReady } from './db/db'
|
||||||
import { cacheReady } from './cache/redis'
|
import processAll from './process/process'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const port = 3000
|
const port = 3000
|
||||||
@@ -19,13 +19,11 @@ app.get('/', (req, res) => {
|
|||||||
app.use('/', client)
|
app.use('/', client)
|
||||||
|
|
||||||
export default
|
export default
|
||||||
new Promise((resolve, reject) => {
|
new Promise(async (resolve, reject) => {
|
||||||
dbReady().then((e) => {
|
await dbReady()
|
||||||
cacheReady().then((e) => {
|
await processAll()
|
||||||
const listenServer = app.listen(port, '0.0.0.0', () => {
|
const listenServer = app.listen(port, '0.0.0.0', () => {
|
||||||
console.log(`Tone client api listening on port ${port}`)
|
console.log(`Tone client api listening on port ${port}`)
|
||||||
resolve(listenServer)
|
resolve(listenServer)
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
+6
-58
@@ -34,77 +34,25 @@ interface RemoveFromKill {
|
|||||||
type KillRecord = Omit<KillTable, keyof RemoveFromKill>
|
type KillRecord = Omit<KillTable, keyof RemoveFromKill>
|
||||||
|
|
||||||
export async function CreateKillRecord(data: KillRecord) {
|
export async function CreateKillRecord(data: KillRecord) {
|
||||||
//TODO : Handle server foreign key crashes
|
|
||||||
await db
|
await db
|
||||||
.insertInto('kill')
|
.insertInto('kill')
|
||||||
.values({ ...data })
|
.values({ ...data })
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
export async function getHostList() {
|
||||||
export async function FindServer({ name }: { name: string }) {
|
return await db.selectFrom("host").select(['id', 'host.name']).execute()
|
||||||
return await db
|
|
||||||
.selectFrom('server')
|
|
||||||
.select(['server.name', 'server.description'])
|
|
||||||
.where('server.name', '=', name)
|
|
||||||
.executeTakeFirst()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
//tokens are stored in raw... maybe we should use something better in the future
|
||||||
//Using callback for express-basic-auth
|
//Using callback for express-basic-auth
|
||||||
export function CheckServerToken(
|
export function CheckServerToken(token: string) {
|
||||||
this: { params: any, body: any },
|
return db.selectFrom('host').select('id')
|
||||||
name: string,
|
.where('token', '=', Buffer.from(token, 'base64').toString())
|
||||||
password: string,
|
|
||||||
cb: (error: Error | null, success: boolean) => void
|
|
||||||
) {
|
|
||||||
db.selectFrom('server')
|
|
||||||
.where('id', '=', Number(name))
|
|
||||||
.where('token', '=', password)
|
|
||||||
.executeTakeFirst()
|
.executeTakeFirst()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (name != this.params.serverId) return cb(null, false)
|
return result
|
||||||
return cb(null, !!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>> {
|
export async function dbReady(): Promise<Kysely<Database>> {
|
||||||
await migration
|
await migration
|
||||||
return db
|
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
@@ -2,7 +2,8 @@ import { ColumnType, Generated } from 'kysely'
|
|||||||
|
|
||||||
export interface KillTable {
|
export interface KillTable {
|
||||||
id: Generated<number>
|
id: Generated<number>
|
||||||
server: number
|
servername: string
|
||||||
|
host: number
|
||||||
killstat_version: string
|
killstat_version: string
|
||||||
match_id: string
|
match_id: string
|
||||||
game_mode: string
|
game_mode: string
|
||||||
@@ -37,13 +38,15 @@ export interface KillTable {
|
|||||||
cause_of_death: string
|
cause_of_death: string
|
||||||
distance: number
|
distance: number
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
interface PlayerTable {
|
interface PlayerTable {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
'opt-out': boolean
|
'opt-out': boolean
|
||||||
hide_TOS: boolean
|
hide_TOS: boolean
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
interface WeaponTable {
|
interface WeaponTable {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -56,18 +59,17 @@ interface MapTable {
|
|||||||
description: string
|
description: string
|
||||||
image: string
|
image: string
|
||||||
}
|
}
|
||||||
interface ServerTable {
|
interface HosterTable {
|
||||||
id: Generated<number>
|
id: Generated<number>
|
||||||
name: string
|
name: string
|
||||||
description: string
|
|
||||||
token: Generated<string>
|
token: Generated<string>
|
||||||
}
|
}
|
||||||
interface Database {
|
interface Database {
|
||||||
kill: KillTable
|
kill: KillTable
|
||||||
player: PlayerTable
|
//player: PlayerTable
|
||||||
weapon: WeaponTable
|
weapon: WeaponTable
|
||||||
maps: MapTable
|
maps: MapTable
|
||||||
server: ServerTable
|
host: HosterTable
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Database
|
export default Database
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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()
|
|
||||||
@@ -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
@@ -1,37 +1,31 @@
|
|||||||
import { NextFunction, Router } from 'express'
|
import { Router } from 'express'
|
||||||
import expressBasicAuth from 'express-basic-auth'
|
import { body, header } from 'express-validator'
|
||||||
import { body, header, param } from 'express-validator'
|
|
||||||
import register from './register'
|
|
||||||
import { CreateKillRecord, CheckServerToken } from '../db/db'
|
import { CreateKillRecord, CheckServerToken } from '../db/db'
|
||||||
import { validateErrors } from '../common'
|
import { validateErrors } from '../common'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router.use('/', register)
|
|
||||||
|
|
||||||
//auth middleware
|
//auth middleware
|
||||||
router.post(
|
router.post(
|
||||||
'/:serverId*',
|
'/*',
|
||||||
header('authorization')
|
header('authorization')
|
||||||
.exists({ checkFalsy: true })
|
.exists({ checkFalsy: true })
|
||||||
.withMessage('Missing Authorization Header')
|
.withMessage('Missing Authorization Header')
|
||||||
.bail()
|
.bail()
|
||||||
.contains('Basic')
|
.custom(e => e.split(' ')[0].toLowerCase() == 'bearer')
|
||||||
.withMessage('Authorization Token is not Basic'),
|
.withMessage('Authorization Token is not Bearer'),
|
||||||
validateErrors,
|
validateErrors,
|
||||||
//Huge mess to retrieve server id from expressBasicAuth. We probably should fix it.
|
async (req, res, next) => {
|
||||||
(req, res, next) => {
|
if (!req) return res.sendStatus(500)
|
||||||
if (!req) res.sendStatus(500)
|
if (!req.headers.authorization) return res.sendStatus(403)
|
||||||
return expressBasicAuth({
|
const query = await CheckServerToken(req.headers.authorization.split(' ')[1])
|
||||||
authorizeAsync: true,
|
if (!query || !query.id) return res.sendStatus(403)
|
||||||
authorizer: CheckServerToken.bind(req),
|
next()
|
||||||
unauthorizedResponse: { error: 'invalid credentials' }
|
|
||||||
})(req as any, res, next)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
//Route to check auth
|
//Route to check auth
|
||||||
router.post('/:serverId', (req, res) => {
|
router.post('/', (req, res) => {
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -39,8 +33,8 @@ const serversCount: { [id: string]: number } = {}
|
|||||||
const serversTimeout: { [id: string]: NodeJS.Timeout } = {}
|
const serversTimeout: { [id: string]: NodeJS.Timeout } = {}
|
||||||
|
|
||||||
//same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
|
//same rate limiting code as register. max 10 kills per server every 1 sec. should be enough.
|
||||||
router.post('/:serverId/kill', (req, res, next) => {
|
/*router.post('/kill', (req, res, next) => {
|
||||||
let serverId = Number(req.query.serverId)
|
let host = Number(req.query.serverId)
|
||||||
if (serversCount[serverId] > 2) {
|
if (serversCount[serverId] > 2) {
|
||||||
return res.status(429).json({
|
return res.status(429).json({
|
||||||
error: 'too many requests. Are players really making that much kills ?'
|
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
|
serversCount[serverId] = 0
|
||||||
}, 1000)
|
}, 1000)
|
||||||
next()
|
next()
|
||||||
})
|
})*/
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:serverId/kill',
|
'/kill',
|
||||||
param('serverId').exists().toInt().isInt(),
|
|
||||||
body([
|
body([
|
||||||
'attacker_current_weapon_mods',
|
'attacker_current_weapon_mods',
|
||||||
'attacker_weapon_1_mods',
|
'attacker_weapon_1_mods',
|
||||||
@@ -104,6 +97,7 @@ router.post(
|
|||||||
.withMessage('must be a valid float'),
|
.withMessage('must be a valid float'),
|
||||||
body(
|
body(
|
||||||
[
|
[
|
||||||
|
'servername',
|
||||||
'attacker_id',
|
'attacker_id',
|
||||||
'victim_id',
|
'victim_id',
|
||||||
'killstat_version',
|
'killstat_version',
|
||||||
@@ -131,9 +125,17 @@ router.post(
|
|||||||
min: 0
|
min: 0
|
||||||
}),
|
}),
|
||||||
body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(),
|
body(['cause_of_death', 'victim_id'], 'mandatory').exists().notEmpty(),
|
||||||
|
body('servername').customSanitizer(e => e.replace(/[^a-z0-9]/gi, '')),
|
||||||
validateErrors,
|
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 {
|
const {
|
||||||
|
servername,
|
||||||
killstat_version,
|
killstat_version,
|
||||||
match_id,
|
match_id,
|
||||||
game_mode,
|
game_mode,
|
||||||
@@ -167,13 +169,10 @@ router.post(
|
|||||||
cause_of_death,
|
cause_of_death,
|
||||||
distance
|
distance
|
||||||
} = req.body
|
} = req.body
|
||||||
if (!req.params.serverId) {
|
|
||||||
res.status(500).send('serverId cannot be undefined')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
CreateKillRecord({
|
CreateKillRecord({
|
||||||
killstat_version,
|
killstat_version,
|
||||||
server: Number(req.params.serverId),
|
servername,
|
||||||
|
host,
|
||||||
match_id,
|
match_id,
|
||||||
game_mode,
|
game_mode,
|
||||||
map,
|
map,
|
||||||
@@ -209,16 +208,15 @@ router.post(
|
|||||||
.then((e) => {
|
.then((e) => {
|
||||||
res.sendStatus(201)
|
res.sendStatus(201)
|
||||||
console.log(
|
console.log(
|
||||||
`[${Date.now().toLocaleString()}] Kill submitted for server ${
|
`[${Date.now().toLocaleString()}] Kill submitted for server ${servername}, ${attacker_name} killed ${victim_name}`
|
||||||
req.params.serverId
|
|
||||||
}, ${attacker_name} killed ${victim_name}`
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
res.sendStatus(500)
|
res.sendStatus(500)
|
||||||
console.log({
|
console.log({
|
||||||
killstat_version,
|
killstat_version,
|
||||||
server: Number(req.params.serverId),
|
servername,
|
||||||
|
host,
|
||||||
match_id,
|
match_id,
|
||||||
game_mode,
|
game_mode,
|
||||||
map,
|
map,
|
||||||
|
|||||||
@@ -25,7 +25,3 @@ export default new Promise((resolve, reject) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('close', () => {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|||||||
+25
-5
@@ -1,6 +1,7 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'
|
import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'
|
||||||
import clientMain from '../src/clientMain'
|
import clientMain from '../src/clientMain'
|
||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
|
import db from '../src/db/db'
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
let listenServer
|
let listenServer
|
||||||
@@ -13,10 +14,10 @@ describe('client', () => {
|
|||||||
test('server list', async () => {
|
test('server list', async () => {
|
||||||
const request = await fetch("http://127.0.0.1:3000/servers")
|
const request = await fetch("http://127.0.0.1:3000/servers")
|
||||||
const data = await request.json()
|
const data = await request.json()
|
||||||
expect(data.length).toBeGreaterThan(0)
|
const first = Object.entries(data)[0]
|
||||||
expect(data[0]).toHaveProperty('name')
|
expect(first[1]).toHaveProperty('max_distance')
|
||||||
expect(data[0]).toHaveProperty('id')
|
expect(first[1]).toHaveProperty('total_distance')
|
||||||
expect(data[0]).toHaveProperty('description')
|
expect(first[1]).toHaveProperty('kills')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('player list', async () => {
|
test('player list', async () => {
|
||||||
@@ -28,6 +29,15 @@ describe('client', () => {
|
|||||||
expect(first[1]).toHaveProperty('kills')
|
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 () => {
|
test('weapon list', async () => {
|
||||||
const request = await fetch("http://127.0.0.1:3000/weapons")
|
const request = await fetch("http://127.0.0.1:3000/weapons")
|
||||||
const data = await request.json()
|
const data = await request.json()
|
||||||
@@ -36,10 +46,20 @@ describe('client', () => {
|
|||||||
expect(first[1]).toHaveProperty('total_distance')
|
expect(first[1]).toHaveProperty('total_distance')
|
||||||
expect(first[1]).toHaveProperty('kills')
|
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) => {
|
afterAll((done) => {
|
||||||
listenServer.close(() => {
|
listenServer.close(async () => {
|
||||||
|
await db.destroy()
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -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
@@ -11,13 +11,49 @@ beforeAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('server', () => {
|
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.
|
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||||
credentials: "same-origin", // include, *same-origin, omit
|
credentials: "same-origin", // include, *same-origin, omit
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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)
|
expect(response.status).toBe(200)
|
||||||
@@ -25,6 +61,7 @@ describe('server', () => {
|
|||||||
|
|
||||||
test('register a kill', async () => {
|
test('register a kill', async () => {
|
||||||
const data = {
|
const data = {
|
||||||
|
servername: 'testserver',
|
||||||
attacker_weapon_1_mods: 0,
|
attacker_weapon_1_mods: 0,
|
||||||
victim_id: '0',
|
victim_id: '0',
|
||||||
victim_name: 'TestVictim',
|
victim_name: 'TestVictim',
|
||||||
@@ -60,21 +97,23 @@ describe('server', () => {
|
|||||||
victim_weapon_3: 'defender',
|
victim_weapon_3: 'defender',
|
||||||
attacker_name: 'TestAttacker'
|
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.
|
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||||
credentials: "same-origin", // include, *same-origin, omit
|
credentials: "same-origin", // include, *same-origin, omit
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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
|
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||||
});
|
});
|
||||||
|
console.log(await response.text())
|
||||||
expect(response.status).toBe(201)
|
expect(response.status).toBe(201)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
test('register a kill with missing data', async () => {
|
test('register a kill with missing data', async () => {
|
||||||
const data = {
|
const data = {
|
||||||
|
servername: 'testserver',
|
||||||
attacker_weapon_1_mods: NaN,
|
attacker_weapon_1_mods: NaN,
|
||||||
victim_id: '0',
|
victim_id: '0',
|
||||||
victim_name: 'TestVictim',
|
victim_name: 'TestVictim',
|
||||||
@@ -110,12 +149,12 @@ describe('server', () => {
|
|||||||
victim_weapon_3: 'arc_launcher',
|
victim_weapon_3: 'arc_launcher',
|
||||||
attacker_name: 'TestAttacker'
|
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.
|
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||||
credentials: "same-origin", // include, *same-origin, omit
|
credentials: "same-origin", // include, *same-origin, omit
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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
|
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||||
});
|
});
|
||||||
@@ -124,9 +163,10 @@ describe('server', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterAll((done) => {
|
afterAll((done) => {
|
||||||
listenServer.close(() => {
|
db.deleteFrom('kill').where('attacker_id', '=', '0').orWhere('attacker_id', '=', '1').execute().then(() => {
|
||||||
db.deleteFrom('kill').where('attacker_id', '=', '0').orWhere('attacker_id', '=', '1').execute().then(() => {
|
listenServer.close(async () => {
|
||||||
db.destroy().then(() => { done() })
|
await db.destroy()
|
||||||
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user