From 6cecc96f13238ef205dd7a6f0b96d4838bae7d02 Mon Sep 17 00:00:00 2001 From: JensForstmann <19289807+JensForstmann@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:57:19 +0100 Subject: [PATCH] Add docs to endpoints and types. --- backend/src/configController.ts | 3 + backend/src/debugController.ts | 3 + backend/src/gameServersController.ts | 16 +- backend/src/loginController.ts | 3 + backend/src/matchesController.ts | 45 +++++ backend/src/presetsController.ts | 12 ++ backend/src/routes.ts | 10 +- backend/swagger.json | 213 ++++++++++++++++------ backend/tsoa.json | 4 + common/types/election.ts | 2 +- common/types/electionStep.ts | 16 ++ common/types/gameServer.ts | 9 + common/types/match.ts | 31 +++- common/types/matchMap.ts | 17 +- common/types/player.ts | 10 +- common/types/team.ts | 22 ++- frontend/src/components/MatchCard.tsx | 4 +- frontend/src/components/MatchEditCard.tsx | 4 +- 18 files changed, 348 insertions(+), 76 deletions(-) diff --git a/backend/src/configController.ts b/backend/src/configController.ts index 1df5cb1..88c63e4 100644 --- a/backend/src/configController.ts +++ b/backend/src/configController.ts @@ -4,6 +4,9 @@ import { TMT_LOG_ADDRESS } from '.'; @Route('/api/config') @Security('bearer_token') export class ConfigController extends Controller { + /** + * Get some internal config variables. Currently only the set TMT_LOG_ADDRESS. + */ @Get() @NoSecurity() async getConfig(): Promise<{ diff --git a/backend/src/debugController.ts b/backend/src/debugController.ts index f3bbf37..db80bdf 100644 --- a/backend/src/debugController.ts +++ b/backend/src/debugController.ts @@ -7,6 +7,9 @@ import { STORAGE_FOLDER } from './storage'; @Route('/api/debug') @Security('bearer_token') export class DebugController extends Controller { + /** + * Get all connected web socket clients. + */ @Get('webSockets') async getWebSocketClients() { return WebSocket.getClients(); diff --git a/backend/src/gameServersController.ts b/backend/src/gameServersController.ts index 2afab7c..1fe9c8b 100644 --- a/backend/src/gameServersController.ts +++ b/backend/src/gameServersController.ts @@ -9,11 +9,17 @@ import * as ManagedGameServers from './managedGameServers'; @Route('/api/gameservers') @Security('bearer_token') export class GameServersController extends Controller { + /** + * Get all managed game servers. + */ @Get() async getGameServers(): Promise { return ManagedGameServers.getAll(); } + /** + * Add a new managed game server. + */ @Post() async createGameServer( @Body() requestBody: IManagedGameServerCreateDto @@ -30,13 +36,21 @@ export class GameServersController extends Controller { return managedGameServer; } + /** + * Change an existing managed game server. + */ @Patch('{ip}/{port}') async updateGameServer( - @Body() requestBody: IManagedGameServerUpdateDto + @Body() requestBody: IManagedGameServerUpdateDto, + ip: string, + port: number ): Promise { return await ManagedGameServers.update(requestBody); } + /** + * Delete an existing managed game server. + */ @Delete('{ip}/{port}') async deleteGameServer(ip: string, port: number): Promise { await ManagedGameServers.remove({ diff --git a/backend/src/loginController.ts b/backend/src/loginController.ts index fc54c66..c26d4a5 100644 --- a/backend/src/loginController.ts +++ b/backend/src/loginController.ts @@ -3,6 +3,9 @@ import { Controller, Post, Route, Security } from '@tsoa/runtime'; @Route('/api/login') @Security('bearer_token') export class LoginController extends Controller { + /** + * Dummy endpoint to check if given token is valid without executing anything. + */ @Post() async login() {} } diff --git a/backend/src/matchesController.ts b/backend/src/matchesController.ts index bc568b5..04609b2 100644 --- a/backend/src/matchesController.ts +++ b/backend/src/matchesController.ts @@ -29,6 +29,9 @@ import * as MatchService from './matchService'; @Route('/api/matches') @Security('bearer_token') export class MatchesController extends Controller { + /** + * Create and supervise a new match. + */ @Post() @SuccessResponse(201) @Security('bearer_token_optional') @@ -42,6 +45,13 @@ export class MatchesController extends Controller { return match.data; } + /** + * Get all matches. + * @param state State filter + * @param passthrough Passthrough filter + * @param isStopped Get only stopped or not stopped matches. + * @param isLive Filter for only live (currently active) matches, or the opposite. + */ @Get() async getAllMatches( @Request() { user }: { user: IAuthResponse }, @@ -66,6 +76,9 @@ export class MatchesController extends Controller { .map((m) => MatchService.hideRconPassword(m)); } + /** + * Get a specific match by id. + */ @Get('{id}') async getMatch( id: string, @@ -91,16 +104,26 @@ export class MatchesController extends Controller { return; } + /** + * Get the last 1000 log lines from a specific match. + */ @Get('{id}/logs') async getLogs(id: string, @Request() { user }: { user: IAuthResponse }): Promise { return await Match.getLogsTail(id); } + /** + * Get the last 1000 events from a specific match. + */ @Get('{id}/events') async getEvents(id: string, @Request() { user }: { user: IAuthResponse }): Promise { return await Events.getEventsTail(id); } + /** + * Get the last known round backups for a specific match. + * @param count The max. number of round backups to be returned. + */ @Get('{id}/server/round_backups') async getRoundBackups( id: string, @@ -116,6 +139,10 @@ export class MatchesController extends Controller { } } + /** + * Load a round backup file for a specific match. + * @param file Name of the round backup file. + */ @Post('{id}/server/round_backups/{file}') async loadRoundBackup( id: string, @@ -136,6 +163,9 @@ export class MatchesController extends Controller { } } + /** + * Update a specific match. + */ @Patch('{id}') async updateMatch( id: string, @@ -150,6 +180,9 @@ export class MatchesController extends Controller { } } + /** + * Update a specific match map. First map has the map number 0. + */ @Patch('{id}/matchMap/{mapNumber}') async updateMatchMap( id: string, @@ -170,6 +203,9 @@ export class MatchesController extends Controller { await MatchMap.update(match, matchMap, requestBody, mapNumber); } + /** + * Stop supervising a specific match. TMT will no longer listen to the game server and will not execute any rcon commands. + */ @Delete('{id}') async deleteMatch(id: string, @Request() { user }: { user: IAuthResponse }): Promise { if (!(await MatchService.remove(id))) { @@ -177,6 +213,9 @@ export class MatchesController extends Controller { } } + /** + * Revive a specific match. TMT will start supervising a (stopped) match again (listen to the game sever and execute rcon commands). + */ @Patch('{id}/revive') async reviveMatch(id: string, @Request() { user }: { user: IAuthResponse }): Promise { if (!(await MatchService.revive(id))) { @@ -184,6 +223,9 @@ export class MatchesController extends Controller { } } + /** + * Execute a rcon command on the game server. + */ @Post('{id}/server/rcon') async rcon( id: string, @@ -202,6 +244,9 @@ export class MatchesController extends Controller { return await Match.execManyRcon(match, requestBody); } + /** + * Endpoint the game server sends its log file to. Not meant for direct use! + */ @NoSecurity() @Post('{id}/server/log/{secret}') receiveLog(id: string, secret: string, @Body() requestBody: any): void { diff --git a/backend/src/presetsController.ts b/backend/src/presetsController.ts index d513e05..500b542 100644 --- a/backend/src/presetsController.ts +++ b/backend/src/presetsController.ts @@ -17,11 +17,17 @@ import * as Presets from './presets'; @Route('/api/presets') @Security('bearer_token') export class PresetsController extends Controller { + /** + * Get all configured presets. + */ @Get() async getPresets(@Request() { user }: { user: IAuthResponse }): Promise { return Presets.getAll(); } + /** + * Create a new preset. + */ @Post() @SuccessResponse(201) async createPreset( @@ -33,6 +39,9 @@ export class PresetsController extends Controller { return preset; } + /** + * Update an existing preset. + */ @Put() async updatePreset(@Body() requestBody: IPreset, @Request() { user }: { user: IAuthResponse }) { if (!(await Presets.update(requestBody))) { @@ -40,6 +49,9 @@ export class PresetsController extends Controller { } } + /** + * Delete an existing preset. + */ @Delete('{id}') async deletePreset(id: string, @Request() { user }: { user: IAuthResponse }): Promise { if (!(await Presets.remove(id))) { diff --git a/backend/src/routes.ts b/backend/src/routes.ts index d69e4ce..588b277 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -31,7 +31,7 @@ import type { RequestHandler, Router } from 'express'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const models: TsoaRoute.Models = { - TMatchSate: { + TMatchState: { dataType: 'refAlias', type: { dataType: 'union', @@ -472,7 +472,7 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { id: { dataType: 'string', required: true }, - state: { ref: 'TMatchSate', required: true }, + state: { ref: 'TMatchState', required: true }, passthrough: { dataType: 'string' }, mapPool: { dataType: 'array', array: { dataType: 'string' }, required: true }, teamA: { ref: 'ITeam', required: true }, @@ -584,7 +584,7 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { id: { dataType: 'string', required: true }, - state: { ref: 'TMatchSate', required: true }, + state: { ref: 'TMatchState', required: true }, passthrough: { dataType: 'string' }, mapPool: { dataType: 'array', array: { dataType: 'string' }, required: true }, teamA: { ref: 'ITeam', required: true }, @@ -1007,7 +1007,7 @@ const models: TsoaRoute.Models = { matchEndAction: { ref: 'TMatchEndAction' }, tmtLogAddress: { dataType: 'string' }, mode: { ref: 'TMatchMode' }, - state: { ref: 'TMatchSate' }, + state: { ref: 'TMatchState' }, logSecret: { dataType: 'string' }, currentMap: { dataType: 'double' }, _restartElection: { dataType: 'boolean' }, @@ -1632,6 +1632,8 @@ export function RegisterRoutes(app: Router) { required: true, ref: 'IManagedGameServerUpdateDto', }, + ip: { in: 'path', name: 'ip', required: true, dataType: 'string' }, + port: { in: 'path', name: 'port', required: true, dataType: 'double' }, }; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/backend/swagger.json b/backend/swagger.json index 696956b..b9e328c 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -6,25 +6,30 @@ "requestBodies": {}, "responses": {}, "schemas": { - "TMatchSate": { + "TMatchState": { "type": "string", "enum": [ "ELECTION", "MATCH_MAP", "FINISHED" - ] + ], + "description": "Possible match states." }, "ITeam": { + "description": "Team.", "properties": { "passthrough": { - "type": "string" + "type": "string", + "description": "Passthrough data to identify team in other systems.\nWill be present in every response/webhook." }, "name": { - "type": "string" + "type": "string", + "description": "Team name." }, "advantage": { "type": "number", - "format": "double" + "format": "double", + "description": "Advantage in map wins, useful for double elemination tournament finals." } }, "required": [ @@ -35,6 +40,7 @@ "additionalProperties": false }, "IFixedMap": { + "description": "Play on a fixed map. Map will not be removed from the map pool.", "properties": { "mode": { "type": "string", @@ -44,7 +50,8 @@ "nullable": false }, "fixed": { - "type": "string" + "type": "string", + "description": "The name of the map, e.g. de_anubis." } }, "required": [ @@ -64,6 +71,7 @@ ] }, "IPickMap": { + "description": "Pick a map from the map pool.", "properties": { "mode": { "type": "string", @@ -84,6 +92,7 @@ "additionalProperties": false }, "IAgreeOrRandomMap": { + "description": "Either pick a random map from the map pool. Or let both teams agree on a map from the map pool.", "properties": { "mode": { "type": "string", @@ -113,6 +122,7 @@ ] }, "IFixedSide": { + "description": "Use fixed starting sides.", "properties": { "mode": { "type": "string", @@ -133,6 +143,7 @@ "additionalProperties": false }, "IPickSide": { + "description": "Let one team choose its starting side.", "properties": { "mode": { "type": "string", @@ -153,6 +164,7 @@ "additionalProperties": false }, "IRandomOrKnifeSide": { + "description": "Either randomly set starting sides. Or do a knife round befor the map starts and let the winner decide.", "properties": { "mode": { "type": "string", @@ -169,6 +181,7 @@ "additionalProperties": false }, "IElectionStepAdd": { + "description": "Election steps which will result in a match map that will be played.", "properties": { "map": { "anyOf": [ @@ -205,6 +218,7 @@ "additionalProperties": false }, "IRandomMapBan": { + "description": "Randomly ban a map from the map pool.", "properties": { "mode": { "type": "string", @@ -221,6 +235,7 @@ "additionalProperties": false }, "IBanMap": { + "description": "Ban a map from the map pool.", "properties": { "mode": { "type": "string", @@ -241,6 +256,7 @@ "additionalProperties": false }, "IElectionStepSkip": { + "description": "Election steps which will remove a map from the map pool.", "properties": { "map": { "anyOf": [ @@ -267,7 +283,8 @@ { "$ref": "#/components/schemas/IElectionStepSkip" } - ] + ], + "description": "Election step to either add map to the list of match maps. Or to remove a map from the map pool." }, "TElectionState": { "type": "string", @@ -381,7 +398,8 @@ "type": "string" }, "hideRconPassword": { - "type": "boolean" + "type": "boolean", + "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." } }, "required": [ @@ -403,13 +421,14 @@ "IN_PROGRESS", "PAUSED", "FINISHED" - ] + ], + "description": "Possible match map states." }, "IMatchMap": { "properties": { "name": { "type": "string", - "description": "map name" + "description": "Map name, e.g. de_anubis." }, "knifeForSide": { "type": "boolean" @@ -422,7 +441,8 @@ "$ref": "#/components/schemas/TMatchMapSate" }, "knifeWinner": { - "$ref": "#/components/schemas/TTeamAB" + "$ref": "#/components/schemas/TTeamAB", + "description": "Winner of the knife round which is able to or already has picked a starting side." }, "readyTeams": { "properties": { @@ -469,18 +489,22 @@ "teamB", "teamA" ], - "type": "object" + "type": "object", + "description": "Current score of both teams." }, "overTimeEnabled": { - "type": "boolean" + "type": "boolean", + "description": "If overtime is enabled (mp_overtime_enable)." }, "overTimeMaxRounds": { "type": "number", - "format": "double" + "format": "double", + "description": "Max rounds in overtime (mp_overtime_maxrounds)." }, "maxRounds": { "type": "number", - "format": "double" + "format": "double", + "description": "Max rounds (mp_maxrounds)." } }, "required": [ @@ -602,15 +626,19 @@ ] }, "IPlayer": { + "description": "Player.", "properties": { "steamId64": { - "type": "string" + "type": "string", + "description": "Steam ID 64" }, "name": { - "type": "string" + "type": "string", + "description": "Name." }, "team": { - "$ref": "#/components/schemas/TTeamAB" + "$ref": "#/components/schemas/TTeamAB", + "description": "Current team as they joined with `.team`." }, "side": { "allOf": [ @@ -618,10 +646,12 @@ "$ref": "#/components/schemas/TTeamSides" } ], - "nullable": true + "nullable": true, + "description": "Current ingame side." }, "online": { - "type": "boolean" + "type": "boolean", + "description": "Player currently on the game server (online)?" } }, "required": [ @@ -636,7 +666,8 @@ "enum": [ "SINGLE", "LOOP" - ] + ], + "description": "Possible match modes." }, "IMatch": { "properties": { @@ -645,7 +676,7 @@ "description": "tmt2 identifier for this match" }, "state": { - "$ref": "#/components/schemas/TMatchSate" + "$ref": "#/components/schemas/TMatchState" }, "passthrough": { "type": "string", @@ -659,31 +690,34 @@ "description": "The maps the players can pick or ban.\nWill also be used if a map is chosen randomly.\nIf the map is fixed it will not be removed from the map pool." }, "teamA": { - "$ref": "#/components/schemas/ITeam" + "$ref": "#/components/schemas/ITeam", + "description": "Team A for this match.\nTeam A will always be Team A in responses and webhooks.\nNo matter on which side (CT/T) it is currently." }, "teamB": { - "$ref": "#/components/schemas/ITeam" + "$ref": "#/components/schemas/ITeam", + "description": "Team B for this match.\nTeam B will always be Team B in responses and webhooks.\nNo matter on which side (CT/T) it is currently." }, "electionSteps": { "items": { "$ref": "#/components/schemas/IElectionStep" }, - "type": "array" + "type": "array", + "description": "List of election steps to determine the played map(s)." }, "election": { "$ref": "#/components/schemas/IElection", - "description": "election state data" + "description": "Data for the election process." }, "gameServer": { "$ref": "#/components/schemas/IGameServer" }, "logSecret": { "type": "string", - "description": "log secret that is given as part of the url to the cs2 server it will send the logs to" + "description": "Log secret that is given as part of the url to the CS2 server as a log receiver (logaddress_add_http)." }, "parseIncomingLogs": { "type": "boolean", - "description": "Indicates if incoming logs from the cs2 server are parsed (otherwise they will be dropped without any action).\nWill be set to true if match is loaded from storage (after a short delay)." + "description": "Indicates if incoming logs from the CS2 server are parsed (otherwise they will be dropped without any action).\nWill be set to true if match is loaded from storage (after a short delay)." }, "matchMaps": { "items": { @@ -816,18 +850,19 @@ "additionalProperties": false }, "ITeamCreateDto": { + "description": "Team (create structure).", "properties": { "name": { "type": "string" }, "passthrough": { "type": "string", - "description": "e.g. remote identifier, will be present in every response/webhook" + "description": "Passthrough data to identify team in other systems.\nWill be present in every response/webhook." }, "advantage": { "type": "number", "format": "double", - "description": "defaults to 0 (no advantage)" + "description": "Advantage in map wins, useful for double elemination tournament finals." } }, "required": [ @@ -948,7 +983,7 @@ "description": "tmt2 identifier for this match" }, "state": { - "$ref": "#/components/schemas/TMatchSate" + "$ref": "#/components/schemas/TMatchState" }, "passthrough": { "type": "string", @@ -962,31 +997,34 @@ "description": "The maps the players can pick or ban.\nWill also be used if a map is chosen randomly.\nIf the map is fixed it will not be removed from the map pool." }, "teamA": { - "$ref": "#/components/schemas/ITeam" + "$ref": "#/components/schemas/ITeam", + "description": "Team A for this match.\nTeam A will always be Team A in responses and webhooks.\nNo matter on which side (CT/T) it is currently." }, "teamB": { - "$ref": "#/components/schemas/ITeam" + "$ref": "#/components/schemas/ITeam", + "description": "Team B for this match.\nTeam B will always be Team B in responses and webhooks.\nNo matter on which side (CT/T) it is currently." }, "electionSteps": { "items": { "$ref": "#/components/schemas/IElectionStep" }, - "type": "array" + "type": "array", + "description": "List of election steps to determine the played map(s)." }, "election": { "$ref": "#/components/schemas/IElection", - "description": "election state data" + "description": "Data for the election process." }, "gameServer": { "$ref": "#/components/schemas/IGameServer" }, "logSecret": { "type": "string", - "description": "log secret that is given as part of the url to the cs2 server it will send the logs to" + "description": "Log secret that is given as part of the url to the CS2 server as a log receiver (logaddress_add_http)." }, "parseIncomingLogs": { "type": "boolean", - "description": "Indicates if incoming logs from the cs2 server are parsed (otherwise they will be dropped without any action).\nWill be set to true if match is loaded from storage (after a short delay)." + "description": "Indicates if incoming logs from the CS2 server are parsed (otherwise they will be dropped without any action).\nWill be set to true if match is loaded from storage (after a short delay)." }, "matchMaps": { "items": { @@ -1557,7 +1595,8 @@ "AGREE", "BAN", "RANDOM_BAN" - ] + ], + "description": "Possible map modes for a election step." }, "ElectionMapStep": { "properties": { @@ -1607,7 +1646,8 @@ "FIXED", "PICK", "RANDOM" - ] + ], + "description": "Possible side modes to determine the starting sides of each team." }, "ElectionSideStep": { "properties": { @@ -1871,7 +1911,7 @@ "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" }, "state": { - "$ref": "#/components/schemas/TMatchSate" + "$ref": "#/components/schemas/TMatchState" }, "logSecret": { "type": "string", @@ -1907,10 +1947,11 @@ "additionalProperties": false }, "IMatchMapUpdateDto": { + "description": "Structure to update a match map.", "properties": { "name": { "type": "string", - "description": "map name" + "description": "Map name, e.g. de_anubis." }, "knifeForSide": { "type": "boolean" @@ -1923,7 +1964,8 @@ "$ref": "#/components/schemas/TMatchMapSate" }, "knifeWinner": { - "$ref": "#/components/schemas/TTeamAB" + "$ref": "#/components/schemas/TTeamAB", + "description": "Winner of the knife round which is able to or already has picked a starting side." }, "readyTeams": { "properties": { @@ -1970,18 +2012,22 @@ "teamB", "teamA" ], - "type": "object" + "type": "object", + "description": "Current score of both teams." }, "overTimeEnabled": { - "type": "boolean" + "type": "boolean", + "description": "If overtime is enabled (mp_overtime_enable)." }, "overTimeMaxRounds": { "type": "number", - "format": "double" + "format": "double", + "description": "Max rounds in overtime (mp_overtime_maxrounds)." }, "maxRounds": { "type": "number", - "format": "double" + "format": "double", + "description": "Max rounds (mp_maxrounds)." }, "_refreshOvertimeAndMaxRoundsSettings": { "type": "boolean", @@ -2008,14 +2054,17 @@ "type": "string" }, "hideRconPassword": { - "type": "boolean" + "type": "boolean", + "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." }, "canBeUsed": { - "type": "boolean" + "type": "boolean", + "description": "Can the server be used for new matches?" }, "usedBy": { "type": "string", - "nullable": true + "nullable": true, + "description": "Match id which is currently using this managed game server." } }, "required": [ @@ -2041,10 +2090,12 @@ "type": "string" }, "hideRconPassword": { - "type": "boolean" + "type": "boolean", + "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." }, "canBeUsed": { - "type": "boolean" + "type": "boolean", + "description": "Can the server be used for new matches?" } }, "required": [ @@ -2068,11 +2119,13 @@ "type": "string" }, "canBeUsed": { - "type": "boolean" + "type": "boolean", + "description": "Set if the server can be used for new matches." }, "usedBy": { "type": "string", - "nullable": true + "nullable": true, + "description": "Set or delete the link to a match. If it's null and `canBeUsed` is true, the game server is available." } }, "required": [ @@ -2123,6 +2176,10 @@ "bearer_token": { "type": "http", "scheme": "bearer" + }, + "bearer_token_optional": { + "type": "http", + "scheme": "bearer" } } }, @@ -2146,6 +2203,7 @@ "description": "No content" } }, + "description": "Dummy endpoint to check if given token is valid without executing anything.", "security": [ { "bearer_token": [] @@ -2169,6 +2227,7 @@ } } }, + "description": "Create and supervise a new match.", "security": [ { "bearer_token_optional": [] @@ -2203,6 +2262,7 @@ } } }, + "description": "Get all matches.", "security": [ { "bearer_token": [] @@ -2210,6 +2270,7 @@ ], "parameters": [ { + "description": "State filter", "in": "query", "name": "state", "required": false, @@ -2221,6 +2282,7 @@ } }, { + "description": "Passthrough filter", "in": "query", "name": "passthrough", "required": false, @@ -2232,6 +2294,7 @@ } }, { + "description": "Get only stopped or not stopped matches.", "in": "query", "name": "isStopped", "required": false, @@ -2240,6 +2303,7 @@ } }, { + "description": "Filter for only live (currently active) matches, or the opposite.", "in": "query", "name": "isLive", "required": false, @@ -2270,6 +2334,7 @@ } } }, + "description": "Get a specific match by id.", "security": [ { "bearer_token": [] @@ -2293,6 +2358,7 @@ "description": "No content" } }, + "description": "Update a specific match.", "security": [ { "bearer_token": [] @@ -2326,6 +2392,7 @@ "description": "No content" } }, + "description": "Stop supervising a specific match. TMT will no longer listen to the game server and will not execute any rcon commands.", "security": [ { "bearer_token": [] @@ -2361,6 +2428,7 @@ } } }, + "description": "Get the last 1000 log lines from a specific match.", "security": [ { "bearer_token": [] @@ -2396,6 +2464,7 @@ } } }, + "description": "Get the last 1000 events from a specific match.", "security": [ { "bearer_token": [] @@ -2449,6 +2518,7 @@ } } }, + "description": "Get the last known round backups for a specific match.", "security": [ { "bearer_token": [] @@ -2464,6 +2534,7 @@ } }, { + "description": "The max. number of round backups to be returned.", "in": "query", "name": "count", "required": false, @@ -2495,6 +2566,7 @@ } } }, + "description": "Load a round backup file for a specific match.", "security": [ { "bearer_token": [] @@ -2510,6 +2582,7 @@ } }, { + "description": "Name of the round backup file.", "in": "path", "name": "file", "required": true, @@ -2528,6 +2601,7 @@ "description": "No content" } }, + "description": "Update a specific match map. First map has the map number 0.", "security": [ { "bearer_token": [] @@ -2572,6 +2646,7 @@ "description": "No content" } }, + "description": "Revive a specific match. TMT will start supervising a (stopped) match again (listen to the game sever and execute rcon commands).", "security": [ { "bearer_token": [] @@ -2612,6 +2687,7 @@ } } }, + "description": "Execute a rcon command on the game server.", "security": [ { "bearer_token": [] @@ -2650,6 +2726,7 @@ "description": "No content" } }, + "description": "Endpoint the game server sends its log file to. Not meant for direct use!", "security": [], "parameters": [ { @@ -2697,6 +2774,7 @@ } } }, + "description": "Get all managed game servers.", "security": [ { "bearer_token": [] @@ -2718,6 +2796,7 @@ } } }, + "description": "Add a new managed game server.", "security": [ { "bearer_token": [] @@ -2751,12 +2830,31 @@ } } }, + "description": "Change an existing managed game server.", "security": [ { "bearer_token": [] } ], - "parameters": [], + "parameters": [ + { + "in": "path", + "name": "ip", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "port", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ], "requestBody": { "required": true, "content": { @@ -2775,6 +2873,7 @@ "description": "No content" } }, + "description": "Delete an existing managed game server.", "security": [ { "bearer_token": [] @@ -2819,6 +2918,7 @@ } } }, + "description": "Get all configured presets.", "security": [ { "bearer_token": [] @@ -2840,6 +2940,7 @@ } } }, + "description": "Create a new preset.", "security": [ { "bearer_token": [] @@ -2864,6 +2965,7 @@ "description": "No content" } }, + "description": "Update an existing preset.", "security": [ { "bearer_token": [] @@ -2890,6 +2992,7 @@ "description": "No content" } }, + "description": "Delete an existing preset.", "security": [ { "bearer_token": [] @@ -2923,6 +3026,7 @@ } } }, + "description": "Get all connected web socket clients.", "security": [ { "bearer_token": [] @@ -3015,6 +3119,7 @@ } } }, + "description": "Get some internal config variables. Currently only the set TMT_LOG_ADDRESS.", "security": [], "parameters": [] } diff --git a/backend/tsoa.json b/backend/tsoa.json index 25417ae..71d2b7f 100644 --- a/backend/tsoa.json +++ b/backend/tsoa.json @@ -9,6 +9,10 @@ "bearer_token": { "type": "http", "scheme": "bearer" + }, + "bearer_token_optional": { + "type": "http", + "scheme": "bearer" } } }, diff --git a/common/types/election.ts b/common/types/election.ts index 0f3c750..169b7bb 100644 --- a/common/types/election.ts +++ b/common/types/election.ts @@ -1,4 +1,4 @@ -import { TTeamAB } from './matchMap'; +import { TTeamAB } from './team'; export type TElectionState = 'NOT_STARTED' | 'IN_PROGRESS' | 'FINISHED'; diff --git a/common/types/electionStep.ts b/common/types/electionStep.ts index 0e1edbc..77b98b9 100644 --- a/common/types/electionStep.ts +++ b/common/types/electionStep.ts @@ -14,61 +14,77 @@ export const SideFixeds = [ export type TSideFixed = (typeof SideFixeds)[number]; export const MapModesAdd = ['FIXED', 'PICK', 'RANDOM_PICK', 'AGREE'] as const; +/** Modes to select a map that will be played. */ export type TMapModeAdd = (typeof MapModesAdd)[number]; export const MapModesSkip = ['BAN', 'RANDOM_BAN'] as const; +/** Modes to remove a map without playing on it. */ export type TMapModeSkip = (typeof MapModesSkip)[number]; export const MapModes = [...MapModesAdd, ...MapModesSkip] as const; +/** Possible map modes for a election step. */ export type TMapMode = (typeof MapModes)[number]; +/** Play on a fixed map. Map will not be removed from the map pool. */ export interface IFixedMap { mode: 'FIXED'; + /** The name of the map, e.g. de_anubis. */ fixed: string; } +/** Ban a map from the map pool. */ export interface IBanMap { mode: 'BAN'; who: TWho; } +/** Pick a map from the map pool. */ export interface IPickMap { mode: 'PICK'; who: TWho; } +/** Either pick a random map from the map pool. Or let both teams agree on a map from the map pool. */ export interface IAgreeOrRandomMap { mode: 'RANDOM_PICK' | 'AGREE'; } +/** Randomly ban a map from the map pool. */ export interface IRandomMapBan { mode: 'RANDOM_BAN'; } export const SideModes = ['FIXED', 'PICK', 'RANDOM', 'KNIFE'] as const; +/** Possible side modes to determine the starting sides of each team. */ export type TSideMode = (typeof SideModes)[number]; +/** Use fixed starting sides. */ export interface IFixedSide { mode: 'FIXED'; fixed: TSideFixed; } +/** Let one team choose its starting side. */ export interface IPickSide { mode: 'PICK'; who: TWho; } +/** Either randomly set starting sides. Or do a knife round befor the map starts and let the winner decide. */ export interface IRandomOrKnifeSide { mode: 'RANDOM' | 'KNIFE'; } +/** Election steps which will result in a match map that will be played. */ export interface IElectionStepAdd { map: IFixedMap | IPickMap | IAgreeOrRandomMap; side: IFixedSide | IPickSide | IRandomOrKnifeSide; } +/** Election steps which will remove a map from the map pool. */ export interface IElectionStepSkip { map: IRandomMapBan | IBanMap; } +/** Election step to either add map to the list of match maps. Or to remove a map from the map pool. */ export type IElectionStep = IElectionStepAdd | IElectionStepSkip; diff --git a/common/types/gameServer.ts b/common/types/gameServer.ts index 5473c4a..fc12e82 100644 --- a/common/types/gameServer.ts +++ b/common/types/gameServer.ts @@ -4,15 +4,22 @@ export interface IGameServer { ip: string; port: number; rconPassword: string; + /** + * If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true. + * This will prevent executing rcon commands from the frontend by the (unauthorized) user. + */ hideRconPassword?: boolean; } export interface IManagedGameServer extends IGameServer { + /** Can the server be used for new matches? */ canBeUsed: boolean; + /** Match id which is currently using this managed game server. */ usedBy: IMatch['id'] | null; } export interface IManagedGameServerCreateDto extends IGameServer { + /** Can the server be used for new matches? */ canBeUsed?: boolean; } @@ -20,6 +27,8 @@ export interface IManagedGameServerUpdateDto { ip: string; port: number; rconPassword?: string; + /** Set if the server can be used for new matches. */ canBeUsed?: boolean; + /** Set or delete the link to a match. If it's null and `canBeUsed` is true, the game server is available. */ usedBy?: IMatch['id'] | null; } diff --git a/common/types/match.ts b/common/types/match.ts index 16116d6..065cb5d 100644 --- a/common/types/match.ts +++ b/common/types/match.ts @@ -10,14 +10,20 @@ export const MatchEndActions = ['KICK_ALL', 'QUIT_SERVER', 'NONE'] as const; export type TMatchEndAction = (typeof MatchEndActions)[number]; export const MatchStates = ['ELECTION', 'MATCH_MAP', 'FINISHED'] as const; -export type TMatchSate = (typeof MatchStates)[number]; +/** + * Possible match states. + */ +export type TMatchState = (typeof MatchStates)[number]; +/** + * Possible match modes. + */ export type TMatchMode = 'SINGLE' | 'LOOP'; export interface IMatch { /** tmt2 identifier for this match */ id: string; - state: TMatchSate; + state: TMatchState; /** e.g. remote identifier, will be present in every response/webhook */ passthrough?: string; /** @@ -26,16 +32,29 @@ export interface IMatch { * If the map is fixed it will not be removed from the map pool. */ mapPool: string[]; + /** + * Team A for this match. + * Team A will always be Team A in responses and webhooks. + * No matter on which side (CT/T) it is currently. + */ teamA: ITeam; + /** + * Team B for this match. + * Team B will always be Team B in responses and webhooks. + * No matter on which side (CT/T) it is currently. + */ teamB: ITeam; + /** + * List of election steps to determine the played map(s). + */ electionSteps: IElectionStep[]; - /** election state data */ + /** Data for the election process. */ election: IElection; gameServer: IGameServer; - /** log secret that is given as part of the url to the cs2 server it will send the logs to */ + /** Log secret that is given as part of the url to the CS2 server as a log receiver (logaddress_add_http). */ logSecret: string; /** - * Indicates if incoming logs from the cs2 server are parsed (otherwise they will be dropped without any action). + * Indicates if incoming logs from the CS2 server are parsed (otherwise they will be dropped without any action). * Will be set to true if match is loaded from storage (after a short delay). */ parseIncomingLogs: boolean; @@ -115,7 +134,7 @@ export interface IMatchCreateDto { } export interface IMatchUpdateDto extends Partial { - state?: TMatchSate; + state?: TMatchState; /** updates the server's log address automatically */ logSecret?: string; currentMap?: number; diff --git a/common/types/matchMap.ts b/common/types/matchMap.ts index cc4f7b3..989a507 100644 --- a/common/types/matchMap.ts +++ b/common/types/matchMap.ts @@ -1,3 +1,8 @@ +import { TTeamAB } from './team'; + +/** + * Possible match map states. + */ export type TMatchMapSate = /** map will be played in the future, server has not changed to this map, yet */ | 'PENDING' @@ -11,15 +16,14 @@ export type TMatchMapSate = | 'PAUSED' | 'FINISHED'; -export type TTeamAB = 'TEAM_A' | 'TEAM_B'; - export interface IMatchMap { - /** map name */ + /** Map name, e.g. de_anubis. */ name: string; knifeForSide: boolean; /** may change after knife round */ startAsCtTeam: TTeamAB; state: TMatchMapSate; + /** Winner of the knife round which is able to or already has picked a starting side. */ knifeWinner?: TTeamAB; readyTeams: { teamA: boolean; @@ -29,15 +33,22 @@ export interface IMatchMap { teamA: boolean; teamB: boolean; }; + /** Current score of both teams. */ score: { teamA: number; teamB: number; }; + /** If overtime is enabled (mp_overtime_enable). */ overTimeEnabled: boolean; + /** Max rounds in overtime (mp_overtime_maxrounds). */ overTimeMaxRounds: number; + /** Max rounds (mp_maxrounds). */ maxRounds: number; } +/** + * Structure to update a match map. + */ export interface IMatchMapUpdateDto extends Partial { /** reads and refreshes mp_overtime_enable, mp_overtime_maxrounds and mp_maxrounds from rcon */ _refreshOvertimeAndMaxRoundsSettings?: boolean; diff --git a/common/types/player.ts b/common/types/player.ts index 557132e..af99bb1 100644 --- a/common/types/player.ts +++ b/common/types/player.ts @@ -1,10 +1,18 @@ -import { TTeamAB } from './matchMap'; import { TTeamSides } from './stuff'; +import { TTeamAB } from './team'; +/** + * Player. + */ export interface IPlayer { + /** Steam ID 64 */ steamId64: string; + /** Name. */ name: string; + /** Current team as they joined with `.team`. */ team?: TTeamAB; + /** Current ingame side. */ side?: TTeamSides | null; + /** Player currently on the game server (online)? */ online?: boolean; } diff --git a/common/types/team.ts b/common/types/team.ts index 695313a..495beea 100644 --- a/common/types/team.ts +++ b/common/types/team.ts @@ -1,15 +1,33 @@ +/** + * Team. + */ export interface ITeam { + /** + * Passthrough data to identify team in other systems. + * Will be present in every response/webhook. + */ passthrough?: string; + /** Team name. */ name: string; + /** Advantage in map wins, useful for double elemination tournament finals. */ advantage: number; } +/** + * Team (create structure). + */ export interface ITeamCreateDto { name: string; - /** e.g. remote identifier, will be present in every response/webhook */ + /** + * Passthrough data to identify team in other systems. + * Will be present in every response/webhook. + */ passthrough?: string; - /** defaults to 0 (no advantage) */ + /** Advantage in map wins, useful for double elemination tournament finals. */ advantage?: number; } +/** Possible ingame sides of a player. */ export type TTeamString = 'Unassigned' | 'CT' | 'TERRORIST' | '' | 'Spectator'; + +export type TTeamAB = 'TEAM_A' | 'TEAM_B'; diff --git a/frontend/src/components/MatchCard.tsx b/frontend/src/components/MatchCard.tsx index 2b3167e..aa862c6 100644 --- a/frontend/src/components/MatchCard.tsx +++ b/frontend/src/components/MatchCard.tsx @@ -5,7 +5,7 @@ import { getMapScore, IMatchResponse, IMatchUpdateDto, - TMatchSate, + TMatchState, } from '../../../common'; import { SvgCopyAll } from '../assets/Icons'; import { createFetcher } from '../utils/fetcher'; @@ -32,7 +32,7 @@ export const MatchCard: Component<{ const response = prompt(t('enter state'), 'MATCH_MAP'); if (response) { patchMatch({ - state: response as TMatchSate, + state: response as TMatchState, }); } }; diff --git a/frontend/src/components/MatchEditCard.tsx b/frontend/src/components/MatchEditCard.tsx index d97fd5f..c435536 100644 --- a/frontend/src/components/MatchEditCard.tsx +++ b/frontend/src/components/MatchEditCard.tsx @@ -6,7 +6,7 @@ import { MatchEndActions, MatchStates, TMatchEndAction, - TMatchSate, + TMatchState, } from '../../../common'; import { t } from '../utils/locale'; import { Card } from './Card'; @@ -91,7 +91,7 @@ export const MatchEditCard: Component<{ setStore('state', e.currentTarget.value as TMatchSate)} + onInput={(e) => setStore('state', e.currentTarget.value as TMatchState)} > {(state) => (