From 3f12ba743ef26625158249e6df79fb53a787df42 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sun, 6 Jul 2025 18:48:18 -0400 Subject: [PATCH 1/4] Fix map doc --- pkg/api/dto/map.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/api/dto/map.go b/pkg/api/dto/map.go index 7986e36..5251df1 100644 --- a/pkg/api/dto/map.go +++ b/pkg/api/dto/map.go @@ -7,7 +7,8 @@ import ( type MapFilter struct { GameID *int32 `json:"game_id" form:"game_id"` -} // @name UserFilter +} // @name MapFilter + type Map struct { ID int64 `json:"id"` DisplayName string `json:"display_name"` -- 2.49.1 From fbb22751f0b6a68153f5fae87facd20fd6a822f8 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sun, 6 Jul 2025 18:49:05 -0400 Subject: [PATCH 2/4] Add time rank endpoint --- pkg/api/dto/times.go | 16 ++++++++ pkg/api/handlers/times.go | 81 +++++++++++++++++++++++++++++++++++++++ pkg/api/router.go | 1 + 3 files changed, 98 insertions(+) diff --git a/pkg/api/dto/times.go b/pkg/api/dto/times.go index 9d34285..90b87cc 100644 --- a/pkg/api/dto/times.go +++ b/pkg/api/dto/times.go @@ -5,6 +5,21 @@ import ( "time" ) +type TimeRank struct { + ID int64 `json:"id"` + Rank int64 `json:"rank"` +} // @name TimeRank + +func (r TimeRank) FromGRPC(rank *times.RankResponse) *TimeRank { + if rank == nil { + return nil + } + return &TimeRank{ + ID: rank.ID, + Rank: rank.Rank, + } +} + type TimeFilter struct { UserID *int64 `json:"user_id" form:"user_id"` MapID *int64 `json:"map_id" form:"map_id"` @@ -12,6 +27,7 @@ type TimeFilter struct { ModeID *int32 `json:"mode_id" form:"mode_id"` GameID *int32 `json:"game_id" form:"game_id"` } // @TimeFilter + type TimeData struct { ID int64 `json:"id"` Time int64 `json:"time"` diff --git a/pkg/api/handlers/times.go b/pkg/api/handlers/times.go index 132fee8..13158fd 100644 --- a/pkg/api/handlers/times.go +++ b/pkg/api/handlers/times.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "git.itzana.me/strafesnet/go-grpc/times" "git.itzana.me/strafesnet/public-api/pkg/api/dto" "github.com/gin-gonic/gin" @@ -10,6 +11,7 @@ import ( "math" "net/http" "strconv" + "strings" ) // TimesHandler handles HTTP requests related to times. @@ -235,3 +237,82 @@ func (h *TimesHandler) WrList(ctx *gin.Context) { }, }) } + +// @Summary Get rank batch +// @Description Get rank information for multiple times +// @Tags times +// @Produce json +// @Security ApiKeyAuth +// @Param ids query []int64 true "Comma-separated array of time IDs (25 Limit)" +// @Success 200 {object} dto.Response[[]dto.TimeRank] +// @Failure 400 {object} dto.Error "Invalid request" +// @Failure default {object} dto.Error "General error response" +// @Router /time/rank [get] +func (h *TimesHandler) GetRanks(ctx *gin.Context) { + // Get the comma-separated IDs from query parameter + idsParam := ctx.Query("ids") + if idsParam == "" { + ctx.JSON(http.StatusBadRequest, dto.Error{ + Error: "ids parameter is required", + }) + return + } + + // Split the comma-separated string and convert to int64 slice + idStrings := strings.Split(idsParam, ",") + var ids []int64 + + for _, idStr := range idStrings { + idStr = strings.TrimSpace(idStr) + if idStr == "" { + continue + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + ctx.JSON(http.StatusBadRequest, dto.Error{ + Error: fmt.Sprintf("Invalid ID format: %s", idStr), + }) + return + } + ids = append(ids, id) + } + + // Validate that we have at least one time ID + if len(ids) == 0 { + ctx.JSON(http.StatusBadRequest, dto.Error{ + Error: "At least one time ID is required", + }) + return + } + + // Ensure we don't have more than 25 + if len(ids) > 25 { + ctx.JSON(http.StatusBadRequest, dto.Error{ + Error: "Maximum of 25 IDs allowed", + }) + return + } + + // Call the gRPC service + rankList, err := times.NewTimesServiceClient(h.dataClient).RankBatch(ctx, ×.IdListMessage{ + ID: ids, + }) + if err != nil { + ctx.JSON(http.StatusInternalServerError, dto.Error{ + Error: "Failed to get rank data", + }) + log.WithError(err).Error("Failed to get rank data") + return + } + + // Convert gRPC response to DTO format + ranks := make([]dto.TimeRank, len(rankList.Ranks)) + for i, rank := range rankList.Ranks { + var timeRank dto.TimeRank + ranks[i] = *timeRank.FromGRPC(rank) + } + ctx.JSON(http.StatusOK, dto.Response[[]dto.TimeRank]{ + Data: ranks, + }) +} diff --git a/pkg/api/router.go b/pkg/api/router.go index 6c42c6d..2de57ca 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -107,6 +107,7 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) { // Times v1.GET("/time", timesHandler.List) v1.GET("/time/worldrecord", timesHandler.WrList) + v1.GET("/time/rank", timesHandler.GetRanks) v1.GET("/time/:id", timesHandler.Get) // Users -- 2.49.1 From 212c3086d4c58c7b3a4812ba9253b621b5f3be92 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sun, 6 Jul 2025 18:49:26 -0400 Subject: [PATCH 3/4] Update docs --- docs/docs.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++ docs/swagger.json | 73 +++++++++++++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 47 ++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index 8e02385..1ad0292 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -282,6 +282,56 @@ const docTemplate = `{ } } }, + "/time/rank": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get rank information for multiple times", + "produces": [ + "application/json" + ], + "tags": [ + "times" + ], + "summary": "Get rank batch", + "parameters": [ + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "Comma-separated array of time IDs (25 Limit)", + "name": "ids", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response-array_TimeRank" + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "default": { + "description": "General error response", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/time/worldrecord": { "get": { "security": [ @@ -813,6 +863,18 @@ const docTemplate = `{ } } }, + "Response-array_TimeRank": { + "type": "object", + "properties": { + "data": { + "description": "Data contains the actual response payload", + "type": "array", + "items": { + "$ref": "#/definitions/TimeRank" + } + } + } + }, "Time": { "type": "object", "properties": { @@ -845,6 +907,17 @@ const docTemplate = `{ } } }, + "TimeRank": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "rank": { + "type": "integer" + } + } + }, "User": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 7298afd..2cb44dd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -275,6 +275,56 @@ } } }, + "/time/rank": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get rank information for multiple times", + "produces": [ + "application/json" + ], + "tags": [ + "times" + ], + "summary": "Get rank batch", + "parameters": [ + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "Comma-separated array of time IDs (25 Limit)", + "name": "ids", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response-array_TimeRank" + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "default": { + "description": "General error response", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/time/worldrecord": { "get": { "security": [ @@ -806,6 +856,18 @@ } } }, + "Response-array_TimeRank": { + "type": "object", + "properties": { + "data": { + "description": "Data contains the actual response payload", + "type": "array", + "items": { + "$ref": "#/definitions/TimeRank" + } + } + } + }, "Time": { "type": "object", "properties": { @@ -838,6 +900,17 @@ } } }, + "TimeRank": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "rank": { + "type": "integer" + } + } + }, "User": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a7e60a8..01bd598 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -149,6 +149,14 @@ definitions: - $ref: '#/definitions/User' description: Data contains the actual response payload type: object + Response-array_TimeRank: + properties: + data: + description: Data contains the actual response payload + items: + $ref: '#/definitions/TimeRank' + type: array + type: object Time: properties: date: @@ -170,6 +178,13 @@ definitions: user: $ref: '#/definitions/User' type: object + TimeRank: + properties: + id: + type: integer + rank: + type: integer + type: object User: properties: id: @@ -393,6 +408,38 @@ paths: summary: Get time by ID tags: - times + /time/rank: + get: + description: Get rank information for multiple times + parameters: + - collectionFormat: csv + description: Comma-separated array of time IDs (25 Limit) + in: query + items: + type: integer + name: ids + required: true + type: array + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/Response-array_TimeRank' + "400": + description: Invalid request + schema: + $ref: '#/definitions/Error' + default: + description: General error response + schema: + $ref: '#/definitions/Error' + security: + - ApiKeyAuth: [] + summary: Get rank batch + tags: + - times /time/worldrecord: get: description: |- -- 2.49.1 From 1a6809f0d4b86ef85bc417ac97deae7db563f7fa Mon Sep 17 00:00:00 2001 From: itzaname Date: Sun, 6 Jul 2025 19:01:09 -0400 Subject: [PATCH 4/4] Update docs and rename endpoint --- docs/docs.go | 16 ++++++++-------- docs/swagger.json | 16 ++++++++-------- docs/swagger.yaml | 18 ++++++++++-------- pkg/api/dto/times.go | 16 ++++++++-------- pkg/api/handlers/times.go | 17 +++++++++-------- pkg/api/router.go | 2 +- 6 files changed, 44 insertions(+), 41 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 1ad0292..f8a7214 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -282,21 +282,21 @@ const docTemplate = `{ } } }, - "/time/rank": { + "/time/placement": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Get rank information for multiple times", + "description": "Get placement information for multiple times\nInvalid or not found time IDs are omitted in the response", "produces": [ "application/json" ], "tags": [ "times" ], - "summary": "Get rank batch", + "summary": "Get placement batch", "parameters": [ { "type": "array", @@ -314,7 +314,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/Response-array_TimeRank" + "$ref": "#/definitions/Response-array_TimePlacement" } }, "400": { @@ -863,14 +863,14 @@ const docTemplate = `{ } } }, - "Response-array_TimeRank": { + "Response-array_TimePlacement": { "type": "object", "properties": { "data": { "description": "Data contains the actual response payload", "type": "array", "items": { - "$ref": "#/definitions/TimeRank" + "$ref": "#/definitions/TimePlacement" } } } @@ -907,13 +907,13 @@ const docTemplate = `{ } } }, - "TimeRank": { + "TimePlacement": { "type": "object", "properties": { "id": { "type": "integer" }, - "rank": { + "placement": { "type": "integer" } } diff --git a/docs/swagger.json b/docs/swagger.json index 2cb44dd..6d393e7 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -275,21 +275,21 @@ } } }, - "/time/rank": { + "/time/placement": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Get rank information for multiple times", + "description": "Get placement information for multiple times\nInvalid or not found time IDs are omitted in the response", "produces": [ "application/json" ], "tags": [ "times" ], - "summary": "Get rank batch", + "summary": "Get placement batch", "parameters": [ { "type": "array", @@ -307,7 +307,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/Response-array_TimeRank" + "$ref": "#/definitions/Response-array_TimePlacement" } }, "400": { @@ -856,14 +856,14 @@ } } }, - "Response-array_TimeRank": { + "Response-array_TimePlacement": { "type": "object", "properties": { "data": { "description": "Data contains the actual response payload", "type": "array", "items": { - "$ref": "#/definitions/TimeRank" + "$ref": "#/definitions/TimePlacement" } } } @@ -900,13 +900,13 @@ } } }, - "TimeRank": { + "TimePlacement": { "type": "object", "properties": { "id": { "type": "integer" }, - "rank": { + "placement": { "type": "integer" } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 01bd598..69e9114 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -149,12 +149,12 @@ definitions: - $ref: '#/definitions/User' description: Data contains the actual response payload type: object - Response-array_TimeRank: + Response-array_TimePlacement: properties: data: description: Data contains the actual response payload items: - $ref: '#/definitions/TimeRank' + $ref: '#/definitions/TimePlacement' type: array type: object Time: @@ -178,11 +178,11 @@ definitions: user: $ref: '#/definitions/User' type: object - TimeRank: + TimePlacement: properties: id: type: integer - rank: + placement: type: integer type: object User: @@ -408,9 +408,11 @@ paths: summary: Get time by ID tags: - times - /time/rank: + /time/placement: get: - description: Get rank information for multiple times + description: |- + Get placement information for multiple times + Invalid or not found time IDs are omitted in the response parameters: - collectionFormat: csv description: Comma-separated array of time IDs (25 Limit) @@ -426,7 +428,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/Response-array_TimeRank' + $ref: '#/definitions/Response-array_TimePlacement' "400": description: Invalid request schema: @@ -437,7 +439,7 @@ paths: $ref: '#/definitions/Error' security: - ApiKeyAuth: [] - summary: Get rank batch + summary: Get placement batch tags: - times /time/worldrecord: diff --git a/pkg/api/dto/times.go b/pkg/api/dto/times.go index 90b87cc..652a8c2 100644 --- a/pkg/api/dto/times.go +++ b/pkg/api/dto/times.go @@ -5,18 +5,18 @@ import ( "time" ) -type TimeRank struct { - ID int64 `json:"id"` - Rank int64 `json:"rank"` -} // @name TimeRank +type TimePlacement struct { + ID int64 `json:"id"` + Placement int64 `json:"placement"` +} // @name TimePlacement -func (r TimeRank) FromGRPC(rank *times.RankResponse) *TimeRank { +func (r TimePlacement) FromGRPC(rank *times.RankResponse) *TimePlacement { if rank == nil { return nil } - return &TimeRank{ - ID: rank.ID, - Rank: rank.Rank, + return &TimePlacement{ + ID: rank.ID, + Placement: rank.Rank, } } diff --git a/pkg/api/handlers/times.go b/pkg/api/handlers/times.go index 13158fd..73e87ab 100644 --- a/pkg/api/handlers/times.go +++ b/pkg/api/handlers/times.go @@ -238,17 +238,18 @@ func (h *TimesHandler) WrList(ctx *gin.Context) { }) } -// @Summary Get rank batch -// @Description Get rank information for multiple times +// @Summary Get placement batch +// @Description Get placement information for multiple times +// @Description Invalid or not found time IDs are omitted in the response // @Tags times // @Produce json // @Security ApiKeyAuth // @Param ids query []int64 true "Comma-separated array of time IDs (25 Limit)" -// @Success 200 {object} dto.Response[[]dto.TimeRank] +// @Success 200 {object} dto.Response[[]dto.TimePlacement] // @Failure 400 {object} dto.Error "Invalid request" // @Failure default {object} dto.Error "General error response" -// @Router /time/rank [get] -func (h *TimesHandler) GetRanks(ctx *gin.Context) { +// @Router /time/placement [get] +func (h *TimesHandler) GetPlacements(ctx *gin.Context) { // Get the comma-separated IDs from query parameter idsParam := ctx.Query("ids") if idsParam == "" { @@ -307,12 +308,12 @@ func (h *TimesHandler) GetRanks(ctx *gin.Context) { } // Convert gRPC response to DTO format - ranks := make([]dto.TimeRank, len(rankList.Ranks)) + ranks := make([]dto.TimePlacement, len(rankList.Ranks)) for i, rank := range rankList.Ranks { - var timeRank dto.TimeRank + var timeRank dto.TimePlacement ranks[i] = *timeRank.FromGRPC(rank) } - ctx.JSON(http.StatusOK, dto.Response[[]dto.TimeRank]{ + ctx.JSON(http.StatusOK, dto.Response[[]dto.TimePlacement]{ Data: ranks, }) } diff --git a/pkg/api/router.go b/pkg/api/router.go index 2de57ca..6d56d64 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -107,7 +107,7 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) { // Times v1.GET("/time", timesHandler.List) v1.GET("/time/worldrecord", timesHandler.WrList) - v1.GET("/time/rank", timesHandler.GetRanks) + v1.GET("/time/placement", timesHandler.GetPlacements) v1.GET("/time/:id", timesHandler.Get) // Users -- 2.49.1