feat: tribe - sort - add more options (#63)
Reviewed-on: twhelp/corev3#63
This commit is contained in:
parent
52c3ccb01b
commit
e5d8ba5390
|
@ -264,20 +264,7 @@ func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
|
||||||
}
|
}
|
||||||
case sortLen > 1:
|
case sortLen > 1:
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
switch sort[0] {
|
for i := 0; i < sortLen; i++ {
|
||||||
case domain.ServerSortKeyASC:
|
|
||||||
q = q.Where("server.key > ?", cursorKey)
|
|
||||||
case domain.ServerSortKeyDESC:
|
|
||||||
q = q.Where("server.key < ?", cursorKey)
|
|
||||||
case domain.ServerSortOpenASC:
|
|
||||||
q = q.Where("server.open > ?", cursorOpen)
|
|
||||||
case domain.ServerSortOpenDESC:
|
|
||||||
q = q.Where("server.open < ?", cursorOpen)
|
|
||||||
default:
|
|
||||||
return q.Err(errUnsupportedSortValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < sortLen; i++ {
|
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
current := sort[i]
|
current := sort[i]
|
||||||
|
|
||||||
|
@ -296,15 +283,23 @@ func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
greaterSymbol := bun.Safe(">")
|
||||||
|
lessSymbol := bun.Safe("<")
|
||||||
|
|
||||||
|
if i == sortLen-1 {
|
||||||
|
greaterSymbol = ">="
|
||||||
|
lessSymbol = "<="
|
||||||
|
}
|
||||||
|
|
||||||
switch current {
|
switch current {
|
||||||
case domain.ServerSortKeyASC:
|
case domain.ServerSortKeyASC:
|
||||||
q = q.Where("server.key >= ?", cursorKey)
|
q = q.Where("server.key ? ?", greaterSymbol, cursorKey)
|
||||||
case domain.ServerSortKeyDESC:
|
case domain.ServerSortKeyDESC:
|
||||||
q = q.Where("server.key <= ?", cursorKey)
|
q = q.Where("server.key ? ?", lessSymbol, cursorKey)
|
||||||
case domain.ServerSortOpenASC:
|
case domain.ServerSortOpenASC:
|
||||||
q = q.Where("server.open >= ?", cursorOpen)
|
q = q.Where("server.open ? ?", greaterSymbol, cursorOpen)
|
||||||
case domain.ServerSortOpenDESC:
|
case domain.ServerSortOpenDESC:
|
||||||
q = q.Where("server.open <= ?", cursorOpen)
|
q = q.Where("server.open ? ?", lessSymbol, cursorOpen)
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,30 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
q = q.Order("tribe.server_key ASC")
|
q = q.Order("tribe.server_key ASC")
|
||||||
case domain.TribeSortServerKeyDESC:
|
case domain.TribeSortServerKeyDESC:
|
||||||
q = q.Order("tribe.server_key DESC")
|
q = q.Order("tribe.server_key DESC")
|
||||||
|
case domain.TribeSortODScoreAttASC:
|
||||||
|
q = q.Order("tribe.score_att ASC")
|
||||||
|
case domain.TribeSortODScoreAttDESC:
|
||||||
|
q = q.Order("tribe.score_att DESC")
|
||||||
|
case domain.TribeSortODScoreDefASC:
|
||||||
|
q = q.Order("tribe.score_def ASC")
|
||||||
|
case domain.TribeSortODScoreDefDESC:
|
||||||
|
q = q.Order("tribe.score_def DESC")
|
||||||
|
case domain.TribeSortODScoreTotalASC:
|
||||||
|
q = q.Order("tribe.score_total ASC")
|
||||||
|
case domain.TribeSortODScoreTotalDESC:
|
||||||
|
q = q.Order("tribe.score_total DESC")
|
||||||
|
case domain.TribeSortPointsASC:
|
||||||
|
q = q.Order("tribe.points ASC")
|
||||||
|
case domain.TribeSortPointsDESC:
|
||||||
|
q = q.Order("tribe.points DESC")
|
||||||
|
case domain.TribeSortDominanceASC:
|
||||||
|
q = q.Order("tribe.dominance ASC")
|
||||||
|
case domain.TribeSortDominanceDESC:
|
||||||
|
q = q.Order("tribe.dominance DESC")
|
||||||
|
case domain.TribeSortDeletedAtASC:
|
||||||
|
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) ASC", time.Time{})
|
||||||
|
case domain.TribeSortDeletedAtDESC:
|
||||||
|
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) DESC", time.Time{})
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
|
@ -198,8 +222,15 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
||||||
}
|
}
|
||||||
|
|
||||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
cursorID := a.params.Cursor().ID()
|
cursor := a.params.Cursor()
|
||||||
cursorServerKey := a.params.Cursor().ServerKey()
|
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()
|
sort := a.params.Sort()
|
||||||
sortLen := len(sort)
|
sortLen := len(sort)
|
||||||
|
@ -213,27 +244,27 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
||||||
q = q.Where("tribe.id >= ?", cursorID)
|
q = q.Where("tribe.id >= ?", cursorID)
|
||||||
case domain.TribeSortIDDESC:
|
case domain.TribeSortIDDESC:
|
||||||
q = q.Where("tribe.id <= ?", cursorID)
|
q = q.Where("tribe.id <= ?", cursorID)
|
||||||
case domain.TribeSortServerKeyASC, domain.TribeSortServerKeyDESC:
|
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)
|
return q.Err(errSortNoUniqueField)
|
||||||
default:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
case sortLen > 1:
|
case sortLen > 1:
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
switch sort[0] {
|
for i := 0; i < sortLen; i++ {
|
||||||
case domain.TribeSortIDASC:
|
|
||||||
q = q.Where("tribe.id > ?", cursorID)
|
|
||||||
case domain.TribeSortIDDESC:
|
|
||||||
q = q.Where("tribe.id < ?", cursorID)
|
|
||||||
case domain.TribeSortServerKeyASC:
|
|
||||||
q = q.Where("tribe.server_key > ?", cursorServerKey)
|
|
||||||
case domain.TribeSortServerKeyDESC:
|
|
||||||
q = q.Where("tribe.server_key < ?", cursorServerKey)
|
|
||||||
default:
|
|
||||||
return q.Err(errUnsupportedSortValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < sortLen; i++ {
|
|
||||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
current := sort[i]
|
current := sort[i]
|
||||||
|
|
||||||
|
@ -247,20 +278,70 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
||||||
case domain.TribeSortServerKeyASC,
|
case domain.TribeSortServerKeyASC,
|
||||||
domain.TribeSortServerKeyDESC:
|
domain.TribeSortServerKeyDESC:
|
||||||
q = q.Where("tribe.server_key = ?", cursorServerKey)
|
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:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
greaterSymbol := bun.Safe(">")
|
||||||
|
lessSymbol := bun.Safe("<")
|
||||||
|
|
||||||
|
if i == sortLen-1 {
|
||||||
|
greaterSymbol = ">="
|
||||||
|
lessSymbol = "<="
|
||||||
|
}
|
||||||
|
|
||||||
switch current {
|
switch current {
|
||||||
case domain.TribeSortIDASC:
|
case domain.TribeSortIDASC:
|
||||||
q = q.Where("tribe.id >= ?", cursorID)
|
q = q.Where("tribe.id ? ?", greaterSymbol, cursorID)
|
||||||
case domain.TribeSortIDDESC:
|
case domain.TribeSortIDDESC:
|
||||||
q = q.Where("tribe.id <= ?", cursorID)
|
q = q.Where("tribe.id ? ?", lessSymbol, cursorID)
|
||||||
case domain.TribeSortServerKeyASC:
|
case domain.TribeSortServerKeyASC:
|
||||||
q = q.Where("tribe.server_key >= ?", cursorServerKey)
|
q = q.Where("tribe.server_key ? ?", greaterSymbol, cursorServerKey)
|
||||||
case domain.TribeSortServerKeyDESC:
|
case domain.TribeSortServerKeyDESC:
|
||||||
q = q.Where("tribe.server_key <= ?", cursorServerKey)
|
q = q.Where("tribe.server_key ? ?", lessSymbol, cursorServerKey)
|
||||||
|
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:
|
default:
|
||||||
return q.Err(errUnsupportedSortValue)
|
return q.Err(errUnsupportedSortValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,209 @@ func testTribeRepository(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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortODScoreAttDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortODScoreDefDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortODScoreTotalDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortPointsDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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=[dominance ASC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortDominanceASC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.Dominance(), b.Dominance()),
|
||||||
|
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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortDeletedAtASC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortDeletedAtDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) 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.ListTribesParams {
|
params: func(t *testing.T) domain.ListTribesParams {
|
||||||
|
@ -430,6 +633,98 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OK: cursor sort=[points DESC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortPointsDESC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
|
||||||
|
res, err := repos.tribe.List(ctx, params)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Greater(t, len(res.Tribes()), 2)
|
||||||
|
|
||||||
|
require.NoError(t, params.SetCursor(domaintest.NewTribeCursor(t, func(cfg *domaintest.TribeCursorConfig) {
|
||||||
|
cfg.ID = res.Tribes()[1].ID()
|
||||||
|
cfg.ServerKey = res.Tribes()[1].ServerKey()
|
||||||
|
cfg.Points = res.Tribes()[1].Points()
|
||||||
|
})))
|
||||||
|
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, params domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(a.Points(), b.Points())*-1,
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
assert.GreaterOrEqual(t, tribes[0].ID(), params.Cursor().ID())
|
||||||
|
assert.GreaterOrEqual(t, tribes[0].ServerKey(), params.Cursor().ServerKey())
|
||||||
|
for _, tr := range res.Tribes() {
|
||||||
|
assert.LessOrEqual(t, tr.Points(), params.Cursor().Points())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: cursor sort=[deletedAt ASC, serverKey ASC, id ASC]",
|
||||||
|
params: func(t *testing.T) domain.ListTribesParams {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
params := domain.NewListTribesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||||
|
domain.TribeSortDeletedAtASC,
|
||||||
|
domain.TribeSortServerKeyASC,
|
||||||
|
domain.TribeSortIDASC,
|
||||||
|
}))
|
||||||
|
|
||||||
|
res, err := repos.tribe.List(ctx, params)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Greater(t, len(res.Tribes()), 2)
|
||||||
|
|
||||||
|
require.NoError(t, params.SetCursor(domaintest.NewTribeCursor(t, func(cfg *domaintest.TribeCursorConfig) {
|
||||||
|
cfg.ID = res.Tribes()[1].ID()
|
||||||
|
cfg.ServerKey = res.Tribes()[1].ServerKey()
|
||||||
|
cfg.DeletedAt = res.Tribes()[1].DeletedAt()
|
||||||
|
})))
|
||||||
|
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertResult: func(t *testing.T, params domain.ListTribesParams, res domain.ListTribesResult) {
|
||||||
|
t.Helper()
|
||||||
|
tribes := res.Tribes()
|
||||||
|
assert.NotEmpty(t, len(tribes))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||||
|
return cmp.Or(
|
||||||
|
a.DeletedAt().Compare(b.DeletedAt()),
|
||||||
|
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||||
|
cmp.Compare(a.ID(), b.ID()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
assert.GreaterOrEqual(t, tribes[0].ID(), params.Cursor().ID())
|
||||||
|
assert.GreaterOrEqual(t, tribes[0].ServerKey(), params.Cursor().ServerKey())
|
||||||
|
for _, tr := range res.Tribes() {
|
||||||
|
assert.GreaterOrEqual(t, tr.DeletedAt(), params.Cursor().DeletedAt())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "OK: limit=2",
|
name: "OK: limit=2",
|
||||||
params: func(t *testing.T) domain.ListTribesParams {
|
params: func(t *testing.T) domain.ListTribesParams {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package domaintest
|
package domaintest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
@ -13,23 +14,44 @@ func RandTribeTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TribeCursorConfig struct {
|
type TribeCursorConfig struct {
|
||||||
ID int
|
ID int
|
||||||
ServerKey string
|
ServerKey string
|
||||||
|
ODScoreAtt int
|
||||||
|
ODScoreDef int
|
||||||
|
ODScoreTotal int
|
||||||
|
Points int
|
||||||
|
Dominance float64
|
||||||
|
DeletedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.TribeCursor {
|
func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.TribeCursor {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
cfg := &TribeCursorConfig{
|
cfg := &TribeCursorConfig{
|
||||||
ID: RandID(),
|
ID: RandID(),
|
||||||
ServerKey: RandServerKey(),
|
ServerKey: RandServerKey(),
|
||||||
|
ODScoreAtt: gofakeit.IntRange(0, math.MaxInt),
|
||||||
|
ODScoreDef: gofakeit.IntRange(0, math.MaxInt),
|
||||||
|
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
|
||||||
|
Points: gofakeit.IntRange(0, math.MaxInt),
|
||||||
|
Dominance: gofakeit.Float64Range(0.1, 99.9),
|
||||||
|
DeletedAt: time.Time{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(cfg)
|
opt(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
tc, err := domain.NewTribeCursor(cfg.ID, cfg.ServerKey)
|
tc, err := domain.NewTribeCursor(
|
||||||
|
cfg.ID,
|
||||||
|
cfg.ServerKey,
|
||||||
|
cfg.ODScoreAtt,
|
||||||
|
cfg.ODScoreDef,
|
||||||
|
cfg.ODScoreTotal,
|
||||||
|
cfg.Points,
|
||||||
|
cfg.Dominance,
|
||||||
|
cfg.DeletedAt,
|
||||||
|
)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
return tc
|
return tc
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -494,12 +493,17 @@ func decodeServerCursor(encoded string) (ServerCursor, error) {
|
||||||
return ServerCursor{}, err
|
return ServerCursor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
open, err := strconv.ParseBool(m["open"])
|
key, err := m.string("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServerCursor{}, ErrInvalidCursor
|
return ServerCursor{}, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
vc, err := NewServerCursor(m["key"], open)
|
open, err := m.bool("open")
|
||||||
|
if err != nil {
|
||||||
|
return ServerCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
vc, err := NewServerCursor(key, open)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServerCursor{}, ErrInvalidCursor
|
return ServerCursor{}, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
@ -524,9 +528,9 @@ func (sc ServerCursor) Encode() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeCursor([][2]string{
|
return encodeCursor([]keyValuePair{
|
||||||
{"key", sc.key},
|
{"key", sc.key},
|
||||||
{"open", strconv.FormatBool(sc.open)},
|
{"open", sc.open},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -367,16 +366,43 @@ const (
|
||||||
TribeSortIDDESC
|
TribeSortIDDESC
|
||||||
TribeSortServerKeyASC
|
TribeSortServerKeyASC
|
||||||
TribeSortServerKeyDESC
|
TribeSortServerKeyDESC
|
||||||
|
TribeSortODScoreAttASC
|
||||||
|
TribeSortODScoreAttDESC
|
||||||
|
TribeSortODScoreDefASC
|
||||||
|
TribeSortODScoreDefDESC
|
||||||
|
TribeSortODScoreTotalASC
|
||||||
|
TribeSortODScoreTotalDESC
|
||||||
|
TribeSortPointsASC
|
||||||
|
TribeSortPointsDESC
|
||||||
|
TribeSortDominanceASC
|
||||||
|
TribeSortDominanceDESC
|
||||||
|
TribeSortDeletedAtASC
|
||||||
|
TribeSortDeletedAtDESC
|
||||||
)
|
)
|
||||||
|
|
||||||
type TribeCursor struct {
|
type TribeCursor struct {
|
||||||
id int
|
id int
|
||||||
serverKey string
|
serverKey string
|
||||||
|
odScoreAtt int
|
||||||
|
odScoreDef int
|
||||||
|
odScoreTotal int
|
||||||
|
points int
|
||||||
|
dominance float64
|
||||||
|
deletedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
const tribeCursorModelName = "TribeCursor"
|
const tribeCursorModelName = "TribeCursor"
|
||||||
|
|
||||||
func NewTribeCursor(id int, serverKey string) (TribeCursor, error) {
|
func NewTribeCursor(
|
||||||
|
id int,
|
||||||
|
serverKey string,
|
||||||
|
odScoreAtt int,
|
||||||
|
odScoreDef int,
|
||||||
|
odScoreTotal int,
|
||||||
|
points int,
|
||||||
|
dominance float64,
|
||||||
|
deletedAt time.Time,
|
||||||
|
) (TribeCursor, error) {
|
||||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||||
return TribeCursor{}, ValidationError{
|
return TribeCursor{}, ValidationError{
|
||||||
Model: tribeCursorModelName,
|
Model: tribeCursorModelName,
|
||||||
|
@ -393,24 +419,107 @@ func NewTribeCursor(id int, serverKey string) (TribeCursor, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(odScoreAtt, 0, math.MaxInt); err != nil {
|
||||||
|
return TribeCursor{}, ValidationError{
|
||||||
|
Model: tribeCursorModelName,
|
||||||
|
Field: "odScoreAtt",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(odScoreDef, 0, math.MaxInt); err != nil {
|
||||||
|
return TribeCursor{}, ValidationError{
|
||||||
|
Model: tribeCursorModelName,
|
||||||
|
Field: "odScoreDef",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(odScoreTotal, 0, math.MaxInt); err != nil {
|
||||||
|
return TribeCursor{}, ValidationError{
|
||||||
|
Model: tribeCursorModelName,
|
||||||
|
Field: "odScoreTotal",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
|
||||||
|
return TribeCursor{}, ValidationError{
|
||||||
|
Model: tribeCursorModelName,
|
||||||
|
Field: "points",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TribeCursor{
|
return TribeCursor{
|
||||||
id: id,
|
id: id,
|
||||||
serverKey: serverKey,
|
serverKey: serverKey,
|
||||||
|
odScoreAtt: odScoreAtt,
|
||||||
|
odScoreDef: odScoreDef,
|
||||||
|
odScoreTotal: odScoreTotal,
|
||||||
|
points: points,
|
||||||
|
dominance: dominance,
|
||||||
|
deletedAt: deletedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func decodeTribeCursor(encoded string) (TribeCursor, error) {
|
func decodeTribeCursor(encoded string) (TribeCursor, error) {
|
||||||
m, err := decodeCursor(encoded)
|
m, err := decodeCursor(encoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TribeCursor{}, err
|
return TribeCursor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := strconv.Atoi(m["id"])
|
id, err := m.int("id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TribeCursor{}, ErrInvalidCursor
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
tc, err := NewTribeCursor(id, m["serverKey"])
|
serverKey, err := m.string("serverKey")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
odScoreAtt, err := m.int("odScoreAtt")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
odScoreDef, err := m.int("odScoreDef")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
odScoreTotal, err := m.int("odScoreTotal")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
points, err := m.int("points")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
dominance, err := m.float64("dominance")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedAt, err := m.time("deletedAt")
|
||||||
|
if err != nil {
|
||||||
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err := NewTribeCursor(
|
||||||
|
id,
|
||||||
|
serverKey,
|
||||||
|
odScoreAtt,
|
||||||
|
odScoreDef,
|
||||||
|
odScoreTotal,
|
||||||
|
points,
|
||||||
|
dominance,
|
||||||
|
deletedAt,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TribeCursor{}, ErrInvalidCursor
|
return TribeCursor{}, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
@ -426,6 +535,30 @@ func (tc TribeCursor) ServerKey() string {
|
||||||
return tc.serverKey
|
return tc.serverKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) ODScoreAtt() int {
|
||||||
|
return tc.odScoreAtt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) ODScoreDef() int {
|
||||||
|
return tc.odScoreDef
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) ODScoreTotal() int {
|
||||||
|
return tc.odScoreTotal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) Points() int {
|
||||||
|
return tc.points
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) Dominance() float64 {
|
||||||
|
return tc.dominance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeCursor) DeletedAt() time.Time {
|
||||||
|
return tc.deletedAt
|
||||||
|
}
|
||||||
|
|
||||||
func (tc TribeCursor) IsZero() bool {
|
func (tc TribeCursor) IsZero() bool {
|
||||||
return tc == TribeCursor{}
|
return tc == TribeCursor{}
|
||||||
}
|
}
|
||||||
|
@ -435,9 +568,15 @@ func (tc TribeCursor) Encode() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeCursor([][2]string{
|
return encodeCursor([]keyValuePair{
|
||||||
{"id", strconv.Itoa(tc.id)},
|
{"id", tc.id},
|
||||||
{"serverKey", tc.serverKey},
|
{"serverKey", tc.serverKey},
|
||||||
|
{"odScoreAtt", tc.odScoreAtt},
|
||||||
|
{"odScoreDef", tc.odScoreDef},
|
||||||
|
{"odScoreTotal", tc.odScoreTotal},
|
||||||
|
{"points", tc.points},
|
||||||
|
{"dominance", tc.dominance},
|
||||||
|
{"deletedAt", tc.deletedAt},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,7 +649,7 @@ func (params *ListTribesParams) Sort() []TribeSort {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tribeSortMinLength = 1
|
tribeSortMinLength = 1
|
||||||
tribeSortMaxLength = 2
|
tribeSortMaxLength = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
||||||
|
@ -584,7 +723,17 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tribes) > 0 {
|
if len(tribes) > 0 {
|
||||||
res.self, err = NewTribeCursor(tribes[0].ID(), tribes[0].ServerKey())
|
od := tribes[0].OD()
|
||||||
|
res.self, err = NewTribeCursor(
|
||||||
|
tribes[0].ID(),
|
||||||
|
tribes[0].ServerKey(),
|
||||||
|
od.ScoreAtt(),
|
||||||
|
od.ScoreDef(),
|
||||||
|
od.ScoreTotal(),
|
||||||
|
tribes[0].Points(),
|
||||||
|
tribes[0].Dominance(),
|
||||||
|
tribes[0].DeletedAt(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ListTribesResult{}, ValidationError{
|
return ListTribesResult{}, ValidationError{
|
||||||
Model: listTribesResultModelName,
|
Model: listTribesResultModelName,
|
||||||
|
@ -595,7 +744,17 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !next.IsZero() {
|
if !next.IsZero() {
|
||||||
res.next, err = NewTribeCursor(next.ID(), next.ServerKey())
|
od := next.OD()
|
||||||
|
res.next, err = NewTribeCursor(
|
||||||
|
next.ID(),
|
||||||
|
next.ServerKey(),
|
||||||
|
od.ScoreAtt(),
|
||||||
|
od.ScoreDef(),
|
||||||
|
od.ScoreTotal(),
|
||||||
|
next.Points(),
|
||||||
|
next.Dominance(),
|
||||||
|
next.DeletedAt(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ListTribesResult{}, ValidationError{
|
return ListTribesResult{}, ValidationError{
|
||||||
Model: listTribesResultModelName,
|
Model: listTribesResultModelName,
|
||||||
|
|
|
@ -186,8 +186,14 @@ func TestNewTribeCursor(t *testing.T) {
|
||||||
validTribeCursor := domaintest.NewTribeCursor(t)
|
validTribeCursor := domaintest.NewTribeCursor(t)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
id int
|
id int
|
||||||
serverKey string
|
serverKey string
|
||||||
|
odScoreAtt int
|
||||||
|
odScoreDef int
|
||||||
|
odScoreTotal int
|
||||||
|
points int
|
||||||
|
dominance float64
|
||||||
|
deletedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
|
@ -200,16 +206,28 @@ func TestNewTribeCursor(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "OK",
|
name: "OK",
|
||||||
args: args{
|
args: args{
|
||||||
id: validTribeCursor.ID(),
|
id: validTribeCursor.ID(),
|
||||||
serverKey: validTribeCursor.ServerKey(),
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ERR: id < 1",
|
name: "ERR: id < 1",
|
||||||
args: args{
|
args: args{
|
||||||
id: 0,
|
id: 0,
|
||||||
serverKey: validTribeCursor.ServerKey(),
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
},
|
},
|
||||||
expectedErr: domain.ValidationError{
|
expectedErr: domain.ValidationError{
|
||||||
Model: "TribeCursor",
|
Model: "TribeCursor",
|
||||||
|
@ -220,14 +238,104 @@ func TestNewTribeCursor(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: odScoreAtt < 0",
|
||||||
|
args: args{
|
||||||
|
id: validTribeCursor.ID(),
|
||||||
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: -1,
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "TribeCursor",
|
||||||
|
Field: "odScoreAtt",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: odScoreDef < 0",
|
||||||
|
args: args{
|
||||||
|
id: validTribeCursor.ID(),
|
||||||
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: -1,
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "TribeCursor",
|
||||||
|
Field: "odScoreDef",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: odScoreTotal < 0",
|
||||||
|
args: args{
|
||||||
|
id: validTribeCursor.ID(),
|
||||||
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: -1,
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "TribeCursor",
|
||||||
|
Field: "odScoreTotal",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: points < 0",
|
||||||
|
args: args{
|
||||||
|
id: validTribeCursor.ID(),
|
||||||
|
serverKey: validTribeCursor.ServerKey(),
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: -1,
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "TribeCursor",
|
||||||
|
Field: "points",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||||
tests = append(tests, test{
|
tests = append(tests, test{
|
||||||
name: serverKeyTest.name,
|
name: serverKeyTest.name,
|
||||||
args: args{
|
args: args{
|
||||||
id: validTribeCursor.ID(),
|
id: validTribeCursor.ID(),
|
||||||
serverKey: serverKeyTest.key,
|
serverKey: serverKeyTest.key,
|
||||||
|
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||||
|
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||||
|
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||||
|
points: validTribeCursor.Points(),
|
||||||
|
dominance: validTribeCursor.Dominance(),
|
||||||
|
deletedAt: validTribeCursor.DeletedAt(),
|
||||||
},
|
},
|
||||||
expectedErr: domain.ValidationError{
|
expectedErr: domain.ValidationError{
|
||||||
Model: "TribeCursor",
|
Model: "TribeCursor",
|
||||||
|
@ -243,7 +351,16 @@ func TestNewTribeCursor(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tc, err := domain.NewTribeCursor(tt.args.id, tt.args.serverKey)
|
tc, err := domain.NewTribeCursor(
|
||||||
|
tt.args.id,
|
||||||
|
tt.args.serverKey,
|
||||||
|
tt.args.odScoreAtt,
|
||||||
|
tt.args.odScoreDef,
|
||||||
|
tt.args.odScoreTotal,
|
||||||
|
tt.args.points,
|
||||||
|
tt.args.dominance,
|
||||||
|
tt.args.deletedAt,
|
||||||
|
)
|
||||||
require.ErrorIs(t, err, tt.expectedErr)
|
require.ErrorIs(t, err, tt.expectedErr)
|
||||||
if tt.expectedErr != nil {
|
if tt.expectedErr != nil {
|
||||||
return
|
return
|
||||||
|
@ -333,6 +450,7 @@ func TestListTribesParams_SetSort(t *testing.T) {
|
||||||
name: "OK",
|
name: "OK",
|
||||||
args: args{
|
args: args{
|
||||||
sort: []domain.TribeSort{
|
sort: []domain.TribeSort{
|
||||||
|
domain.TribeSortPointsASC,
|
||||||
domain.TribeSortIDASC,
|
domain.TribeSortIDASC,
|
||||||
domain.TribeSortServerKeyASC,
|
domain.TribeSortServerKeyASC,
|
||||||
},
|
},
|
||||||
|
@ -348,18 +466,19 @@ func TestListTribesParams_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.TribeSort{
|
sort: []domain.TribeSort{
|
||||||
|
domain.TribeSortDominanceASC,
|
||||||
|
domain.TribeSortPointsASC,
|
||||||
domain.TribeSortIDASC,
|
domain.TribeSortIDASC,
|
||||||
domain.TribeSortServerKeyASC,
|
domain.TribeSortServerKeyASC,
|
||||||
domain.TribeSortServerKeyDESC,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: domain.ValidationError{
|
expectedErr: domain.ValidationError{
|
||||||
|
@ -367,8 +486,8 @@ func TestListTribesParams_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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,8 +3,11 @@ package domain
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseURL(rawURL string) (*url.URL, error) {
|
func parseURL(rawURL string) (*url.URL, error) {
|
||||||
|
@ -23,38 +26,76 @@ const (
|
||||||
cursorKeyValueSeparator = "="
|
cursorKeyValueSeparator = "="
|
||||||
)
|
)
|
||||||
|
|
||||||
func encodeCursor(s [][2]string) string {
|
type keyValuePair struct {
|
||||||
if len(s) == 0 {
|
key string
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kvp keyValuePair) valueString() string {
|
||||||
|
switch v := kvp.value.(type) {
|
||||||
|
case string:
|
||||||
|
return v
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(v)
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(v)
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
|
case time.Time:
|
||||||
|
return v.Format(time.RFC3339)
|
||||||
|
case fmt.Stringer:
|
||||||
|
return v.String()
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeCursor(kvps []keyValuePair) string {
|
||||||
|
if len(kvps) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(cursorSeparator) * (len(s) - 1)
|
values := make([]string, len(kvps))
|
||||||
for _, el := range s {
|
n := len(cursorSeparator) * (len(kvps) - 1)
|
||||||
n += len(el[0]) + len(el[1]) + len(cursorKeyValueSeparator)
|
|
||||||
|
for i, kvp := range kvps {
|
||||||
|
value := kvp.valueString()
|
||||||
|
n += len(kvp.key) + len(value) + len(cursorKeyValueSeparator)
|
||||||
|
values[i] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
b.Grow(n)
|
b.Grow(n)
|
||||||
|
|
||||||
for _, el := range s {
|
for i, kvp := range kvps {
|
||||||
if b.Len() > 0 {
|
if b.Len() > 0 {
|
||||||
_, _ = b.WriteString(cursorSeparator)
|
_, _ = b.WriteString(cursorSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = b.WriteString(el[0])
|
_, _ = b.WriteString(kvp.key)
|
||||||
_, _ = b.WriteString(cursorKeyValueSeparator)
|
_, _ = b.WriteString(cursorKeyValueSeparator)
|
||||||
_, _ = b.WriteString(el[1])
|
_, _ = b.WriteString(values[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(b.Bytes())
|
return base64.StdEncoding.EncodeToString(b.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cursorKeyNotFoundError struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e cursorKeyNotFoundError) Error() string {
|
||||||
|
return "key " + e.key + " not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
type decodedCursor map[string]string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
encodedCursorMinLength = 1
|
encodedCursorMinLength = 1
|
||||||
encodedCursorMaxLength = 1000
|
encodedCursorMaxLength = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeCursor(s string) (map[string]string, error) {
|
func decodeCursor(s string) (decodedCursor, error) {
|
||||||
if err := validateStringLen(s, encodedCursorMinLength, encodedCursorMaxLength); err != nil {
|
if err := validateStringLen(s, encodedCursorMinLength, encodedCursorMaxLength); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -65,12 +106,12 @@ func decodeCursor(s string) (map[string]string, error) {
|
||||||
}
|
}
|
||||||
decoded := string(decodedBytes)
|
decoded := string(decodedBytes)
|
||||||
|
|
||||||
kvs := strings.Split(decoded, cursorSeparator)
|
kvps := strings.Split(decoded, cursorSeparator)
|
||||||
|
|
||||||
m := make(map[string]string, len(kvs))
|
m := make(decodedCursor, len(kvps))
|
||||||
|
|
||||||
for _, kv := range kvs {
|
for _, kvp := range kvps {
|
||||||
k, v, found := strings.Cut(kv, cursorKeyValueSeparator)
|
k, v, found := strings.Cut(kvp, cursorKeyValueSeparator)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, ErrInvalidCursor
|
return nil, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
@ -79,3 +120,68 @@ func decodeCursor(s string) (map[string]string, error) {
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c decodedCursor) string(k string) (string, error) {
|
||||||
|
val, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
return "", cursorKeyNotFoundError{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c decodedCursor) int(k string) (int, error) {
|
||||||
|
valStr, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
return 0, cursorKeyNotFoundError{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.Atoi(valStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c decodedCursor) float64(k string) (float64, error) {
|
||||||
|
valStr, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
return 0, cursorKeyNotFoundError{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseFloat(valStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c decodedCursor) bool(k string) (bool, error) {
|
||||||
|
valStr, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
return false, cursorKeyNotFoundError{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseBool(valStr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c decodedCursor) time(k string) (time.Time, error) {
|
||||||
|
valStr, ok := c[k]
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, cursorKeyNotFoundError{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := time.Parse(time.RFC3339, valStr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
|
@ -131,7 +131,12 @@ func decodeVersionCursor(encoded string) (VersionCursor, error) {
|
||||||
return VersionCursor{}, err
|
return VersionCursor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vc, err := NewVersionCursor(m["code"])
|
code, err := m.string("code")
|
||||||
|
if err != nil {
|
||||||
|
return VersionCursor{}, ErrInvalidCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
vc, err := NewVersionCursor(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return VersionCursor{}, ErrInvalidCursor
|
return VersionCursor{}, ErrInvalidCursor
|
||||||
}
|
}
|
||||||
|
@ -152,7 +157,7 @@ func (vc VersionCursor) Encode() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeCursor([][2]string{
|
return encodeCursor([]keyValuePair{
|
||||||
{"code", vc.code},
|
{"code", vc.code},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue