diff --git a/src/templates/match.ts b/src/templates/match.ts index 7e2b36c..a2910bc 100644 --- a/src/templates/match.ts +++ b/src/templates/match.ts @@ -1,103 +1,164 @@ -import { Router, Response } from "express"; -import { param } from "express-validator"; -import { validateErrors } from "../common"; -import { MatchData, validateBody, RequestBody } from "../types"; -import db from "../db"; -import typia from "typia"; +/* eslint-disable @typescript-eslint/naming-convention */ +import { Router, type Response } from 'express' +import { param } from 'express-validator' +import { validateErrors } from '../common' +import { type MatchData, type MatchCloseData, validateBody, type RequestBody } from '../types' +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( - "/match", - validateBody(typia.createValidate()), - (req: RequestBody, 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(); - } - + '/', + validateBody(typia.createValidate()), + (req: RequestBody, 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 - .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 }); - })(); - } - ); + .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() - 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()), - (req: RequestBody, 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(); + 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()), + (req: RequestBody, 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> = [] + 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 } - - 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") + const playerData = req.body[playerId] + console.log(req.body) + + const promises: Array> = [] + 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 + }) + }) + ) + } + + playerPromises.push(Promise.all(promises).then(async e => { + await db.insertInto('ToneAPI_v3.weapon_stats_in_match') + .values(weaponStats) + .execute() + })) + + playerPromises.push(Promise.all(promises).then(async e => { + await db.insertInto('ToneAPI_v3.titan_stats_in_match') + .values(titanStats) + .execute() + })) + + playerPromises.push(db.insertInto('ToneAPI_v3.player_stats_in_match') .values({ - air_accel, - server_name, - game_map, - gamemode, - host_id, - ongoing: true, + distance_air: playerData.stats.distance.air, + distance_ground: playerData.stats.distance.ground, + distance_wall: playerData.stats.distance.wall, + time_air: playerData.stats.time.air, + time_ground: playerData.stats.time.ground, + time_wall: playerData.stats.time.wall, + match_id, + player_id: NumPlayerId }) - .returning("ToneAPI_v3.match.match_id") - .executeTakeFirstOrThrow(); - - res.status(201).send({ match: match?.match_id }); - })(); - } - ); + .execute()) + } + await Promise.all(playerPromises) + res.sendStatus(201) + })() + } +) - - -export default router; \ No newline at end of file +export default router diff --git a/src/templates/server.ts b/src/templates/server.ts index 09271cd..f0e7794 100644 --- a/src/templates/server.ts +++ b/src/templates/server.ts @@ -1,63 +1,70 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { Router } from "express"; -import { header } from "express-validator"; +import { Router } from 'express' +import { header } from 'express-validator' -import { /* createKillRecord, */ checkServerToken } from "../db"; -import { validateErrors } from "../common"; +import { /* createKillRecord, */ checkServerToken } from '../db' +import { validateErrors } from '../common' import match from './match' 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 router.post( - "/*", - header("authorization") + '/*', + header('authorization') .exists({ checkFalsy: true }) - .withMessage("Missing Authorization Header") + .withMessage('Missing Authorization Header') .bail() - .custom((e) => e.split(" ")[0].toLowerCase() === "bearer") - .withMessage("Authorization Token is not Bearer"), + .custom((e) => e.split(' ')[0].toLowerCase() === 'bearer') + .withMessage('Authorization Token is not Bearer'), validateErrors, (req, res, next) => { void (async () => { if (!req.headers.authorization) { - return res.sendStatus(401); + return res.sendStatus(401) } const query = await checkServerToken( - req.headers.authorization.split(" ")[1] - ); + req.headers.authorization.split(' ')[1] + ) if (!query?.host_id) { console.error( - `incorrect token : ${ - req.headers.authorization.split(" ")[1] - } with IP ${ - req.headers["x-forwarded-for"]?.toString() ?? - req.socket.remoteAddress?.toString() ?? - "" + `incorrect token : ${req.headers.authorization.split(' ')[1] + } with IP ${req.headers['x-forwarded-for']?.toString() ?? + req.socket.remoteAddress?.toString() ?? + '' }` - ); + ) return res.status(401).send({ errors: [ { - msg: "Incorrect Token", - param: "authorization", - location: "headers", - }, - ], - }); + msg: 'Incorrect Token', + param: 'authorization', + location: 'headers' + } + ] + }) } - res.locals.host_id = query.host_id; - next(); - })(); + res.locals.host_id = query.host_id + next() + })() } -); +) // Route to check auth -router.post("/", (req, res) => { - res.sendStatus(200); -}); +router.post('/', (req, res) => { + res.sendStatus(200) +}) // const serversCount: Record = {} // const serversTimeout: Record = {} @@ -79,7 +86,7 @@ router.post("/", (req, res) => { next() }) */ - -router.use("/kill", kill) -router.use("/match", match) -export default router; +router.use('/kill', kill) +router.use('/match', match) +router.use('/player', player) +export default router diff --git a/src/types.ts b/src/types.ts index 5cdd11f..bb20390 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,32 @@ export interface KillData { cause_of_death: string } +export type MatchCloseData = Record +export interface MatchClosePlayerData { + weapons: Record + titans: Record + 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 = Request export const validateBody = @@ -52,6 +78,7 @@ export const validateBody = const result: typia.IValidation = checker(req.body) if (!result.success) { res.status(400).send({ errors: result.errors }) + console.log(req.body) console.error(result.errors) } else { next()