4 Commits

Author SHA1 Message Date
a0f65394d4 wip: fields
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-07-25 22:28:18 -07:00
7a37224487 connect to maps grpc
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2025-07-25 20:45:40 -07:00
b98b184a32 switch maps queries to maps_extended
times continues to use the embedded map
2025-07-25 20:38:50 -07:00
dace105a9a update go-grpc for maps_extended 2025-07-25 20:31:56 -07:00
15 changed files with 122 additions and 383 deletions

View File

@@ -61,9 +61,4 @@ steps:
when: when:
branch: branch:
- master - master
- staging - staging
---
kind: signature
hmac: 7655eb6dead73d2ad977685120cee8562931036bb5d7fa59d30d5917840c4a22
...

View File

@@ -1,5 +1,6 @@
clean: clean:
rm -rf build rm -rf build
rm -rf web/dist
test: test:
go fmt ./... go fmt ./...

View File

@@ -301,8 +301,7 @@ const docTemplate = `{
{ {
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer"
"format": "int64"
}, },
"collectionFormat": "csv", "collectionFormat": "csv",
"description": "Comma-separated array of time IDs (25 Limit)", "description": "Comma-separated array of time IDs (25 Limit)",
@@ -454,52 +453,6 @@ const docTemplate = `{
} }
} }
}, },
"/time/{id}/bot": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Get a HTTP 302 Redirect to the download url for the bot replay of a time by its ID if it exists",
"tags": [
"times"
],
"summary": "Get redirect to bot download url by time ID",
"parameters": [
{
"type": "integer",
"description": "Time ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"302": {
"description": "Found",
"headers": {
"Location": {
"type": "string",
"description": "Redirect URL"
}
}
},
"404": {
"description": "Time does not have a Bot",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "General error response",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/user": { "/user": {
"get": { "get": {
"security": [ "security": [
@@ -678,6 +631,12 @@ const docTemplate = `{
"Map": { "Map": {
"type": "object", "type": "object",
"properties": { "properties": {
"creator": {
"type": "string"
},
"date": {
"type": "string"
},
"display_name": { "display_name": {
"type": "string" "type": "string"
}, },
@@ -686,9 +645,6 @@ const docTemplate = `{
}, },
"id": { "id": {
"type": "integer" "type": "integer"
},
"thumbnail": {
"type": "integer"
} }
} }
}, },

View File

@@ -294,8 +294,7 @@
{ {
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer"
"format": "int64"
}, },
"collectionFormat": "csv", "collectionFormat": "csv",
"description": "Comma-separated array of time IDs (25 Limit)", "description": "Comma-separated array of time IDs (25 Limit)",
@@ -447,52 +446,6 @@
} }
} }
}, },
"/time/{id}/bot": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Get a HTTP 302 Redirect to the download url for the bot replay of a time by its ID if it exists",
"tags": [
"times"
],
"summary": "Get redirect to bot download url by time ID",
"parameters": [
{
"type": "integer",
"description": "Time ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"302": {
"description": "Found",
"headers": {
"Location": {
"type": "string",
"description": "Redirect URL"
}
}
},
"404": {
"description": "Time does not have a Bot",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "General error response",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/user": { "/user": {
"get": { "get": {
"security": [ "security": [
@@ -671,6 +624,12 @@
"Map": { "Map": {
"type": "object", "type": "object",
"properties": { "properties": {
"creator": {
"type": "string"
},
"date": {
"type": "string"
},
"display_name": { "display_name": {
"type": "string" "type": "string"
}, },
@@ -679,9 +638,6 @@
}, },
"id": { "id": {
"type": "integer" "type": "integer"
},
"thumbnail": {
"type": "integer"
} }
} }
}, },

View File

@@ -7,14 +7,16 @@ definitions:
type: object type: object
Map: Map:
properties: properties:
creator:
type: string
date:
type: string
display_name: display_name:
type: string type: string
game_id: game_id:
type: integer type: integer
id: id:
type: integer type: integer
thumbnail:
type: integer
type: object type: object
PagedResponse-Map: PagedResponse-Map:
properties: properties:
@@ -406,36 +408,6 @@ paths:
summary: Get time by ID summary: Get time by ID
tags: tags:
- times - times
/time/{id}/bot:
get:
description: Get a HTTP 302 Redirect to the download url for the bot replay
of a time by its ID if it exists
parameters:
- description: Time ID
in: path
name: id
required: true
type: integer
responses:
"302":
description: Found
headers:
Location:
description: Redirect URL
type: string
"404":
description: Time does not have a Bot
schema:
$ref: '#/definitions/Error'
default:
description: General error response
schema:
$ref: '#/definitions/Error'
security:
- ApiKeyAuth: []
summary: Get redirect to bot download url by time ID
tags:
- times
/time/placement: /time/placement:
get: get:
description: |- description: |-
@@ -446,7 +418,6 @@ paths:
description: Comma-separated array of time IDs (25 Limit) description: Comma-separated array of time IDs (25 Limit)
in: query in: query
items: items:
format: int64
type: integer type: integer
name: ids name: ids
required: true required: true

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.24.0
require ( require (
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1 git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef git.itzana.me/strafesnet/go-grpc v0.0.0-20250724030029-845bea991815
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1

4
go.sum
View File

@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1 h1:Ndpa4d93gCQrW5pm64u9IRaDJTfu7fKfLqm8ctzML1o= git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1 h1:Ndpa4d93gCQrW5pm64u9IRaDJTfu7fKfLqm8ctzML1o=
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg= git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef h1:SJi4V4+xzScFnbMRN1gkZxcqR1xKfiT7CaXanLltEzw= git.itzana.me/strafesnet/go-grpc v0.0.0-20250724030029-845bea991815 h1:hkuOnehphRXUq/2z2UYgoqTq5MJj1GsWfshyc7bXda8=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs= git.itzana.me/strafesnet/go-grpc v0.0.0-20250724030029-845bea991815/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=

View File

@@ -2,17 +2,15 @@ package dto
import ( import (
"git.itzana.me/strafesnet/go-grpc/maps" "git.itzana.me/strafesnet/go-grpc/maps"
"time"
) )
type MapFilter struct {
GameID *int32 `json:"game_id" form:"game_id"`
} // @name MapFilter
type Map struct { type Map struct {
ID int64 `json:"id"` ID int64 `json:"id"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
Creator string `json:"creator"`
GameID int32 `json:"game_id"` GameID int32 `json:"game_id"`
Thumbnail uint64 `json:"thumbnail"` Date time.Time `json:"date"`
} // @name Map } // @name Map
// FromGRPC converts a maps.MapResponse protobuf message to a Map domain object // FromGRPC converts a maps.MapResponse protobuf message to a Map domain object
@@ -23,8 +21,9 @@ func (m *Map) FromGRPC(resp *maps.MapResponse) *Map {
m.ID = resp.ID m.ID = resp.ID
m.DisplayName = resp.DisplayName m.DisplayName = resp.DisplayName
m.Creator = resp.Creator
m.Date = time.Unix(resp.Date, 0)
m.GameID = resp.GameID m.GameID = resp.GameID
m.Thumbnail = resp.Thumbnail
return m return m
} }

View File

@@ -0,0 +1,40 @@
package dto
import (
"git.itzana.me/strafesnet/go-grpc/maps_extended"
"time"
)
type MapExtendedFilter struct {
GameID *uint32 `json:"game_id" form:"game_id"`
} // @name MapFilter
type MapExtended struct {
ID int64 `json:"id"`
DisplayName string `json:"display_name"`
Creator string `json:"creator"`
GameID uint32 `json:"game_id"`
Date time.Time `json:"date"`
CreatedAt time.Time `json:created_at`
UpdatedAt time.Time `json:updated_at`
Submitter uint64 `json:submitter`
Thumbnail uint64 `json:thumbnail`
AssetVersion uint64 `json:asset_version`
LoadCount uint32 `json:load_count`
Modes uint32 `json:modes`
} // @name Map
// FromGRPC converts a maps.MapResponse protobuf message to a Map domain object
func (m *MapExtended) FromGRPC(resp *maps_extended.MapResponse) *MapExtended {
if resp == nil {
return nil
}
m.ID = resp.ID
m.DisplayName = resp.DisplayName
m.Creator = resp.Creator
m.Date = time.Unix(resp.Date, 0)
m.GameID = resp.GameID
return m
}

View File

@@ -1,10 +1,9 @@
package dto package dto
import ( import (
"git.itzana.me/strafesnet/go-grpc/times"
"strconv" "strconv"
"time" "time"
"git.itzana.me/strafesnet/go-grpc/times"
) )
type TimePlacement struct { type TimePlacement struct {

View File

@@ -2,10 +2,9 @@ package handlers
import ( import (
"fmt" "fmt"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"google.golang.org/grpc" "google.golang.org/grpc"
"strconv"
) )
const ( const (
@@ -15,7 +14,7 @@ const (
// Handler is a base handler that provides common functionality for all HTTP handlers. // Handler is a base handler that provides common functionality for all HTTP handlers.
type Handler struct { type Handler struct {
dataClient *grpc.ClientConn dataClient *grpc.ClientConn
storageUrl string mapsClient *grpc.ClientConn
} }
// HandlerOption defines a functional option for configuring a Handler // HandlerOption defines a functional option for configuring a Handler
@@ -28,10 +27,10 @@ func WithDataClient(dataClient *grpc.ClientConn) HandlerOption {
} }
} }
// WithStorageUrl sets the storage url // WithMapsClient sets the maps client for the Handler
func WithStorageUrl(storageUrl string) HandlerOption { func WithMapsClient(mapsClient *grpc.ClientConn) HandlerOption {
return func(cfg *Handler) { return func(h *Handler) {
cfg.storageUrl = storageUrl h.mapsClient = mapsClient
} }
} }

View File

@@ -1,7 +1,7 @@
package handlers package handlers
import ( import (
"git.itzana.me/strafesnet/go-grpc/maps" "git.itzana.me/strafesnet/go-grpc/maps_extended"
"git.itzana.me/strafesnet/public-api/pkg/api/dto" "git.itzana.me/strafesnet/public-api/pkg/api/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -49,7 +49,7 @@ func (h *MapHandler) Get(ctx *gin.Context) {
} }
// Call the gRPC service // Call the gRPC service
mapData, err := maps.NewMapsServiceClient(h.dataClient).Get(ctx, &maps.IdMessage{ mapData, err := maps_extended.NewMapsServiceClient(h.mapsClient).Get(ctx, &maps_extended.MapId{
ID: mapID, ID: mapID,
}) })
if err != nil { if err != nil {
@@ -72,11 +72,11 @@ func (h *MapHandler) Get(ctx *gin.Context) {
} }
// Convert gRPC MapResponse object to dto.Map object // Convert gRPC MapResponse object to dto.Map object
var mapDto dto.Map var mapDto dto.MapExtended
result := mapDto.FromGRPC(mapData) result := mapDto.FromGRPC(mapData)
// Return the map data // Return the map data
ctx.JSON(http.StatusOK, dto.Response[dto.Map]{ ctx.JSON(http.StatusOK, dto.Response[dto.MapExtended]{
Data: *result, Data: *result,
}) })
} }
@@ -107,7 +107,7 @@ func (h *MapHandler) List(ctx *gin.Context) {
} }
// Get list filter // Get list filter
var filter dto.MapFilter var filter dto.MapExtendedFilter
if err := ctx.ShouldBindQuery(&filter); err != nil { if err := ctx.ShouldBindQuery(&filter); err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{ ctx.JSON(http.StatusBadRequest, dto.Error{
Error: err.Error(), Error: err.Error(),
@@ -116,13 +116,13 @@ func (h *MapHandler) List(ctx *gin.Context) {
} }
// Call the gRPC service // Call the gRPC service
mapList, err := maps.NewMapsServiceClient(h.dataClient).List(ctx, &maps.ListRequest{ mapList, err := maps_extended.NewMapsServiceClient(h.mapsClient).List(ctx, &maps_extended.ListRequest{
Filter: &maps.MapFilter{ Filter: &maps_extended.MapFilter{
GameID: filter.GameID, GameID: filter.GameID,
}, },
Page: &maps.Pagination{ Page: &maps_extended.Pagination{
Size: int32(query.PageSize), Size: uint32(query.PageSize),
Number: int32(query.PageNumber), Number: uint32(query.PageNumber),
}, },
}) })
if err != nil { if err != nil {
@@ -136,14 +136,14 @@ func (h *MapHandler) List(ctx *gin.Context) {
} }
// Convert gRPC MapResponse objects to dto.Map objects // Convert gRPC MapResponse objects to dto.Map objects
dtoMaps := make([]dto.Map, len(mapList.Maps)) dtoMaps := make([]dto.MapExtended, len(mapList.Maps))
for i, m := range mapList.Maps { for i, m := range mapList.Maps {
var mapDto dto.Map var mapDto dto.MapExtended
dtoMaps[i] = *mapDto.FromGRPC(m) dtoMaps[i] = *mapDto.FromGRPC(m)
} }
// Return the paged response // Return the paged response
ctx.JSON(http.StatusOK, dto.PagedResponse[dto.Map]{ ctx.JSON(http.StatusOK, dto.PagedResponse[dto.MapExtended]{
Data: dtoMaps, Data: dtoMaps,
Pagination: dto.Pagination{ Pagination: dto.Pagination{
Page: query.PageNumber, Page: query.PageNumber,

View File

@@ -1,22 +1,17 @@
package handlers package handlers
import ( import (
"encoding/json"
"fmt" "fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"git.itzana.me/strafesnet/go-grpc/bots"
"git.itzana.me/strafesnet/go-grpc/times" "git.itzana.me/strafesnet/go-grpc/times"
"git.itzana.me/strafesnet/public-api/pkg/api/dto" "git.itzana.me/strafesnet/public-api/pkg/api/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"math"
"net/http"
"strconv"
"strings"
) )
// TimesHandler handles HTTP requests related to times. // TimesHandler handles HTTP requests related to times.
@@ -322,167 +317,3 @@ func (h *TimesHandler) GetPlacements(ctx *gin.Context) {
Data: ranks, Data: ranks,
}) })
} }
// @Summary Get redirect to bot download url by time ID
// @Description Get a HTTP 302 Redirect to the download url for the bot replay of a time by its ID if it exists
// @Tags times
// @Security ApiKeyAuth
// @Param id path int true "Time ID"
// @Success 302
// @Header 302 {string} Location "Redirect URL"
// @Failure 404 {object} dto.Error "Time does not have a Bot"
// @Failure default {object} dto.Error "General error response"
// @Router /time/{id}/bot [get]
func (h *TimesHandler) GetDownloadUrl(ctx *gin.Context) {
// Extract time ID from path parameter
id := ctx.Param("id")
timeID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.Error{
Error: "Invalid time ID format",
})
return
}
// Call the gRPC service
timeData, err := times.NewTimesServiceClient(h.dataClient).Get(ctx, &times.IdMessage{
ID: timeID,
})
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Failed to get time"
// Check if it's a "not found" error
if status.Code(err) == codes.NotFound {
statusCode = http.StatusNotFound
errorMessage = "Time not found"
}
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Failed to get time",
)
return
}
// check if bot exists
if timeData.Bot == nil {
statusCode := http.StatusNotFound
errorMessage := "Time does not have a Bot"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.Error("Time does not have a Bot")
return
}
// Call the gRPC service
botData, err := bots.NewBotsServiceClient(h.dataClient).Get(ctx, &bots.IdMessage{
ID: timeData.Bot.ID,
})
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Failed to get bot"
// Check if it's a "not found" error
if status.Code(err) == codes.NotFound {
statusCode = http.StatusNotFound
errorMessage = "Bot not found"
}
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Failed to get bot",
)
return
}
// fetch download url from storage service
// Build the full URL.
fullURL, err := url.JoinPath(h.storageUrl, botData.FileID)
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Error joining Url"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Error joining Url",
)
return
}
// Create the request with the supplied context so callers can cancel it.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Error creating http request to storage"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Error creating http request to storage",
)
return
}
// Send the request.
resp, err := (&http.Client{
Timeout: 10 * time.Second,
}).Do(req)
if err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Storage http request failed"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Storage http request failed",
)
return
}
defer resp.Body.Close()
// check status
if resp.StatusCode != 200 {
statusCode := http.StatusInternalServerError
errorMessage := "Unexpected status"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.Error("Unexpected status")
return
}
type storageResp struct {
ID string `json:"ID"`
Created int64 `json:"Created"`
Url string `json:"Url"`
}
// Decode the JSON body into the storageResp struct.
var info storageResp
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
statusCode := http.StatusInternalServerError
errorMessage := "Error decoding json"
ctx.JSON(statusCode, dto.Error{
Error: errorMessage,
})
log.WithError(err).Error(
"Error decoding json",
)
return
}
// Return the download url
ctx.Redirect(http.StatusFound, info.Url)
}

View File

@@ -4,9 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"time"
"git.itzana.me/StrafesNET/dev-service/pkg/api/middleware" "git.itzana.me/StrafesNET/dev-service/pkg/api/middleware"
"git.itzana.me/strafesnet/public-api/docs" "git.itzana.me/strafesnet/public-api/docs"
"git.itzana.me/strafesnet/public-api/pkg/api/handlers" "git.itzana.me/strafesnet/public-api/pkg/api/handlers"
@@ -16,6 +13,8 @@ import (
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
"net/http"
"time"
) )
// Option defines a function that configures a Router // Option defines a function that configures a Router
@@ -26,8 +25,7 @@ type RouterConfig struct {
port int port int
devClient *grpc.ClientConn devClient *grpc.ClientConn
dataClient *grpc.ClientConn dataClient *grpc.ClientConn
httpClient *http.Client mapsClient *grpc.ClientConn
storageUrl string
context *cli.Context context *cli.Context
shutdownTimeout time.Duration shutdownTimeout time.Duration
} }
@@ -60,10 +58,10 @@ func WithDataClient(conn *grpc.ClientConn) Option {
} }
} }
// WithStorageUrl sets the storage url // WithMapsClient sets the maps gRPC client
func WithStorageUrl(storageUrl string) Option { func WithMapsClient(conn *grpc.ClientConn) Option {
return func(cfg *RouterConfig) { return func(cfg *RouterConfig) {
cfg.storageUrl = storageUrl cfg.mapsClient = conn
} }
} }
@@ -82,13 +80,11 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
handlerOptions := []handlers.HandlerOption{ handlerOptions := []handlers.HandlerOption{
handlers.WithDataClient(cfg.dataClient), handlers.WithDataClient(cfg.dataClient),
handlers.WithMapsClient(cfg.mapsClient),
} }
// Times handler // Times handler
timesHandler, err := handlers.NewTimesHandler( timesHandler, err := handlers.NewTimesHandler(handlerOptions...)
handlers.WithDataClient(cfg.dataClient),
handlers.WithStorageUrl(cfg.storageUrl),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -112,36 +108,29 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
} }
docs.SwaggerInfo.BasePath = "/api/v1" docs.SwaggerInfo.BasePath = "/api/v1"
dataGroup := r.Group("/api/v1") v1 := r.Group("/api/v1")
{ {
// Auth middleware // Auth middleware
dataGroup.Use(middleware.ValidateRequest("Data", "Read", cfg.devClient)) v1.Use(middleware.ValidateRequest("Data", "Read", cfg.devClient))
// Times // Times
dataGroup.GET("/time", timesHandler.List) v1.GET("/time", timesHandler.List)
dataGroup.GET("/time/worldrecord", timesHandler.WrList) v1.GET("/time/worldrecord", timesHandler.WrList)
dataGroup.GET("/time/placement", timesHandler.GetPlacements) v1.GET("/time/placement", timesHandler.GetPlacements)
dataGroup.GET("/time/:id", timesHandler.Get) v1.GET("/time/:id", timesHandler.Get)
// Users // Users
dataGroup.GET("/user", usersHandler.List) v1.GET("/user", usersHandler.List)
dataGroup.GET("/user/:id", usersHandler.Get) v1.GET("/user/:id", usersHandler.Get)
dataGroup.GET("/user/:id/rank", usersHandler.GetRank) v1.GET("/user/:id/rank", usersHandler.GetRank)
// Maps // Maps
dataGroup.GET("/map", mapsHandler.List) v1.GET("/map", mapsHandler.List)
dataGroup.GET("/map/:id", mapsHandler.Get) v1.GET("/map/:id", mapsHandler.Get)
// Rank // Rank
dataGroup.GET("/rank", rankHandler.List) v1.GET("/rank", rankHandler.List)
}
botsGroup := r.Group("/api/v1")
{
// Auth middleware
botsGroup.Use(middleware.ValidateRequest("Data", "Bots", cfg.devClient))
botsGroup.GET("/time/:id/bot", timesHandler.GetDownloadUrl)
} }
r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
r.GET("/", func(ctx *gin.Context) { r.GET("/", func(ctx *gin.Context) {

View File

@@ -32,10 +32,10 @@ func NewApiCommand() *cli.Command {
Value: "data-service:9000", Value: "data-service:9000",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "storage-host", Name: "maps-rpc-host",
Usage: "Host of storage", Usage: "Host of maps rpc",
EnvVars: []string{"STORAGE_HOST"}, EnvVars: []string{"MAPS_RPC_HOST"},
Value: "http://storage-service:9000/v1/file/", Value: "maptest-api:8081",
}, },
}, },
} }
@@ -54,14 +54,17 @@ func runAPI(ctx *cli.Context) error {
return err return err
} }
// Storage service http client // Maps service client
storageUrl := ctx.String("storage-host") mapsConn, err := grpc.Dial(ctx.String("maps-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
}
return api.NewRouter( return api.NewRouter(
api.WithContext(ctx), api.WithContext(ctx),
api.WithPort(ctx.Int("port")), api.WithPort(ctx.Int("port")),
api.WithDevClient(devConn), api.WithDevClient(devConn),
api.WithDataClient(dataConn), api.WithDataClient(dataConn),
api.WithStorageUrl(storageUrl), api.WithMapsClient(mapsConn),
) )
} }