diff --git a/README.md b/README.md index c95efc4..18c3390 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The steps below are all written with the presumption that you're using Ubuntu. 2. Now we need to make our database and a user that can connect to it. ``` CREATE USER 'league'@'%' IDENTIFIED BY '{password}'; -CREATE DATABASE panel; +CREATE DATABASE panel CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON panel.* TO 'league'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; ``` diff --git a/app/Router.php b/app/Router.php index 592b0f6..b9340a9 100644 --- a/app/Router.php +++ b/app/Router.php @@ -68,6 +68,9 @@ protected function registerRoutes() // Authorised match endpoints Route::post('/match/start', MatchController::class . '@startMatch'); Route::get('/match/end/{matchId}', MatchController::class . '@endMatch'); + Route::get('/match/status/{matchId}', MatchController::class . '@isMatchLive'); + Route::get('/match/status', MatchController::class . '@getMatchesStatus'); + Route::get('/match/scoreboard/{matchId}', MatchController::class . '@getMatchScoreboard'); }); diff --git a/package-lock.json b/package-lock.json index 1173ade..e941f9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1476,21 +1476,12 @@ "dev": true }, "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dev": true, "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", - "dev": true - } + "follow-redirects": "^1.10.0" } }, "babel-code-frame": { @@ -3387,9 +3378,9 @@ "dev": true }, "elliptic": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", - "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -3828,24 +3819,10 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", + "dev": true }, "for-in": { "version": "1.0.2", @@ -4126,12 +4103,6 @@ "dev": true, "optional": true }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -5045,9 +5016,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "interpret": { @@ -6108,9 +6079,9 @@ "dev": true }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-gyp": { "version": "3.8.0", diff --git a/package.json b/package.json index 0b75c6f..7dda672 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@babel/core": "^7.7.5", "@babel/preset-env": "^7.7.6", "@babel/register": "^7.7.4", - "axios": "^0.18.1", + "axios": "^0.21.1", "babel-core": "^6.26.3", "babel-loader": "^8.0.6", "babel-polyfill": "^6.26.0", diff --git a/src/Controllers/MatchController.php b/src/Controllers/MatchController.php index 058ef46..dc78e2b 100644 --- a/src/Controllers/MatchController.php +++ b/src/Controllers/MatchController.php @@ -112,30 +112,44 @@ public function getMatch(string $matchId): string */ public function endMatch(string $matchId): string { - $input = input()->all(); + return response()->json( + $this->matchHelper->endMatch($matchId) + ); + } - if (empty($input['ip'])) { - return response()->json([ - 'success' => false, - 'error' => 'ip_empty' - ]); - } elseif (!array_key_exists('port', $input)) { - return response()->json([ - 'success' => false, - 'error' => 'port_missing' - ]); - } elseif (empty($input['port'])) { - return response()->json([ - 'success' => false, - 'error' => 'port_empty' - ]); - } + /** + * Check whether the match is live. + * + * @param string $matchId + * @return string + */ + public function isMatchLive(string $matchId): string + { + return response()->json( + $this->matchHelper->checkLive($matchId) + ); + } - $ip = $input['ip']; - $port = $input['port']; + /** + * Check whether the matches are live. + * + * @return string + */ + public function getMatchesStatus(): string + { + return response()->json( + $this->matchHelper->getMatchesStatus() + ); + } + /** + * @param string $matchId + * @return string + */ + public function getMatchScoreboard(string $matchId): string + { return response()->json( - $this->matchHelper->endMatch($matchId, $ip, $port) + $this->matchHelper->getMatchScoreboard($matchId) ); } } diff --git a/src/Helpers/MatchHelper.php b/src/Helpers/MatchHelper.php index 4c60734..1fc24cd 100644 --- a/src/Helpers/MatchHelper.php +++ b/src/Helpers/MatchHelper.php @@ -64,6 +64,41 @@ public function getMatchPlayers(string $matchId): ?array return $matchPlayers ? $this->formatMatchPlayers($matchPlayers) : null; } + /** + * @param string $matchId + * @return array + */ + public function getMatchScoreboard(string $matchId): array + { + $query = $this->db->query(' + SELECT DISTINCT + players.discord, + matches_players.team, + matches_players.kills, + matches_players.deaths, + matches_players.assists, + matches_players.playerscore, + matches_maps.team1_score, + matches_maps.team2_score + FROM matches + LEFT JOIN matches_maps ON matches_maps.matchid = matches.matchid + LEFT JOIN matches_players ON matches_players.matchid = matches.matchid + LEFT JOIN players ON matches_players.steam = players.steam + WHERE matches_maps.matchid = :matchId + ORDER BY matches_players.playerscore DESC + ', [ + ':matchId' => $matchId, + ]); + + $matchScoreboard = $query->fetchAll(); + + if (!$matchScoreboard) { + return []; + } + + return $this->formatMatchPlayer($matchScoreboard); + } + /** * @param array $players * @return array @@ -116,7 +151,9 @@ protected function formatMatchPlayer(array $player): array $player['kdr'] = 0; } - $player['name'] = htmlspecialchars(substr($player['name'], 0, 32)); + if (array_key_exists('name', $player) && !empty($player['name'])) { + $player['name'] = htmlspecialchars(substr($player['name'], 0, 32)); + } return $player; } @@ -191,9 +228,50 @@ public function getMatch(string $matchId): array * @throws RconAuthException * @throws RconConnectException */ - public function endMatch(string $matchId, string $ip, string $port): array + public function endMatch(string $matchId): array { - $server = new Rcon($ip, $port, env('RCON')); + $query = $this->db->query(' + SELECT + end_time, + server_ip, + server_port + FROM matches + WHERE matches.matchid = :matchId + ', [ + ':matchId' => $matchId, + ]); + + $match = $query->fetch(); + + if (!$match) { + return [ + 'success' => false, + 'error' => 'Match not found', + ]; + } + + if ($match['end_time']) { + return [ + 'success' => false, + 'error' => 'Match is already over', + ]; + } + + if (!array_key_exists('server_ip', $match) || !$match['server_ip']) { + return [ + 'success' => false, + 'error' => 'server_ip does not exist or is not valid', + ]; + } + + if (!array_key_exists('server_port', $match) || !$match['server_port']) { + return [ + 'success' => false, + 'error' => 'server_port does not exist or is not valid', + ]; + } + + $server = new Rcon($match['server_ip'], $match['server_port'], env('RCON')); $server->connect(); $server->exec('get5_endmatch; map de_mirage'); @@ -201,7 +279,7 @@ public function endMatch(string $matchId, string $ip, string $port): array $matchConfig = self::MATCHES_CACHE . "/$matchId.json"; return [ - 'success' => unlink($matchConfig) + 'success' => unlink($matchConfig), ]; } @@ -339,4 +417,57 @@ protected function getMostRecentMatchId(): int return $response['match_id'] ?? 1; } + + /** + * @param string $matchId + * @return array + */ + public function checkLive(string $matchId): array + { + return [ + 'success' => true, + 'live' => $this->isMatchLive($matchId) + ]; + } + + /** + * Return whether the match is live. + * + * @param string $matchId + * @return bool + */ + public function isMatchLive(string $matchId): bool + { + $query = $this->db->query(' + SELECT + end_time + FROM matches + WHERE matches.matchid = :matchId + AND matches.end_time IS NULL + ', [ + ':matchId' => $matchId, + ]); + + return $query->rowCount() !== 0; + } + + /** + * Return matches status. + * + * @return array + */ + public function getMatchesStatus(): array + { + $query = $this->db->query('SELECT matchid, end_time FROM matches'); + + $matches = $query->fetchAll(); + + $response = []; + + foreach ($matches as $match) { + $response[$match['matchid']] = !isset($match['end_time']); + } + + return $response; + } } diff --git a/src/Migrations/20200816080154_AddMatchServerInfo.php b/src/Migrations/20200816080154_AddMatchServerInfo.php new file mode 100644 index 0000000..b48c028 --- /dev/null +++ b/src/Migrations/20200816080154_AddMatchServerInfo.php @@ -0,0 +1,44 @@ +get('db'); + + $query = $db->query(' + ALTER TABLE matches ADD COLUMN `server_ip` varchar(255); + ALTER TABLE matches ADD COLUMN `server_port` int(11) DEFAULT 27015; + '); + + return $query->execute(); + } + + /** + * Undo the migration + */ + public function down() + { + /** + * @var $db Medoo + */ + $db = $this->get('db'); + + $query = $db->query(' + ALTER TABLE matches DROP COLUMN `server_ip`; + ALTER TABLE matches DROP COLUMN `server_port`; + '); + + return $query->execute(); + } +} diff --git a/src/Views/partials/player/player-bar.twig b/src/Views/partials/player/player-bar.twig index 0c6fdf7..2f4e50b 100644 --- a/src/Views/partials/player/player-bar.twig +++ b/src/Views/partials/player/player-bar.twig @@ -8,8 +8,8 @@