Performance update
This commit is contained in:
@@ -3,3 +3,4 @@ POSTGRES_DATABASE="postgres"
|
|||||||
POSTGRES_USER="postgres"
|
POSTGRES_USER="postgres"
|
||||||
POSTGRES_PASSWORD="postgres"
|
POSTGRES_PASSWORD="postgres"
|
||||||
SERVERAUTH_TOKEN="1:f9f8fc8c-9185-47a8-8a99-9e3acf6ca007"
|
SERVERAUTH_TOKEN="1:f9f8fc8c-9185-47a8-8a99-9e3acf6ca007"
|
||||||
|
ENVIRONMENT='prod'
|
||||||
+4
-1
@@ -10,5 +10,8 @@ module.exports = {
|
|||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
project: ['./tsconfig.json']
|
project: ['./tsconfig.json']
|
||||||
},
|
},
|
||||||
rules: {}
|
rules: {
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': 0,
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
-6
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll": true
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"typescript.preferences.quoteStyle": "single",
|
"typescript.preferences.quoteStyle": "single",
|
||||||
"javascript.preferences.quoteStyle": "single",
|
"javascript.preferences.quoteStyle": "single",
|
||||||
"prettier.singleQuote": true,
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||||
"prettier.trailingComma": "none",
|
"eslint.format.enable": true,
|
||||||
"prettier.semi": false
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-131
@@ -1,149 +1,118 @@
|
|||||||
import { Router } from 'express'
|
import { Router } from 'express'
|
||||||
import { param, query } from 'express-validator'
|
import { param } from 'express-validator'
|
||||||
import { validateErrors } from '../common'
|
import { validateErrors } from '../common'
|
||||||
import { allData } from '../process/process'
|
import db, { getHostList } from '../db/db'
|
||||||
import { getHostList } from '../db/db'
|
import { sql } from 'kysely'
|
||||||
|
|
||||||
|
const { max, sum } = db.fn
|
||||||
const router = Router()
|
const router = Router()
|
||||||
//timeout middleware ?
|
// timeout middleware ?
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
function computeQueryParam(
|
const queryFilters = {
|
||||||
queryParam: string,
|
player: 'attacker_id',
|
||||||
comparison: string | number
|
server: 'servername',
|
||||||
): boolean {
|
map: 'map',
|
||||||
return queryParam.startsWith('!')
|
weapon: 'cause_of_death',
|
||||||
? queryParam.substring(1) != comparison
|
gamemode: 'game_mode',
|
||||||
: queryParam == comparison
|
host: 'host'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const path = {
|
||||||
|
player: 'attacker_id',
|
||||||
|
servers: 'servername',
|
||||||
|
maps: 'map',
|
||||||
|
weapons: 'cause_of_death',
|
||||||
|
gamemodes: 'game_mode',
|
||||||
|
hosts: 'host'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
interface KillRecord {
|
||||||
|
kills: number
|
||||||
|
deaths?: number
|
||||||
|
deaths_while_equipped?: number
|
||||||
|
username?: string
|
||||||
|
max_distance: number
|
||||||
|
total_distance: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function filters(e: typeof allData[0], query: any) {
|
router.get('/hosts', (_req, res) => {
|
||||||
return (
|
void (async () => {
|
||||||
(query.player ? computeQueryParam(query.player, e.attacker_id) : true) &&
|
const result = await getHostList()
|
||||||
(query.server ? computeQueryParam(query.server, e.servername) : true) &&
|
const data: Record<number, string> = {}
|
||||||
(query.host ? computeQueryParam(query.host, e.host) : true) &&
|
result.forEach((e) => (data[Number(e.id)] = e.name))
|
||||||
(query.map ? computeQueryParam(query.map, e.map) : true) &&
|
res.status(200).send(data)
|
||||||
(query.weapon ? computeQueryParam(query.weapon, e.cause_of_death) : true) &&
|
})()
|
||||||
(query.gamemode ? computeQueryParam(query.gamemode, e.game_mode) : true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
function processQueryArgs (data: ReturnType<typeof db.selectFrom<'kill_view'>>, queryArgs: string | string[]): ReturnType<typeof db.selectFrom<'kill_view'>> {
|
||||||
'/:dataType',
|
Object.entries(queryArgs).forEach(([one, two]) => {
|
||||||
|
if (!two) return
|
||||||
|
if (!(one in queryFilters)) return
|
||||||
|
const key = one as keyof typeof queryFilters
|
||||||
|
if (Array.isArray(two)) {
|
||||||
|
data = data.where((qb) => {
|
||||||
|
(two as string[]).forEach((e) => { qb = qb.orWhere(queryFilters[key], '=', e) })
|
||||||
|
return qb
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
data = data.where(queryFilters[key], '=', two)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/players',
|
||||||
|
(req, res) => {
|
||||||
|
void (async () => {
|
||||||
|
let data = db.selectFrom('kill_view')
|
||||||
|
if (req.query) {
|
||||||
|
data = processQueryArgs(data, req.query as unknown as string | string[])
|
||||||
|
}
|
||||||
|
const selection = data.select([sum<number>('kills').as('kills'), sum<number>('deaths').as('deaths'), sum<number>('deaths_with_weapon').as('deaths_while_equipped'), 'attacker_id', sql<string>`last(attacker_name)`.as('username'), sum<number>('total_distance').as('total_distance'), max('max_distance').as('max_distance')]).groupBy('attacker_id')
|
||||||
|
const test = (await selection.execute()).reduce<Record<string, KillRecord>>((acc, curr) => {
|
||||||
|
acc[curr.attacker_id] = {
|
||||||
|
kills: Number(curr.kills),
|
||||||
|
deaths: Number(curr.deaths),
|
||||||
|
max_distance: Number(curr.max_distance),
|
||||||
|
total_distance: Number(curr.total_distance),
|
||||||
|
deaths_while_equipped: Number(curr.deaths_while_equipped),
|
||||||
|
username: curr.username
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
res.send(test)
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/:dataType',
|
||||||
param('dataType')
|
param('dataType')
|
||||||
.custom(
|
.custom((e) => e in path)
|
||||||
(e) =>
|
.withMessage('Only weapons, players, maps, gamemodes or servers are valid paths'),
|
||||||
e == 'weapons' ||
|
|
||||||
e == 'players' ||
|
|
||||||
e == 'maps' ||
|
|
||||||
e == 'servers' ||
|
|
||||||
e == 'gamemodes'
|
|
||||||
)
|
|
||||||
.withMessage('Only weapons, players, maps or servers are valid paths'),
|
|
||||||
query(['server', 'map', 'weapon', 'gamemode', 'player', 'host'])
|
|
||||||
.optional()
|
|
||||||
.isString(),
|
|
||||||
validateErrors,
|
validateErrors,
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
const data: {
|
void (async () => {
|
||||||
[key: string]: {
|
let data = db.selectFrom('kill_view')
|
||||||
deaths: number
|
if (req.query) {
|
||||||
kills: number
|
data = processQueryArgs(data, req.query as unknown as string | string[])
|
||||||
max_distance: number
|
|
||||||
total_distance: number
|
|
||||||
username?: string
|
|
||||||
host?: number
|
|
||||||
deaths_while_equipped?: number
|
|
||||||
}
|
}
|
||||||
} = {}
|
const dataType = req.params.dataType as keyof typeof path
|
||||||
let index:
|
|
||||||
| 'cause_of_death'
|
const selection = data.select([sum<number>('kills').as('kills'), sum<number>('deaths').as('deaths'), sum<number>('deaths_with_weapon').as('deaths_while_equipped'), path[dataType], sum<number>('total_distance').as('total_distance'), max('max_distance').as('max_distance')]).groupBy(path[dataType])
|
||||||
| 'attacker_id'
|
const test = (await selection.execute()).reduce<Record<string, KillRecord>>((acc, curr) => {
|
||||||
| 'map'
|
acc[curr[path[dataType]]] = {
|
||||||
| 'servername'
|
kills: Number(curr.kills),
|
||||||
| 'game_mode'
|
deaths: Number(curr.deaths),
|
||||||
switch (req.params.dataType) {
|
max_distance: Number(curr.max_distance),
|
||||||
case 'weapons':
|
total_distance: Number(curr.total_distance),
|
||||||
index = 'cause_of_death'
|
deaths_while_equipped: Number(curr.deaths_while_equipped)
|
||||||
break
|
}
|
||||||
case 'players':
|
return acc
|
||||||
index = 'attacker_id'
|
}, {})
|
||||||
break
|
res.send(test)
|
||||||
case 'maps':
|
})()
|
||||||
index = 'map'
|
})
|
||||||
break
|
|
||||||
case 'servers':
|
|
||||||
index = 'servername'
|
|
||||||
break
|
|
||||||
case 'gamemodes':
|
|
||||||
index = 'game_mode'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return res.status(400).send()
|
|
||||||
}
|
|
||||||
const timeStart = new Date()
|
|
||||||
allData
|
|
||||||
.filter((e) => filters(e, req.query))
|
|
||||||
.forEach((e) => {
|
|
||||||
if (
|
|
||||||
!e.cause_of_death ||
|
|
||||||
!e.attacker_id ||
|
|
||||||
!e.map ||
|
|
||||||
!e.servername ||
|
|
||||||
!e.game_mode
|
|
||||||
)
|
|
||||||
return
|
|
||||||
const requestIndex = e[index]
|
|
||||||
if (!requestIndex) return
|
|
||||||
if (!data[requestIndex])
|
|
||||||
data[requestIndex] = {
|
|
||||||
deaths: 0,
|
|
||||||
kills: 0,
|
|
||||||
max_distance: 0,
|
|
||||||
total_distance: 0
|
|
||||||
}
|
|
||||||
if (index === 'attacker_id')
|
|
||||||
data[requestIndex].username = e.attacker_name
|
|
||||||
if (index === 'servername') data[requestIndex].host = e.host
|
|
||||||
if (
|
|
||||||
index === 'cause_of_death' ||
|
|
||||||
(req.query.weapon &&
|
|
||||||
!req.query.weapon.toString().startsWith('!') &&
|
|
||||||
index === 'attacker_id')
|
|
||||||
)
|
|
||||||
data[requestIndex].deaths_while_equipped =
|
|
||||||
Number(e.deaths_with_weapon) +
|
|
||||||
(data[requestIndex].deaths_while_equipped || 0)
|
|
||||||
data[requestIndex].deaths += Number(e.deaths)
|
|
||||||
data[requestIndex].kills += Number(e.kills)
|
|
||||||
data[requestIndex].total_distance += Number(e.total_distance)
|
|
||||||
data[requestIndex].max_distance = Math.max(
|
|
||||||
data[requestIndex].max_distance,
|
|
||||||
Number(e.max_distance)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
console.log(
|
|
||||||
new Date().toLocaleString() + ',' +
|
|
||||||
(req.headers['x-forwarded-for']?.toString() ||
|
|
||||||
req.socket.remoteAddress?.toString() ||
|
|
||||||
'') +
|
|
||||||
',' +
|
|
||||||
Math.abs(new Date().getTime() - timeStart.getTime()) / 1000 + ',' + req.originalUrl
|
|
||||||
)
|
|
||||||
const dataString = JSON.stringify(data)
|
|
||||||
const buffer = Buffer.from(dataString)
|
|
||||||
const size = buffer.length
|
|
||||||
res.status(200).setHeader('X-File-Size', size).setHeader('Content-Type', 'application/json').send(buffer)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
+13
-17
@@ -1,36 +1,33 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import cluster from 'cluster'
|
import cluster from 'cluster'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
dotenv.config()
|
|
||||||
import express from 'express'
|
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 processAll from './process/process'
|
dotenv.config()
|
||||||
import listenKills from './process/onKill'
|
|
||||||
|
|
||||||
const cCPUs = os.cpus().length;
|
const cCPUs = os.cpus().length
|
||||||
|
|
||||||
const port = 3000
|
const port = 3000
|
||||||
|
|
||||||
if (cluster.isPrimary) {
|
if (cluster.isPrimary && process.env.ENVIRONMENT !== 'dev') {
|
||||||
// Create a worker for each CPU
|
// Create a worker for each CPU
|
||||||
for (let i = 0; i < cCPUs; i++) {
|
for (let i = 0; i < cCPUs; i++) {
|
||||||
cluster.fork();
|
cluster.fork()
|
||||||
}
|
}
|
||||||
cluster.on('online', function (worker) {
|
cluster.on('online', function (worker) {
|
||||||
console.log('Worker ' + worker.process.pid + ' is online.');
|
console.log(`Worker ${worker.process.pid ?? ''} is online`)
|
||||||
});
|
})
|
||||||
cluster.on('exit', function (worker, code, signal) {
|
cluster.on('exit', function (worker, code, signal) {
|
||||||
console.log('worker ' + worker.process.pid + ' died.');
|
console.log(`Worker ${worker.process.pid ?? ''} died`)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default
|
export default
|
||||||
new Promise(async (resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
if (!cluster.isPrimary) {
|
void (async () => {
|
||||||
|
if (!cluster.isPrimary || process.env.ENVIRONMENT === 'dev') {
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
app.use(cors({ exposedHeaders: ['X-File-Size', 'Content-Encoding'] }))
|
app.use(cors({ exposedHeaders: ['X-File-Size', 'Content-Encoding'] }))
|
||||||
@@ -41,11 +38,10 @@ export default
|
|||||||
app.use('/', client)
|
app.use('/', client)
|
||||||
|
|
||||||
await dbReady()
|
await dbReady()
|
||||||
await processAll()
|
|
||||||
await listenKills()
|
|
||||||
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})()
|
||||||
|
})
|
||||||
|
|||||||
+5
-5
@@ -1,11 +1,11 @@
|
|||||||
import { RequestHandler } from 'express'
|
import { type RequestHandler } from 'express'
|
||||||
import { Result, validationResult } from 'express-validator'
|
import { validationResult } from 'express-validator'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import https from 'https'
|
import https from 'https'
|
||||||
|
|
||||||
export function GetRequest(url: string) {
|
export async function GetRequest (url: string) {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return await new Promise<string>((resolve, reject) => {
|
||||||
let handler = (resp: http.IncomingMessage) => {
|
const handler = (resp: http.IncomingMessage) => {
|
||||||
let data = ''
|
let data = ''
|
||||||
|
|
||||||
// A chunk of data has been received.
|
// A chunk of data has been received.
|
||||||
|
|||||||
+14
-13
@@ -1,9 +1,10 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
dotenv.config()
|
import { Kysely, PostgresDialect } from 'kysely'
|
||||||
import { InsertObject, Kysely, PostgresDialect } from 'kysely'
|
|
||||||
import { Pool } from 'pg'
|
import { Pool } from 'pg'
|
||||||
import migrateToLatest from './migrations'
|
import migrateToLatest from './migrations'
|
||||||
import Database, { KillTable } from './model'
|
import { type KillTable } from './model'
|
||||||
|
import type Database from './model'
|
||||||
|
dotenv.config()
|
||||||
const migration = migrateToLatest()
|
const migration = migrateToLatest()
|
||||||
const db = new Kysely<Database>({
|
const db = new Kysely<Database>({
|
||||||
dialect: new PostgresDialect({
|
dialect: new PostgresDialect({
|
||||||
@@ -15,8 +16,8 @@ const db = new Kysely<Database>({
|
|||||||
max: 30
|
max: 30
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
log(event) {
|
log (event) {
|
||||||
if (process.env.ENVIRONMENT != 'production') {
|
if (process.env.ENVIRONMENT !== 'dev') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.level === 'query') {
|
if (event.level === 'query') {
|
||||||
@@ -33,19 +34,19 @@ 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) {
|
||||||
await db
|
await db
|
||||||
.insertInto('kill')
|
.insertInto('kill')
|
||||||
.values({ ...data })
|
.values({ ...data })
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
export async function getHostList() {
|
export async function getHostList () {
|
||||||
return await db.selectFrom("host").select(['id', 'host.name']).execute()
|
return await db.selectFrom('host').select(['id', 'host.name']).execute()
|
||||||
}
|
}
|
||||||
//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(token: string) {
|
export async function CheckServerToken (token: string) {
|
||||||
return db.selectFrom('host').select('id')
|
return await db.selectFrom('host').select('id')
|
||||||
.where('token', '=', Buffer.from(token, 'base64').toString())
|
.where('token', '=', Buffer.from(token, 'base64').toString())
|
||||||
.executeTakeFirst()
|
.executeTakeFirst()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
@@ -53,7 +54,7 @@ export function CheckServerToken(token: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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,126 @@
|
|||||||
|
import { type Kysely } 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 kill_view;')
|
||||||
|
console.log('creating table kill_view')
|
||||||
|
await pgClient.query(`CREATE TABLE kill_view
|
||||||
|
AS (
|
||||||
|
with "kills" as (select count("id") as "kills", coalesce(max("distance"), 0) as "max_distance", coalesce(sum("distance"), 0) as "total_distance", "attacker_id", last(attacker_name) as "attacker_name", "cause_of_death", "map", "game_mode", "servername", "host" from "kill" where "attacker_id" != "victim_id" group by "attacker_id", "cause_of_death", "map", "servername", "host", "game_mode"), "deaths" as (select count("id") as "deaths", "victim_id", last(victim_name) as "victim_name", "cause_of_death", "map", "game_mode", "servername", "host" from "kill" group by "victim_id", "cause_of_death", "map", "servername", "host", "game_mode"), "deathswithweapon" as (select count("id") as "deaths", "victim_id", "victim_current_weapon", "map", "game_mode", "servername", "host" from "kill" group by "victim_id", "victim_current_weapon", "map", "servername", "host", "game_mode") select COALESCE(kills.attacker_name, deaths.victim_name) as "attacker_name", COALESCE(kills.attacker_id, deaths.victim_id) as "attacker_id", COALESCE(kills.kills, 0) as "kills", COALESCE(kills.total_distance, 0) as "total_distance", COALESCE(deathswithweapon.deaths, 0) as "deaths_with_weapon", COALESCE(kills.max_distance, 0) as "max_distance", COALESCE(deaths.deaths, 0) as "deaths", COALESCE(kills.servername, deaths.servername) as "servername", COALESCE(kills.host, deaths.host) as "host", COALESCE(kills.cause_of_death, deaths.cause_of_death) as "cause_of_death", COALESCE(kills.map, deaths.map) as "map", COALESCE(kills.game_mode, deaths.game_mode) as "game_mode" from "kills" full join "deaths" on "deaths"."victim_id" = "kills"."attacker_id" and "deaths"."cause_of_death" = "kills"."cause_of_death" and "deaths"."servername" = "kills"."servername" and "deaths"."host" = "kills"."host" and "deaths"."game_mode" = "kills"."game_mode" and "deaths"."map" = "kills"."map" left join "deathswithweapon" on "deathswithweapon"."victim_id" = "kills"."attacker_id" and "deathswithweapon"."victim_current_weapon" = "kills"."cause_of_death" and "deathswithweapon"."servername" = "kills"."servername" and "deathswithweapon"."host" = "kills"."host" and "deathswithweapon"."game_mode" = "kills"."game_mode" and "deathswithweapon"."map" = "kills"."map"
|
||||||
|
);`)
|
||||||
|
console.log('creating index on attacker_id')
|
||||||
|
await pgClient.query('CREATE INDEX ON kill_view USING HASH ("attacker_id");')
|
||||||
|
console.log('creating index on map')
|
||||||
|
await pgClient.query('CREATE INDEX ON kill_view USING HASH ("map");')
|
||||||
|
console.log('creating index on game_mode')
|
||||||
|
await pgClient.query('CREATE INDEX ON kill_view USING HASH ("game_mode");')
|
||||||
|
console.log('creating index on cause_of_death')
|
||||||
|
await pgClient.query('CREATE INDEX ON kill_view USING HASH ("cause_of_death");')
|
||||||
|
console.log('creating index on server')
|
||||||
|
await pgClient.query('CREATE INDEX ON kill_view ("servername", "host");')
|
||||||
|
console.log('creating function update_kill_view_fnct')
|
||||||
|
await pgClient.query(`CREATE OR REPLACE FUNCTION update_kill_view_fnct()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS(
|
||||||
|
SELECT * FROM kill_view
|
||||||
|
WHERE
|
||||||
|
new.attacker_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.cause_of_death = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host
|
||||||
|
) THEN
|
||||||
|
UPDATE kill_view SET kills = kills + 1,
|
||||||
|
max_distance = (SELECT Max(v) FROM (VALUES (new.distance), (max_distance)) AS value(v)),
|
||||||
|
total_distance = total_distance + new.distance,
|
||||||
|
attacker_name = new.attacker_name
|
||||||
|
WHERE
|
||||||
|
new.attacker_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.cause_of_death = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host;
|
||||||
|
ELSE
|
||||||
|
INSERT INTO kill_view(kills,deaths, deaths_with_weapon, attacker_id, map, game_mode, cause_of_death, servername, host, max_distance, total_distance)
|
||||||
|
VALUES(1,0,0, new.attacker_id, new.map, new.game_mode, new.cause_of_death, new.servername, new.host,new.distance, new.distance);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
IF EXISTS(
|
||||||
|
SELECT * FROM kill_view
|
||||||
|
WHERE
|
||||||
|
new.victim_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.cause_of_death = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host
|
||||||
|
) THEN
|
||||||
|
UPDATE kill_view SET deaths = deaths + 1
|
||||||
|
WHERE
|
||||||
|
new.victim_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.cause_of_death = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host;
|
||||||
|
ELSE
|
||||||
|
INSERT INTO kill_view(deaths, kills, deaths_with_weapon, attacker_id, map, game_mode, cause_of_death, servername, host, max_distance, total_distance)
|
||||||
|
VALUES(1,0,0, new.victim_id, new.map, new.game_mode, new.cause_of_death, new.servername, new.host,0,0);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
IF EXISTS(
|
||||||
|
SELECT * FROM kill_view
|
||||||
|
WHERE
|
||||||
|
new.victim_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.attacker_current_weapon = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host
|
||||||
|
) THEN
|
||||||
|
UPDATE kill_view SET deaths_with_weapon = deaths_with_weapon + 1
|
||||||
|
WHERE
|
||||||
|
new.victim_id = attacker_id AND
|
||||||
|
new.map = map AND
|
||||||
|
new.game_mode = game_mode AND
|
||||||
|
new.attacker_current_weapon = cause_of_death AND
|
||||||
|
new.servername = servername AND
|
||||||
|
new.host = host;
|
||||||
|
ELSE
|
||||||
|
INSERT INTO kill_view(deaths, kills, deaths_with_weapon, attacker_id, map, game_mode, cause_of_death, servername, host, max_distance, total_distance)
|
||||||
|
VALUES(0,0,1, new.victim_id, new.map, new.game_mode, new.attacker_current_weapon, new.servername, new.host,0,0);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE PLPGSQL;`)
|
||||||
|
console.log('creating trigger update_kill_view')
|
||||||
|
await pgClient.query(`CREATE OR REPLACE TRIGGER update_kill_view
|
||||||
|
AFTER INSERT ON kill
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_kill_view_fnct();`)
|
||||||
|
await pgClient.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down (db: Kysely<any>): Promise<void> {
|
||||||
|
await pgClient.connect()
|
||||||
|
await pgClient.query('DROP TRIGGER IF EXISTS update_kill_view;')
|
||||||
|
await pgClient.query('DROP FUNCTION IF EXISTS update_kill_view_fnct')
|
||||||
|
await pgClient.query('DROP TABLE IF EXISTS kill_view;')
|
||||||
|
await pgClient.end()
|
||||||
|
}
|
||||||
+16
-23
@@ -1,4 +1,4 @@
|
|||||||
import { ColumnType, Generated } from 'kysely'
|
import { type Generated } from 'kysely'
|
||||||
|
|
||||||
export interface KillTable {
|
export interface KillTable {
|
||||||
id: Generated<number>
|
id: Generated<number>
|
||||||
@@ -43,37 +43,30 @@ export interface KillTable {
|
|||||||
distance: number
|
distance: number
|
||||||
titan?: string
|
titan?: string
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
interface PlayerTable {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
'opt-out': boolean
|
|
||||||
hide_TOS: boolean
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface WeaponTable {
|
export interface KillViewTable {
|
||||||
id: string
|
kills: number
|
||||||
name: string
|
deaths: number
|
||||||
description: string
|
deaths_with_weapon: number
|
||||||
image: string
|
attacker_id: number
|
||||||
}
|
attacker_name: string
|
||||||
interface MapTable {
|
map: string
|
||||||
id: string
|
game_mode: string
|
||||||
name: string
|
cause_of_death: string
|
||||||
description: string
|
servername: string
|
||||||
image: string
|
host: number
|
||||||
|
max_distance: number
|
||||||
|
total_distance: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HosterTable {
|
interface HosterTable {
|
||||||
id: Generated<number>
|
id: Generated<number>
|
||||||
name: string
|
name: string
|
||||||
token: Generated<string>
|
token: Generated<string>
|
||||||
}
|
}
|
||||||
interface Database {
|
interface Database {
|
||||||
|
kill_view: KillViewTable
|
||||||
kill: KillTable
|
kill: KillTable
|
||||||
//player: PlayerTable
|
|
||||||
weapon: WeaponTable
|
|
||||||
maps: MapTable
|
|
||||||
host: HosterTable
|
host: HosterTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+88
-86
@@ -1,111 +1,113 @@
|
|||||||
import { Client } from 'pg'
|
import { Client } from 'pg'
|
||||||
import { allData } from './process'
|
// import { allData } from './process'
|
||||||
|
|
||||||
export const pgClient = new Client({
|
export const pgClient = new Client({
|
||||||
host: process.env.POSTGRES_HOST,
|
host: process.env.POSTGRES_HOST,
|
||||||
database: process.env.POSTGRES_DATABASE,
|
database: process.env.POSTGRES_DATABASE,
|
||||||
user: process.env.POSTGRES_USER,
|
user: process.env.POSTGRES_USER,
|
||||||
password: process.env.POSTGRES_PASSWORD
|
password: process.env.POSTGRES_PASSWORD
|
||||||
})
|
})
|
||||||
|
|
||||||
export default async function listenKills() {
|
export default async function listenKills () {
|
||||||
await pgClient.connect()
|
await pgClient.connect()
|
||||||
await pgClient.query('LISTEN new_kill')
|
await pgClient.query('LISTEN new_kill')
|
||||||
|
|
||||||
pgClient.on('notification', (data) => {
|
/* pgClient.on('notification', (data) => {
|
||||||
if (!data.payload) return
|
if (!data.payload) return
|
||||||
const payload = JSON.parse(data.payload)
|
const payload = JSON.parse(data.payload)
|
||||||
let killEntry = allData.find(
|
|
||||||
(e) =>
|
let killEntry = allData.find(
|
||||||
e.attacker_id == payload.attacker_id &&
|
(e) =>
|
||||||
|
e.attacker_id == payload.attacker_id &&
|
||||||
e.cause_of_death == payload.cause_of_death &&
|
e.cause_of_death == payload.cause_of_death &&
|
||||||
e.game_mode == payload.game_mode &&
|
e.game_mode == payload.game_mode &&
|
||||||
e.host == payload.host &&
|
e.host == payload.host &&
|
||||||
e.map == payload.map &&
|
e.map == payload.map &&
|
||||||
e.servername == payload.servername
|
e.servername == payload.servername
|
||||||
)
|
)
|
||||||
let deathEntry = allData.find(
|
let deathEntry = allData.find(
|
||||||
(e) =>
|
(e) =>
|
||||||
e.attacker_id == payload.victim_id &&
|
e.attacker_id == payload.victim_id &&
|
||||||
e.cause_of_death == payload.cause_of_death &&
|
e.cause_of_death == payload.cause_of_death &&
|
||||||
e.game_mode == payload.game_mode &&
|
e.game_mode == payload.game_mode &&
|
||||||
e.host == payload.host &&
|
e.host == payload.host &&
|
||||||
e.map == payload.map &&
|
e.map == payload.map &&
|
||||||
e.servername == payload.servername
|
e.servername == payload.servername
|
||||||
)
|
)
|
||||||
let deathWithWeaponEntry = allData.find(
|
let deathWithWeaponEntry = allData.find(
|
||||||
(e) =>
|
(e) =>
|
||||||
e.attacker_id == payload.victim_id &&
|
e.attacker_id == payload.victim_id &&
|
||||||
e.cause_of_death == payload.victim_current_weapon &&
|
e.cause_of_death == payload.victim_current_weapon &&
|
||||||
e.game_mode == payload.game_mode &&
|
e.game_mode == payload.game_mode &&
|
||||||
e.host == payload.host &&
|
e.host == payload.host &&
|
||||||
e.map == payload.map &&
|
e.map == payload.map &&
|
||||||
e.servername == payload.servername
|
e.servername == payload.servername
|
||||||
)
|
)
|
||||||
if (!killEntry) {
|
if (!killEntry) {
|
||||||
killEntry = {
|
killEntry = {
|
||||||
kills: 0,
|
kills: 0,
|
||||||
deaths: 0,
|
deaths: 0,
|
||||||
max_distance: 0,
|
max_distance: 0,
|
||||||
total_distance: 0,
|
total_distance: 0,
|
||||||
deaths_with_weapon: 0,
|
deaths_with_weapon: 0,
|
||||||
attacker_name: payload.attacker_name,
|
attacker_name: payload.attacker_name,
|
||||||
attacker_id: payload.attacker_id,
|
attacker_id: payload.attacker_id,
|
||||||
cause_of_death: payload.cause_of_death,
|
cause_of_death: payload.cause_of_death,
|
||||||
game_mode: payload.game_mode,
|
game_mode: payload.game_mode,
|
||||||
host: payload.host,
|
host: payload.host,
|
||||||
map: payload.map,
|
map: payload.map,
|
||||||
servername: payload.servername
|
servername: payload.servername
|
||||||
}
|
}
|
||||||
allData.push(killEntry)
|
allData.push(killEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deathEntry) {
|
if (!deathEntry) {
|
||||||
deathEntry = {
|
deathEntry = {
|
||||||
kills: 0,
|
kills: 0,
|
||||||
deaths: 0,
|
deaths: 0,
|
||||||
max_distance: 0,
|
max_distance: 0,
|
||||||
total_distance: 0,
|
total_distance: 0,
|
||||||
deaths_with_weapon: 0,
|
deaths_with_weapon: 0,
|
||||||
attacker_name: payload.victim_name,
|
attacker_name: payload.victim_name,
|
||||||
attacker_id: payload.victim_id,
|
attacker_id: payload.victim_id,
|
||||||
cause_of_death: payload.cause_of_death,
|
cause_of_death: payload.cause_of_death,
|
||||||
game_mode: payload.game_mode,
|
game_mode: payload.game_mode,
|
||||||
host: payload.host,
|
host: payload.host,
|
||||||
map: payload.map,
|
map: payload.map,
|
||||||
servername: payload.servername
|
servername: payload.servername
|
||||||
}
|
}
|
||||||
allData.push(deathEntry)
|
allData.push(deathEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deathWithWeaponEntry) {
|
if (!deathWithWeaponEntry) {
|
||||||
deathWithWeaponEntry = {
|
deathWithWeaponEntry = {
|
||||||
kills: 0,
|
kills: 0,
|
||||||
deaths: 0,
|
deaths: 0,
|
||||||
max_distance: 0,
|
max_distance: 0,
|
||||||
total_distance: 0,
|
total_distance: 0,
|
||||||
deaths_with_weapon: 0,
|
deaths_with_weapon: 0,
|
||||||
attacker_name: payload.victim_name,
|
attacker_name: payload.victim_name,
|
||||||
attacker_id: payload.victim_id,
|
attacker_id: payload.victim_id,
|
||||||
cause_of_death: payload.victim_current_weapon,
|
cause_of_death: payload.victim_current_weapon,
|
||||||
game_mode: payload.game_mode,
|
game_mode: payload.game_mode,
|
||||||
host: payload.host,
|
host: payload.host,
|
||||||
map: payload.map,
|
map: payload.map,
|
||||||
servername: payload.servername
|
servername: payload.servername
|
||||||
}
|
}
|
||||||
allData.push(deathWithWeaponEntry)
|
allData.push(deathWithWeaponEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.victim_id != payload.attacker_id) {
|
if (payload.victim_id != payload.attacker_id) {
|
||||||
killEntry.kills++
|
killEntry.kills++
|
||||||
killEntry.total_distance = Number(killEntry.total_distance) + Number(payload.distance)
|
killEntry.total_distance = Number(killEntry.total_distance) + Number(payload.distance)
|
||||||
killEntry.max_distance = Math.max(killEntry.max_distance || 0, payload.distance)
|
killEntry.max_distance = Math.max(killEntry.max_distance || 0, payload.distance)
|
||||||
}
|
}
|
||||||
deathEntry.deaths++
|
deathEntry.deaths++
|
||||||
deathEntry.attacker_name = payload.victim_name
|
deathEntry.attacker_name = payload.victim_name
|
||||||
deathWithWeaponEntry.deaths_with_weapon++
|
deathWithWeaponEntry.deaths_with_weapon++
|
||||||
deathWithWeaponEntry.attacker_name = payload.victim_name
|
deathWithWeaponEntry.attacker_name = payload.victim_name
|
||||||
killEntry.attacker_name = payload.attacker_name
|
killEntry.attacker_name = payload.attacker_name
|
||||||
})
|
|
||||||
return pgClient
|
}) */
|
||||||
|
return pgClient
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-14
@@ -1,6 +1,6 @@
|
|||||||
import db from '../db/db'
|
import db from '../db/db'
|
||||||
const { count, max, sum, coalesce } = db.fn
|
|
||||||
import { sql } from 'kysely'
|
import { sql } from 'kysely'
|
||||||
|
const { count, max, sum, coalesce } = db.fn
|
||||||
|
|
||||||
export let allData: Awaited<ReturnType<typeof processGlobalStats>>
|
export let allData: Awaited<ReturnType<typeof processGlobalStats>>
|
||||||
|
|
||||||
@@ -8,23 +8,16 @@ export let allData: Awaited<ReturnType<typeof processGlobalStats>>
|
|||||||
* populates allData
|
* populates allData
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function processAll() {
|
async function processAll (): Promise<void> {
|
||||||
console.log('Starting data calculation...')
|
console.log('Starting data calculation...')
|
||||||
const timeStart = new Date()
|
const timeStart = new Date()
|
||||||
|
|
||||||
allData = await processGlobalStats()
|
// allData = await processGlobalStats()
|
||||||
|
|
||||||
console.log(
|
console.log(`Data calculation finished. Took + ${Math.abs(new Date().getTime() - timeStart.getTime()) / 1000} seconds`)
|
||||||
'Data calculation finished. Took + ' +
|
|
||||||
Math.abs(new Date().getTime() - timeStart.getTime()) / 1000 +
|
|
||||||
' seconds'
|
|
||||||
)
|
|
||||||
if (process.env.ENVIRONMENT == 'production') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processGlobalStats() {
|
async function processGlobalStats () {
|
||||||
return await db
|
return await db
|
||||||
.with('kills', (db) =>
|
.with('kills', (db) =>
|
||||||
db
|
db
|
||||||
@@ -117,7 +110,7 @@ async function processGlobalStats() {
|
|||||||
.onRef('deathswithweapon.game_mode', '=', 'kills.game_mode')
|
.onRef('deathswithweapon.game_mode', '=', 'kills.game_mode')
|
||||||
.onRef('deathswithweapon.map', '=', 'kills.map')
|
.onRef('deathswithweapon.map', '=', 'kills.map')
|
||||||
)
|
)
|
||||||
//.selectAll('kills')
|
// .selectAll('kills')
|
||||||
.select([
|
.select([
|
||||||
sql<string>`COALESCE(kills.attacker_name, deaths.victim_name)`.as('attacker_name'),
|
sql<string>`COALESCE(kills.attacker_name, deaths.victim_name)`.as('attacker_name'),
|
||||||
sql<string>`COALESCE(kills.attacker_id, deaths.victim_id)`.as('attacker_id'),
|
sql<string>`COALESCE(kills.attacker_id, deaths.victim_id)`.as('attacker_id'),
|
||||||
@@ -130,7 +123,7 @@ async function processGlobalStats() {
|
|||||||
sql<number>`COALESCE(kills.host, deaths.host)`.as('host'),
|
sql<number>`COALESCE(kills.host, deaths.host)`.as('host'),
|
||||||
sql<string>`COALESCE(kills.cause_of_death, deaths.cause_of_death)`.as('cause_of_death'),
|
sql<string>`COALESCE(kills.cause_of_death, deaths.cause_of_death)`.as('cause_of_death'),
|
||||||
sql<string>`COALESCE(kills.map, deaths.map)`.as('map'),
|
sql<string>`COALESCE(kills.map, deaths.map)`.as('map'),
|
||||||
sql<string>`COALESCE(kills.game_mode, deaths.game_mode)`.as('game_mode'),
|
sql<string>`COALESCE(kills.game_mode, deaths.game_mode)`.as('game_mode')
|
||||||
])
|
])
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user