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"