From 0a2c6021e36be31aaffe8ace232179e2211b3140 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Tue, 2 May 2023 23:25:48 +0300 Subject: feat: game, chapter, map endpoints for records page --- backend/controllers/mapController.go | 108 ++++++++++++++++- backend/models/models.go | 17 +++ backend/routes/routes.go | 3 + docs/docs.go | 228 ++++++++++++++++++++++++++++++++++- docs/swagger.json | 228 ++++++++++++++++++++++++++++++++++- docs/swagger.yaml | 138 ++++++++++++++++++++- 6 files changed, 703 insertions(+), 19 deletions(-) diff --git a/backend/controllers/mapController.go b/backend/controllers/mapController.go index bd85a97..2bf1fdc 100644 --- a/backend/controllers/mapController.go +++ b/backend/controllers/mapController.go @@ -15,7 +15,6 @@ import ( // // @Summary Get map summary with specified id. // @Tags maps -// @Accept json // @Produce json // @Param id path int true "Map ID" // @Success 200 {object} models.Response{data=models.Map{data=models.MapSummary}} @@ -103,7 +102,6 @@ func FetchMapSummary(c *gin.Context) { // // @Summary Get map leaderboards with specified id. // @Tags maps -// @Accept json // @Produce json // @Param id path int true "Map ID" // @Success 200 {object} models.Response{data=models.Map{data=models.MapRecords}} @@ -215,3 +213,109 @@ func FetchMapLeaderboards(c *gin.Context) { Data: mapData, }) } + +// GET Games +// +// @Summary Get games from the leaderboards. +// @Tags games +// @Produce json +// @Success 200 {object} models.Response{data=[]models.Game} +// @Failure 400 {object} models.Response +// @Router /games [get] +func FetchGames(c *gin.Context) { + rows, err := database.DB.Query(`SELECT id, name FROM games`) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + var games []models.Game + for rows.Next() { + var game models.Game + if err := rows.Scan(&game.ID, &game.Name); err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + games = append(games, game) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved games.", + Data: games, + }) +} + +// GET Chapters of a Game +// +// @Summary Get chapters from the specified game id. +// @Tags chapters +// @Produce json +// @Param id path int true "Game ID" +// @Success 200 {object} models.Response{data=[]models.Chapter} +// @Failure 400 {object} models.Response +// @Router /chapters/{id} [get] +func FetchChapters(c *gin.Context) { + gameID := c.Param("id") + intID, err := strconv.Atoi(gameID) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + rows, err := database.DB.Query(`SELECT id, name FROM chapters WHERE game_id = $1`, gameID) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + var chapters []models.Chapter + for rows.Next() { + var chapter models.Chapter + if err := rows.Scan(&chapter.ID, &chapter.Name); err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + chapter.GameID = intID + chapters = append(chapters, chapter) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved chapters.", + Data: chapters, + }) +} + +// GET Maps of a Chapter +// +// @Summary Get maps from the specified chapter id. +// @Tags maps +// @Produce json +// @Param id path int true "Chapter ID" +// @Success 200 {object} models.Response{data=[]models.MapShort} +// @Failure 400 {object} models.Response +// @Router /maps/{id} [get] +func FetchChapterMaps(c *gin.Context) { + chapterID := c.Param("id") + intID, err := strconv.Atoi(chapterID) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + rows, err := database.DB.Query(`SELECT id, name FROM maps WHERE chapter_id = $1`, chapterID) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + var maps []models.MapShort + for rows.Next() { + var mapShort models.MapShort + if err := rows.Scan(&mapShort.ID, &mapShort.Name); err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + mapShort.ChapterID = intID + maps = append(maps, mapShort) + } + c.JSON(http.StatusOK, models.Response{ + Success: true, + Message: "Successfully retrieved maps.", + Data: maps, + }) +} diff --git a/backend/models/models.go b/backend/models/models.go index cdcd111..8f77a93 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -153,6 +153,23 @@ type PlayerSummaries struct { GameServerIp string `json:"gameserverip"` } +type Game struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type Chapter struct { + ID int `json:"id"` + GameID int `json:"game_id"` + Name string `json:"name"` +} + +type MapShort struct { + ID int `json:"id"` + ChapterID int `json:"chapter_id"` + Name string `json:"name"` +} + func ErrorResponse(message string) Response { return Response{ Success: false, diff --git a/backend/routes/routes.go b/backend/routes/routes.go index 53d4e78..2741208 100644 --- a/backend/routes/routes.go +++ b/backend/routes/routes.go @@ -26,7 +26,10 @@ func InitRoutes(router *gin.Engine) { v1.GET("/maps/:id/summary", middleware.CheckAuth, controllers.FetchMapSummary) v1.GET("/maps/:id/leaderboards", middleware.CheckAuth, controllers.FetchMapLeaderboards) v1.POST("/maps/:id/record", middleware.CheckAuth, controllers.CreateRecordWithDemo) + v1.GET("/maps/:id", controllers.FetchChapterMaps) v1.GET("/rankings", middleware.CheckAuth, controllers.Rankings) v1.GET("/search", controllers.Search) + v1.GET("/games", controllers.FetchGames) + v1.GET("/chapters/:id", controllers.FetchChapters) } } diff --git a/docs/docs.go b/docs/docs.go index 22d4362..788ca3d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -20,6 +20,55 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/chapters/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "chapters" + ], + "summary": "Get chapters from the specified game id.", + "parameters": [ + { + "type": "integer", + "description": "Game ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Chapter" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, "/demo": { "get": { "produces": [ @@ -94,6 +143,46 @@ const docTemplate = `{ } } }, + "/games": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "games" + ], + "summary": "Get games from the leaderboards.", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Game" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, "/login": { "get": { "consumes": [ @@ -134,11 +223,57 @@ const docTemplate = `{ } } }, - "/maps/{id}/leaderboards": { + "/maps/{id}": { "get": { - "consumes": [ + "produces": [ "application/json" ], + "tags": [ + "maps" + ], + "summary": "Get maps from the specified chapter id.", + "parameters": [ + { + "type": "integer", + "description": "Chapter ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MapShort" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, + "/maps/{id}/leaderboards": { + "get": { "produces": [ "application/json" ], @@ -290,9 +425,6 @@ const docTemplate = `{ }, "/maps/{id}/summary": { "get": { - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], @@ -530,7 +662,19 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Response" + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.SearchResponse" + } + } + } + ] } }, "400": { @@ -599,6 +743,31 @@ const docTemplate = `{ } }, "definitions": { + "models.Chapter": { + "type": "object", + "properties": { + "game_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "models.Game": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.LoginResponse": { "type": "object", "properties": { @@ -662,6 +831,20 @@ const docTemplate = `{ "records": {} } }, + "models.MapShort": { + "type": "object", + "properties": { + "chapter_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.MapSummary": { "type": "object", "properties": { @@ -784,6 +967,39 @@ const docTemplate = `{ "records": {} } }, + "models.SearchResponse": { + "type": "object", + "properties": { + "maps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + } + }, + "players": { + "type": "array", + "items": { + "type": "object", + "properties": { + "steam_id": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + } + } + } + }, "models.UserRanking": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 0bebe1c..ba0e8a3 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -13,6 +13,55 @@ "host": "lp.ardapektezol.com/api", "basePath": "/v1", "paths": { + "/chapters/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "chapters" + ], + "summary": "Get chapters from the specified game id.", + "parameters": [ + { + "type": "integer", + "description": "Game ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Chapter" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, "/demo": { "get": { "produces": [ @@ -87,6 +136,46 @@ } } }, + "/games": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "games" + ], + "summary": "Get games from the leaderboards.", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Game" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, "/login": { "get": { "consumes": [ @@ -127,11 +216,57 @@ } } }, - "/maps/{id}/leaderboards": { + "/maps/{id}": { "get": { - "consumes": [ + "produces": [ "application/json" ], + "tags": [ + "maps" + ], + "summary": "Get maps from the specified chapter id.", + "parameters": [ + { + "type": "integer", + "description": "Chapter ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MapShort" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } + } + }, + "/maps/{id}/leaderboards": { + "get": { "produces": [ "application/json" ], @@ -283,9 +418,6 @@ }, "/maps/{id}/summary": { "get": { - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], @@ -523,7 +655,19 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Response" + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.SearchResponse" + } + } + } + ] } }, "400": { @@ -592,6 +736,31 @@ } }, "definitions": { + "models.Chapter": { + "type": "object", + "properties": { + "game_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "models.Game": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.LoginResponse": { "type": "object", "properties": { @@ -655,6 +824,20 @@ "records": {} } }, + "models.MapShort": { + "type": "object", + "properties": { + "chapter_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.MapSummary": { "type": "object", "properties": { @@ -777,6 +960,39 @@ "records": {} } }, + "models.SearchResponse": { + "type": "object", + "properties": { + "maps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + } + }, + "players": { + "type": "array", + "items": { + "type": "object", + "properties": { + "steam_id": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + } + } + } + }, "models.UserRanking": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f719008..8b66ec8 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,21 @@ basePath: /v1 definitions: + models.Chapter: + properties: + game_id: + type: integer + id: + type: integer + name: + type: string + type: object + models.Game: + properties: + id: + type: integer + name: + type: string + type: object models.LoginResponse: properties: token: @@ -41,6 +57,15 @@ definitions: properties: records: {} type: object + models.MapShort: + properties: + chapter_id: + type: integer + id: + type: integer + name: + type: string + type: object models.MapSummary: properties: category_scores: @@ -122,6 +147,27 @@ definitions: type: integer records: {} type: object + models.SearchResponse: + properties: + maps: + items: + properties: + id: + type: integer + name: + type: string + type: object + type: array + players: + items: + properties: + steam_id: + type: string + user_name: + type: string + type: object + type: array + type: object models.UserRanking: properties: total_score: @@ -141,6 +187,35 @@ info: title: Least Portals Database API version: "1.0" paths: + /chapters/{id}: + get: + parameters: + - description: Game ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + items: + $ref: '#/definitions/models.Chapter' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Response' + summary: Get chapters from the specified game id. + tags: + - chapters /demo: get: produces: @@ -186,6 +261,29 @@ paths: summary: Get demo with specified demo uuid. tags: - demo + /games: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + items: + $ref: '#/definitions/models.Game' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Response' + summary: Get games from the leaderboards. + tags: + - games /login: get: consumes: @@ -209,10 +307,37 @@ paths: summary: Get (redirect) login page for Steam auth. tags: - login - /maps/{id}/leaderboards: + /maps/{id}: get: - consumes: + parameters: + - description: Chapter ID + in: path + name: id + required: true + type: integer + produces: - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + items: + $ref: '#/definitions/models.MapShort' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Response' + summary: Get maps from the specified chapter id. + tags: + - maps + /maps/{id}/leaderboards: + get: parameters: - description: Map ID in: path @@ -305,8 +430,6 @@ paths: - maps /maps/{id}/summary: get: - consumes: - - application/json parameters: - description: Map ID in: path @@ -447,7 +570,12 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.Response' + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/models.SearchResponse' + type: object "400": description: Bad Request schema: -- cgit v1.2.3