bot download endpoint
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"git.itzana.me/strafesnet/go-grpc/times"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/times"
|
||||
)
|
||||
|
||||
type TimePlacement struct {
|
||||
@@ -41,6 +42,16 @@ type TimeData struct {
|
||||
GameID int32 `json:"game_id"`
|
||||
} // @name Time
|
||||
|
||||
type BotDownloadUrl struct {
|
||||
Url string `json:"url"`
|
||||
} // @name BotDownloadUrl
|
||||
|
||||
type FileInfo struct {
|
||||
ID string `json:"ID"`
|
||||
Created int64 `json:"Created"`
|
||||
Url string `json:"Url"`
|
||||
} // @name FileInfo
|
||||
|
||||
// FromGRPC converts a TimeResponse protobuf message to a TimeData domain object
|
||||
func (t *TimeData) FromGRPC(resp *times.TimeResponse) *TimeData {
|
||||
if resp == nil {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/bots"
|
||||
"git.itzana.me/strafesnet/go-grpc/times"
|
||||
"git.itzana.me/strafesnet/public-api/pkg/api/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -18,16 +20,20 @@ import (
|
||||
// TimesHandler handles HTTP requests related to times.
|
||||
type TimesHandler struct {
|
||||
*Handler
|
||||
client *http.Client
|
||||
url string
|
||||
}
|
||||
|
||||
// NewTimesHandler creates a new TimesHandler with the provided options.
|
||||
func NewTimesHandler(options ...HandlerOption) (*TimesHandler, error) {
|
||||
func NewTimesHandler(http_client *http.Client, storage_url string, options ...HandlerOption) (*TimesHandler, error) {
|
||||
baseHandler, err := NewHandler(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TimesHandler{
|
||||
Handler: baseHandler,
|
||||
client: http_client,
|
||||
url: storage_url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -318,3 +324,158 @@ func (h *TimesHandler) GetPlacements(ctx *gin.Context) {
|
||||
Data: ranks,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Get bot download url by time ID
|
||||
// @Description Get a download url for the bot replay of a time by its ID if it exists
|
||||
// @Tags times
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param id path int true "Time ID"
|
||||
// @Success 200 {object} dto.Response[dto.BotDownloadUrl]
|
||||
// @Failure 404 {object} dto.Error "Time not found"
|
||||
// @Failure 404 {object} dto.Error "Time does not have a Bot"
|
||||
// @Failure 404 {object} dto.Error "Bot not found"
|
||||
// @Failure default {object} dto.Error "General error response"
|
||||
// @Router /time/{id} [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, ×.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.WithError(err).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 := h.url + botData.FileID
|
||||
|
||||
// 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 := h.client.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.WithError(err).Error(
|
||||
"Unexpected status",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the JSON body into the FileInfo struct.
|
||||
var info dto.FileInfo
|
||||
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 time data
|
||||
ctx.JSON(http.StatusOK, dto.Response[dto.BotDownloadUrl]{
|
||||
Data: dto.BotDownloadUrl{
|
||||
Url: info.Url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/StrafesNET/dev-service/pkg/api/middleware"
|
||||
"git.itzana.me/strafesnet/public-api/docs"
|
||||
"git.itzana.me/strafesnet/public-api/pkg/api/handlers"
|
||||
@@ -13,8 +16,6 @@ import (
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Option defines a function that configures a Router
|
||||
@@ -75,7 +76,7 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
|
||||
}
|
||||
|
||||
// Times handler
|
||||
timesHandler, err := handlers.NewTimesHandler(handlerOptions...)
|
||||
timesHandler, err := handlers.NewTimesHandler(HTTP_CLIENT, STORAGE_URL, handlerOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -121,7 +122,14 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
|
||||
|
||||
// Rank
|
||||
v1.GET("/rank", rankHandler.List)
|
||||
}
|
||||
|
||||
v1_bots := r.Group("/api/v1")
|
||||
{
|
||||
// Auth middleware
|
||||
v1_bots.Use(middleware.ValidateRequest("Storage", "Read", cfg.devClient))
|
||||
|
||||
v1_bots.GET("/time/:id/download-url", timesHandler.GetDownloadUrl)
|
||||
}
|
||||
r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
r.GET("/", func(ctx *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user