feat: api - GET /api/v2/versions/{versionCode}/servers/{serverKey}/players - add more sort options (#7)
Reviewed-on: twhelp/corev3#7
This commit is contained in:
parent
70deae8696
commit
d12bcf5e4e
|
@ -180,6 +180,7 @@ paths:
|
||||||
- $ref: "#/components/parameters/CursorQueryParam"
|
- $ref: "#/components/parameters/CursorQueryParam"
|
||||||
- $ref: "#/components/parameters/LimitQueryParam"
|
- $ref: "#/components/parameters/LimitQueryParam"
|
||||||
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
|
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
|
||||||
|
- $ref: "#/components/parameters/PlayerSortQueryParam"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/ListPlayersResponse"
|
$ref: "#/components/responses/ListPlayersResponse"
|
||||||
|
@ -1100,6 +1101,26 @@ components:
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
required: false
|
required: false
|
||||||
|
PlayerSortQueryParam:
|
||||||
|
name: sort
|
||||||
|
in: query
|
||||||
|
description: Order matters!
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- odScoreAtt:ASC
|
||||||
|
- odScoreAtt:DESC
|
||||||
|
- odScoreDef:ASC
|
||||||
|
- odScoreDef:DESC
|
||||||
|
- odScoreTotal:ASC
|
||||||
|
- odScoreTotal:DESC
|
||||||
|
- points:ASC
|
||||||
|
- points:DESC
|
||||||
|
- deletedAt:ASC
|
||||||
|
- deletedAt:DESC
|
||||||
|
maxItems: 2
|
||||||
VersionCodePathParam:
|
VersionCodePathParam:
|
||||||
in: path
|
in: path
|
||||||
name: versionCode
|
name: versionCode
|
||||||
|
|
|
@ -3,6 +3,5 @@ package adapter
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errUnsupportedSortValue = errors.New("unsupported sort value")
|
errInvalidSortValue = errors.New("invalid sort value")
|
||||||
errSortNoUniqueField = errors.New("no unique field used for sorting")
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import "github.com/uptrace/bun"
|
import (
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
|
func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
|
||||||
return q.Set("rank_att = EXCLUDED.rank_att").
|
return q.Set("rank_att = EXCLUDED.rank_att").
|
||||||
|
@ -23,3 +28,90 @@ func separateListResultAndNext[T any](res []T, limit int) ([]T, T) {
|
||||||
|
|
||||||
return res, next
|
return res, next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sortDirection uint8
|
||||||
|
|
||||||
|
var errInvalidSortDirection = errors.New("invalid sort direction")
|
||||||
|
|
||||||
|
const (
|
||||||
|
sortDirectionASC sortDirection = iota + 1
|
||||||
|
sortDirectionDESC
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sd sortDirection) String() string {
|
||||||
|
switch sd {
|
||||||
|
case sortDirectionASC:
|
||||||
|
return "ASC"
|
||||||
|
case sortDirectionDESC:
|
||||||
|
return "DESC"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cursorPaginationApplierDataElement struct {
|
||||||
|
unique bool
|
||||||
|
column string
|
||||||
|
direction sortDirection
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
|
||||||
|
type cursorPaginationApplier struct {
|
||||||
|
data []cursorPaginationApplierDataElement
|
||||||
|
}
|
||||||
|
|
||||||
|
var errCursorNoUniqueFieldUsedForSorting = errors.New("cursor - no unique field used for sorting")
|
||||||
|
|
||||||
|
func (a cursorPaginationApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
if len(a.data) == 0 {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueIndex := slices.IndexFunc(a.data, func(element cursorPaginationApplierDataElement) bool {
|
||||||
|
return element.unique
|
||||||
|
})
|
||||||
|
if uniqueIndex < 0 {
|
||||||
|
return q.Err(errCursorNoUniqueFieldUsedForSorting)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
dataLen := len(a.data)
|
||||||
|
|
||||||
|
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
||||||
|
|
||||||
|
return q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
for i := 0; i < dataLen; i++ {
|
||||||
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
current := a.data[i]
|
||||||
|
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
prev := a.data[j]
|
||||||
|
q = q.Where("? = ?", bun.Safe(prev.column), prev.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
column := bun.Safe(current.column)
|
||||||
|
greaterSymbol := bun.Safe(">")
|
||||||
|
lessSymbol := bun.Safe("<")
|
||||||
|
|
||||||
|
if i == dataLen-1 {
|
||||||
|
greaterSymbol = ">="
|
||||||
|
lessSymbol = "<="
|
||||||
|
}
|
||||||
|
|
||||||
|
switch current.direction {
|
||||||
|
case sortDirectionASC:
|
||||||
|
q = q.Where("? ? ?", column, greaterSymbol, current.value)
|
||||||
|
case sortDirectionDESC:
|
||||||
|
q = q.Where("? ? ?", column, lessSymbol, current.value)
|
||||||
|
default:
|
||||||
|
return q.Err(errInvalidSortDirection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,8 +162,28 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
q = q.Order("player.server_key ASC")
|
q = q.Order("player.server_key ASC")
|
||||||
case domain.PlayerSortServerKeyDESC:
|
case domain.PlayerSortServerKeyDESC:
|
||||||
q = q.Order("player.server_key DESC")
|
q = q.Order("player.server_key DESC")
|
||||||
|
case domain.PlayerSortODScoreAttASC:
|
||||||
|
q = q.Order("player.score_att ASC")
|
||||||
|
case domain.PlayerSortODScoreAttDESC:
|
||||||
|
q = q.Order("player.score_att DESC")
|
||||||
|
case domain.PlayerSortODScoreDefASC:
|
||||||
|
q = q.Order("player.score_def ASC")
|
||||||
|
case domain.PlayerSortODScoreDefDESC:
|
||||||
|
q = q.Order("player.score_def DESC")
|
||||||
|
case domain.PlayerSortODScoreTotalASC:
|
||||||
|
q = q.Order("player.score_total ASC")
|
||||||
|
case domain.PlayerSortODScoreTotalDESC:
|
||||||
|
q = q.Order("player.score_total DESC")
|
||||||
|
case domain.PlayerSortPointsASC:
|
||||||
|
q = q.Order("player.points ASC")
|
||||||
|
case domain.PlayerSortPointsDESC:
|
||||||
|
q = q.Order("player.points DESC")
|
||||||
|
case domain.PlayerSortDeletedAtASC:
|
||||||
|
q = q.OrderExpr("COALESCE(player.deleted_at, ?) ASC", time.Time{})
|
||||||
|
case domain.PlayerSortDeletedAtDESC:
|
||||||
|
q = q.OrderExpr("COALESCE(player.deleted_at, ?) DESC", time.Time{})
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,85 +192,97 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
if a.params.Cursor().IsZero() {
|
cursor := a.params.Cursor()
|
||||||
|
|
||||||
|
if cursor.IsZero() {
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
sort := a.params.Sort()
|
||||||
cursor := a.params.Cursor()
|
cursorApplier := cursorPaginationApplier{
|
||||||
cursorID := cursor.ID()
|
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||||
cursorServerKey := cursor.ServerKey()
|
}
|
||||||
|
|
||||||
sort := a.params.Sort()
|
for _, s := range sort {
|
||||||
sortLen := len(sort)
|
var el cursorPaginationApplierDataElement
|
||||||
|
|
||||||
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
switch s {
|
||||||
|
case domain.PlayerSortIDASC:
|
||||||
switch {
|
el.value = cursor.ID()
|
||||||
case sortLen == 1:
|
el.unique = true
|
||||||
switch sort[0] {
|
el.column = "player.id"
|
||||||
case domain.PlayerSortIDASC:
|
el.direction = sortDirectionASC
|
||||||
q = q.Where("player.id >= ?", cursorID)
|
case domain.PlayerSortIDDESC:
|
||||||
case domain.PlayerSortIDDESC:
|
el.value = cursor.ID()
|
||||||
q = q.Where("player.id <= ?", cursorID)
|
el.unique = true
|
||||||
case domain.PlayerSortServerKeyASC,
|
el.column = "player.id"
|
||||||
domain.PlayerSortServerKeyDESC:
|
el.direction = sortDirectionDESC
|
||||||
return q.Err(errSortNoUniqueField)
|
case domain.PlayerSortServerKeyASC:
|
||||||
default:
|
el.value = cursor.ServerKey()
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.unique = false
|
||||||
}
|
el.column = "player.server_key"
|
||||||
case sortLen > 1:
|
el.direction = sortDirectionASC
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
case domain.PlayerSortServerKeyDESC:
|
||||||
for i := 0; i < sortLen; i++ {
|
el.value = cursor.ServerKey()
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
el.unique = false
|
||||||
current := sort[i]
|
el.column = "player.server_key"
|
||||||
|
el.direction = sortDirectionDESC
|
||||||
for j := 0; j < i; j++ {
|
case domain.PlayerSortODScoreAttASC:
|
||||||
s := sort[j]
|
el.value = cursor.ODScoreAtt()
|
||||||
|
el.unique = false
|
||||||
switch s {
|
el.column = "player.score_att"
|
||||||
case domain.PlayerSortIDASC,
|
el.direction = sortDirectionASC
|
||||||
domain.PlayerSortIDDESC:
|
case domain.PlayerSortODScoreAttDESC:
|
||||||
q = q.Where("player.id = ?", cursorID)
|
el.value = cursor.ODScoreAtt()
|
||||||
case domain.PlayerSortServerKeyASC,
|
el.unique = false
|
||||||
domain.PlayerSortServerKeyDESC:
|
el.column = "player.score_att"
|
||||||
q = q.Where("player.server_key = ?", cursorServerKey)
|
el.direction = sortDirectionDESC
|
||||||
default:
|
case domain.PlayerSortODScoreDefASC:
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.value = cursor.ODScoreDef()
|
||||||
}
|
el.unique = false
|
||||||
}
|
el.column = "player.score_def"
|
||||||
|
el.direction = sortDirectionASC
|
||||||
greaterSymbol := bun.Safe(">")
|
case domain.PlayerSortODScoreDefDESC:
|
||||||
lessSymbol := bun.Safe("<")
|
el.value = cursor.ODScoreDef()
|
||||||
|
el.unique = false
|
||||||
if i == sortLen-1 {
|
el.column = "player.score_def"
|
||||||
greaterSymbol = ">="
|
el.direction = sortDirectionDESC
|
||||||
lessSymbol = "<="
|
case domain.PlayerSortODScoreTotalASC:
|
||||||
}
|
el.value = cursor.ODScoreTotal()
|
||||||
|
el.unique = false
|
||||||
switch current {
|
el.column = "player.score_total"
|
||||||
case domain.PlayerSortIDASC:
|
el.direction = sortDirectionASC
|
||||||
q = q.Where("player.id ? ?", greaterSymbol, cursorID)
|
case domain.PlayerSortODScoreTotalDESC:
|
||||||
case domain.PlayerSortIDDESC:
|
el.value = cursor.ODScoreTotal()
|
||||||
q = q.Where("player.id ? ?", lessSymbol, cursorID)
|
el.unique = false
|
||||||
case domain.PlayerSortServerKeyASC:
|
el.column = "player.score_total"
|
||||||
q = q.Where("player.server_key ? ?", greaterSymbol, cursorServerKey)
|
el.direction = sortDirectionDESC
|
||||||
case domain.PlayerSortServerKeyDESC:
|
case domain.PlayerSortPointsASC:
|
||||||
q = q.Where("player.server_key ? ?", lessSymbol, cursorServerKey)
|
el.value = cursor.Points()
|
||||||
default:
|
el.unique = false
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.column = "player.points"
|
||||||
}
|
el.direction = sortDirectionASC
|
||||||
|
case domain.PlayerSortPointsDESC:
|
||||||
return q
|
el.value = cursor.Points()
|
||||||
})
|
el.unique = false
|
||||||
}
|
el.column = "player.points"
|
||||||
|
el.direction = sortDirectionDESC
|
||||||
return q
|
case domain.PlayerSortDeletedAtASC:
|
||||||
})
|
el.value = cursor.DeletedAt()
|
||||||
|
el.unique = false
|
||||||
|
el.column = "COALESCE(player.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||||
|
el.direction = sortDirectionASC
|
||||||
|
case domain.PlayerSortDeletedAtDESC:
|
||||||
|
el.value = cursor.DeletedAt()
|
||||||
|
el.unique = false
|
||||||
|
el.column = "COALESCE(player.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||||
|
el.direction = sortDirectionDESC
|
||||||
|
default:
|
||||||
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return q
|
cursorApplier.data = append(cursorApplier.data, el)
|
||||||
})
|
}
|
||||||
|
|
||||||
return q
|
return q.Apply(cursorApplier.apply)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,92 +228,55 @@ 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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.Apply(a.applyCursor).Limit(a.params.Limit() + 1)
|
return q.Apply(a.applyCursor).Limit(a.params.Limit() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
if a.params.Cursor().IsZero() {
|
cursor := a.params.Cursor()
|
||||||
|
|
||||||
|
if cursor.IsZero() {
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
sort := a.params.Sort()
|
||||||
cursorKey := a.params.Cursor().Key()
|
cursorApplier := cursorPaginationApplier{
|
||||||
cursorOpen := a.params.Cursor().Open()
|
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||||
|
}
|
||||||
|
|
||||||
sort := a.params.Sort()
|
for _, s := range sort {
|
||||||
sortLen := len(sort)
|
var el cursorPaginationApplierDataElement
|
||||||
|
|
||||||
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
switch s {
|
||||||
|
case domain.ServerSortKeyASC:
|
||||||
switch {
|
el.value = cursor.Key()
|
||||||
case sortLen == 1:
|
el.unique = true
|
||||||
switch sort[0] {
|
el.column = "server.key"
|
||||||
case domain.ServerSortKeyASC:
|
el.direction = sortDirectionASC
|
||||||
q = q.Where("server.key >= ?", cursorKey)
|
case domain.ServerSortKeyDESC:
|
||||||
case domain.ServerSortKeyDESC:
|
el.value = cursor.Key()
|
||||||
q = q.Where("server.key <= ?", cursorKey)
|
el.unique = true
|
||||||
case domain.ServerSortOpenASC, domain.ServerSortOpenDESC:
|
el.column = "server.key"
|
||||||
return q.Err(errSortNoUniqueField)
|
el.direction = sortDirectionDESC
|
||||||
default:
|
case domain.ServerSortOpenASC:
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.value = cursor.Open()
|
||||||
}
|
el.unique = false
|
||||||
case sortLen > 1:
|
el.column = "server.open"
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
el.direction = sortDirectionASC
|
||||||
for i := 0; i < sortLen; i++ {
|
case domain.ServerSortOpenDESC:
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
el.value = cursor.Open()
|
||||||
current := sort[i]
|
el.unique = false
|
||||||
|
el.column = "server.open"
|
||||||
for j := 0; j < i; j++ {
|
el.direction = sortDirectionDESC
|
||||||
s := sort[j]
|
default:
|
||||||
|
return q.Err(errInvalidSortValue)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
greaterSymbol := bun.Safe(">")
|
|
||||||
lessSymbol := bun.Safe("<")
|
|
||||||
|
|
||||||
if i == sortLen-1 {
|
|
||||||
greaterSymbol = ">="
|
|
||||||
lessSymbol = "<="
|
|
||||||
}
|
|
||||||
|
|
||||||
switch current {
|
|
||||||
case domain.ServerSortKeyASC:
|
|
||||||
q = q.Where("server.key ? ?", greaterSymbol, cursorKey)
|
|
||||||
case domain.ServerSortKeyDESC:
|
|
||||||
q = q.Where("server.key ? ?", lessSymbol, cursorKey)
|
|
||||||
case domain.ServerSortOpenASC:
|
|
||||||
q = q.Where("server.open ? ?", greaterSymbol, cursorOpen)
|
|
||||||
case domain.ServerSortOpenDESC:
|
|
||||||
q = q.Where("server.open ? ?", lessSymbol, cursorOpen)
|
|
||||||
default:
|
|
||||||
return q.Err(errUnsupportedSortValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return q
|
cursorApplier.data = append(cursorApplier.data, el)
|
||||||
})
|
}
|
||||||
|
|
||||||
return q
|
return q.Apply(cursorApplier.apply)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,10 @@ func (repo *TribeBunRepository) List(
|
||||||
) (domain.ListTribesResult, error) {
|
) (domain.ListTribesResult, error) {
|
||||||
var tribes bunmodel.Tribes
|
var tribes bunmodel.Tribes
|
||||||
|
|
||||||
|
fmt.Println(repo.db.NewSelect().
|
||||||
|
Model(&tribes).
|
||||||
|
Apply(listTribesParamsApplier{params: params}.apply).String())
|
||||||
|
|
||||||
if err := repo.db.NewSelect().
|
if err := repo.db.NewSelect().
|
||||||
Model(&tribes).
|
Model(&tribes).
|
||||||
Apply(listTribesParamsApplier{params: params}.apply).
|
Apply(listTribesParamsApplier{params: params}.apply).
|
||||||
|
@ -212,7 +216,7 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
case domain.TribeSortDeletedAtDESC:
|
case domain.TribeSortDeletedAtDESC:
|
||||||
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) DESC", time.Time{})
|
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) DESC", time.Time{})
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,145 +225,107 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
if a.params.Cursor().IsZero() {
|
cursor := a.params.Cursor()
|
||||||
|
|
||||||
|
if cursor.IsZero() {
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
sort := a.params.Sort()
|
||||||
cursor := a.params.Cursor()
|
cursorApplier := cursorPaginationApplier{
|
||||||
cursorID := cursor.ID()
|
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||||
cursorServerKey := cursor.ServerKey()
|
}
|
||||||
cursorODScoreAtt := cursor.ODScoreAtt()
|
|
||||||
cursorODScoreDef := cursor.ODScoreDef()
|
|
||||||
cursorODScoreTotal := cursor.ODScoreTotal()
|
|
||||||
cursorPoints := cursor.Points()
|
|
||||||
cursorDominance := cursor.Dominance()
|
|
||||||
cursorDeletedAt := cursor.DeletedAt()
|
|
||||||
|
|
||||||
sort := a.params.Sort()
|
for _, s := range sort {
|
||||||
sortLen := len(sort)
|
var el cursorPaginationApplierDataElement
|
||||||
|
|
||||||
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
switch s {
|
||||||
|
case domain.TribeSortIDASC:
|
||||||
switch {
|
el.value = cursor.ID()
|
||||||
case sortLen == 1:
|
el.unique = true
|
||||||
switch sort[0] {
|
el.column = "tribe.id"
|
||||||
case domain.TribeSortIDASC:
|
el.direction = sortDirectionASC
|
||||||
q = q.Where("tribe.id >= ?", cursorID)
|
case domain.TribeSortIDDESC:
|
||||||
case domain.TribeSortIDDESC:
|
el.value = cursor.ID()
|
||||||
q = q.Where("tribe.id <= ?", cursorID)
|
el.unique = true
|
||||||
case domain.TribeSortServerKeyASC,
|
el.column = "tribe.id"
|
||||||
domain.TribeSortServerKeyDESC,
|
el.direction = sortDirectionDESC
|
||||||
domain.TribeSortODScoreAttASC,
|
case domain.TribeSortServerKeyASC:
|
||||||
domain.TribeSortODScoreAttDESC,
|
el.value = cursor.ServerKey()
|
||||||
domain.TribeSortODScoreDefASC,
|
el.unique = false
|
||||||
domain.TribeSortODScoreDefDESC,
|
el.column = "tribe.server_key"
|
||||||
domain.TribeSortODScoreTotalASC,
|
el.direction = sortDirectionASC
|
||||||
domain.TribeSortODScoreTotalDESC,
|
case domain.TribeSortServerKeyDESC:
|
||||||
domain.TribeSortPointsASC,
|
el.value = cursor.ServerKey()
|
||||||
domain.TribeSortPointsDESC,
|
el.unique = false
|
||||||
domain.TribeSortDominanceASC,
|
el.column = "tribe.server_key"
|
||||||
domain.TribeSortDominanceDESC,
|
el.direction = sortDirectionDESC
|
||||||
domain.TribeSortDeletedAtASC,
|
case domain.TribeSortODScoreAttASC:
|
||||||
domain.TribeSortDeletedAtDESC:
|
el.value = cursor.ODScoreAtt()
|
||||||
return q.Err(errSortNoUniqueField)
|
el.unique = false
|
||||||
default:
|
el.column = "tribe.score_att"
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.direction = sortDirectionASC
|
||||||
}
|
case domain.TribeSortODScoreAttDESC:
|
||||||
case sortLen > 1:
|
el.value = cursor.ODScoreAtt()
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
el.unique = false
|
||||||
for i := 0; i < sortLen; i++ {
|
el.column = "tribe.score_att"
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
el.direction = sortDirectionDESC
|
||||||
current := sort[i]
|
case domain.TribeSortODScoreDefASC:
|
||||||
|
el.value = cursor.ODScoreDef()
|
||||||
for j := 0; j < i; j++ {
|
el.unique = false
|
||||||
s := sort[j]
|
el.column = "tribe.score_def"
|
||||||
|
el.direction = sortDirectionASC
|
||||||
switch s {
|
case domain.TribeSortODScoreDefDESC:
|
||||||
case domain.TribeSortIDASC,
|
el.value = cursor.ODScoreDef()
|
||||||
domain.TribeSortIDDESC:
|
el.unique = false
|
||||||
q = q.Where("tribe.id = ?", cursorID)
|
el.column = "tribe.score_def"
|
||||||
case domain.TribeSortServerKeyASC,
|
el.direction = sortDirectionDESC
|
||||||
domain.TribeSortServerKeyDESC:
|
case domain.TribeSortODScoreTotalASC:
|
||||||
q = q.Where("tribe.server_key = ?", cursorServerKey)
|
el.value = cursor.ODScoreTotal()
|
||||||
case domain.TribeSortODScoreAttASC,
|
el.unique = false
|
||||||
domain.TribeSortODScoreAttDESC:
|
el.column = "tribe.score_total"
|
||||||
q = q.Where("tribe.score_att = ?", cursorODScoreAtt)
|
el.direction = sortDirectionASC
|
||||||
case domain.TribeSortODScoreDefASC,
|
case domain.TribeSortODScoreTotalDESC:
|
||||||
domain.TribeSortODScoreDefDESC:
|
el.value = cursor.ODScoreTotal()
|
||||||
q = q.Where("tribe.score_def = ?", cursorODScoreDef)
|
el.unique = false
|
||||||
case domain.TribeSortODScoreTotalASC,
|
el.column = "tribe.score_total"
|
||||||
domain.TribeSortODScoreTotalDESC:
|
el.direction = sortDirectionDESC
|
||||||
q = q.Where("tribe.score_total = ?", cursorODScoreTotal)
|
case domain.TribeSortPointsASC:
|
||||||
case domain.TribeSortPointsASC,
|
el.value = cursor.Points()
|
||||||
domain.TribeSortPointsDESC:
|
el.unique = false
|
||||||
q = q.Where("tribe.points = ?", cursorPoints)
|
el.column = "tribe.points"
|
||||||
case domain.TribeSortDominanceASC,
|
el.direction = sortDirectionASC
|
||||||
domain.TribeSortDominanceDESC:
|
case domain.TribeSortPointsDESC:
|
||||||
q = q.Where("tribe.dominance = ?", cursorDominance)
|
el.value = cursor.Points()
|
||||||
case domain.TribeSortDeletedAtASC,
|
el.unique = false
|
||||||
domain.TribeSortDeletedAtDESC:
|
el.column = "tribe.points"
|
||||||
q = q.Where("COALESCE(tribe.deleted_at, ?) = ?", time.Time{}, cursorDeletedAt)
|
el.direction = sortDirectionDESC
|
||||||
default:
|
case domain.TribeSortDominanceASC:
|
||||||
return q.Err(errUnsupportedSortValue)
|
el.value = cursor.Dominance()
|
||||||
}
|
el.unique = false
|
||||||
}
|
el.column = "tribe.dominance"
|
||||||
|
el.direction = sortDirectionASC
|
||||||
greaterSymbol := bun.Safe(">")
|
case domain.TribeSortDominanceDESC:
|
||||||
lessSymbol := bun.Safe("<")
|
el.value = cursor.Dominance()
|
||||||
|
el.unique = false
|
||||||
if i == sortLen-1 {
|
el.column = "tribe.dominance"
|
||||||
greaterSymbol = ">="
|
el.direction = sortDirectionDESC
|
||||||
lessSymbol = "<="
|
case domain.TribeSortDeletedAtASC:
|
||||||
}
|
el.value = cursor.DeletedAt()
|
||||||
|
el.unique = false
|
||||||
switch current {
|
el.column = "COALESCE(tribe.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||||
case domain.TribeSortIDASC:
|
el.direction = sortDirectionASC
|
||||||
q = q.Where("tribe.id ? ?", greaterSymbol, cursorID)
|
case domain.TribeSortDeletedAtDESC:
|
||||||
case domain.TribeSortIDDESC:
|
el.value = cursor.DeletedAt()
|
||||||
q = q.Where("tribe.id ? ?", lessSymbol, cursorID)
|
el.unique = false
|
||||||
case domain.TribeSortServerKeyASC:
|
el.column = "COALESCE(tribe.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||||
q = q.Where("tribe.server_key ? ?", greaterSymbol, cursorServerKey)
|
el.direction = sortDirectionDESC
|
||||||
case domain.TribeSortServerKeyDESC:
|
default:
|
||||||
q = q.Where("tribe.server_key ? ?", lessSymbol, cursorServerKey)
|
return q.Err(errInvalidSortValue)
|
||||||
case domain.TribeSortODScoreAttASC:
|
|
||||||
q = q.Where("tribe.score_att ? ?", greaterSymbol, cursorODScoreAtt)
|
|
||||||
case domain.TribeSortODScoreAttDESC:
|
|
||||||
q = q.Where("tribe.score_att ? ?", lessSymbol, cursorODScoreAtt)
|
|
||||||
case domain.TribeSortODScoreDefASC:
|
|
||||||
q = q.Where("tribe.score_def ? ?", greaterSymbol, cursorODScoreDef)
|
|
||||||
case domain.TribeSortODScoreDefDESC:
|
|
||||||
q = q.Where("tribe.score_def ? ?", lessSymbol, cursorODScoreDef)
|
|
||||||
case domain.TribeSortODScoreTotalASC:
|
|
||||||
q = q.Where("tribe.score_total ? ?", greaterSymbol, cursorODScoreTotal)
|
|
||||||
case domain.TribeSortODScoreTotalDESC:
|
|
||||||
q = q.Where("tribe.score_total ? ?", lessSymbol, cursorODScoreTotal)
|
|
||||||
case domain.TribeSortPointsASC:
|
|
||||||
q = q.Where("tribe.points ? ?", greaterSymbol, cursorPoints)
|
|
||||||
case domain.TribeSortPointsDESC:
|
|
||||||
q = q.Where("tribe.points ? ?", lessSymbol, cursorPoints)
|
|
||||||
case domain.TribeSortDominanceASC:
|
|
||||||
q = q.Where("tribe.dominance ? ?", greaterSymbol, cursorDominance)
|
|
||||||
case domain.TribeSortDominanceDESC:
|
|
||||||
q = q.Where("tribe.dominance ? ?", lessSymbol, cursorDominance)
|
|
||||||
case domain.TribeSortDeletedAtASC:
|
|
||||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, greaterSymbol, cursorDeletedAt)
|
|
||||||
case domain.TribeSortDeletedAtDESC:
|
|
||||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, lessSymbol, cursorDeletedAt)
|
|
||||||
default:
|
|
||||||
return q.Err(errUnsupportedSortValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return q
|
cursorApplier.data = append(cursorApplier.data, el)
|
||||||
})
|
}
|
||||||
|
|
||||||
return q
|
return q.Apply(cursorApplier.apply)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,26 +50,51 @@ func (a listVersionsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range a.params.Sort() {
|
for _, s := range a.params.Sort() {
|
||||||
cursorZero := a.params.Cursor().IsZero()
|
|
||||||
cursorCode := a.params.Cursor().Code()
|
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
case domain.VersionSortCodeASC:
|
case domain.VersionSortCodeASC:
|
||||||
if !cursorZero {
|
|
||||||
q = q.Where("version.code >= ?", cursorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
q = q.Order("version.code ASC")
|
q = q.Order("version.code ASC")
|
||||||
case domain.VersionSortCodeDESC:
|
case domain.VersionSortCodeDESC:
|
||||||
if !cursorZero {
|
|
||||||
q = q.Where("version.code <= ?", cursorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
q = q.Order("version.code DESC")
|
q = q.Order("version.code DESC")
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.Limit(a.params.Limit() + 1)
|
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a listVersionsParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
cursor := a.params.Cursor()
|
||||||
|
|
||||||
|
if cursor.IsZero() {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
sort := a.params.Sort()
|
||||||
|
cursorApplier := cursorPaginationApplier{
|
||||||
|
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range sort {
|
||||||
|
var el cursorPaginationApplierDataElement
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case domain.VersionSortCodeASC:
|
||||||
|
el.value = cursor.Code()
|
||||||
|
el.unique = true
|
||||||
|
el.column = "version.code"
|
||||||
|
el.direction = sortDirectionASC
|
||||||
|
case domain.VersionSortCodeDESC:
|
||||||
|
el.value = cursor.Code()
|
||||||
|
el.unique = true
|
||||||
|
el.column = "version.code"
|
||||||
|
el.direction = sortDirectionDESC
|
||||||
|
default:
|
||||||
|
return q.Err(errInvalidSortValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorApplier.data = append(cursorApplier.data, el)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.Apply(cursorApplier.apply)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(errUnsupportedSortValue)
|
return q.Err(errInvalidSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,180 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[scoreAtt DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortODScoreAttDESC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.OD().ScoreAtt(), b.OD().ScoreAtt())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[scoreDef DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortODScoreDefDESC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.OD().ScoreDef(), b.OD().ScoreDef())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[scoreTotal DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortODScoreTotalDESC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.OD().ScoreTotal(), b.OD().ScoreTotal())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[points DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortPointsDESC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.Points(), b.Points())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortDeletedAtASC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
a.DeletedAt().Compare(b.DeletedAt()),
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[deletedAt DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{
|
||||||
|
domain.PlayerSortDeletedAtDESC,
|
||||||
|
domain.PlayerSortServerKeyASC,
|
||||||
|
domain.PlayerSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
|
||||||
|
t.Helper()
|
||||||
|
players := res.Players()
|
||||||
|
assert.NotEmpty(t, len(players))
|
||||||
|
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
a.DeletedAt().Compare(b.DeletedAt())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "OK: ids serverKeys",
|
name: "OK: ids serverKeys",
|
||||||
params: func(t *testing.T) domain.ListPlayersParams {
|
params: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -371,8 +372,48 @@ const (
|
||||||
PlayerSortIDDESC
|
PlayerSortIDDESC
|
||||||
PlayerSortServerKeyASC
|
PlayerSortServerKeyASC
|
||||||
PlayerSortServerKeyDESC
|
PlayerSortServerKeyDESC
|
||||||
|
PlayerSortODScoreAttASC
|
||||||
|
PlayerSortODScoreAttDESC
|
||||||
|
PlayerSortODScoreDefASC
|
||||||
|
PlayerSortODScoreDefDESC
|
||||||
|
PlayerSortODScoreTotalASC
|
||||||
|
PlayerSortODScoreTotalDESC
|
||||||
|
PlayerSortPointsASC
|
||||||
|
PlayerSortPointsDESC
|
||||||
|
PlayerSortDeletedAtASC
|
||||||
|
PlayerSortDeletedAtDESC
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func newPlayerSortFromString(s string) (PlayerSort, error) {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "odscoreatt:asc":
|
||||||
|
return PlayerSortODScoreAttASC, nil
|
||||||
|
case "odscoreatt:desc":
|
||||||
|
return PlayerSortODScoreAttDESC, nil
|
||||||
|
case "odscoredef:asc":
|
||||||
|
return PlayerSortODScoreDefASC, nil
|
||||||
|
case "odscoredef:desc":
|
||||||
|
return PlayerSortODScoreDefDESC, nil
|
||||||
|
case "odscoretotal:asc":
|
||||||
|
return PlayerSortODScoreTotalASC, nil
|
||||||
|
case "odscoretotal:desc":
|
||||||
|
return PlayerSortODScoreTotalDESC, nil
|
||||||
|
case "points:asc":
|
||||||
|
return PlayerSortPointsASC, nil
|
||||||
|
case "points:desc":
|
||||||
|
return PlayerSortPointsDESC, nil
|
||||||
|
case "deletedat:asc":
|
||||||
|
return PlayerSortDeletedAtASC, nil
|
||||||
|
case "deletedat:desc":
|
||||||
|
return PlayerSortDeletedAtDESC, nil
|
||||||
|
default:
|
||||||
|
return 0, UnsupportedSortStringError{
|
||||||
|
Sort: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type PlayerCursor struct {
|
type PlayerCursor struct {
|
||||||
id int
|
id int
|
||||||
serverKey string
|
serverKey string
|
||||||
|
@ -628,7 +669,7 @@ func (params *ListPlayersParams) Sort() []PlayerSort {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
playerSortMinLength = 1
|
playerSortMinLength = 1
|
||||||
playerSortMaxLength = 2
|
playerSortMaxLength = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
|
func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
|
||||||
|
@ -645,6 +686,31 @@ func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (params *ListPlayersParams) PrependSortString(sort []string) error {
|
||||||
|
if err := validateSliceLen(sort, playerSortMinLength, max(playerSortMaxLength-len(params.sort), 0)); err != nil {
|
||||||
|
return ValidationError{
|
||||||
|
Model: listPlayersParamsModelName,
|
||||||
|
Field: "sort",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(sort) - 1; i >= 0; i-- {
|
||||||
|
converted, err := newPlayerSortFromString(sort[i])
|
||||||
|
if err != nil {
|
||||||
|
return SliceElementValidationError{
|
||||||
|
Model: listPlayersParamsModelName,
|
||||||
|
Field: "sort",
|
||||||
|
Index: i,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.sort = append([]PlayerSort{converted}, params.sort...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (params *ListPlayersParams) Cursor() PlayerCursor {
|
func (params *ListPlayersParams) Cursor() PlayerCursor {
|
||||||
return params.cursor
|
return params.cursor
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,6 +490,7 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
||||||
name: "OK",
|
name: "OK",
|
||||||
args: args{
|
args: args{
|
||||||
sort: []domain.PlayerSort{
|
sort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortPointsASC,
|
||||||
domain.PlayerSortIDASC,
|
domain.PlayerSortIDASC,
|
||||||
domain.PlayerSortServerKeyASC,
|
domain.PlayerSortServerKeyASC,
|
||||||
},
|
},
|
||||||
|
@ -505,18 +506,19 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
||||||
Field: "sort",
|
Field: "sort",
|
||||||
Err: domain.LenOutOfRangeError{
|
Err: domain.LenOutOfRangeError{
|
||||||
Min: 1,
|
Min: 1,
|
||||||
Max: 2,
|
Max: 3,
|
||||||
Current: 0,
|
Current: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ERR: len(sort) > 2",
|
name: "ERR: len(sort) > 3",
|
||||||
args: args{
|
args: args{
|
||||||
sort: []domain.PlayerSort{
|
sort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortDeletedAtDESC,
|
||||||
|
domain.PlayerSortPointsASC,
|
||||||
domain.PlayerSortIDASC,
|
domain.PlayerSortIDASC,
|
||||||
domain.PlayerSortServerKeyASC,
|
domain.PlayerSortServerKeyASC,
|
||||||
domain.PlayerSortServerKeyDESC,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: domain.ValidationError{
|
expectedErr: domain.ValidationError{
|
||||||
|
@ -524,8 +526,8 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
||||||
Field: "sort",
|
Field: "sort",
|
||||||
Err: domain.LenOutOfRangeError{
|
Err: domain.LenOutOfRangeError{
|
||||||
Min: 1,
|
Min: 1,
|
||||||
Max: 2,
|
Max: 3,
|
||||||
Current: 3,
|
Current: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -546,6 +548,181 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListPlayersParams_PrependSortString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
defaultNewParams := func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
return domain.ListPlayersParams{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
sort []string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newParams func(t *testing.T) domain.ListPlayersParams
|
||||||
|
args args
|
||||||
|
expectedSort []domain.PlayerSort
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: [odScoreAtt:ASC, odScoreDef:ASC, odScoreTotal:ASC]",
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"odScoreAtt:ASC",
|
||||||
|
"odScoreDef:ASC",
|
||||||
|
"odScoreTotal:ASC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortODScoreAttASC,
|
||||||
|
domain.PlayerSortODScoreDefASC,
|
||||||
|
domain.PlayerSortODScoreTotalASC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: [odScoreAtt:DESC, odScoreDef:DESC, odScoreTotal:DESC]",
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"odScoreAtt:DESC",
|
||||||
|
"odScoreDef:DESC",
|
||||||
|
"odScoreTotal:DESC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortODScoreAttDESC,
|
||||||
|
domain.PlayerSortODScoreDefDESC,
|
||||||
|
domain.PlayerSortODScoreTotalDESC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: [points:ASC, deletedAt:ASC]",
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"points:ASC",
|
||||||
|
"deletedAt:ASC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortPointsASC,
|
||||||
|
domain.PlayerSortDeletedAtASC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: [points:DESC, deletedAt:DESC]",
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"points:DESC",
|
||||||
|
"deletedAt:DESC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSort: []domain.PlayerSort{
|
||||||
|
domain.PlayerSortPointsDESC,
|
||||||
|
domain.PlayerSortDeletedAtDESC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) < 1",
|
||||||
|
args: args{
|
||||||
|
sort: nil,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListPlayersParams",
|
||||||
|
Field: "sort",
|
||||||
|
Err: domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 3,
|
||||||
|
Current: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) > 3",
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"odScoreAtt:ASC",
|
||||||
|
"odScoreDef:ASC",
|
||||||
|
"odScoreTotal:ASC",
|
||||||
|
"points:ASC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListPlayersParams",
|
||||||
|
Field: "sort",
|
||||||
|
Err: domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 3,
|
||||||
|
Current: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: custom params + len(sort) > 2",
|
||||||
|
newParams: func(t *testing.T) domain.ListPlayersParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListPlayersParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"odScoreAtt:ASC",
|
||||||
|
"odScoreDef:ASC",
|
||||||
|
"odScoreTotal:ASC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListPlayersParams",
|
||||||
|
Field: "sort",
|
||||||
|
Err: domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 2,
|
||||||
|
Current: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: unsupported sort string",
|
||||||
|
newParams: defaultNewParams,
|
||||||
|
args: args{
|
||||||
|
sort: []string{
|
||||||
|
"odScoreAtt:ASC",
|
||||||
|
"odScoreDef:ASC",
|
||||||
|
"odScoreTotal:",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: domain.SliceElementValidationError{
|
||||||
|
Model: "ListPlayersParams",
|
||||||
|
Field: "sort",
|
||||||
|
Index: 2,
|
||||||
|
Err: domain.UnsupportedSortStringError{
|
||||||
|
Sort: "odScoreTotal:",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
newParams := defaultNewParams
|
||||||
|
if tt.newParams != nil {
|
||||||
|
newParams = tt.newParams
|
||||||
|
}
|
||||||
|
params := newParams(t)
|
||||||
|
|
||||||
|
require.ErrorIs(t, params.PrependSortString(tt.args.sort), tt.expectedErr)
|
||||||
|
if tt.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expectedSort, params.Sort())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListPlayersParams_SetEncodedCursor(t *testing.T) {
|
func TestListPlayersParams_SetEncodedCursor(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func (h *apiHTTPHandler) ListPlayers(
|
func (h *apiHTTPHandler) ListPlayers(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
|
@ -22,6 +23,13 @@ func (h *apiHTTPHandler) ListPlayers(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Sort != nil {
|
||||||
|
if err := domainParams.PrependSortString(*params.Sort); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||||
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
|
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||||
|
@ -111,6 +112,70 @@ func TestListPlayers(t *testing.T) {
|
||||||
assert.Len(t, body.Data, limit)
|
assert.Len(t, body.Data, limit)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[deletedAt:DESC,points:ASC]",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "deletedAt:DESC")
|
||||||
|
q.Add("sort", "points:ASC")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
},
|
||||||
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
// body
|
||||||
|
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||||
|
assert.Zero(t, body.Cursor.Next)
|
||||||
|
assert.NotZero(t, body.Cursor.Self)
|
||||||
|
assert.NotZero(t, body.Data)
|
||||||
|
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.Player) int {
|
||||||
|
var aDeletedAt time.Time
|
||||||
|
if a.DeletedAt != nil {
|
||||||
|
aDeletedAt = *a.DeletedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
var bDeletedAt time.Time
|
||||||
|
if b.DeletedAt != nil {
|
||||||
|
bDeletedAt = *b.DeletedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmp.Or(
|
||||||
|
aDeletedAt.Compare(bDeletedAt)*-1,
|
||||||
|
cmp.Compare(a.Points, b.Points),
|
||||||
|
cmp.Compare(a.Id, b.Id),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[odScoreAtt:DESC]",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Set("sort", "odScoreAtt:DESC")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
},
|
||||||
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
// body
|
||||||
|
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||||
|
assert.Zero(t, body.Cursor.Next)
|
||||||
|
assert.NotZero(t, body.Cursor.Self)
|
||||||
|
assert.NotZero(t, body.Data)
|
||||||
|
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.Player) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.OpponentsDefeated.ScoreAtt, b.OpponentsDefeated.ScoreAtt)*-1,
|
||||||
|
cmp.Compare(a.Id, b.Id),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "OK: deleted=false",
|
name: "OK: deleted=false",
|
||||||
reqModifier: func(t *testing.T, req *http.Request) {
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
@ -127,8 +192,8 @@ func TestListPlayers(t *testing.T) {
|
||||||
// body
|
// body
|
||||||
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||||
assert.NotZero(t, body.Data)
|
assert.NotZero(t, body.Data)
|
||||||
for _, tr := range body.Data {
|
for _, p := range body.Data {
|
||||||
assert.Nil(t, tr.DeletedAt)
|
assert.Nil(t, p.DeletedAt)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -148,8 +213,8 @@ func TestListPlayers(t *testing.T) {
|
||||||
// body
|
// body
|
||||||
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||||
assert.NotZero(t, body.Data)
|
assert.NotZero(t, body.Data)
|
||||||
for _, tr := range body.Data {
|
for _, p := range body.Data {
|
||||||
assert.NotNil(t, tr.DeletedAt)
|
assert.NotNil(t, p.DeletedAt)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -354,6 +419,77 @@ func TestListPlayers(t *testing.T) {
|
||||||
}, body)
|
}, body)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) > 2",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "odScoreAtt:DESC")
|
||||||
|
q.Add("sort", "odScoreAtt:ASC")
|
||||||
|
q.Add("sort", "points:ASC")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
},
|
||||||
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
|
||||||
|
// body
|
||||||
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
||||||
|
domainErr := domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 2,
|
||||||
|
Current: len(req.URL.Query()["sort"]),
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: domainErr.Code(),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"current": float64(domainErr.Current),
|
||||||
|
"max": float64(domainErr.Max),
|
||||||
|
"min": float64(domainErr.Min),
|
||||||
|
},
|
||||||
|
Path: []string{"$query", "sort"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid sort",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "odScoreAtt:ASC")
|
||||||
|
q.Add("sort", "test:DESC")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
},
|
||||||
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
|
||||||
|
// body
|
||||||
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
||||||
|
domainErr := domain.UnsupportedSortStringError{
|
||||||
|
Sort: req.URL.Query()["sort"][1],
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: domainErr.Code(),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"sort": domainErr.Sort,
|
||||||
|
},
|
||||||
|
Path: []string{"$query", "sort", "1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ERR: deleted is not a valid boolean",
|
name: "ERR: deleted is not a valid boolean",
|
||||||
reqModifier: func(t *testing.T, req *http.Request) {
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
|
Loading…
Reference in New Issue