feat: api - add a new endpoint - GET /api/v2/versions/{versionCode}/servers (#59)
Reviewed-on: twhelp/corev3#59
This commit is contained in:
parent
8087f5ba7b
commit
79337cd60a
|
@ -11,6 +11,7 @@ info:
|
||||||
name: MIT
|
name: MIT
|
||||||
tags:
|
tags:
|
||||||
- name: versions
|
- name: versions
|
||||||
|
- name: servers
|
||||||
servers:
|
servers:
|
||||||
- url: "{scheme}://{hostname}/api"
|
- url: "{scheme}://{hostname}/api"
|
||||||
variables:
|
variables:
|
||||||
|
@ -49,6 +50,23 @@ paths:
|
||||||
$ref: "#/components/responses/GetVersionResponse"
|
$ref: "#/components/responses/GetVersionResponse"
|
||||||
default:
|
default:
|
||||||
$ref: "#/components/responses/ErrorResponse"
|
$ref: "#/components/responses/ErrorResponse"
|
||||||
|
/v2/versions/{versionCode}/servers:
|
||||||
|
get:
|
||||||
|
operationId: listServers
|
||||||
|
tags:
|
||||||
|
- versions
|
||||||
|
- servers
|
||||||
|
description: List servers
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/VersionCodePathParam"
|
||||||
|
- $ref: "#/components/parameters/CursorQueryParam"
|
||||||
|
- $ref: "#/components/parameters/LimitQueryParam"
|
||||||
|
- $ref: "#/components/parameters/ServerOpenQueryParam"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/ListServersResponse"
|
||||||
|
default:
|
||||||
|
$ref: "#/components/responses/ErrorResponse"
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Error:
|
Error:
|
||||||
|
@ -94,6 +112,55 @@ components:
|
||||||
timezone:
|
timezone:
|
||||||
type: string
|
type: string
|
||||||
example: Europe/Warsaw
|
example: Europe/Warsaw
|
||||||
|
Server:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- open
|
||||||
|
- url
|
||||||
|
- numPlayers
|
||||||
|
- numTribes
|
||||||
|
- numVillages
|
||||||
|
- numBarbarianVillages
|
||||||
|
- numBonusVillages
|
||||||
|
- numPlayerVillages
|
||||||
|
- createdAt
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
example: pl151
|
||||||
|
open:
|
||||||
|
type: boolean
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: https://pl151.plemiona.pl
|
||||||
|
numPlayers:
|
||||||
|
type: integer
|
||||||
|
playerDataSyncedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
numTribes:
|
||||||
|
type: integer
|
||||||
|
tribeDataSyncedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
numVillages:
|
||||||
|
type: integer
|
||||||
|
numBarbarianVillages:
|
||||||
|
type: integer
|
||||||
|
numBonusVillages:
|
||||||
|
type: integer
|
||||||
|
numPlayerVillages:
|
||||||
|
type: integer
|
||||||
|
villageDataSyncedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
ennoblementDataSyncedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
Cursor:
|
Cursor:
|
||||||
type: object
|
type: object
|
||||||
x-go-type-skip-optional-pointer: true
|
x-go-type-skip-optional-pointer: true
|
||||||
|
@ -124,6 +191,14 @@ components:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
required: false
|
required: false
|
||||||
|
ServerOpenQueryParam:
|
||||||
|
name: open
|
||||||
|
in: query
|
||||||
|
description: true=only open servers, false=only closed servers,
|
||||||
|
by default both open and closed servers are returned
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
VersionCodePathParam:
|
VersionCodePathParam:
|
||||||
in: path
|
in: path
|
||||||
name: versionCode
|
name: versionCode
|
||||||
|
@ -157,6 +232,21 @@ components:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: "#/components/schemas/Version"
|
$ref: "#/components/schemas/Version"
|
||||||
|
ListServersResponse:
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PaginationResponse"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Server"
|
||||||
ErrorResponse:
|
ErrorResponse:
|
||||||
description: Default error response.
|
description: Default error response.
|
||||||
content:
|
content:
|
||||||
|
|
|
@ -105,9 +105,11 @@ var cmdServe = &cli.Command{
|
||||||
|
|
||||||
// adapters
|
// adapters
|
||||||
versionRepo := adapter.NewVersionBunRepository(bunDB)
|
versionRepo := adapter.NewVersionBunRepository(bunDB)
|
||||||
|
serverRepo := adapter.NewServerBunRepository(bunDB)
|
||||||
|
|
||||||
// services
|
// services
|
||||||
versionSvc := app.NewVersionService(versionRepo)
|
versionSvc := app.NewVersionService(versionRepo)
|
||||||
|
serverSvc := app.NewServerService(serverRepo, nil, nil)
|
||||||
|
|
||||||
// health
|
// health
|
||||||
h := health.New()
|
h := health.New()
|
||||||
|
@ -132,6 +134,7 @@ var cmdServe = &cli.Command{
|
||||||
|
|
||||||
r.Mount(apiBasePath, port.NewAPIHTTPHandler(
|
r.Mount(apiBasePath, port.NewAPIHTTPHandler(
|
||||||
versionSvc,
|
versionSvc,
|
||||||
|
serverSvc,
|
||||||
port.WithOpenAPIConfig(oapiCfg),
|
port.WithOpenAPIConfig(oapiCfg),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnsupportedSortValue = errors.New("unsupported sort value")
|
||||||
|
errSortNoUniqueField = errors.New("no unique field used for sorting")
|
||||||
|
)
|
|
@ -92,7 +92,7 @@ func (a listEnnoblementsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuer
|
||||||
case domain.EnnoblementSortServerKeyDESC:
|
case domain.EnnoblementSortServerKeyDESC:
|
||||||
q = q.Order("ennoblement.server_key DESC")
|
q = q.Order("ennoblement.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.PlayerSortServerKeyDESC:
|
case domain.PlayerSortServerKeyDESC:
|
||||||
q = q.Order("player.server_key DESC")
|
q = q.Order("player.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ func (a listPlayerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQ
|
||||||
case domain.PlayerSnapshotSortServerKeyDESC:
|
case domain.PlayerSnapshotSortServerKeyDESC:
|
||||||
q = q.Order("ps.server_key DESC")
|
q = q.Order("ps.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,33 +228,97 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.ServerSortOpenDESC:
|
case domain.ServerSortOpenDESC:
|
||||||
q = q.Order("server.open DESC")
|
q = q.Order("server.open DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.params.Cursor().IsZero() {
|
return q.Apply(a.applyCursor).Limit(a.params.Limit() + 1)
|
||||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
}
|
||||||
cursorKey := a.params.Cursor().Key()
|
|
||||||
cursorOpen := a.params.Cursor().Open()
|
|
||||||
|
|
||||||
for _, s := range a.params.Sort() {
|
//nolint:gocyclo
|
||||||
switch s {
|
func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.ServerSortKeyASC:
|
if a.params.Cursor().IsZero() {
|
||||||
q = q.Where("server.key >= ?", cursorKey)
|
return q
|
||||||
case domain.ServerSortKeyDESC:
|
|
||||||
q = q.Where("server.key <= ?", cursorKey)
|
|
||||||
case domain.ServerSortOpenASC:
|
|
||||||
q = q.Where("server.open >= ?", cursorOpen)
|
|
||||||
case domain.ServerSortOpenDESC:
|
|
||||||
q = q.Where("server.open <= ?", cursorOpen)
|
|
||||||
default:
|
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.Limit(a.params.Limit() + 1)
|
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
cursorKey := a.params.Cursor().Key()
|
||||||
|
cursorOpen := a.params.Cursor().Open()
|
||||||
|
|
||||||
|
sort := a.params.Sort()
|
||||||
|
sortLen := len(sort)
|
||||||
|
|
||||||
|
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sortLen == 1:
|
||||||
|
switch sort[0] {
|
||||||
|
case domain.ServerSortKeyASC:
|
||||||
|
q = q.Where("server.key >= ?", cursorKey)
|
||||||
|
case domain.ServerSortKeyDESC:
|
||||||
|
q = q.Where("server.key <= ?", cursorKey)
|
||||||
|
case domain.ServerSortOpenASC, domain.ServerSortOpenDESC:
|
||||||
|
return q.Err(errSortNoUniqueField)
|
||||||
|
default:
|
||||||
|
return q.Err(errUnsupportedSortValue)
|
||||||
|
}
|
||||||
|
case sortLen > 1:
|
||||||
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
switch sort[0] {
|
||||||
|
case domain.ServerSortKeyASC:
|
||||||
|
q = q.Where("server.key > ?", cursorKey)
|
||||||
|
case domain.ServerSortKeyDESC:
|
||||||
|
q = q.Where("server.key < ?", cursorKey)
|
||||||
|
case domain.ServerSortOpenASC:
|
||||||
|
q = q.Where("server.open > ?", cursorOpen)
|
||||||
|
case domain.ServerSortOpenDESC:
|
||||||
|
q = q.Where("server.open < ?", cursorOpen)
|
||||||
|
default:
|
||||||
|
return q.Err(errUnsupportedSortValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < sortLen; i++ {
|
||||||
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
current := sort[i]
|
||||||
|
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
s := sort[j]
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case domain.ServerSortKeyASC,
|
||||||
|
domain.ServerSortKeyDESC:
|
||||||
|
q = q.Where("server.key = ?", cursorKey)
|
||||||
|
case domain.ServerSortOpenASC,
|
||||||
|
domain.ServerSortOpenDESC:
|
||||||
|
q = q.Where("server.open = ?", cursorOpen)
|
||||||
|
default:
|
||||||
|
return q.Err(errUnsupportedSortValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch current {
|
||||||
|
case domain.ServerSortKeyASC:
|
||||||
|
q = q.Where("server.key >= ?", cursorKey)
|
||||||
|
case domain.ServerSortKeyDESC:
|
||||||
|
q = q.Where("server.key <= ?", cursorKey)
|
||||||
|
case domain.ServerSortOpenASC:
|
||||||
|
q = q.Where("server.open >= ?", cursorOpen)
|
||||||
|
case domain.ServerSortOpenDESC:
|
||||||
|
q = q.Where("server.open <= ?", cursorOpen)
|
||||||
|
default:
|
||||||
|
return q.Err(errUnsupportedSortValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
|
||||||
|
return q
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.TribeSortServerKeyDESC:
|
case domain.TribeSortServerKeyDESC:
|
||||||
q = q.Order("tribe.server_key DESC")
|
q = q.Order("tribe.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (a listTribeChangesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuer
|
||||||
case domain.TribeChangeSortServerKeyDESC:
|
case domain.TribeChangeSortServerKeyDESC:
|
||||||
q = q.Order("tc.server_key DESC")
|
q = q.Order("tc.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (a listTribeSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQu
|
||||||
case domain.TribeSnapshotSortServerKeyDESC:
|
case domain.TribeSnapshotSortServerKeyDESC:
|
||||||
q = q.Order("ts.server_key DESC")
|
q = q.Order("ts.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (a listVersionsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
|
||||||
q = q.Order("version.code DESC")
|
q = q.Order("version.code DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ func (a listVillagesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.VillageSortServerKeyDESC:
|
case domain.VillageSortServerKeyDESC:
|
||||||
q = q.Order("village.server_key DESC")
|
q = q.Order("village.server_key DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errors.New("unsupported sort value"))
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -355,6 +355,40 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OK: cursor sort=[key ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListServersParams {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
params := domain.NewListServersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}))
|
||||||
|
|
||||||
|
res, err := repos.server.List(ctx, params)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Greater(t, len(res.Servers()), 2)
|
||||||
|
|
||||||
|
require.NoError(t, params.SetCursor(domaintest.NewServerCursor(t, func(cfg *domaintest.ServerCursorConfig) {
|
||||||
|
cfg.Key = res.Servers()[1].Key()
|
||||||
|
})))
|
||||||
|
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, params domain.ListServersParams, res domain.ListServersResult) {
|
||||||
|
t.Helper()
|
||||||
|
servers := res.Servers()
|
||||||
|
assert.NotEmpty(t, len(servers))
|
||||||
|
assert.True(t, slices.IsSortedFunc(servers, func(a, b domain.Server) int {
|
||||||
|
return cmp.Compare(a.Key(), b.Key())
|
||||||
|
}))
|
||||||
|
for _, s := range res.Servers() {
|
||||||
|
assert.GreaterOrEqual(t, s.Key(), params.Cursor().Key(), s.Key())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "OK: cursor sort=[open ASC, key ASC]",
|
name: "OK: cursor sort=[open ASC, key ASC]",
|
||||||
params: func(t *testing.T) domain.ListServersParams {
|
params: func(t *testing.T) domain.ListServersParams {
|
||||||
|
|
|
@ -109,6 +109,9 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
|
||||||
if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
|
if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := params.SetCursor(domain.ServerCursor{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var servers domain.Servers
|
var servers domain.Servers
|
||||||
|
|
||||||
|
@ -130,6 +133,10 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *ServerService) List(ctx context.Context, params domain.ListServersParams) (domain.ListServersResult, error) {
|
||||||
|
return svc.repo.List(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
|
func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
|
||||||
key := payload.Key()
|
key := payload.Key()
|
||||||
u := payload.URL()
|
u := payload.URL()
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
type apiHTTPHandler struct {
|
type apiHTTPHandler struct {
|
||||||
versionSvc *app.VersionService
|
versionSvc *app.VersionService
|
||||||
|
serverSvc *app.ServerService
|
||||||
openAPISchema func() (*openapi3.T, error)
|
openAPISchema func() (*openapi3.T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +24,16 @@ func WithOpenAPIConfig(oapiCfg OpenAPIConfig) APIHTTPHandlerOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIHTTPHandler(versionSvc *app.VersionService, opts ...APIHTTPHandlerOption) http.Handler {
|
func NewAPIHTTPHandler(
|
||||||
|
versionSvc *app.VersionService,
|
||||||
|
serverSvc *app.ServerService,
|
||||||
|
opts ...APIHTTPHandlerOption,
|
||||||
|
) http.Handler {
|
||||||
cfg := newAPIHTTPHandlerConfig(opts...)
|
cfg := newAPIHTTPHandlerConfig(opts...)
|
||||||
|
|
||||||
h := &apiHTTPHandler{
|
h := &apiHTTPHandler{
|
||||||
versionSvc: versionSvc,
|
versionSvc: versionSvc,
|
||||||
|
serverSvc: serverSvc,
|
||||||
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||||
return getOpenAPISchema(cfg.openAPI)
|
return getOpenAPISchema(cfg.openAPI)
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -64,7 +64,7 @@ func (es apiErrors) toResponse() apimodel.ErrorResponse {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorPathElement struct {
|
type errorPathSegment struct {
|
||||||
model string
|
model string
|
||||||
field string
|
field string
|
||||||
index int // index may be <0 and this means that it is unset
|
index int // index may be <0 and this means that it is unset
|
||||||
|
@ -75,7 +75,7 @@ type apiErrorRenderer struct {
|
||||||
// formatErrorPath allows to override the default path formatter
|
// formatErrorPath allows to override the default path formatter
|
||||||
// for domain.ValidationError and domain.SliceElementValidationError.
|
// for domain.ValidationError and domain.SliceElementValidationError.
|
||||||
// If formatErrorPath returns an empty slice, an internal server error is rendered.
|
// If formatErrorPath returns an empty slice, an internal server error is rendered.
|
||||||
formatErrorPath func(elems []errorPathElement) []string
|
formatErrorPath func(segments []errorPathSegment) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInternalServerError = apiError{
|
var errInternalServerError = apiError{
|
||||||
|
@ -115,7 +115,7 @@ func (re apiErrorRenderer) render(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiError {
|
func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiError {
|
||||||
message := domainErr.Error()
|
message := domainErr.Error()
|
||||||
var pathElems []errorPathElement
|
var pathElems []errorPathSegment
|
||||||
|
|
||||||
var err error = domainErr
|
var err error = domainErr
|
||||||
for err != nil {
|
for err != nil {
|
||||||
|
@ -124,7 +124,7 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &validationErr):
|
case errors.As(err, &validationErr):
|
||||||
pathElems = append(pathElems, errorPathElement{
|
pathElems = append(pathElems, errorPathSegment{
|
||||||
model: validationErr.Model,
|
model: validationErr.Model,
|
||||||
field: validationErr.Field,
|
field: validationErr.Field,
|
||||||
index: -1,
|
index: -1,
|
||||||
|
@ -132,7 +132,7 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
|
||||||
err = validationErr.Unwrap()
|
err = validationErr.Unwrap()
|
||||||
message = err.Error()
|
message = err.Error()
|
||||||
case errors.As(err, &sliceElementValidationErr):
|
case errors.As(err, &sliceElementValidationErr):
|
||||||
pathElems = append(pathElems, errorPathElement{
|
pathElems = append(pathElems, errorPathSegment{
|
||||||
model: sliceElementValidationErr.Model,
|
model: sliceElementValidationErr.Model,
|
||||||
field: sliceElementValidationErr.Field,
|
field: sliceElementValidationErr.Field,
|
||||||
index: sliceElementValidationErr.Index,
|
index: sliceElementValidationErr.Index,
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package port
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *apiHTTPHandler) ListServers(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
versionCode apimodel.VersionCodePathParam,
|
||||||
|
params apimodel.ListServersParams,
|
||||||
|
) {
|
||||||
|
domainParams := domain.NewListServersParams()
|
||||||
|
|
||||||
|
if err := domainParams.SetSort([]domain.ServerSort{domain.ServerSortOpenDESC, domain.ServerSortKeyASC}); err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := domainParams.SetVersionCodes([]string{versionCode}); err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Open != nil {
|
||||||
|
if err := domainParams.SetOpen(domain.NullBool{
|
||||||
|
Value: *params.Open,
|
||||||
|
Valid: true,
|
||||||
|
}); err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Limit != nil {
|
||||||
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Cursor != nil {
|
||||||
|
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListServersParamsErrorPath}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.serverSvc.List(r.Context(), domainParams)
|
||||||
|
if err != nil {
|
||||||
|
apiErrorRenderer{errors: []error{err}}.render(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJSON(w, r, http.StatusOK, apimodel.NewListServersResponse(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatListServersParamsErrorPath(segments []errorPathSegment) []string {
|
||||||
|
if segments[0].model != "ListServersParams" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch segments[0].field {
|
||||||
|
case "cursor":
|
||||||
|
return []string{"$query", "cursor"}
|
||||||
|
case "limit":
|
||||||
|
return []string{"$query", "limit"}
|
||||||
|
case "open":
|
||||||
|
return []string{"$query", "open"}
|
||||||
|
case "sort":
|
||||||
|
return []string{"$query", "sort"}
|
||||||
|
case "versionCodes":
|
||||||
|
return []string{"$path", "versionCode"}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,10 +25,14 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
||||||
opt(cfg)
|
opt(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionRepo := adapter.NewVersionBunRepository(buntest.NewSQLiteDB(tb))
|
db := buntest.NewSQLiteDB(tb)
|
||||||
|
|
||||||
|
versionRepo := adapter.NewVersionBunRepository(db)
|
||||||
|
serverRepo := adapter.NewServerBunRepository(db)
|
||||||
|
|
||||||
return port.NewAPIHTTPHandler(
|
return port.NewAPIHTTPHandler(
|
||||||
app.NewVersionService(versionRepo),
|
app.NewVersionService(versionRepo),
|
||||||
|
app.NewServerService(serverRepo, nil, nil),
|
||||||
cfg.options...,
|
cfg.options...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,12 +43,12 @@ func (h *apiHTTPHandler) GetVersion(w http.ResponseWriter, r *http.Request, vers
|
||||||
renderJSON(w, r, http.StatusOK, apimodel.NewGetVersionResponse(version))
|
renderJSON(w, r, http.StatusOK, apimodel.NewGetVersionResponse(version))
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatListVersionsParamsErrorPath(elems []errorPathElement) []string {
|
func formatListVersionsParamsErrorPath(segments []errorPathSegment) []string {
|
||||||
if elems[0].model != "ListVersionsParams" {
|
if segments[0].model != "ListVersionsParams" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch elems[0].field {
|
switch segments[0].field {
|
||||||
case "cursor":
|
case "cursor":
|
||||||
return []string{"$query", "cursor"}
|
return []string{"$query", "cursor"}
|
||||||
case "limit":
|
case "limit":
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package apimodel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServer(s domain.Server) Server {
|
||||||
|
converted := Server{
|
||||||
|
Key: s.Key(),
|
||||||
|
CreatedAt: s.CreatedAt(),
|
||||||
|
NumBarbarianVillages: s.NumBarbarianVillages(),
|
||||||
|
NumBonusVillages: s.NumBonusVillages(),
|
||||||
|
NumPlayerVillages: s.NumPlayerVillages(),
|
||||||
|
NumPlayers: s.NumPlayers(),
|
||||||
|
NumTribes: s.NumTribes(),
|
||||||
|
NumVillages: s.NumVillages(),
|
||||||
|
Open: s.Open(),
|
||||||
|
Url: s.URL().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := s.PlayerDataSyncedAt(); !v.IsZero() {
|
||||||
|
converted.PlayerDataSyncedAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := s.TribeDataSyncedAt(); !v.IsZero() {
|
||||||
|
converted.TribeDataSyncedAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := s.VillageDataSyncedAt(); !v.IsZero() {
|
||||||
|
converted.VillageDataSyncedAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := s.EnnoblementDataSyncedAt(); !v.IsZero() {
|
||||||
|
converted.EnnoblementDataSyncedAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListServersResponse(res domain.ListServersResult) ListServersResponse {
|
||||||
|
servers := res.Servers()
|
||||||
|
|
||||||
|
resp := ListServersResponse{
|
||||||
|
Data: make([]Server, 0, len(servers)),
|
||||||
|
Cursor: Cursor{
|
||||||
|
Next: res.Next().Encode(),
|
||||||
|
Self: res.Self().Encode(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range servers {
|
||||||
|
resp.Data = append(resp.Data, NewServer(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
Loading…
Reference in New Issue