38 Commits

Author SHA1 Message Date
d434c71821 Merge pull request 'Remove Bots category, use Data.Bots subcategory' (#27) from cat into staging
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Reviewed-on: #27
Reviewed-by: itzaname <itzaname@noreply@itzana.me>
2026-02-26 17:46:26 +00:00
9ca285bb86 Categorize bots requests as Data.Bots
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-26 09:45:04 -08:00
485860ef52 Surface hidden infix
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-25 20:24:42 -08:00
fde19a2378 Add scheme prefix to default storage host
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-25 20:19:17 -08:00
04cfcc9ddc Fix file path for bot download
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-25 20:08:36 -08:00
128aaf4c06 Merge pull request 'Bot Download API' (#26) from bot-dl into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #26
Reviewed-by: itzaname <itzaname@noreply@itzana.me>
2026-02-26 02:55:30 +00:00
9df5e4a8dd rename variables
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:47:34 -08:00
46ebb5574e change permission name to "Bots"
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:41:11 -08:00
2ea3808f12 generate
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:36:15 -08:00
d3771a874a update description
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:31:20 -08:00
cb86bafa7c update summary
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:29:22 -08:00
b38ddd2fae omit HandlerOption slice
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 18:13:50 -08:00
c8d0502616 use handler pattern for storage url
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-25 09:17:19 -08:00
069b1fd711 transient http client 2026-02-25 09:10:29 -08:00
21a764f298 fix comment
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 14:26:25 -08:00
c341c881e3 set an http timeout
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 08:21:26 -08:00
a10a18d0a9 generate
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 08:19:44 -08:00
322951d28b fix doc 2026-02-24 08:19:33 -08:00
30dee1ec2c change endpoint 2026-02-24 08:09:26 -08:00
fee1b968c5 respond with http 302 2026-02-24 08:07:10 -08:00
e9c999c7b5 fix comment 2026-02-24 08:02:00 -08:00
af1c35b618 there is no err
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 08:00:29 -08:00
5a9bc0ea6c use url.JoinPath
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 08:00:01 -08:00
69344551b9 there is no err
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 07:58:30 -08:00
1ab40fdc78 use most likely error msg in doc
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 07:54:56 -08:00
c7c64cd8f7 inline internal storage struct
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 07:53:29 -08:00
31802ac9fc use cases consistently
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-24 07:51:53 -08:00
79016134b6 fix comment
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-23 16:50:14 -08:00
010494ed0e fix api
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-23 10:25:30 -08:00
03695e773d generate
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-23 10:24:10 -08:00
33f55524a8 initialize properly
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2026-02-23 09:21:57 -08:00
b7c0f8b917 bot download endpoint 2026-02-23 09:21:57 -08:00
3396778882 fmt 2026-02-23 08:42:10 -08:00
f1743f4ed8 sign ci
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-18 17:31:30 -08:00
a7524c2766 Update Docs (#24)
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: #24
Co-authored-by: Rhys Lloyd <krakow20@gmail.com>
Co-committed-by: Rhys Lloyd <krakow20@gmail.com>
2025-08-08 00:55:21 +00:00
fabd0a6759 Cut Down Maps Fields (#22)
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Closes #20

I'm assuming protobuf will fill Thumbnail with a default value (0) since it dosn't exist yet.

Reviewed-on: #22
Reviewed-by: itzaname <itzaname@noreply@itzana.me>
Co-authored-by: Rhys Lloyd <krakow20@gmail.com>
Co-committed-by: Rhys Lloyd <krakow20@gmail.com>
2025-08-07 03:20:37 +00:00
b0fbb6f934 Merge pull request 'Makefile: remove operation on nonexistent directory' (#21) from makefile into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #21
2025-08-06 04:39:15 +00:00
517a56ef13 Makefile: remove operation on nonexistent directory
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-08-05 18:51:26 -07:00
13 changed files with 376 additions and 49 deletions

View File

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

View File

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

View File

@@ -301,7 +301,8 @@ const docTemplate = `{
{
"type": "array",
"items": {
"type": "integer"
"type": "integer",
"format": "int64"
},
"collectionFormat": "csv",
"description": "Comma-separated array of time IDs (25 Limit)",
@@ -453,6 +454,52 @@ 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": {
"get": {
"security": [
@@ -631,12 +678,6 @@ const docTemplate = `{
"Map": {
"type": "object",
"properties": {
"creator": {
"type": "string"
},
"date": {
"type": "string"
},
"display_name": {
"type": "string"
},
@@ -645,6 +686,9 @@ const docTemplate = `{
},
"id": {
"type": "integer"
},
"thumbnail": {
"type": "integer"
}
}
},

View File

@@ -294,7 +294,8 @@
{
"type": "array",
"items": {
"type": "integer"
"type": "integer",
"format": "int64"
},
"collectionFormat": "csv",
"description": "Comma-separated array of time IDs (25 Limit)",
@@ -446,6 +447,52 @@
}
}
},
"/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": {
"get": {
"security": [
@@ -624,12 +671,6 @@
"Map": {
"type": "object",
"properties": {
"creator": {
"type": "string"
},
"date": {
"type": "string"
},
"display_name": {
"type": "string"
},
@@ -638,6 +679,9 @@
},
"id": {
"type": "integer"
},
"thumbnail": {
"type": "integer"
}
}
},

View File

@@ -7,16 +7,14 @@ definitions:
type: object
Map:
properties:
creator:
type: string
date:
type: string
display_name:
type: string
game_id:
type: integer
id:
type: integer
thumbnail:
type: integer
type: object
PagedResponse-Map:
properties:
@@ -408,6 +406,36 @@ paths:
summary: Get time by ID
tags:
- 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:
get:
description: |-
@@ -418,6 +446,7 @@ paths:
description: Comma-separated array of time IDs (25 Limit)
in: query
items:
format: int64
type: integer
name: ids
required: true

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.24.0
require (
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628022558-4cf59e46f9f1
git.itzana.me/strafesnet/go-grpc v0.0.0-20250628021738-df0a4ad40969
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef
github.com/gin-gonic/gin v1.10.1
github.com/sirupsen/logrus v1.9.3
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=
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/go-grpc v0.0.0-20250628021738-df0a4ad40969 h1:bGfsRi9WrklI5cwvfvI/jJAGa3PQgJVNAzseFBW56mY=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250628021738-df0a4ad40969/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef h1:SJi4V4+xzScFnbMRN1gkZxcqR1xKfiT7CaXanLltEzw=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
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/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=

View File

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

View File

@@ -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 {

View File

@@ -2,9 +2,10 @@ package handlers
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"google.golang.org/grpc"
"strconv"
)
const (
@@ -14,6 +15,7 @@ const (
// Handler is a base handler that provides common functionality for all HTTP handlers.
type Handler struct {
dataClient *grpc.ClientConn
storageUrl string
}
// HandlerOption defines a functional option for configuring a Handler
@@ -26,6 +28,13 @@ func WithDataClient(dataClient *grpc.ClientConn) HandlerOption {
}
}
// WithStorageUrl sets the storage url
func WithStorageUrl(storageUrl string) HandlerOption {
return func(cfg *Handler) {
cfg.storageUrl = storageUrl
}
}
// NewHandler creates a new Handler with the provided options.
// It requires both a datastore and an authentication service to function properly.
func NewHandler(options ...HandlerOption) (*Handler, error) {

View File

@@ -1,17 +1,22 @@
package handlers
import (
"encoding/json"
"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/public-api/pkg/api/dto"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"math"
"net/http"
"strconv"
"strings"
)
// TimesHandler handles HTTP requests related to times.
@@ -317,3 +322,167 @@ func (h *TimesHandler) GetPlacements(ctx *gin.Context) {
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,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
@@ -25,6 +26,8 @@ type RouterConfig struct {
port int
devClient *grpc.ClientConn
dataClient *grpc.ClientConn
httpClient *http.Client
storageUrl string
context *cli.Context
shutdownTimeout time.Duration
}
@@ -57,6 +60,13 @@ func WithDataClient(conn *grpc.ClientConn) Option {
}
}
// WithStorageUrl sets the storage url
func WithStorageUrl(storageUrl string) Option {
return func(cfg *RouterConfig) {
cfg.storageUrl = storageUrl
}
}
// WithShutdownTimeout sets the graceful shutdown timeout
func WithShutdownTimeout(timeout time.Duration) Option {
return func(cfg *RouterConfig) {
@@ -75,7 +85,10 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
}
// Times handler
timesHandler, err := handlers.NewTimesHandler(handlerOptions...)
timesHandler, err := handlers.NewTimesHandler(
handlers.WithDataClient(cfg.dataClient),
handlers.WithStorageUrl(cfg.storageUrl),
)
if err != nil {
return nil, err
}
@@ -99,29 +112,36 @@ func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
}
docs.SwaggerInfo.BasePath = "/api/v1"
v1 := r.Group("/api/v1")
dataGroup := r.Group("/api/v1")
{
// Auth middleware
v1.Use(middleware.ValidateRequest("Data", "Read", cfg.devClient))
dataGroup.Use(middleware.ValidateRequest("Data", "Read", cfg.devClient))
// Times
v1.GET("/time", timesHandler.List)
v1.GET("/time/worldrecord", timesHandler.WrList)
v1.GET("/time/placement", timesHandler.GetPlacements)
v1.GET("/time/:id", timesHandler.Get)
dataGroup.GET("/time", timesHandler.List)
dataGroup.GET("/time/worldrecord", timesHandler.WrList)
dataGroup.GET("/time/placement", timesHandler.GetPlacements)
dataGroup.GET("/time/:id", timesHandler.Get)
// Users
v1.GET("/user", usersHandler.List)
v1.GET("/user/:id", usersHandler.Get)
v1.GET("/user/:id/rank", usersHandler.GetRank)
dataGroup.GET("/user", usersHandler.List)
dataGroup.GET("/user/:id", usersHandler.Get)
dataGroup.GET("/user/:id/rank", usersHandler.GetRank)
// Maps
v1.GET("/map", mapsHandler.List)
v1.GET("/map/:id", mapsHandler.Get)
dataGroup.GET("/map", mapsHandler.List)
dataGroup.GET("/map/:id", mapsHandler.Get)
// Rank
v1.GET("/rank", rankHandler.List)
dataGroup.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("/", func(ctx *gin.Context) {

View File

@@ -31,6 +31,12 @@ func NewApiCommand() *cli.Command {
EnvVars: []string{"DATA_RPC_HOST"},
Value: "data-service:9000",
},
&cli.StringFlag{
Name: "storage-host",
Usage: "Host of storage",
EnvVars: []string{"STORAGE_HOST"},
Value: "http://storage-service:9000/v1/file/",
},
},
}
}
@@ -48,10 +54,14 @@ func runAPI(ctx *cli.Context) error {
return err
}
// Storage service http client
storageUrl := ctx.String("storage-host")
return api.NewRouter(
api.WithContext(ctx),
api.WithPort(ctx.Int("port")),
api.WithDevClient(devConn),
api.WithDataClient(dataConn),
api.WithStorageUrl(storageUrl),
)
}