feat: api - GET /api/v2/versions/{versionCode}/servers/{serverKey}/players - add more sort options #7
|
@ -180,6 +180,7 @@ paths:
|
|||
- $ref: "#/components/parameters/CursorQueryParam"
|
||||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
- $ref: "#/components/parameters/PlayerDeletedQueryParam"
|
||||
- $ref: "#/components/parameters/PlayerSortQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListPlayersResponse"
|
||||
|
@ -1100,6 +1101,26 @@ components:
|
|||
schema:
|
||||
type: boolean
|
||||
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:
|
||||
in: path
|
||||
name: versionCode
|
||||
|
|
|
@ -3,6 +3,5 @@ package adapter
|
|||
import "errors"
|
||||
|
||||
var (
|
||||
errUnsupportedSortValue = errors.New("unsupported sort value")
|
||||
errSortNoUniqueField = errors.New("no unique field used for sorting")
|
||||
errInvalidSortValue = errors.New("invalid sort value")
|
||||
)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package adapter
|
||||
|
||||
import "github.com/uptrace/bun"
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
|
||||
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
|
||||
}
|
||||
|
||||
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:
|
||||
q = q.Order("ennoblement.server_key DESC")
|
||||
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")
|
||||
case domain.PlayerSortServerKeyDESC:
|
||||
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:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,85 +192,97 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
|
||||
//nolint:gocyclo
|
||||
func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if a.params.Cursor().IsZero() {
|
||||
cursor := a.params.Cursor()
|
||||
|
||||
if cursor.IsZero() {
|
||||
return q
|
||||
}
|
||||
|
||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
cursor := a.params.Cursor()
|
||||
cursorID := cursor.ID()
|
||||
cursorServerKey := cursor.ServerKey()
|
||||
|
||||
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.PlayerSortIDASC:
|
||||
q = q.Where("player.id >= ?", cursorID)
|
||||
case domain.PlayerSortIDDESC:
|
||||
q = q.Where("player.id <= ?", cursorID)
|
||||
case domain.PlayerSortServerKeyASC,
|
||||
domain.PlayerSortServerKeyDESC:
|
||||
return q.Err(errSortNoUniqueField)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
cursorApplier := cursorPaginationApplier{
|
||||
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||
}
|
||||
case sortLen > 1:
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
for i := 0; i < sortLen; i++ {
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
current := sort[i]
|
||||
|
||||
for j := 0; j < i; j++ {
|
||||
s := sort[j]
|
||||
for _, s := range sort {
|
||||
var el cursorPaginationApplierDataElement
|
||||
|
||||
switch s {
|
||||
case domain.PlayerSortIDASC,
|
||||
domain.PlayerSortIDDESC:
|
||||
q = q.Where("player.id = ?", cursorID)
|
||||
case domain.PlayerSortServerKeyASC,
|
||||
domain.PlayerSortServerKeyDESC:
|
||||
q = q.Where("player.server_key = ?", cursorServerKey)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
greaterSymbol := bun.Safe(">")
|
||||
lessSymbol := bun.Safe("<")
|
||||
|
||||
if i == sortLen-1 {
|
||||
greaterSymbol = ">="
|
||||
lessSymbol = "<="
|
||||
}
|
||||
|
||||
switch current {
|
||||
case domain.PlayerSortIDASC:
|
||||
q = q.Where("player.id ? ?", greaterSymbol, cursorID)
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "player.id"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortIDDESC:
|
||||
q = q.Where("player.id ? ?", lessSymbol, cursorID)
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "player.id"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.PlayerSortServerKeyASC:
|
||||
q = q.Where("player.server_key ? ?", greaterSymbol, cursorServerKey)
|
||||
el.value = cursor.ServerKey()
|
||||
el.unique = false
|
||||
el.column = "player.server_key"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortServerKeyDESC:
|
||||
q = q.Where("player.server_key ? ?", lessSymbol, cursorServerKey)
|
||||
el.value = cursor.ServerKey()
|
||||
el.unique = false
|
||||
el.column = "player.server_key"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.PlayerSortODScoreAttASC:
|
||||
el.value = cursor.ODScoreAtt()
|
||||
el.unique = false
|
||||
el.column = "player.score_att"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortODScoreAttDESC:
|
||||
el.value = cursor.ODScoreAtt()
|
||||
el.unique = false
|
||||
el.column = "player.score_att"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.PlayerSortODScoreDefASC:
|
||||
el.value = cursor.ODScoreDef()
|
||||
el.unique = false
|
||||
el.column = "player.score_def"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortODScoreDefDESC:
|
||||
el.value = cursor.ODScoreDef()
|
||||
el.unique = false
|
||||
el.column = "player.score_def"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.PlayerSortODScoreTotalASC:
|
||||
el.value = cursor.ODScoreTotal()
|
||||
el.unique = false
|
||||
el.column = "player.score_total"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortODScoreTotalDESC:
|
||||
el.value = cursor.ODScoreTotal()
|
||||
el.unique = false
|
||||
el.column = "player.score_total"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.PlayerSortPointsASC:
|
||||
el.value = cursor.Points()
|
||||
el.unique = false
|
||||
el.column = "player.points"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.PlayerSortPointsDESC:
|
||||
el.value = cursor.Points()
|
||||
el.unique = false
|
||||
el.column = "player.points"
|
||||
el.direction = sortDirectionDESC
|
||||
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(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
cursorApplier.data = append(cursorApplier.data, el)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
|
||||
return q
|
||||
return q.Apply(cursorApplier.apply)
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ func (a listPlayerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQ
|
|||
case domain.PlayerSnapshotSortServerKeyDESC:
|
||||
q = q.Order("ps.server_key DESC")
|
||||
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:
|
||||
q = q.Order("server.open DESC")
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
return q.Apply(a.applyCursor).Limit(a.params.Limit() + 1)
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if a.params.Cursor().IsZero() {
|
||||
cursor := a.params.Cursor()
|
||||
|
||||
if cursor.IsZero() {
|
||||
return q
|
||||
}
|
||||
|
||||
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)
|
||||
cursorApplier := cursorPaginationApplier{
|
||||
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||
}
|
||||
case sortLen > 1:
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
for i := 0; i < sortLen; i++ {
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
current := sort[i]
|
||||
|
||||
for j := 0; j < i; j++ {
|
||||
s := sort[j]
|
||||
for _, s := range sort {
|
||||
var el cursorPaginationApplierDataElement
|
||||
|
||||
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)
|
||||
el.value = cursor.Key()
|
||||
el.unique = true
|
||||
el.column = "server.key"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.ServerSortKeyDESC:
|
||||
q = q.Where("server.key ? ?", lessSymbol, cursorKey)
|
||||
el.value = cursor.Key()
|
||||
el.unique = true
|
||||
el.column = "server.key"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.ServerSortOpenASC:
|
||||
q = q.Where("server.open ? ?", greaterSymbol, cursorOpen)
|
||||
el.value = cursor.Open()
|
||||
el.unique = false
|
||||
el.column = "server.open"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.ServerSortOpenDESC:
|
||||
q = q.Where("server.open ? ?", lessSymbol, cursorOpen)
|
||||
el.value = cursor.Open()
|
||||
el.unique = false
|
||||
el.column = "server.open"
|
||||
el.direction = sortDirectionDESC
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
cursorApplier.data = append(cursorApplier.data, el)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
|
||||
return q
|
||||
return q.Apply(cursorApplier.apply)
|
||||
}
|
||||
|
|
|
@ -117,6 +117,10 @@ func (repo *TribeBunRepository) List(
|
|||
) (domain.ListTribesResult, error) {
|
||||
var tribes bunmodel.Tribes
|
||||
|
||||
fmt.Println(repo.db.NewSelect().
|
||||
Model(&tribes).
|
||||
Apply(listTribesParamsApplier{params: params}.apply).String())
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&tribes).
|
||||
Apply(listTribesParamsApplier{params: params}.apply).
|
||||
|
@ -212,7 +216,7 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
case domain.TribeSortDeletedAtDESC:
|
||||
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) DESC", time.Time{})
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,145 +225,107 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
|
||||
//nolint:gocyclo
|
||||
func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if a.params.Cursor().IsZero() {
|
||||
cursor := a.params.Cursor()
|
||||
|
||||
if cursor.IsZero() {
|
||||
return q
|
||||
}
|
||||
|
||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
cursor := a.params.Cursor()
|
||||
cursorID := cursor.ID()
|
||||
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()
|
||||
sortLen := len(sort)
|
||||
|
||||
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
|
||||
|
||||
switch {
|
||||
case sortLen == 1:
|
||||
switch sort[0] {
|
||||
case domain.TribeSortIDASC:
|
||||
q = q.Where("tribe.id >= ?", cursorID)
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id <= ?", cursorID)
|
||||
case domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC,
|
||||
domain.TribeSortODScoreAttASC,
|
||||
domain.TribeSortODScoreAttDESC,
|
||||
domain.TribeSortODScoreDefASC,
|
||||
domain.TribeSortODScoreDefDESC,
|
||||
domain.TribeSortODScoreTotalASC,
|
||||
domain.TribeSortODScoreTotalDESC,
|
||||
domain.TribeSortPointsASC,
|
||||
domain.TribeSortPointsDESC,
|
||||
domain.TribeSortDominanceASC,
|
||||
domain.TribeSortDominanceDESC,
|
||||
domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortDeletedAtDESC:
|
||||
return q.Err(errSortNoUniqueField)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
cursorApplier := cursorPaginationApplier{
|
||||
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||
}
|
||||
case sortLen > 1:
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
for i := 0; i < sortLen; i++ {
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
current := sort[i]
|
||||
|
||||
for j := 0; j < i; j++ {
|
||||
s := sort[j]
|
||||
for _, s := range sort {
|
||||
var el cursorPaginationApplierDataElement
|
||||
|
||||
switch s {
|
||||
case domain.TribeSortIDASC,
|
||||
domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id = ?", cursorID)
|
||||
case domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC:
|
||||
q = q.Where("tribe.server_key = ?", cursorServerKey)
|
||||
case domain.TribeSortODScoreAttASC,
|
||||
domain.TribeSortODScoreAttDESC:
|
||||
q = q.Where("tribe.score_att = ?", cursorODScoreAtt)
|
||||
case domain.TribeSortODScoreDefASC,
|
||||
domain.TribeSortODScoreDefDESC:
|
||||
q = q.Where("tribe.score_def = ?", cursorODScoreDef)
|
||||
case domain.TribeSortODScoreTotalASC,
|
||||
domain.TribeSortODScoreTotalDESC:
|
||||
q = q.Where("tribe.score_total = ?", cursorODScoreTotal)
|
||||
case domain.TribeSortPointsASC,
|
||||
domain.TribeSortPointsDESC:
|
||||
q = q.Where("tribe.points = ?", cursorPoints)
|
||||
case domain.TribeSortDominanceASC,
|
||||
domain.TribeSortDominanceDESC:
|
||||
q = q.Where("tribe.dominance = ?", cursorDominance)
|
||||
case domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortDeletedAtDESC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) = ?", time.Time{}, cursorDeletedAt)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
greaterSymbol := bun.Safe(">")
|
||||
lessSymbol := bun.Safe("<")
|
||||
|
||||
if i == sortLen-1 {
|
||||
greaterSymbol = ">="
|
||||
lessSymbol = "<="
|
||||
}
|
||||
|
||||
switch current {
|
||||
case domain.TribeSortIDASC:
|
||||
q = q.Where("tribe.id ? ?", greaterSymbol, cursorID)
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "tribe.id"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id ? ?", lessSymbol, cursorID)
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "tribe.id"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortServerKeyASC:
|
||||
q = q.Where("tribe.server_key ? ?", greaterSymbol, cursorServerKey)
|
||||
el.value = cursor.ServerKey()
|
||||
el.unique = false
|
||||
el.column = "tribe.server_key"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortServerKeyDESC:
|
||||
q = q.Where("tribe.server_key ? ?", lessSymbol, cursorServerKey)
|
||||
el.value = cursor.ServerKey()
|
||||
el.unique = false
|
||||
el.column = "tribe.server_key"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortODScoreAttASC:
|
||||
q = q.Where("tribe.score_att ? ?", greaterSymbol, cursorODScoreAtt)
|
||||
el.value = cursor.ODScoreAtt()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_att"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortODScoreAttDESC:
|
||||
q = q.Where("tribe.score_att ? ?", lessSymbol, cursorODScoreAtt)
|
||||
el.value = cursor.ODScoreAtt()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_att"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortODScoreDefASC:
|
||||
q = q.Where("tribe.score_def ? ?", greaterSymbol, cursorODScoreDef)
|
||||
el.value = cursor.ODScoreDef()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_def"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortODScoreDefDESC:
|
||||
q = q.Where("tribe.score_def ? ?", lessSymbol, cursorODScoreDef)
|
||||
el.value = cursor.ODScoreDef()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_def"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortODScoreTotalASC:
|
||||
q = q.Where("tribe.score_total ? ?", greaterSymbol, cursorODScoreTotal)
|
||||
el.value = cursor.ODScoreTotal()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_total"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortODScoreTotalDESC:
|
||||
q = q.Where("tribe.score_total ? ?", lessSymbol, cursorODScoreTotal)
|
||||
el.value = cursor.ODScoreTotal()
|
||||
el.unique = false
|
||||
el.column = "tribe.score_total"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortPointsASC:
|
||||
q = q.Where("tribe.points ? ?", greaterSymbol, cursorPoints)
|
||||
el.value = cursor.Points()
|
||||
el.unique = false
|
||||
el.column = "tribe.points"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortPointsDESC:
|
||||
q = q.Where("tribe.points ? ?", lessSymbol, cursorPoints)
|
||||
el.value = cursor.Points()
|
||||
el.unique = false
|
||||
el.column = "tribe.points"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortDominanceASC:
|
||||
q = q.Where("tribe.dominance ? ?", greaterSymbol, cursorDominance)
|
||||
el.value = cursor.Dominance()
|
||||
el.unique = false
|
||||
el.column = "tribe.dominance"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortDominanceDESC:
|
||||
q = q.Where("tribe.dominance ? ?", lessSymbol, cursorDominance)
|
||||
el.value = cursor.Dominance()
|
||||
el.unique = false
|
||||
el.column = "tribe.dominance"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.TribeSortDeletedAtASC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, greaterSymbol, cursorDeletedAt)
|
||||
el.value = cursor.DeletedAt()
|
||||
el.unique = false
|
||||
el.column = "COALESCE(tribe.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.TribeSortDeletedAtDESC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, lessSymbol, cursorDeletedAt)
|
||||
el.value = cursor.DeletedAt()
|
||||
el.unique = false
|
||||
el.column = "COALESCE(tribe.deleted_at, '0001-01-01 00:00:00+00:00')"
|
||||
el.direction = sortDirectionDESC
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
cursorApplier.data = append(cursorApplier.data, el)
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
}
|
||||
|
||||
return q
|
||||
})
|
||||
|
||||
return q
|
||||
return q.Apply(cursorApplier.apply)
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ func (a listTribeChangesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuer
|
|||
case domain.TribeChangeSortServerKeyDESC:
|
||||
q = q.Order("tc.server_key DESC")
|
||||
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:
|
||||
q = q.Order("ts.server_key DESC")
|
||||
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() {
|
||||
cursorZero := a.params.Cursor().IsZero()
|
||||
cursorCode := a.params.Cursor().Code()
|
||||
switch s {
|
||||
case domain.VersionSortCodeASC:
|
||||
q = q.Order("version.code ASC")
|
||||
case domain.VersionSortCodeDESC:
|
||||
q = q.Order("version.code DESC")
|
||||
default:
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
if !cursorZero {
|
||||
q = q.Where("version.code >= ?", cursorCode)
|
||||
}
|
||||
|
||||
q = q.Order("version.code ASC")
|
||||
el.value = cursor.Code()
|
||||
el.unique = true
|
||||
el.column = "version.code"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.VersionSortCodeDESC:
|
||||
if !cursorZero {
|
||||
q = q.Where("version.code <= ?", cursorCode)
|
||||
}
|
||||
|
||||
q = q.Order("version.code DESC")
|
||||
el.value = cursor.Code()
|
||||
el.unique = true
|
||||
el.column = "version.code"
|
||||
el.direction = sortDirectionDESC
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
return q.Err(errInvalidSortValue)
|
||||
}
|
||||
|
||||
return q.Limit(a.params.Limit() + 1)
|
||||
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:
|
||||
q = q.Order("village.server_key DESC")
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
params: func(t *testing.T) domain.ListPlayersParams {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"math"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -371,8 +372,48 @@ const (
|
|||
PlayerSortIDDESC
|
||||
PlayerSortServerKeyASC
|
||||
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 {
|
||||
id int
|
||||
serverKey string
|
||||
|
@ -628,7 +669,7 @@ func (params *ListPlayersParams) Sort() []PlayerSort {
|
|||
|
||||
const (
|
||||
playerSortMinLength = 1
|
||||
playerSortMaxLength = 2
|
||||
playerSortMaxLength = 3
|
||||
)
|
||||
|
||||
func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
|
||||
|
@ -645,6 +686,31 @@ func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
|
|||
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 {
|
||||
return params.cursor
|
||||
}
|
||||
|
|
|
@ -490,6 +490,7 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
|||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.PlayerSort{
|
||||
domain.PlayerSortPointsASC,
|
||||
domain.PlayerSortIDASC,
|
||||
domain.PlayerSortServerKeyASC,
|
||||
},
|
||||
|
@ -505,18 +506,19 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
|||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Max: 3,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 2",
|
||||
name: "ERR: len(sort) > 3",
|
||||
args: args{
|
||||
sort: []domain.PlayerSort{
|
||||
domain.PlayerSortDeletedAtDESC,
|
||||
domain.PlayerSortPointsASC,
|
||||
domain.PlayerSortIDASC,
|
||||
domain.PlayerSortServerKeyASC,
|
||||
domain.PlayerSortServerKeyDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
|
@ -524,8 +526,8 @@ func TestListPlayersParams_SetSort(t *testing.T) {
|
|||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Current: 3,
|
||||
Max: 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) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
)
|
||||
|
||||
//nolint:gocyclo
|
||||
func (h *apiHTTPHandler) ListPlayers(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
|
@ -22,6 +23,13 @@ func (h *apiHTTPHandler) ListPlayers(
|
|||
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 {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err)
|
||||
return
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
|
@ -111,6 +112,70 @@ func TestListPlayers(t *testing.T) {
|
|||
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",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
@ -127,8 +192,8 @@ func TestListPlayers(t *testing.T) {
|
|||
// body
|
||||
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||
assert.NotZero(t, body.Data)
|
||||
for _, tr := range body.Data {
|
||||
assert.Nil(t, tr.DeletedAt)
|
||||
for _, p := range body.Data {
|
||||
assert.Nil(t, p.DeletedAt)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -148,8 +213,8 @@ func TestListPlayers(t *testing.T) {
|
|||
// body
|
||||
body := decodeJSON[apimodel.ListPlayersResponse](t, resp.Body)
|
||||
assert.NotZero(t, body.Data)
|
||||
for _, tr := range body.Data {
|
||||
assert.NotNil(t, tr.DeletedAt)
|
||||
for _, p := range body.Data {
|
||||
assert.NotNil(t, p.DeletedAt)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -354,6 +419,77 @@ func TestListPlayers(t *testing.T) {
|
|||
}, 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",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user