Add snfm download endpoints
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -19,6 +19,8 @@ import (
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/validator_controller"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/web_api"
|
||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -123,6 +125,12 @@ func NewServeCommand() *cli.Command {
|
||||
EnvVars: []string{"REDIS_DB"},
|
||||
Value: 0,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "s3-bucket",
|
||||
Usage: "S3 bucket for map assets",
|
||||
EnvVars: []string{"S3_BUCKET"},
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -168,6 +176,13 @@ func serve(ctx *cli.Context) error {
|
||||
ApiKey: ctx.String("rbx-api-key"),
|
||||
}
|
||||
|
||||
// Initialize S3 client
|
||||
awsCfg, err := awsconfig.LoadDefaultConfig(ctx.Context)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("failed to load AWS config")
|
||||
}
|
||||
s3Client := s3.NewFromConfig(awsCfg)
|
||||
|
||||
// connect to main game database
|
||||
conn, err := grpc.Dial(ctx.String("data-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
@@ -180,6 +195,8 @@ func serve(ctx *cli.Context) error {
|
||||
users.NewUsersServiceClient(conn),
|
||||
robloxClient,
|
||||
redisClient,
|
||||
s3Client,
|
||||
ctx.String("s3-bucket"),
|
||||
)
|
||||
|
||||
svc_external := web_api.NewService(
|
||||
|
||||
@@ -195,3 +195,13 @@ func (svc *Maps) IncrementLoadCount(ctx context.Context, request *maps_extended.
|
||||
}
|
||||
return &maps_extended.NullResponse{}, nil
|
||||
}
|
||||
|
||||
func (svc *Maps) GetSnfmDownloadUrl(ctx context.Context, request *maps_extended.MapId) (*maps_extended.SnfmDownloadUrl, error) {
|
||||
url, err := svc.inner.GetSnfmDownloadUrl(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &maps_extended.SnfmDownloadUrl{
|
||||
Url: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ type Map struct {
|
||||
Modes uint32 `json:"modes"`
|
||||
} // @name Map
|
||||
|
||||
type SnfmDownloadUrl struct {
|
||||
Url string `json:"url"`
|
||||
} // @name SnfmDownloadUrl
|
||||
|
||||
// FromGRPC converts a maps.MapResponse protobuf message to a Map domain object
|
||||
func (m *Map) FromGRPC(resp *maps_extended.MapResponse) *Map {
|
||||
if resp == nil {
|
||||
|
||||
@@ -81,6 +81,47 @@ func (h *MapHandler) Get(ctx *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Download SNFM file
|
||||
// @Description Redirects to a signed download URL for a map's SNFM file
|
||||
// @Tags maps
|
||||
// @Security ApiKeyAuth
|
||||
// @Param id path int true "Map ID"
|
||||
// @Success 307 "Redirect to signed S3 URL"
|
||||
// @Failure 404 {object} dto.Error "Map not found"
|
||||
// @Failure default {object} dto.Error "General error response"
|
||||
// @Router /map/{id}/snfm [get]
|
||||
func (h *MapHandler) GetSnfmDownloadUrl(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
mapID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, dto.Error{
|
||||
Error: "Invalid map ID format",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := maps_extended.NewMapsServiceClient(h.mapsClient).GetSnfmDownloadUrl(ctx, &maps_extended.MapId{
|
||||
ID: mapID,
|
||||
})
|
||||
if err != nil {
|
||||
statusCode := http.StatusInternalServerError
|
||||
errorMessage := "Failed to get download URL"
|
||||
|
||||
if status.Code(err) == codes.NotFound {
|
||||
statusCode = http.StatusNotFound
|
||||
errorMessage = "Map not found"
|
||||
}
|
||||
|
||||
ctx.JSON(statusCode, dto.Error{
|
||||
Error: errorMessage,
|
||||
})
|
||||
log.WithError(err).Error("Failed to get SNFM download URL")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, resp.Url)
|
||||
}
|
||||
|
||||
// @Summary List maps
|
||||
// @Description Get a list of maps
|
||||
// @Tags maps
|
||||
|
||||
@@ -93,6 +93,13 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
|
||||
v1.GET("/map/:id", mapsHandler.Get)
|
||||
}
|
||||
|
||||
v1Download := public_api.Group("/v1")
|
||||
{
|
||||
v1Download.Use(middleware.ValidateRequest("Maps", "Download", cfg.devClient))
|
||||
|
||||
v1Download.GET("/map/:id/snfm", mapsHandler.GetSnfmDownloadUrl)
|
||||
}
|
||||
|
||||
// Docs
|
||||
public_api.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
public_api.GET("/", func(ctx *gin.Context) {
|
||||
|
||||
@@ -2,11 +2,14 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/go-grpc/users"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -17,6 +20,8 @@ type Service struct {
|
||||
maps maps.MapsServiceClient
|
||||
users users.UsersServiceClient
|
||||
thumbnailService *ThumbnailService
|
||||
s3Presign *s3.PresignClient
|
||||
s3Bucket string
|
||||
}
|
||||
|
||||
func NewService(
|
||||
@@ -26,6 +31,8 @@ func NewService(
|
||||
users users.UsersServiceClient,
|
||||
robloxClient *roblox.Client,
|
||||
redisClient *redis.Client,
|
||||
s3Client *s3.Client,
|
||||
s3Bucket string,
|
||||
) Service {
|
||||
return Service{
|
||||
db: db,
|
||||
@@ -33,9 +40,23 @@ func NewService(
|
||||
maps: maps,
|
||||
users: users,
|
||||
thumbnailService: NewThumbnailService(robloxClient, redisClient),
|
||||
s3Presign: s3.NewPresignClient(s3Client),
|
||||
s3Bucket: s3Bucket,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GetSnfmDownloadUrl(ctx context.Context, mapID int64) (string, error) {
|
||||
key := fmt.Sprintf("maps/%d.snfm", mapID)
|
||||
presigned, err := s.s3Presign.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &s.s3Bucket,
|
||||
Key: &key,
|
||||
}, s3.WithPresignExpires(5*time.Minute))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return presigned.URL, nil
|
||||
}
|
||||
|
||||
// GetAssetThumbnails proxies to the thumbnail service
|
||||
func (s *Service) GetAssetThumbnails(ctx context.Context, assetIDs []uint64, size roblox.ThumbnailSize) (map[uint64]string, error) {
|
||||
return s.thumbnailService.GetAssetThumbnails(ctx, assetIDs, size)
|
||||
|
||||
Reference in New Issue
Block a user