handle match close

This commit is contained in:
2023-09-07 00:29:56 +02:00
parent 2128ab4980
commit b47b18968a
3 changed files with 226 additions and 131 deletions
+155 -94
View File
@@ -1,103 +1,164 @@
import { Router, Response } from "express"; /* eslint-disable @typescript-eslint/naming-convention */
import { param } from "express-validator"; import { Router, type Response } from 'express'
import { validateErrors } from "../common"; import { param } from 'express-validator'
import { MatchData, validateBody, RequestBody } from "../types"; import { validateErrors } from '../common'
import db from "../db"; import { type MatchData, type MatchCloseData, validateBody, type RequestBody } from '../types'
import typia from "typia"; import db from '../db'
import typia from 'typia'
import { checkOrCreateTitan, checkOrCreateWeapon } from '../utils'
import { type TitanStatsInMatchTable, type WeaponStatsInMatchTable } from '../db/model'
const router = Router(); const router = Router()
router.post( router.post(
"/match", '/',
validateBody(typia.createValidate<MatchData>()), validateBody(typia.createValidate<MatchData>()),
(req: RequestBody<MatchData>, res: Response) => { (req: RequestBody<MatchData>, res: Response) => {
void (async () => { void (async () => {
const host_id = res.locals.host_id; const host_id = res.locals.host_id
const { air_accel, server_name, game_map, gamemode } = req.body; const { air_accel, server_name, game_map, gamemode } = req.body
const server = await db const server = await db
.selectFrom("ToneAPI_v3.server") .selectFrom('ToneAPI_v3.server')
.selectAll() .selectAll()
.where("ToneAPI_v3.server.host_id", "=", host_id) .where('ToneAPI_v3.server.host_id', '=', host_id)
.where("ToneAPI_v3.server.server_name", "=", server_name) .where('ToneAPI_v3.server.server_name', '=', server_name)
.executeTakeFirst(); .executeTakeFirst()
if (!server) { if (!server) {
await db await db
.insertInto("ToneAPI_v3.server") .insertInto('ToneAPI_v3.server')
.values({ host_id, server_name }) .values({ host_id, server_name })
.execute(); .execute()
}
await db
.updateTable('ToneAPI_v3.match')
.set({ ongoing: false })
.where('ToneAPI_v3.match.server_name', '=', server_name)
.execute()
const match = await db
.insertInto('ToneAPI_v3.match')
.values({
air_accel,
server_name,
game_map,
gamemode,
host_id,
ongoing: true
})
.returning('ToneAPI_v3.match.match_id')
.executeTakeFirstOrThrow()
res.status(201).send({ match: match?.match_id })
})()
}
)
router.post(
'/:match_id/close',
param('match_id').exists().withMessage('Missing Match ID').bail().isNumeric().withMessage('Match ID is not numeric'),
validateErrors,
validateBody(typia.createValidate<MatchCloseData>()),
(req: RequestBody<MatchCloseData>, res: Response) => {
void (async () => {
const host_id = res.locals.host_id
const match_id = req.params.match_id
const match = await db
.selectFrom('ToneAPI_v3.match')
.selectAll()
.where('ToneAPI_v3.match.host_id', '=', host_id)
.where('ToneAPI_v3.match.match_id', '=', match_id)
.executeTakeFirst()
if (!match) {
res.status(403).send({
errors: [
{
msg: 'Match does not exists for this host',
param: 'match_id',
location: 'param'
}
]
})
return
}
const playerPromises: Array<Promise<any>> = []
for (const playerId in req.body) {
const NumPlayerId = Number(playerId)
if (isNaN(NumPlayerId)) {
res
.status(400)
.send({ errors: [{ msg: 'playerId is NaN', location: 'body' }] })
return
}
const playerData = req.body[playerId]
console.log(req.body)
const promises: Array<Promise<any>> = []
const weaponStats: WeaponStatsInMatchTable[] = []
const titanStats: TitanStatsInMatchTable[] = []
for (const weaponId in playerData.weapons) {
promises.push(
checkOrCreateWeapon(weaponId)
.then(async () => {
weaponStats.push({
match_id: match.match_id,
weapon_id: weaponId,
player_id: NumPlayerId,
headshots: playerData.weapons[weaponId].shotsHeadshot,
playtime: playerData.weapons[weaponId].playtime,
ricochets: playerData.weapons[weaponId].shotsRichochet,
shots_fired: playerData.weapons[weaponId].shotsFired,
shots_hit: playerData.weapons[weaponId].shotsHit
})
})
)
}
for (const titanId in playerData.titans) {
promises.push(
checkOrCreateTitan(titanId).then(async () => {
titanStats.push({
match_id: match.match_id,
titan_id: titanId,
player_id: NumPlayerId,
headshots: playerData.titans[titanId].shotsHeadshot,
playtime: playerData.titans[titanId].playtime,
ricochets: playerData.titans[titanId].shotsRichochet,
shots_fired: playerData.titans[titanId].shotsFired,
shots_hit: playerData.titans[titanId].shotsHit
})
})
)
} }
await db playerPromises.push(Promise.all(promises).then(async e => {
.updateTable("ToneAPI_v3.match") await db.insertInto('ToneAPI_v3.weapon_stats_in_match')
.set({ ongoing: false }) .values(weaponStats)
.where("ToneAPI_v3.match.server_name", "=", server_name) .execute()
.execute(); }))
const match = await db playerPromises.push(Promise.all(promises).then(async e => {
.insertInto("ToneAPI_v3.match") await db.insertInto('ToneAPI_v3.titan_stats_in_match')
.values(titanStats)
.execute()
}))
playerPromises.push(db.insertInto('ToneAPI_v3.player_stats_in_match')
.values({ .values({
air_accel, distance_air: playerData.stats.distance.air,
server_name, distance_ground: playerData.stats.distance.ground,
game_map, distance_wall: playerData.stats.distance.wall,
gamemode, time_air: playerData.stats.time.air,
host_id, time_ground: playerData.stats.time.ground,
ongoing: true, time_wall: playerData.stats.time.wall,
match_id,
player_id: NumPlayerId
}) })
.returning("ToneAPI_v3.match.match_id") .execute())
.executeTakeFirstOrThrow(); }
await Promise.all(playerPromises)
res.sendStatus(201)
})()
}
)
res.status(201).send({ match: match?.match_id }); export default router
})();
}
);
router.post(
"/match/:matchId(\d+)/close",
param('matchId').exists().withMessage("Missing Match ID").bail().isNumeric().withMessage("Match ID is not numeric"),
validateErrors,
validateBody(typia.createValidate<MatchData>()),
(req: RequestBody<MatchData>, res: Response) => {
void (async () => {
const host_id = res.locals.host_id;
const { air_accel, server_name, game_map, gamemode } = req.body;
const server = await db
.selectFrom("ToneAPI_v3.server")
.selectAll()
.where("ToneAPI_v3.server.host_id", "=", host_id)
.where("ToneAPI_v3.server.server_name", "=", server_name)
.executeTakeFirst();
if (!server) {
await db
.insertInto("ToneAPI_v3.server")
.values({ host_id, server_name })
.execute();
}
await db
.updateTable("ToneAPI_v3.match")
.set({ ongoing: false })
.where("ToneAPI_v3.match.server_name", "=", server_name)
.execute();
const match = await db
.insertInto("ToneAPI_v3.match")
.values({
air_accel,
server_name,
game_map,
gamemode,
host_id,
ongoing: true,
})
.returning("ToneAPI_v3.match.match_id")
.executeTakeFirstOrThrow();
res.status(201).send({ match: match?.match_id });
})();
}
);
export default router;
+45 -38
View File
@@ -1,63 +1,70 @@
/* eslint-disable @typescript-eslint/naming-convention */ import { Router } from 'express'
import { Router } from "express"; import { header } from 'express-validator'
import { header } from "express-validator";
import { /* createKillRecord, */ checkServerToken } from "../db"; import { /* createKillRecord, */ checkServerToken } from '../db'
import { validateErrors } from "../common"; import { validateErrors } from '../common'
import match from './match' import match from './match'
import kill from './kill' import kill from './kill'
import player from './player'
const router = Router(); const router = Router()
router.use('/*', (req, res, next) => {
try {
next()
} catch (error) {
res.status(500).send({ errors: [{ msg: 'Internal Error! You\'d better report this' }] })
console.error(error)
}
})
// auth middleware // auth middleware
router.post( router.post(
"/*", '/*',
header("authorization") header('authorization')
.exists({ checkFalsy: true }) .exists({ checkFalsy: true })
.withMessage("Missing Authorization Header") .withMessage('Missing Authorization Header')
.bail() .bail()
.custom((e) => e.split(" ")[0].toLowerCase() === "bearer") .custom((e) => e.split(' ')[0].toLowerCase() === 'bearer')
.withMessage("Authorization Token is not Bearer"), .withMessage('Authorization Token is not Bearer'),
validateErrors, validateErrors,
(req, res, next) => { (req, res, next) => {
void (async () => { void (async () => {
if (!req.headers.authorization) { if (!req.headers.authorization) {
return res.sendStatus(401); return res.sendStatus(401)
} }
const query = await checkServerToken( const query = await checkServerToken(
req.headers.authorization.split(" ")[1] req.headers.authorization.split(' ')[1]
); )
if (!query?.host_id) { if (!query?.host_id) {
console.error( console.error(
`incorrect token : ${ `incorrect token : ${req.headers.authorization.split(' ')[1]
req.headers.authorization.split(" ")[1] } with IP ${req.headers['x-forwarded-for']?.toString() ??
} with IP ${ req.socket.remoteAddress?.toString() ??
req.headers["x-forwarded-for"]?.toString() ?? ''
req.socket.remoteAddress?.toString() ??
""
}` }`
); )
return res.status(401).send({ return res.status(401).send({
errors: [ errors: [
{ {
msg: "Incorrect Token", msg: 'Incorrect Token',
param: "authorization", param: 'authorization',
location: "headers", location: 'headers'
}, }
], ]
}); })
} }
res.locals.host_id = query.host_id; res.locals.host_id = query.host_id
next(); next()
})(); })()
} }
); )
// Route to check auth // Route to check auth
router.post("/", (req, res) => { router.post('/', (req, res) => {
res.sendStatus(200); res.sendStatus(200)
}); })
// const serversCount: Record<string, number> = {} // const serversCount: Record<string, number> = {}
// const serversTimeout: Record<string, NodeJS.Timeout> = {} // const serversTimeout: Record<string, NodeJS.Timeout> = {}
@@ -79,7 +86,7 @@ router.post("/", (req, res) => {
next() next()
}) */ }) */
router.use('/kill', kill)
router.use("/kill", kill) router.use('/match', match)
router.use("/match", match) router.use('/player', player)
export default router; export default router
+27
View File
@@ -44,6 +44,32 @@ export interface KillData {
cause_of_death: string cause_of_death: string
} }
export type MatchCloseData = Record<string, MatchClosePlayerData>
export interface MatchClosePlayerData {
weapons: Record<string, MatchCloseWeaponData>
titans: Record<string, MatchCloseWeaponData>
stats: {
distance: {
ground: number
wall: number
air: number
}
time: {
ground: number
wall: number
air: number
}
}
}
export interface MatchCloseWeaponData {
shotsFired: number
shotsHit: number
shotsCrit: number
shotsHeadshot: number
shotsRichochet: number
playtime: number
}
export type RequestBody<T> = Request<any, any, T> export type RequestBody<T> = Request<any, any, T>
export const validateBody = export const validateBody =
@@ -52,6 +78,7 @@ export const validateBody =
const result: typia.IValidation<T> = checker(req.body) const result: typia.IValidation<T> = checker(req.body)
if (!result.success) { if (!result.success) {
res.status(400).send({ errors: result.errors }) res.status(400).send({ errors: result.errors })
console.log(req.body)
console.error(result.errors) console.error(result.errors)
} else { } else {
next() next()