From fff92cd5c8e796c5d638fb6833c7ec134537ee23 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Mon, 26 Jun 2023 15:51:57 -0600 Subject: [PATCH] V2.0.0.1 (#253) * Update packages, update queries in API. Tons of bug fixes and hopefully resolves client headers not being sent. * Update api.ts Include a debug log to check the body of requests being sent in. * Update case statement due to missing breaks. De-duplicate match check codes and move them up to the api. Adjust onplayerdeath to take care of any issues with sql pains. * Include default case to throw 400. * Find a better way for concurrent match restores. Update to implement round_end. Update demos to properly upload to folder. Formatting. Update map stats on round end as well. * Remove unused import in backupapi Fixed demoapi to rename the demo to zip as we are zipping the file, just not renaming the extension. Update veto picks in maplflow service to choose the correct maps from the in-game veto pool. Move `updatePlayerStats` to the Utils class as it's used in seriesflow and mapflow services. Update seriesflowservice to update the stats a final time to ensure data correctness at the end of a map. * Update map flow to catch an edge case where player stats do not exist yet. --- .../development/20230625215254-get5db.js | 29 ++ .../production/20230625215254-get5db.js | 29 ++ migrations/test/20230625215254-get5db.js | 29 ++ package.json | 8 +- src/routes/v2/api.ts | 116 ++++--- src/routes/v2/backupapi.ts | 1 - src/routes/v2/demoapi.ts | 16 +- src/routes/vetoes.js | 1 - src/services/mapflowservices.ts | 303 ++++++------------ src/services/seriesflowservices.ts | 226 +++++-------- src/utility/utils.ts | 70 ++++ yarn.lock | 47 +-- 12 files changed, 442 insertions(+), 433 deletions(-) create mode 100644 migrations/development/20230625215254-get5db.js create mode 100644 migrations/production/20230625215254-get5db.js create mode 100644 migrations/test/20230625215254-get5db.js diff --git a/migrations/development/20230625215254-get5db.js b/migrations/development/20230625215254-get5db.js new file mode 100644 index 0000000..9a55045 --- /dev/null +++ b/migrations/development/20230625215254-get5db.js @@ -0,0 +1,29 @@ +"use strict"; + +var dbm; +var type; +var seed; +var async = require("async"); + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; +}; + +exports.up = function (db, callback) { + return db.runSql( + "ALTER TABLE `map_stats` ADD COLUMN round_restored tinyint(1) DEFAULT 0 AFTER end_time;" + ); +}; + +exports.down = function (db, callback) { + return db.removeColumn("map_stats", "round_restored"); +}; +exports._meta = { + version: 24 +}; diff --git a/migrations/production/20230625215254-get5db.js b/migrations/production/20230625215254-get5db.js new file mode 100644 index 0000000..9a55045 --- /dev/null +++ b/migrations/production/20230625215254-get5db.js @@ -0,0 +1,29 @@ +"use strict"; + +var dbm; +var type; +var seed; +var async = require("async"); + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; +}; + +exports.up = function (db, callback) { + return db.runSql( + "ALTER TABLE `map_stats` ADD COLUMN round_restored tinyint(1) DEFAULT 0 AFTER end_time;" + ); +}; + +exports.down = function (db, callback) { + return db.removeColumn("map_stats", "round_restored"); +}; +exports._meta = { + version: 24 +}; diff --git a/migrations/test/20230625215254-get5db.js b/migrations/test/20230625215254-get5db.js new file mode 100644 index 0000000..9a55045 --- /dev/null +++ b/migrations/test/20230625215254-get5db.js @@ -0,0 +1,29 @@ +"use strict"; + +var dbm; +var type; +var seed; +var async = require("async"); + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; +}; + +exports.up = function (db, callback) { + return db.runSql( + "ALTER TABLE `map_stats` ADD COLUMN round_restored tinyint(1) DEFAULT 0 AFTER end_time;" + ); +}; + +exports.down = function (db, callback) { + return db.removeColumn("map_stats", "round_restored"); +}; +exports._meta = { + version: 24 +}; diff --git a/package.json b/package.json index 652744b..a7f44bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "g5api", - "version": "2.0.0.0", + "version": "2.0.0.1", "private": true, "type": "module", "licenses": [ @@ -89,7 +89,7 @@ "passport": "^0.6.0", "passport-local": "^1.0.0", "passport-steam": "^1.0.17", - "pm2": "^5.2.0", + "pm2": "^5.3.0", "randomstring": "^1.2.2", "rcon": "^1.1.0", "redis": "^4.3.0", @@ -103,10 +103,10 @@ "@types/express": "^4.17.17", "@types/node": "^20.2.5", "@types/steamapi": "^2.2.2", - "jest": "^29.1.0", + "jest": "^29.5.0", "jest-ts-webcompat-resolver": "^1.0.0", "jsdoc": "^4.0.2", - "nodemon": "^2.0.15", + "nodemon": "^2.0.22", "passport-mock-strategy": "^2.0.0", "redis-mock": "^0.56.3", "supertest": "^6.3.3", diff --git a/src/routes/v2/api.ts b/src/routes/v2/api.ts index 76a39d0..3c3ea63 100644 --- a/src/routes/v2/api.ts +++ b/src/routes/v2/api.ts @@ -37,6 +37,8 @@ import { Get5_OnMatchPausedUnpaused } from "../../types/map_flow/Get5_OnMatchPau import { Get5_OnPlayerDeath } from "../../types/map_flow/Get5_OnPlayerDeath.js"; import { Get5_OnBombEvent } from "../../types/map_flow/Get5_OnBombEvent.js"; import { Get5_OnRoundStart } from "../../types/map_flow/Get5_OnRoundStart.js"; +import Utils from "../../utility/utils.js"; +import { Get5_OnRoundEnd } from "../../types/map_flow/Get5_OnRoundEnd.js"; /** Basic Rate limiter. * @const @@ -83,14 +85,13 @@ const basicRateLimit = rateLimit({ * $ref: '#/components/responses/Error' */ router.post("/", basicRateLimit, async (req, res) => { - let matchId: string = req.body?.matchId; + let matchId: string = req.body?.matchid; const apiKey: string | undefined = req.get("Authorization"); const eventType: Get5_OnEvent = req.body; try { if (!apiKey) { - res.status(401).send({ message: "API key not provided." }); - return; + return res.status(401).send({ message: "API key not provided." }); } if (!matchId) { @@ -106,81 +107,96 @@ router.post("/", basicRateLimit, async (req, res) => { matchId = dbMatchKey[0].id; } + const matchApiCheck: number = await Utils.checkApiKey(apiKey, matchId); + if (eventType.event == "demo_upload_ended") { + // Ignore demo_upload_ended event. + return res.status(200).send({message: "Success"}); + } else if (eventType.event == "series_end") { + // let forfeit: number = event. + // Match is finalized, this is usually called after a cancel so we just ignore the value with a 200 response. + if (matchApiCheck == 2) { + return res.status(200).send({ + message: + "Match already finalized or and invalid API key has been given." + }); + } else if (matchApiCheck == 1) { + console.error( + "Match already finalized or and invalid API key has been given." + ); + return res.status(401).send({ + message: + "Match already finalized or and invalid API key has been given." + }); + } + } else { + if (matchApiCheck == 2 || matchApiCheck == 1) { + console.error( + "Match already finalized or and invalid API key has been given." + ); + return res.status(401).send({ + message: + "Match already finalized or and invalid API key has been given." + }); + } + } + switch (eventType.event) { // Series Flows case "map_picked": - SeriesFlowService.OnMapPicked( - apiKey, - req.body as Get5_OnMapPicked, - res - ); + SeriesFlowService.OnMapPicked(req.body as Get5_OnMapPicked, res); + break; case "map_vetoed": - SeriesFlowService.OnMapVetoed( - apiKey, - req.body as Get5_OnMapVetoed, - res - ); + SeriesFlowService.OnMapVetoed(req.body as Get5_OnMapVetoed, res); + break; case "side_picked": - SeriesFlowService.OnSidePicked( - apiKey, - req.body as Get5_OnSidePicked, - res - ); + SeriesFlowService.OnSidePicked(req.body as Get5_OnSidePicked, res); + break; case "backup_loaded": SeriesFlowService.OnBackupRestore( - apiKey, req.body as Get5_OnBackupRestore, res ); + break; case "map_result": - SeriesFlowService.OnMapResult( - apiKey, - req.body as Get5_OnMapResult, - res - ); + SeriesFlowService.OnMapResult(req.body as Get5_OnMapResult, res); + break; case "series_end": - SeriesFlowService.OnSeriesResult( - apiKey, - req.body as Get5_OnSeriesResult, - res - ); + SeriesFlowService.OnSeriesResult(req.body as Get5_OnSeriesResult, res); + break; // Map Flows case "going_live": - MapFlowService.OnGoingLive(apiKey, req.body as Get5_OnGoingLive, res); + MapFlowService.OnGoingLive(req.body as Get5_OnGoingLive, res); + break; case "round_start": - MapFlowService.OnRoundStart(apiKey, req.body as Get5_OnRoundStart, res); + MapFlowService.OnRoundStart(req.body as Get5_OnRoundStart, res); + break; + case "round_end": + MapFlowService.OnRoundEnd(req.body as Get5_OnRoundEnd, res); + break; case "player_death": - MapFlowService.OnPlayerDeath( - apiKey, - req.body as Get5_OnPlayerDeath, - res - ); + MapFlowService.OnPlayerDeath(req.body as Get5_OnPlayerDeath, res); + break; case "bomb_planted": - MapFlowService.OnBombEvent( - apiKey, - req.body as Get5_OnBombEvent, - res, - false - ); + MapFlowService.OnBombEvent(req.body as Get5_OnBombEvent, res, false); + break; case "bomb_defused": - MapFlowService.OnBombEvent( - apiKey, - req.body as Get5_OnBombEvent, - res, - true - ); + MapFlowService.OnBombEvent(req.body as Get5_OnBombEvent, res, true); + break; case "game_paused": MapFlowService.OnMatchPausedUnPaused( - apiKey, req.body as Get5_OnMatchPausedUnpaused, res ); + break; case "game_unpaused": MapFlowService.OnMatchPausedUnPaused( - apiKey, req.body as Get5_OnMatchPausedUnpaused, res ); + break; + default: + res.status(400).send({message: `Event ${eventType.event} is not implemented.`}); + break; } // Responses are taken care of in the case statements. return; diff --git a/src/routes/v2/backupapi.ts b/src/routes/v2/backupapi.ts index 162cdb4..bbdf65b 100644 --- a/src/routes/v2/backupapi.ts +++ b/src/routes/v2/backupapi.ts @@ -25,7 +25,6 @@ import { RowDataPacket } from "mysql2"; * @const * Global Server Sent Emitter class for real time data. */ -import GlobalEmitter from "../../utility/emitter.js"; import { existsSync, mkdirSync, writeFile } from "fs"; /** Express module diff --git a/src/routes/v2/demoapi.ts b/src/routes/v2/demoapi.ts index 8d04857..af9c5a7 100644 --- a/src/routes/v2/demoapi.ts +++ b/src/routes/v2/demoapi.ts @@ -104,18 +104,16 @@ router.post("/", async (req: Request, res: Response) => { const demoFilename: string | undefined = req.get("Get5-FileName"); // Check that the values have made it across. if (!apiKey || !matchId || !mapNumber || !demoFilename) { - res + return res .status(401) .send({ message: "API key, Match ID, or Map Number not provided." }); - return; } // Check if our API key is correct. const matchApiCheck: number = await Utils.checkApiKey(apiKey, matchId); if (matchApiCheck == 1) { - res.status(401).send({ + return res.status(401).send({ message: "Invalid API key has been given." }); - return; } // Begin file compression into public/demos and check time variance of 8 minutes. let zip: JSZip = new JSZip(); @@ -126,8 +124,7 @@ router.post("/", async (req: Request, res: Response) => { mapNumber ]); if (mapInfo.length == 0) { - res.status(404).send({ message: "Failed to find map stats object." }); - return; + return res.status(404).send({ message: "Failed to find map stats object." }); } let currentDate: Date = new Date(); let endTimeMs: Date = new Date(mapInfo[0].end_time); @@ -137,15 +134,14 @@ router.post("/", async (req: Request, res: Response) => { let minuteDifference = Math.floor(timeDifference / 1000 / 60); let updateStmt: object; if (minuteDifference > 8) { - res.status(401).json({ message: "Demo can no longer be uploaded." }); - return; + return res.status(401).json({ message: "Demo can no longer be uploaded." }); } zip.file(demoFilename, req.body, { binary: true }); zip .generateAsync({ type: "nodebuffer", compression: "DEFLATE" }) .then((buf) => { - writeFile("public/demos" + demoFilename, buf, "binary", function (err) { + writeFile("public/demos/" + demoFilename.replace(".dem", ".zip"), buf, "binary", function (err) { if (err) { console.error(err); throw err; @@ -159,7 +155,7 @@ router.post("/", async (req: Request, res: Response) => { updateStmt = await db.buildUpdateStatement(updateStmt); sqlString = "UPDATE map_stats SET ? WHERE id = ?"; - await db.query(sqlString, [mapInfo[0].id]); + await db.query(sqlString, [updateStmt, mapInfo[0].id]); GlobalEmitter.emit("demoUpdate"); res.status(200).send({message: "Success"}); return; diff --git a/src/routes/vetoes.js b/src/routes/vetoes.js index 37f7b6d..052316c 100644 --- a/src/routes/vetoes.js +++ b/src/routes/vetoes.js @@ -188,7 +188,6 @@ router.get("/:match_id/stream", async (req, res, next) => { res.write(vetoEventString); req.on("close", () => { - console.log("\n\nCLOSED\n\n"); GlobalEmitter.removeListener("vetoUpdate", vetoStreamData); res.end(); }); diff --git a/src/services/mapflowservices.ts b/src/services/mapflowservices.ts index 915026a..db0c379 100644 --- a/src/services/mapflowservices.ts +++ b/src/services/mapflowservices.ts @@ -23,9 +23,8 @@ import { Get5_OnMatchPausedUnpaused } from "../types/map_flow/Get5_OnMatchPaused import { Get5_OnPlayerDeath } from "../types/map_flow/Get5_OnPlayerDeath.js"; import { Get5_OnBombEvent } from "../types/map_flow/Get5_OnBombEvent.js"; import { Get5_OnRoundEnd } from "../types/map_flow/Get5_OnRoundEnd.js"; -import SeriesFlowService from "./seriesflowservices.js"; import { Get5_OnRoundStart } from "../types/map_flow/Get5_OnRoundStart.js"; -import { Get5_Player } from "../types/Get5_Player.js"; +import update_challonge_match from "./challonge.js"; /** * @class @@ -34,20 +33,14 @@ import { Get5_Player } from "../types/Get5_Player.js"; class MapFlowService { /** * Updates the database and emits mapStatUpdate when the map has gone live. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnGoingLive} event The OnGoingLive event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnGoingLive( - apiKey: string, event: Get5_OnGoingLive, res: Response ) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let sqlString: string; let mapStatInfo: RowDataPacket[]; let vetoInfo: RowDataPacket[]; @@ -58,17 +51,11 @@ class MapFlowService { let insUpdStatement: object; let mapName: string; let matchInfo: RowDataPacket[]; - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } - sqlString = "SELECT map FROM veto WHERE match_id = ? ORDER BY id"; + + sqlString = "SELECT map FROM veto WHERE match_id = ? AND pick_or_veto = 'pick' ORDER BY id"; vetoInfo = await db.query(sqlString, [event.matchid]); if (vetoInfo.length) { - mapName = vetoInfo[event.map_number].map; + mapName = vetoInfo[event.map_number]?.map; } else { sqlString = "SELECT veto_mappool FROM `match` WHERE id = ?"; matchInfo = await db.query(sqlString, [event.matchid]); @@ -88,7 +75,7 @@ class MapFlowService { sqlString = "UPDATE map_stats SET ? WHERE match_id = ? AND map_number = ?"; insUpdStatement = await db.buildUpdateStatement(insUpdStatement); - await db.query(sqlString, insUpdStatement); + await db.query(sqlString, [insUpdStatement, event.matchid, event.map_number]); } else { insUpdStatement = { match_id: event.matchid, @@ -101,41 +88,27 @@ class MapFlowService { sqlString = "INSERT INTO map_stats SET ?"; await db.query(sqlString, insUpdStatement); GlobalEmitter.emit("mapStatUpdate"); - res.status(200).send({ message: "Success" }); - return; + return res.status(200).send({ message: "Success" }); } - } catch (error) { + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } /** * Updates the database and emits playerStatsUpdate when a player has died. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnPlayerDeath} event The Get5_OnPlayerDeath event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnPlayerDeath( - apiKey: string, event: Get5_OnPlayerDeath, res: Response ) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } - // We do not care about bot deaths for live stats. - if (event.player.is_bot) { + if (event.player?.is_bot) { res .status(200) .send({ message: "Bot players do not count towards stats." }); @@ -153,7 +126,7 @@ class MapFlowService { sqlString = "SELECT team_id FROM team_auth_names JOIN `match` m " + "ON (m.team1_id = team_id OR m.team2_id = team_id) WHERE m.id = ? AND auth = ?"; - playerTeamId = await db.query(sqlString, [event.player.steamid]); + playerTeamId = await db.query(sqlString, [event.matchid, event.player.steamid]); insertObj = { match_id: event.matchid, map_id: mapInfo[0].id, @@ -174,11 +147,11 @@ class MapFlowService { no_scope: event.no_scope, suicide: event.suicide, friendly_fire: event.friendly_fire, - assister_steam_id: event.assist.player.steamid, - assister_name: event.assist.player.name, - assister_side: event.assist.player.side, - assist_friendly_fire: event.assist.friendly_fire, - flash_assist: event.assist.flash_assist + assister_steam_id: event.assist?.player.steamid, + assister_name: event.assist?.player.name, + assister_side: event.assist?.player.side, + assist_friendly_fire: event.assist?.friendly_fire, + flash_assist: event.assist?.flash_assist }; insertObj = await db.buildUpdateStatement(insertObj); sqlString = "INSERT INTO player_stat_extras SET ?"; @@ -250,13 +223,15 @@ class MapFlowService { sqlString = "SELECT team_id FROM team_auth_names JOIN `match` m " + "ON (m.team1_id = team_id OR m.team2_id = team_id) WHERE m.id = ? AND auth = ?"; - playerStatVals = await db.query(sqlString, [event.attacker.steamid]); - sqlString = "INESRT INTO player_stats SET ?"; + playerStatVals = await db.query(sqlString, [event.matchid, event.attacker.steamid]); + sqlString = "INSERT INTO player_stats SET ?"; + insertObj = { - match_id: event.matchid, + match_id: +event.matchid, map_id: mapInfo[0].id, team_id: playerStatVals[0].team_id, steam_id: event.attacker.steamid, + name: event.attacker.name, kills: 1, headshot_kills: event.headshot ? 1 : null, teamkills: event.friendly_fire ? 1 : null, @@ -300,7 +275,7 @@ class MapFlowService { playerStatVals = await db.query(sqlString, [ event.assist.player.steamid ]); - sqlString = "INESRT INTO player_stats SET ?"; + sqlString = "INSERT INTO player_stats SET ?"; insertObj = { match_id: event.matchid, map_id: mapInfo[0].id, @@ -314,44 +289,32 @@ class MapFlowService { } } GlobalEmitter.emit("playerStatsUpdate"); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); + } } /** * Updates the database and emits bombEvent when a bomb has been planted or defused. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnBombEvent} event The Get5_OnBombEvent event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnBombEvent( - apiKey: string, event: Get5_OnBombEvent, res: Response, defused: boolean ) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } let sqlString: string; let mapInfo: RowDataPacket[]; let playerStatInfo: RowDataPacket[]; let insObject: object; - if (event.player.is_bot) { + if (event.player?.is_bot) { res .status(200) .send({ message: "Bot players do not count towards stats." }); @@ -367,11 +330,27 @@ class MapFlowService { mapInfo[0].id, event.player.steamid ]); + // If player does not have player stats yet, insert them. + if (!playerStatInfo.length && !event.player?.is_bot) { + let teamId: RowDataPacket[]; + sqlString = + "SELECT t.id FROM team t JOIN team_auth_names ta ON ta.team_id = t.id WHERE ta.auth = ?"; + teamId = await db.query(sqlString, [event.player.steamid]); + await Utils.updatePlayerStats(event.matchid, teamId[0].id, mapInfo[0].id, event.player, null); + // Grab player info again! + sqlString = + "SELECT id FROM player_stats WHERE match_id = ? AND map_id = ? AND steam_id = ?"; + playerStatInfo = await db.query(sqlString, [ + event.matchid, + mapInfo[0].id, + event.player.steamid + ]); + } insObject = { match_id: event.matchid, map_id: mapInfo[0].id, - player_stat_id: playerStatInfo[0].id, + player_stats_id: playerStatInfo[0].id, round_number: event.round_number, round_time: event.round_time, site: event.site, @@ -383,44 +362,33 @@ class MapFlowService { sqlString = "INSERT INTO match_bomb_plants SET ?"; await db.query(sqlString, insObject); GlobalEmitter.emit("bombEvent"); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } /** * Updates the database and emits playerStatsUpdate when a round has ended. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnRoundEnd} event The Get5_OnRoundEnd event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnRoundEnd( - apiKey: string, event: Get5_OnRoundEnd, res: Response ) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let sqlString: string = "SELECT id FROM map_stats WHERE match_id = ? AND map_number = ?"; let insUpdStatement: object; let mapStatInfo: RowDataPacket[]; + let matchSeasonInfo: RowDataPacket[]; let playerStats: RowDataPacket[]; let singlePlayerStat: RowDataPacket[]; - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } + mapStatInfo = await db.query(sqlString, [ event.matchid, event.map_number @@ -429,137 +397,82 @@ class MapFlowService { "SELECT * FROM player_stats WHERE match_id = ? AND map_id = ?"; playerStats = await db.query(sqlString, [ event.matchid, - mapStatInfo[0].id + mapStatInfo[0]?.id ]); for (let player of event.team1.players) { singlePlayerStat = playerStats.filter( (dbPlayer) => dbPlayer.steam_id == player.steamid ); - await this.updatePlayerStats( - event, + await Utils.updatePlayerStats( + event.matchid, + event.team1.id, mapStatInfo[0].id, player, - singlePlayerStat[0].id + singlePlayerStat[0]?.id ); } for (let player of event.team2.players) { singlePlayerStat = playerStats.filter( (dbPlayer) => dbPlayer.steam_id == player.steamid ); - await this.updatePlayerStats( - event, + await Utils.updatePlayerStats( + event.matchid, + event.team2.id, mapStatInfo[0].id, player, - singlePlayerStat[0].id + singlePlayerStat[0]?.id ); } GlobalEmitter.emit("playerStatsUpdate"); - res.status(200).send({ message: "Success" }); - } catch (error) { + + // Update map stats. Grab season info + sqlString = "UPDATE map_stats SET ? WHERE id = ?"; + insUpdStatement = { + team1_score: event.team1.score, + team2_score: event.team2.score + } + await db.query(sqlString, [insUpdStatement, mapStatInfo[0].id]); + // Update Challonge info if needed. + sqlString = "SELECT max_maps, season_id FROM `match` WHERE id = ?"; + matchSeasonInfo = await db.query(sqlString, [event.matchid]); + if (matchSeasonInfo[0]?.season_id && matchSeasonInfo[0].max_maps == 1) { + await update_challonge_match( + event.matchid, + matchSeasonInfo[0].season_id, + +event.team1.id, + +event.team2.id, + matchSeasonInfo[0].max_maps + ); + } + GlobalEmitter.emit("mapStatUpdate"); + + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - /** - * Private helper function to update player stats based on a team. - * @param {Get5_OnRoundEnd} event The Get5_OnRoundEnd event. - * @param {number} mapId The map ID from the database. - * @param {Get5_Player} player The Get5_Player structure. - * @param {number} playerId The player ID from the database. - */ - private static async updatePlayerStats( - event: Get5_OnRoundEnd, - mapId: number, - player: Get5_Player, - playerId: number | null - ) { - let insUpdStatement: object; - let sqlString: string; - insUpdStatement = { - match_id: event.matchid, - map_id: mapId, - team_id: event.team1.id, - steam_id: player.steamid, - name: player.name, - kills: player.stats?.kills, - deaths: player.stats?.deaths, - roundsplayed: player.stats?.rounds_played, - assists: player.stats?.assists, - flashbang_assists: player.stats?.flash_assists, - teamkills: player.stats?.team_kills, - knife_kills: player.stats?.knife_kills, - suicides: player.stats?.suicides, - headshot_kills: player.stats?.headshot_kills, - damage: player.stats?.damage, - util_damage: player.stats?.utility_damage, - enemies_flashed: player.stats?.enemies_flashed, - friendlies_flashed: player.stats?.friendlies_flashed, - bomb_plants: player.stats?.bomb_plants, - bomb_defuses: player.stats?.bomb_defuses, - v1: player.stats?.["1v1"], - v2: player.stats?.["1v2"], - v3: player.stats?.["1v3"], - v4: player.stats?.["1v4"], - v5: player.stats?.["1v5"], - k1: player.stats?.["1k"], - k2: player.stats?.["2k"], - k3: player.stats?.["3k"], - k4: player.stats?.["4k"], - k5: player.stats?.["5k"], - firstdeath_ct: player.stats?.first_deaths_ct, - firstdeath_t: player.stats?.first_deaths_t, - firstkill_ct: player.stats?.first_kills_ct, - firstkill_t: player.stats?.first_kills_t, - kast: player.stats?.kast, - contribution_score: player.stats?.score, - mvp: player.stats?.mvp - }; - - insUpdStatement = await db.buildUpdateStatement(insUpdStatement); - - if (playerId) { - sqlString = "UPDATE player_stats SET ? WHERE id = ?"; - await db.query(sqlString, [insUpdStatement, playerId]); - } else { - sqlString = "INSERT INTO player_stats SET ?"; - await db.query(sqlString, insUpdStatement); - } - } + /** * Updates the database and emits playerStatsUpdate when a round has been restored and the match has started again. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnRoundStart} event The Get5_OnRoundStart event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnRoundStart( - apiKey: string, event: Get5_OnRoundStart, res: Response ) { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let sqlString: string; let mapStatInfo: RowDataPacket[]; - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - } - // Check if round was backed up and nuke the additional player stats and bomb plants. - if (SeriesFlowService.wasRoundRestored) { - sqlString = - "SELECT id FROM map_stats WHERE match_id = ? AND map_number = ?"; - mapStatInfo = await db.query(sqlString, [ - event.matchid, - event.map_number - ]); + // Check if round was backed up and nuke the additional player stats and bomb plants. + sqlString = "SELECT round_restored, id FROM map_stats WHERE match_id = ? AND map_number = ?"; + mapStatInfo = await db.query(sqlString, [event.matchid, event.map_number]); + if (mapStatInfo[0]?.round_restored) { sqlString = "DELETE FROM match_bomb_plants WHERE round_number > ? AND match_id = ? AND map_id = ?"; await db.query(sqlString, [ @@ -575,40 +488,27 @@ class MapFlowService { mapStatInfo[0].id, event.round_number ]); - SeriesFlowService.wasRoundRestored = false; + // Only emit if there was an actual update. + GlobalEmitter.emit("playerStatsUpdate"); } - GlobalEmitter.emit("playerStatsUpdate"); - res.status(200).send({ message: "Success" }); - return; + return res.status(200).send({ message: "Success" }); } /** * Updates the database and emits matchUpdate when a match has been paused or unpaused. - * @param {string} apiKey The API key set by the API and given to the server. * @param {Get5_OnMatchPausedUnpaused} event The Get5_OnMatchPausedUnpaused event provided from the game server. * @param {Response} res The express response object to send status responses to the game server. */ static async OnMatchPausedUnPaused( - apiKey: string, event: Get5_OnMatchPausedUnpaused, res: Response ) { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let sqlString: string; let matchInfo: RowDataPacket[]; let pauseInfo: RowDataPacket[]; let insUpdStatement: object; let teamPaused: string; - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } + sqlString = "SELECT team1_string, team2_string FROM `match` WHERE id = ?"; matchInfo = await db.query(sqlString, [event.matchid]); @@ -627,7 +527,7 @@ class MapFlowService { paused: event.event == "game_paused" ? true : false }; insUpdStatement = await db.buildUpdateStatement(insUpdStatement); - await db.query(sqlString, insUpdStatement); + await db.query(sqlString, [insUpdStatement, event.matchid]); } else { sqlString = "INSERT INTO match_pause SET ?"; insUpdStatement = { @@ -640,8 +540,7 @@ class MapFlowService { await db.query(sqlString, insUpdStatement); } GlobalEmitter.emit("matchUpdate"); - res.status(200).send({ message: "Success" }); - return; + return res.status(200).send({ message: "Success" }); } } diff --git a/src/services/seriesflowservices.ts b/src/services/seriesflowservices.ts index 1579b90..600c007 100644 --- a/src/services/seriesflowservices.ts +++ b/src/services/seriesflowservices.ts @@ -12,18 +12,9 @@ import Utils from "../utility/utils.js"; import update_challonge_match from "../services/challonge.js"; class SeriesFlowService { - static wasRoundRestored: boolean = false; - static async OnSeriesResult( - apiKey: string, - event: Get5_OnSeriesResult, - res: Response - ) { + static async OnSeriesResult(event: Get5_OnSeriesResult, res: Response) { try { // Check if match has been finalized. - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let winnerId: number | null = null; let cancelled: number | null = null; let endTime: string = new Date() @@ -31,32 +22,17 @@ class SeriesFlowService { .slice(0, 19) .replace("T", " "); let updateObject: {}; - // As of right now there is no way to track forfeits via API calls. - // let forfeit: number = event. - // Match is finalized, this is usually called after a cancel so we just ignore the value with a 200 response. - if (matchApiCheck == 2) { - res.status(200).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } else if (matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } const matchInfo: RowDataPacket[] = await db.query( "SELECT team1_id, team2_id, max_maps, start_time, server_id, is_pug, season_id FROM `match` WHERE id = ?", [event.matchid] ); - if (event.winner.team === "team1") winnerId = matchInfo[0]?.team1_id; - else if (event.winner.team === "team2") winnerId = matchInfo[0]?.team2_id; + if (event.winner?.team === "team1") winnerId = matchInfo[0]?.team1_id; + else if (event.winner?.team === "team2") + winnerId = matchInfo[0]?.team2_id; // BO2 situation. else if ( - event.winner.team === "none" && + event.winner?.team === "none" && matchInfo[0].max_maps != 2 && event.team1_series_score == 0 && event.team2_series_score == 0 @@ -126,42 +102,28 @@ class SeriesFlowService { ); } GlobalEmitter.emit("matchUpdate"); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - static async OnMapResult( - apiKey: string, - event: Get5_OnMapResult, - res: Response - ) { + static async OnMapResult(event: Get5_OnMapResult, res: Response) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); let updateStmt: object = {}; let sqlString: string; let matchInfo: RowDataPacket[]; let mapInfo: RowDataPacket[]; + let playerStats: RowDataPacket[]; + let singlePlayerStat: RowDataPacket[]; let mapEndTime: string = new Date() .toISOString() .slice(0, 19) .replace("T", " "); let winnerId: number | null | string = null; - - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } sqlString = "SELECT is_pug, max_maps, season_id FROM `match` WHERE id = ?"; matchInfo = await db.query(sqlString, [event.matchid]); @@ -169,12 +131,13 @@ class SeriesFlowService { "SELECT id FROM `map_stats` WHERE match_id = ? AND map_number = ?"; mapInfo = await db.query(sqlString, [event.matchid, event.map_number]); if (mapInfo.length < 1) { - res.status(404).send({ message: "Failed to find map stats object." }); - return; + return res + .status(404) + .send({ message: "Failed to find map stats object." }); } - if (event.winner.team == "team1") { + if (event.winner?.team == "team1") { winnerId = event.team1.id; - } else if (event.winner.team == "team2") { + } else if (event.winner?.team == "team2") { winnerId = event.team2.id; } updateStmt = { @@ -186,6 +149,36 @@ class SeriesFlowService { updateStmt = await db.buildUpdateStatement(updateStmt); await db.query(sqlString, [updateStmt, mapInfo[0].id]); + // Final update of playerstats. + sqlString = + "SELECT * FROM player_stats WHERE match_id = ? AND map_id = ?"; + playerStats = await db.query(sqlString, [event.matchid, mapInfo[0].id]); + for (let player of event.team1.players) { + singlePlayerStat = playerStats.filter( + (dbPlayer) => dbPlayer.steam_id == player.steamid + ); + await Utils.updatePlayerStats( + event.matchid, + event.team1.id, + mapInfo[0].id, + player, + singlePlayerStat[0].id + ); + } + for (let player of event.team2.players) { + singlePlayerStat = playerStats.filter( + (dbPlayer) => dbPlayer.steam_id == player.steamid + ); + await Utils.updatePlayerStats( + event.matchid, + event.team2.id, + mapInfo[0].id, + player, + singlePlayerStat[0].id + ); + } + GlobalEmitter.emit("playerStatsUpdate"); + // Update match table. updateStmt = { team1_score: event.team1.series_score, @@ -208,96 +201,51 @@ class SeriesFlowService { } GlobalEmitter.emit("mapStatUpdate"); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - static async OnMapVetoed( - apiKey: string, - event: Get5_OnMapVetoed, - res: Response - ) { + static async OnMapVetoed(event: Get5_OnMapVetoed, res: Response) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } await this.insertPickOrBan( "veto", event.matchid, event.map_name, event.team ); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - static async OnMapPicked( - apiKey: string, - event: Get5_OnMapPicked, - res: Response - ) { + static async OnMapPicked(event: Get5_OnMapPicked, res: Response) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } await this.insertPickOrBan( "pick", event.matchid, event.map_name, event.team ); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - static async OnSidePicked( - apiKey: string, - event: Get5_OnSidePicked, - res: Response - ) { + static async OnSidePicked(event: Get5_OnSidePicked, res: Response) { try { - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } let sqlString: string = "SELECT team1_id, team2_id FROM `match` WHERE id = ?"; let teamPickId: number; @@ -307,6 +255,10 @@ class SeriesFlowService { let teamPickSideString: string = "Default"; let vetoId: number; let insertObj: Object; + // No side was chosen, perhaps was default? Ignore the event. + if (!event.side) { + return res.status(200).send({ message: "Success" }); + } if (event.team !== null) { const matchInfo: RowDataPacket[] = await db.query(sqlString, [ event.matchid @@ -335,7 +287,7 @@ class SeriesFlowService { teamPickMapString, event.map_name ]); - vetoId = vetoInfo[0].id; + vetoId = vetoInfo[0]?.id; // Insert into veto_side now. insertObj = { @@ -350,37 +302,22 @@ class SeriesFlowService { sqlString = "INSERT INTO veto_side SET ?"; await db.query(sqlString, [insertObj]); GlobalEmitter.emit("vetoSideUpdate"); - res.status(200).send({ message: "Success" }); - return; - } catch (error) { + return res.status(200).send({ message: "Success" }); + } catch (error: unknown) { console.error(error); - res.status(500).send({ message: error }); - return; + if (error instanceof Error) + return res.status(500).send({ message: error.message }); + else return res.status(500).send({ message: error }); } } - static async OnBackupRestore( - apiKey: string, - event: Get5_OnBackupRestore, - res: Response - ) { + static async OnBackupRestore(event: Get5_OnBackupRestore, res: Response) { // Logic for this is to fix a bug in user stats when a round restore happens. In previous iterations of the API // we would not care if a user would restore the match, which could lead to misrepresentation of stats. // This route will now fix this issue by seeking out all the data that's > the current round where it can, // and mark a value that will ensure the remaining player stats are updated as such. // The main chunk of update logic will then take place in the map flow service, on the OnRoundEnd function // as we get all the player information from that call. - const matchApiCheck: number = await Utils.checkApiKey( - apiKey, - event.matchid - ); - if (matchApiCheck == 2 || matchApiCheck == 1) { - res.status(401).send({ - message: - "Match already finalized or and invalid API key has been given." - }); - return; - } let sqlString: string = "DELETE FROM player_stat_extras " + "WHERE match_id = ? AND " + @@ -392,8 +329,10 @@ class SeriesFlowService { event.map_number, event.round_number ]); - res.status(200).send({ message: "Success" }); - this.wasRoundRestored = true; + sqlString = + "UPDATE `map_stats` SET round_restored = 1 WHERE match_id = ? AND map_number = ?"; + await db.query(sqlString, [event.matchid, event.map_number]); + return res.status(200).send({ message: "Success" }); } private static async insertPickOrBan( @@ -410,10 +349,9 @@ class SeriesFlowService { let matchInfo: RowDataPacket[]; sqlString = "SELECT team1_id, team2_id, id FROM `match` WHERE id = ?"; - // XXX: Maybe change the DB to use team1 and team2 and use a join query to retrieve the actual names? matchInfo = await db.query(sqlString, [matchid]); if (team === "team1") teamId = matchInfo[0].team1_id; - else if (team === "team2") teamId = matchInfo[0].team1_id; + else if (team === "team2") teamId = matchInfo[0].team2_id; else teamId = -1; if (teamId == -1) { diff --git a/src/utility/utils.ts b/src/utility/utils.ts index c46c005..dee62d0 100644 --- a/src/utility/utils.ts +++ b/src/utility/utils.ts @@ -29,6 +29,8 @@ import { ID } from "@node-steam/id"; import {db} from "../services/db.js"; import { RowDataPacket } from 'mysql2'; import { NextFunction, Request, Response } from 'express'; +import { Get5_OnRoundEnd } from '../types/map_flow/Get5_OnRoundEnd.js'; +import { Get5_Player } from '../types/Get5_Player.js'; class Utils { /** Function to get an HLTV rating for a user. @@ -518,6 +520,74 @@ class Utils { return 2; else return 0; } + + /** + * Private helper function to update player stats based on a team. + * @param {string} matchid The current match ID. + * @param {string} teamid The team ID of the player being updated. + * @param {number} mapId The map ID from the database. + * @param {Get5_Player} player The Get5_Player structure. + * @param {number} playerId The player ID from the database. + */ + public static async updatePlayerStats( + matchid: string, + teamid: string, + mapId: number, + player: Get5_Player, + playerId: number | null + ) { + let insUpdStatement: object; + let sqlString: string; + insUpdStatement = { + match_id: matchid, + map_id: mapId, + team_id: teamid, + steam_id: player.steamid, + name: player.name, + kills: player.stats?.kills, + deaths: player.stats?.deaths, + roundsplayed: player.stats?.rounds_played, + assists: player.stats?.assists, + flashbang_assists: player.stats?.flash_assists, + teamkills: player.stats?.team_kills, + knife_kills: player.stats?.knife_kills, + suicides: player.stats?.suicides, + headshot_kills: player.stats?.headshot_kills, + damage: player.stats?.damage, + util_damage: player.stats?.utility_damage, + enemies_flashed: player.stats?.enemies_flashed, + friendlies_flashed: player.stats?.friendlies_flashed, + bomb_plants: player.stats?.bomb_plants, + bomb_defuses: player.stats?.bomb_defuses, + v1: player.stats?.["1v1"], + v2: player.stats?.["1v2"], + v3: player.stats?.["1v3"], + v4: player.stats?.["1v4"], + v5: player.stats?.["1v5"], + k1: player.stats?.["1k"], + k2: player.stats?.["2k"], + k3: player.stats?.["3k"], + k4: player.stats?.["4k"], + k5: player.stats?.["5k"], + firstdeath_ct: player.stats?.first_deaths_ct, + firstdeath_t: player.stats?.first_deaths_t, + firstkill_ct: player.stats?.first_kills_ct, + firstkill_t: player.stats?.first_kills_t, + kast: player.stats?.kast, + contribution_score: player.stats?.score, + mvp: player.stats?.mvp + }; + + insUpdStatement = await db.buildUpdateStatement(insUpdStatement); + + if (playerId) { + sqlString = "UPDATE player_stats SET ? WHERE id = ?"; + await db.query(sqlString, [insUpdStatement, playerId]); + } else { + sqlString = "INSERT INTO player_stats SET ?"; + await db.query(sqlString, insUpdStatement); + } + } } diff --git a/yarn.lock b/yarn.lock index f96c02d..01f7515 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1431,9 +1431,9 @@ charm@~0.1.1: resolved "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz" integrity sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ== -chokidar@^3.5.1, chokidar@^3.5.2: +chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -1708,7 +1708,12 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz" integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== -dayjs@~1.8.24, dayjs@~1.8.25: +dayjs@~1.11.5: + version "1.11.8" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea" + integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== + +dayjs@~1.8.24: version "1.8.36" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz" integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw== @@ -3060,7 +3065,7 @@ jest-worker@^29.5.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.1.0: +jest@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== @@ -3388,7 +3393,7 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3569,15 +3574,15 @@ node-releases@^2.0.6: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== -nodemon@^2.0.15: - version "2.0.19" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz" - integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== +nodemon@^2.0.22: + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== dependencies: chokidar "^3.5.2" debug "^3.2.7" ignore-by-default "^1.0.1" - minimatch "^3.0.4" + minimatch "^3.1.2" pstree.remy "^1.1.8" semver "^5.7.1" simple-update-notifier "^1.0.7" @@ -3930,10 +3935,10 @@ pm2-sysmonit@^1.2.8: systeminformation "^5.7" tx2 "~1.0.4" -pm2@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/pm2/-/pm2-5.2.0.tgz" - integrity sha512-PO5hMVhQ85cTszFM++6v07Me9hPJMkFbHjkFigtMMk+La8ty2wCi2dlBTeZYJDhPUSjK8Ccltpq2buNRcyMOTw== +pm2@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/pm2/-/pm2-5.3.0.tgz#06850810f77cd98495ae1c66fbdd028a8fb5899e" + integrity sha512-xscmQiAAf6ArVmKhjKTeeN8+Td7ZKnuZFFPw1DGkdFPR/0Iyx+m+1+OpCdf9+HQopX3VPc9/wqPQHqVOfHum9w== dependencies: "@pm2/agent" "~2.0.0" "@pm2/io" "~5.0.0" @@ -3942,11 +3947,11 @@ pm2@^5.2.0: async "~3.2.0" blessed "0.1.81" chalk "3.0.0" - chokidar "^3.5.1" + chokidar "^3.5.3" cli-tableau "^2.0.0" commander "2.15.1" croner "~4.1.92" - dayjs "~1.8.25" + dayjs "~1.11.5" debug "^4.3.1" enquirer "2.3.6" eventemitter2 "5.0.1" @@ -3960,7 +3965,7 @@ pm2@^5.2.0: pm2-multimeter "^0.1.2" promptly "^2" semver "^7.2" - source-map-support "0.5.19" + source-map-support "0.5.21" sprintf-js "1.1.2" vizion "~2.2.1" yamljs "0.3.0" @@ -4461,10 +4466,10 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@0.5.19: - version "0.5.19" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== +source-map-support@0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0"