From 345fa5d06a47837fca48137163f8a7bdae33cebf Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:08:27 +0300 Subject: feat: improved map summary response (#43) --- backend/controllers/mapController.go | 79 ++++++++++------------ backend/models/models.go | 37 +++++----- backend/models/responses.go | 5 ++ docs/docs.go | 127 +++++++++++++++-------------------- docs/swagger.json | 127 +++++++++++++++-------------------- docs/swagger.yaml | 82 +++++++++++----------- 6 files changed, 207 insertions(+), 250 deletions(-) diff --git a/backend/controllers/mapController.go b/backend/controllers/mapController.go index d8783b7..37b8e9b 100644 --- a/backend/controllers/mapController.go +++ b/backend/controllers/mapController.go @@ -3,10 +3,8 @@ package controllers import ( "net/http" "strconv" - "time" "github.com/gin-gonic/gin" - "github.com/lib/pq" "github.com/pektezol/leastportals/backend/database" "github.com/pektezol/leastportals/backend/models" ) @@ -17,37 +15,20 @@ import ( // @Tags maps // @Produce json // @Param id path int true "Map ID" -// @Success 200 {object} models.Response{data=models.Map{data=models.MapSummary}} +// @Success 200 {object} models.Response{data=models.MapSummaryResponse} // @Failure 400 {object} models.Response // @Router /maps/{id}/summary [get] func FetchMapSummary(c *gin.Context) { id := c.Param("id") // Get map data - var mapData models.Map - var mapSummaryData models.MapSummary - var mapHistoryData []models.MapHistory + response := models.MapSummaryResponse{Map: models.Map{}, Summary: models.MapSummary{History: []models.MapHistory{}, Routes: []models.MapRoute{}}} intID, err := strconv.Atoi(id) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - mapData.ID = intID - var routers pq.StringArray - sql := `SELECT g.name, c.name, m.name, m.description, m.showcase, - ( - SELECT array_agg(user_name) - FROM map_routers - WHERE map_id = $1 - AND score_count = ( - SELECT score_count - FROM map_history - WHERE map_id = $1 - ORDER BY score_count - LIMIT 1 - ) - GROUP BY map_routers.user_name - ORDER BY user_name - ), + response.Map.ID = intID + sql := `SELECT m.id, g.name, c.name, m.name, ( SELECT COALESCE(avg(rating), 0.0) FROM map_ratings @@ -57,44 +38,53 @@ func FetchMapSummary(c *gin.Context) { INNER JOIN games g ON m.game_id = g.id INNER JOIN chapters c ON m.chapter_id = c.id WHERE m.id = $1` - // TODO: CategoryScores - err = database.DB.QueryRow(sql, id).Scan(&mapData.GameName, &mapData.ChapterName, &mapData.MapName, &mapSummaryData.Description, &mapSummaryData.Showcase, &routers, &mapSummaryData.Rating) + err = database.DB.QueryRow(sql, id).Scan(&response.Map.ID, &response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &response.Summary.Rating) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - var historyNames pq.StringArray - var historyScores pq.Int32Array - var historyDates pq.StringArray - sql = `SELECT array_agg(user_name), array_agg(score_count), array_agg(record_date) + sql = `SELECT user_name, score_count, record_date FROM map_history - WHERE map_id = $1` - err = database.DB.QueryRow(sql, id).Scan(&historyNames, &historyScores, &historyDates) + WHERE map_id = $1 + ORDER BY record_date ASC` + rows, err := database.DB.Query(sql, id) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + for rows.Next() { + history := models.MapHistory{} + err = rows.Scan(&history.RunnerName, &history.ScoreCount, &history.Date) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } + response.Summary.History = append(response.Summary.History, history) + } + sql = `SELECT c.id, c.name, mr.score_count, mr.description, mr.showcase + FROM map_routes mr + INNER JOIN categories c ON mr.category_id = c.id + WHERE mr.map_id = $1 + ORDER BY mr.score_count DESC` + rows, err = database.DB.Query(sql, id) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - for i := 0; i < len(historyNames); i++ { - var history models.MapHistory - history.RunnerName = historyNames[i] - history.ScoreCount = int(historyScores[i]) - layout := "2006-01-02 15:04:05" - date, err := time.Parse(layout, historyDates[i]) + for rows.Next() { + route := models.MapRoute{} + err = rows.Scan(&route.Category.ID, &route.Category.Name, &route.ScoreCount, &route.Description, &route.Showcase) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - history.Date = date - mapHistoryData = append(mapHistoryData, history) + response.Summary.Routes = append(response.Summary.Routes, route) } - mapSummaryData.History = mapHistoryData - mapSummaryData.Routers = routers - mapData.Data = mapSummaryData // Return response c.JSON(http.StatusOK, models.Response{ Success: true, Message: "Successfully retrieved map summary.", - Data: mapData, + Data: response, }) } @@ -108,6 +98,7 @@ func FetchMapSummary(c *gin.Context) { // @Failure 400 {object} models.Response // @Router /maps/{id}/leaderboards [get] func FetchMapLeaderboards(c *gin.Context) { + // TODO: make new response type id := c.Param("id") // Get map data var mapData models.Map @@ -205,7 +196,7 @@ func FetchMapLeaderboards(c *gin.Context) { } mapRecordsData.Records = records } - mapData.Data = mapRecordsData + // mapData.Data = mapRecordsData // Return response c.JSON(http.StatusOK, models.Response{ Success: true, diff --git a/backend/models/models.go b/backend/models/models.go index 49a4f82..5c9b8f8 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -23,7 +23,6 @@ type Map struct { GameName string `json:"game_name"` ChapterName string `json:"chapter_name"` MapName string `json:"map_name"` - Data any `json:"data"` } type MapShort struct { @@ -32,23 +31,9 @@ type MapShort struct { } type MapSummary struct { - Description string `json:"description"` - Showcase string `json:"showcase"` - CategoryScores MapCategoryScores `json:"category_scores"` - Rating float32 `json:"rating"` - Routers []string `json:"routers"` - History []MapHistory `json:"history"` -} - -type MapCategoryScores struct { - CM int `json:"cm"` - NoSLA int `json:"no_sla"` - InboundsSLA int `json:"inbounds_sla"` - Any int `json:"any"` -} - -type MapRecords struct { - Records any `json:"records"` + Rating float32 `json:"rating"` + History []MapHistory `json:"history"` + Routes []MapRoute `json:"routes"` } type MapHistory struct { @@ -57,6 +42,17 @@ type MapHistory struct { Date time.Time `json:"date"` } +type MapRoute struct { + Category Category `json:"category"` + ScoreCount int `json:"score_count"` + Description string `json:"description"` + Showcase string `json:"showcase"` +} + +type MapRecords struct { + Records any `json:"records"` +} + type UserRanking struct { UserID string `json:"user_id"` UserName string `json:"user_name"` @@ -73,6 +69,11 @@ type Chapter struct { Name string `json:"name"` } +type Category struct { + ID int `json:"id"` + Name string `json:"name"` +} + type RecordSP struct { RecordID int `json:"record_id"` Placement int `json:"placement"` diff --git a/backend/models/responses.go b/backend/models/responses.go index 5a88353..dc554ff 100644 --- a/backend/models/responses.go +++ b/backend/models/responses.go @@ -30,6 +30,11 @@ type ScoreResponse struct { Records any `json:"records"` } +type MapSummaryResponse struct { + Map Map `json:"map"` + Summary MapSummary `json:"summary"` +} + type SearchResponse struct { Players []UserShort `json:"players"` Maps []MapShort `json:"maps"` diff --git a/docs/docs.go b/docs/docs.go index 34aa7f4..cf6c00d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -447,19 +447,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "allOf": [ - { - "$ref": "#/definitions/models.Map" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/models.MapSummary" - } - } - } - ] + "$ref": "#/definitions/models.MapSummaryResponse" } } } @@ -560,19 +548,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/models.ProfileResponse" - } - } - } - ] + "$ref": "#/definitions/models.Response" } }, "400": { @@ -817,6 +793,17 @@ const docTemplate = `{ } }, "definitions": { + "models.Category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.Chapter": { "type": "object", "properties": { @@ -881,7 +868,6 @@ const docTemplate = `{ "chapter_name": { "type": "string" }, - "data": {}, "game_name": { "type": "string" }, @@ -893,23 +879,6 @@ const docTemplate = `{ } } }, - "models.MapCategoryScores": { - "type": "object", - "properties": { - "any": { - "type": "integer" - }, - "cm": { - "type": "integer" - }, - "inbounds_sla": { - "type": "integer" - }, - "no_sla": { - "type": "integer" - } - } - }, "models.MapHistory": { "type": "object", "properties": { @@ -930,6 +899,23 @@ const docTemplate = `{ "records": {} } }, + "models.MapRoute": { + "type": "object", + "properties": { + "category": { + "$ref": "#/definitions/models.Category" + }, + "description": { + "type": "string" + }, + "score_count": { + "type": "integer" + }, + "showcase": { + "type": "string" + } + } + }, "models.MapShort": { "type": "object", "properties": { @@ -944,12 +930,6 @@ const docTemplate = `{ "models.MapSummary": { "type": "object", "properties": { - "category_scores": { - "$ref": "#/definitions/models.MapCategoryScores" - }, - "description": { - "type": "string" - }, "history": { "type": "array", "items": { @@ -959,14 +939,22 @@ const docTemplate = `{ "rating": { "type": "number" }, - "routers": { + "routes": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/models.MapRoute" } + } + } + }, + "models.MapSummaryResponse": { + "type": "object", + "properties": { + "map": { + "$ref": "#/definitions/models.Map" }, - "showcase": { - "type": "string" + "summary": { + "$ref": "#/definitions/models.MapSummary" } } }, @@ -1069,29 +1057,13 @@ const docTemplate = `{ "maps": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } + "$ref": "#/definitions/models.MapShort" } }, "players": { "type": "array", "items": { - "type": "object", - "properties": { - "steam_id": { - "type": "string" - }, - "user_name": { - "type": "string" - } - } + "$ref": "#/definitions/models.UserShort" } } } @@ -1109,6 +1081,17 @@ const docTemplate = `{ "type": "string" } } + }, + "models.UserShort": { + "type": "object", + "properties": { + "steam_id": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 8491d2e..f2ed3f0 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -440,19 +440,7 @@ "type": "object", "properties": { "data": { - "allOf": [ - { - "$ref": "#/definitions/models.Map" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/models.MapSummary" - } - } - } - ] + "$ref": "#/definitions/models.MapSummaryResponse" } } } @@ -553,19 +541,7 @@ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/models.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/models.ProfileResponse" - } - } - } - ] + "$ref": "#/definitions/models.Response" } }, "400": { @@ -810,6 +786,17 @@ } }, "definitions": { + "models.Category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "models.Chapter": { "type": "object", "properties": { @@ -874,7 +861,6 @@ "chapter_name": { "type": "string" }, - "data": {}, "game_name": { "type": "string" }, @@ -886,23 +872,6 @@ } } }, - "models.MapCategoryScores": { - "type": "object", - "properties": { - "any": { - "type": "integer" - }, - "cm": { - "type": "integer" - }, - "inbounds_sla": { - "type": "integer" - }, - "no_sla": { - "type": "integer" - } - } - }, "models.MapHistory": { "type": "object", "properties": { @@ -923,6 +892,23 @@ "records": {} } }, + "models.MapRoute": { + "type": "object", + "properties": { + "category": { + "$ref": "#/definitions/models.Category" + }, + "description": { + "type": "string" + }, + "score_count": { + "type": "integer" + }, + "showcase": { + "type": "string" + } + } + }, "models.MapShort": { "type": "object", "properties": { @@ -937,12 +923,6 @@ "models.MapSummary": { "type": "object", "properties": { - "category_scores": { - "$ref": "#/definitions/models.MapCategoryScores" - }, - "description": { - "type": "string" - }, "history": { "type": "array", "items": { @@ -952,14 +932,22 @@ "rating": { "type": "number" }, - "routers": { + "routes": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/models.MapRoute" } + } + } + }, + "models.MapSummaryResponse": { + "type": "object", + "properties": { + "map": { + "$ref": "#/definitions/models.Map" }, - "showcase": { - "type": "string" + "summary": { + "$ref": "#/definitions/models.MapSummary" } } }, @@ -1062,29 +1050,13 @@ "maps": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } + "$ref": "#/definitions/models.MapShort" } }, "players": { "type": "array", "items": { - "type": "object", - "properties": { - "steam_id": { - "type": "string" - }, - "user_name": { - "type": "string" - } - } + "$ref": "#/definitions/models.UserShort" } } } @@ -1102,6 +1074,17 @@ "type": "string" } } + }, + "models.UserShort": { + "type": "object", + "properties": { + "steam_id": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 597df9f..61f2ad7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,12 @@ basePath: /v1 definitions: + models.Category: + properties: + id: + type: integer + name: + type: string + type: object models.Chapter: properties: id: @@ -41,7 +48,6 @@ definitions: properties: chapter_name: type: string - data: {} game_name: type: string id: @@ -49,17 +55,6 @@ definitions: map_name: type: string type: object - models.MapCategoryScores: - properties: - any: - type: integer - cm: - type: integer - inbounds_sla: - type: integer - no_sla: - type: integer - type: object models.MapHistory: properties: date: @@ -73,6 +68,17 @@ definitions: properties: records: {} type: object + models.MapRoute: + properties: + category: + $ref: '#/definitions/models.Category' + description: + type: string + score_count: + type: integer + showcase: + type: string + type: object models.MapShort: properties: id: @@ -82,22 +88,23 @@ definitions: type: object models.MapSummary: properties: - category_scores: - $ref: '#/definitions/models.MapCategoryScores' - description: - type: string history: items: $ref: '#/definitions/models.MapHistory' type: array rating: type: number - routers: + routes: items: - type: string + $ref: '#/definitions/models.MapRoute' type: array - showcase: - type: string + type: object + models.MapSummaryResponse: + properties: + map: + $ref: '#/definitions/models.Map' + summary: + $ref: '#/definitions/models.MapSummary' type: object models.ProfileResponse: properties: @@ -165,21 +172,11 @@ definitions: properties: maps: items: - properties: - id: - type: integer - name: - type: string - type: object + $ref: '#/definitions/models.MapShort' type: array players: items: - properties: - steam_id: - type: string - user_name: - type: string - type: object + $ref: '#/definitions/models.UserShort' type: array type: object models.UserRanking: @@ -191,6 +188,13 @@ definitions: user_name: type: string type: object + models.UserShort: + properties: + steam_id: + type: string + user_name: + type: string + type: object host: lp.ardapektezol.com/api info: contact: {} @@ -456,12 +460,7 @@ paths: - $ref: '#/definitions/models.Response' - properties: data: - allOf: - - $ref: '#/definitions/models.Map' - - properties: - data: - $ref: '#/definitions/models.MapSummary' - type: object + $ref: '#/definitions/models.MapSummaryResponse' type: object "400": description: Bad Request @@ -555,12 +554,7 @@ paths: "200": description: OK schema: - allOf: - - $ref: '#/definitions/models.Response' - - properties: - data: - $ref: '#/definitions/models.ProfileResponse' - type: object + $ref: '#/definitions/models.Response' "400": description: Bad Request schema: -- cgit v1.2.3