2023-12-27 08:07:40 +00:00
|
|
|
package domain
|
|
|
|
|
|
|
|
import (
|
2023-12-29 08:23:05 +00:00
|
|
|
"cmp"
|
2023-12-28 10:56:59 +00:00
|
|
|
"fmt"
|
|
|
|
"math"
|
2023-12-27 08:07:40 +00:00
|
|
|
"net/url"
|
2023-12-28 10:56:59 +00:00
|
|
|
"slices"
|
2023-12-27 08:07:40 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
tribeNameMinLength = 1
|
|
|
|
tribeNameMaxLength = 255
|
|
|
|
tribeTagMinLength = 1
|
|
|
|
tribeTagMaxLength = 10
|
|
|
|
)
|
|
|
|
|
|
|
|
type Tribe struct {
|
|
|
|
id int
|
|
|
|
serverKey string
|
|
|
|
name string
|
|
|
|
tag string
|
|
|
|
numMembers int
|
|
|
|
numVillages int
|
|
|
|
points int
|
|
|
|
allPoints int
|
|
|
|
rank int
|
|
|
|
od OpponentsDefeated
|
|
|
|
profileURL *url.URL
|
|
|
|
dominance float64
|
|
|
|
bestRank int
|
|
|
|
bestRankAt time.Time
|
|
|
|
mostPoints int
|
|
|
|
mostPointsAt time.Time
|
|
|
|
mostVillages int
|
|
|
|
mostVillagesAt time.Time
|
|
|
|
createdAt time.Time
|
|
|
|
deletedAt time.Time
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
const tribeModelName = "Tribe"
|
|
|
|
|
|
|
|
// UnmarshalTribeFromDatabase unmarshals Tribe from the database.
|
|
|
|
//
|
|
|
|
// It should be used only for unmarshalling from the database!
|
|
|
|
// You can't use UnmarshalTribeFromDatabase as constructor - It may put domain into the invalid state!
|
|
|
|
func UnmarshalTribeFromDatabase(
|
|
|
|
id int,
|
|
|
|
serverKey string,
|
|
|
|
name string,
|
|
|
|
tag string,
|
|
|
|
numMembers int,
|
|
|
|
numVillages int,
|
|
|
|
points int,
|
|
|
|
allPoints int,
|
|
|
|
rank int,
|
|
|
|
od OpponentsDefeated,
|
|
|
|
rawProfileURL string,
|
|
|
|
dominance float64,
|
|
|
|
bestRank int,
|
|
|
|
bestRankAt time.Time,
|
|
|
|
mostPoints int,
|
|
|
|
mostPointsAt time.Time,
|
|
|
|
mostVillages int,
|
|
|
|
mostVillagesAt time.Time,
|
|
|
|
createdAt time.Time,
|
|
|
|
deletedAt time.Time,
|
|
|
|
) (Tribe, error) {
|
|
|
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
|
|
|
return Tribe{}, ValidationError{
|
|
|
|
Model: tribeModelName,
|
|
|
|
Field: "id",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-02 09:54:11 +00:00
|
|
|
if err := validateServerKey(serverKey); err != nil {
|
|
|
|
return Tribe{}, ValidationError{
|
|
|
|
Model: tribeModelName,
|
|
|
|
Field: "serverKey",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
profileURL, err := parseURL(rawProfileURL)
|
|
|
|
if err != nil {
|
|
|
|
return Tribe{}, ValidationError{
|
|
|
|
Model: tribeModelName,
|
|
|
|
Field: "profileURL",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Tribe{
|
|
|
|
id: id,
|
|
|
|
serverKey: serverKey,
|
|
|
|
name: name,
|
|
|
|
tag: tag,
|
|
|
|
numMembers: numMembers,
|
|
|
|
numVillages: numVillages,
|
|
|
|
points: points,
|
|
|
|
allPoints: allPoints,
|
|
|
|
rank: rank,
|
|
|
|
od: od,
|
|
|
|
profileURL: profileURL,
|
|
|
|
dominance: dominance,
|
|
|
|
bestRank: bestRank,
|
|
|
|
bestRankAt: bestRankAt,
|
|
|
|
mostPoints: mostPoints,
|
|
|
|
mostPointsAt: mostPointsAt,
|
|
|
|
mostVillages: mostVillages,
|
|
|
|
mostVillagesAt: mostVillagesAt,
|
|
|
|
createdAt: createdAt,
|
|
|
|
deletedAt: deletedAt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-12-27 08:07:40 +00:00
|
|
|
func (t Tribe) ID() int {
|
|
|
|
return t.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) ServerKey() string {
|
|
|
|
return t.serverKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Name() string {
|
|
|
|
return t.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Tag() string {
|
|
|
|
return t.tag
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) NumMembers() int {
|
|
|
|
return t.numMembers
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) NumVillages() int {
|
|
|
|
return t.numVillages
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Points() int {
|
|
|
|
return t.points
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) AllPoints() int {
|
|
|
|
return t.allPoints
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Rank() int {
|
|
|
|
return t.rank
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) OD() OpponentsDefeated {
|
|
|
|
return t.od
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) ProfileURL() *url.URL {
|
2024-03-11 07:37:04 +00:00
|
|
|
if t.profileURL == nil {
|
|
|
|
return &url.URL{}
|
|
|
|
}
|
2023-12-27 08:07:40 +00:00
|
|
|
return t.profileURL
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Dominance() float64 {
|
|
|
|
return t.dominance
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) BestRank() int {
|
|
|
|
return t.bestRank
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) BestRankAt() time.Time {
|
|
|
|
return t.bestRankAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) MostPoints() int {
|
|
|
|
return t.mostPoints
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) MostPointsAt() time.Time {
|
|
|
|
return t.mostPointsAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) MostVillages() int {
|
|
|
|
return t.mostVillages
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) MostVillagesAt() time.Time {
|
|
|
|
return t.mostVillagesAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) CreatedAt() time.Time {
|
|
|
|
return t.createdAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) DeletedAt() time.Time {
|
|
|
|
return t.deletedAt
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
|
2024-03-06 06:35:26 +00:00
|
|
|
func (t Tribe) ToCursor() (TribeCursor, error) {
|
|
|
|
return NewTribeCursor(
|
|
|
|
t.id,
|
|
|
|
t.serverKey,
|
|
|
|
t.od.scoreAtt,
|
|
|
|
t.od.scoreDef,
|
|
|
|
t.od.scoreTotal,
|
|
|
|
t.points,
|
|
|
|
t.dominance,
|
|
|
|
t.deletedAt,
|
|
|
|
)
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t Tribe) Base() BaseTribe {
|
|
|
|
return BaseTribe{
|
|
|
|
id: t.id,
|
|
|
|
name: t.name,
|
|
|
|
tag: t.tag,
|
|
|
|
numMembers: t.numMembers,
|
|
|
|
numVillages: t.numVillages,
|
|
|
|
points: t.points,
|
|
|
|
allPoints: t.allPoints,
|
|
|
|
rank: t.rank,
|
|
|
|
od: t.od,
|
|
|
|
profileURL: t.profileURL,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-28 06:59:10 +00:00
|
|
|
func (t Tribe) Meta() TribeMeta {
|
|
|
|
return TribeMeta{
|
|
|
|
id: t.id,
|
|
|
|
name: t.name,
|
|
|
|
tag: t.tag,
|
|
|
|
profileURL: t.profileURL,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 06:35:26 +00:00
|
|
|
func (t Tribe) IsDeleted() bool {
|
|
|
|
return !t.deletedAt.IsZero()
|
|
|
|
}
|
|
|
|
|
2024-01-15 07:00:08 +00:00
|
|
|
func (t Tribe) IsZero() bool {
|
|
|
|
return t == Tribe{}
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
type Tribes []Tribe
|
|
|
|
|
2024-01-06 09:44:14 +00:00
|
|
|
// Delete finds all tribes with the given serverKey that are not in the given slice with active tribes
|
|
|
|
// and returns their ids. Both slices must be sorted in ascending order by ID
|
|
|
|
// + if vs contains tribes from different servers. they must be sorted in ascending order by server key.
|
|
|
|
func (ts Tribes) Delete(serverKey string, active BaseTribes) []int {
|
2023-12-29 08:23:05 +00:00
|
|
|
//nolint:prealloc
|
|
|
|
var toDelete []int
|
|
|
|
|
|
|
|
for _, t := range ts {
|
2024-01-06 09:44:14 +00:00
|
|
|
if t.IsDeleted() || t.ServerKey() != serverKey {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-12-29 08:23:05 +00:00
|
|
|
_, found := slices.BinarySearchFunc(active, t, func(a BaseTribe, b Tribe) int {
|
|
|
|
return cmp.Compare(a.ID(), b.ID())
|
|
|
|
})
|
|
|
|
if found {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
toDelete = append(toDelete, t.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
return toDelete
|
|
|
|
}
|
|
|
|
|
2024-02-28 06:59:10 +00:00
|
|
|
type TribeMeta struct {
|
|
|
|
id int
|
|
|
|
name string
|
|
|
|
tag string
|
|
|
|
profileURL *url.URL
|
|
|
|
}
|
|
|
|
|
|
|
|
const tribeMetaModelName = "TribeMeta"
|
|
|
|
|
|
|
|
// UnmarshalTribeMetaFromDatabase unmarshals TribeMeta from the database.
|
|
|
|
//
|
|
|
|
// It should be used only for unmarshalling from the database!
|
|
|
|
// You can't use UnmarshalTribeMetaFromDatabase as constructor - It may put domain into the invalid state!
|
|
|
|
func UnmarshalTribeMetaFromDatabase(id int, name string, tag string, rawProfileURL string) (TribeMeta, error) {
|
|
|
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
|
|
|
return TribeMeta{}, ValidationError{
|
|
|
|
Model: tribeMetaModelName,
|
|
|
|
Field: "id",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
profileURL, err := parseURL(rawProfileURL)
|
|
|
|
if err != nil {
|
|
|
|
return TribeMeta{}, ValidationError{
|
|
|
|
Model: tribeMetaModelName,
|
|
|
|
Field: "profileURL",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return TribeMeta{id: id, name: name, tag: tag, profileURL: profileURL}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TribeMeta) ID() int {
|
|
|
|
return t.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TribeMeta) Name() string {
|
|
|
|
return t.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TribeMeta) Tag() string {
|
|
|
|
return t.tag
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TribeMeta) ProfileURL() *url.URL {
|
2024-03-11 07:37:04 +00:00
|
|
|
if t.profileURL == nil {
|
|
|
|
return &url.URL{}
|
|
|
|
}
|
2024-02-28 06:59:10 +00:00
|
|
|
return t.profileURL
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TribeMeta) IsZero() bool {
|
|
|
|
return t == TribeMeta{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type NullTribeMeta NullValue[TribeMeta]
|
|
|
|
|
|
|
|
func (t NullTribeMeta) IsZero() bool {
|
|
|
|
return !t.Valid
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
type CreateTribeParams struct {
|
|
|
|
base BaseTribe
|
|
|
|
serverKey string
|
|
|
|
bestRank int
|
|
|
|
bestRankAt time.Time
|
|
|
|
mostPoints int
|
|
|
|
mostPointsAt time.Time
|
|
|
|
mostVillages int
|
|
|
|
mostVillagesAt time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
const createTribeParamsModelName = "CreateTribeParams"
|
|
|
|
|
2023-12-29 08:23:05 +00:00
|
|
|
// NewCreateTribeParams constructs a slice of CreateTribeParams based on the given parameters.
|
|
|
|
// Both slices must be sorted in ascending order by ID
|
|
|
|
// + if storedTribes contains tribes from different servers. they must be sorted in ascending order by server key.
|
|
|
|
//
|
2023-12-28 10:56:59 +00:00
|
|
|
//nolint:gocyclo
|
|
|
|
func NewCreateTribeParams(serverKey string, tribes BaseTribes, storedTribes Tribes) ([]CreateTribeParams, error) {
|
|
|
|
if err := validateServerKey(serverKey); err != nil {
|
|
|
|
return nil, ValidationError{
|
|
|
|
Model: createTribeParamsModelName,
|
|
|
|
Field: "serverKey",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params := make([]CreateTribeParams, 0, len(tribes))
|
|
|
|
|
|
|
|
for i, t := range tribes {
|
|
|
|
if t.IsZero() {
|
|
|
|
return nil, fmt.Errorf("tribes[%d] is an empty struct", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
var old Tribe
|
2023-12-29 08:23:05 +00:00
|
|
|
idx, found := slices.BinarySearchFunc(storedTribes, t, func(a Tribe, b BaseTribe) int {
|
2024-02-09 08:49:23 +00:00
|
|
|
return cmp.Or(
|
|
|
|
cmp.Compare(a.ServerKey(), serverKey),
|
|
|
|
cmp.Compare(a.ID(), b.ID()),
|
|
|
|
)
|
2023-12-29 08:23:05 +00:00
|
|
|
})
|
|
|
|
if found {
|
|
|
|
old = storedTribes[idx]
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
|
2024-01-01 10:20:30 +00:00
|
|
|
now := time.Now()
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
p := CreateTribeParams{
|
|
|
|
base: t,
|
|
|
|
serverKey: serverKey,
|
2024-01-01 10:20:30 +00:00
|
|
|
bestRank: t.Rank(),
|
|
|
|
bestRankAt: now,
|
|
|
|
mostPoints: t.AllPoints(),
|
|
|
|
mostPointsAt: now,
|
|
|
|
mostVillages: t.NumVillages(),
|
|
|
|
mostVillagesAt: now,
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
|
2024-01-01 10:20:30 +00:00
|
|
|
if old.ID() > 0 {
|
|
|
|
if old.MostPoints() >= p.MostPoints() {
|
|
|
|
p.mostPoints = old.MostPoints()
|
|
|
|
p.mostPointsAt = old.MostPointsAt()
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
|
2024-01-01 10:20:30 +00:00
|
|
|
if old.MostVillages() >= p.MostVillages() {
|
|
|
|
p.mostVillages = old.MostVillages()
|
|
|
|
p.mostVillagesAt = old.MostVillagesAt()
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
|
2024-01-01 10:20:30 +00:00
|
|
|
if old.BestRank() <= p.BestRank() {
|
|
|
|
p.bestRank = old.BestRank()
|
|
|
|
p.bestRankAt = old.BestRankAt()
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
params = append(params, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return params, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) Base() BaseTribe {
|
|
|
|
return params.base
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) ServerKey() string {
|
|
|
|
return params.serverKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) BestRank() int {
|
|
|
|
return params.bestRank
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) BestRankAt() time.Time {
|
|
|
|
return params.bestRankAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) MostPoints() int {
|
|
|
|
return params.mostPoints
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) MostPointsAt() time.Time {
|
|
|
|
return params.mostPointsAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) MostVillages() int {
|
|
|
|
return params.mostVillages
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params CreateTribeParams) MostVillagesAt() time.Time {
|
|
|
|
return params.mostVillagesAt
|
|
|
|
}
|
|
|
|
|
|
|
|
type TribeSort uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
TribeSortIDASC TribeSort = iota + 1
|
|
|
|
TribeSortIDDESC
|
|
|
|
TribeSortServerKeyASC
|
|
|
|
TribeSortServerKeyDESC
|
2024-02-19 07:17:38 +00:00
|
|
|
TribeSortODScoreAttASC
|
|
|
|
TribeSortODScoreAttDESC
|
|
|
|
TribeSortODScoreDefASC
|
|
|
|
TribeSortODScoreDefDESC
|
|
|
|
TribeSortODScoreTotalASC
|
|
|
|
TribeSortODScoreTotalDESC
|
|
|
|
TribeSortPointsASC
|
|
|
|
TribeSortPointsDESC
|
|
|
|
TribeSortDominanceASC
|
|
|
|
TribeSortDominanceDESC
|
|
|
|
TribeSortDeletedAtASC
|
|
|
|
TribeSortDeletedAtDESC
|
2023-12-28 10:56:59 +00:00
|
|
|
)
|
|
|
|
|
2024-03-02 08:49:42 +00:00
|
|
|
// IsInConflict returns true if two sorts can't be used together (e.g. TribeSortIDASC and TribeSortIDDESC).
|
|
|
|
func (s TribeSort) IsInConflict(s2 TribeSort) bool {
|
2024-03-15 06:57:21 +00:00
|
|
|
return isSortInConflict(s, s2)
|
2024-03-02 08:49:42 +00:00
|
|
|
}
|
|
|
|
|
2024-02-20 06:34:44 +00:00
|
|
|
//nolint:gocyclo
|
2024-03-02 08:49:42 +00:00
|
|
|
func (s TribeSort) String() string {
|
|
|
|
switch s {
|
|
|
|
case TribeSortIDASC:
|
|
|
|
return "id:ASC"
|
|
|
|
case TribeSortIDDESC:
|
|
|
|
return "id:DESC"
|
|
|
|
case TribeSortServerKeyASC:
|
|
|
|
return "serverKey:ASC"
|
|
|
|
case TribeSortServerKeyDESC:
|
|
|
|
return "serverKey:DESC"
|
|
|
|
case TribeSortODScoreAttASC:
|
|
|
|
return "odScoreAtt:ASC"
|
|
|
|
case TribeSortODScoreAttDESC:
|
|
|
|
return "odScoreAtt:DESC"
|
|
|
|
case TribeSortODScoreDefASC:
|
|
|
|
return "odScoreDef:ASC"
|
|
|
|
case TribeSortODScoreDefDESC:
|
|
|
|
return "odScoreDef:DESC"
|
|
|
|
case TribeSortODScoreTotalASC:
|
|
|
|
return "odScoreTotal:ASC"
|
|
|
|
case TribeSortODScoreTotalDESC:
|
|
|
|
return "odScoreTotal:DESC"
|
|
|
|
case TribeSortPointsASC:
|
|
|
|
return "points:ASC"
|
|
|
|
case TribeSortPointsDESC:
|
|
|
|
return "points:DESC"
|
|
|
|
case TribeSortDominanceASC:
|
|
|
|
return "dominance:ASC"
|
|
|
|
case TribeSortDominanceDESC:
|
|
|
|
return "dominance:DESC"
|
|
|
|
case TribeSortDeletedAtASC:
|
|
|
|
return "deletedAt:ASC"
|
|
|
|
case TribeSortDeletedAtDESC:
|
|
|
|
return "deletedAt:DESC"
|
2024-02-20 06:34:44 +00:00
|
|
|
default:
|
2024-03-02 08:49:42 +00:00
|
|
|
return "unknown tribe sort"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
type TribeCursor struct {
|
2024-02-19 07:17:38 +00:00
|
|
|
id int
|
|
|
|
serverKey string
|
|
|
|
odScoreAtt int
|
|
|
|
odScoreDef int
|
|
|
|
odScoreTotal int
|
|
|
|
points int
|
|
|
|
dominance float64
|
|
|
|
deletedAt time.Time
|
2024-02-12 07:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const tribeCursorModelName = "TribeCursor"
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
func NewTribeCursor(
|
|
|
|
id int,
|
|
|
|
serverKey string,
|
|
|
|
odScoreAtt int,
|
|
|
|
odScoreDef int,
|
|
|
|
odScoreTotal int,
|
|
|
|
points int,
|
|
|
|
dominance float64,
|
|
|
|
deletedAt time.Time,
|
|
|
|
) (TribeCursor, error) {
|
2024-02-12 07:07:15 +00:00
|
|
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
|
|
|
return TribeCursor{}, ValidationError{
|
|
|
|
Model: tribeCursorModelName,
|
|
|
|
Field: "id",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := validateServerKey(serverKey); err != nil {
|
|
|
|
return TribeCursor{}, ValidationError{
|
|
|
|
Model: tribeCursorModelName,
|
|
|
|
Field: "serverKey",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
return TribeCursor{
|
2024-02-19 07:17:38 +00:00
|
|
|
id: id,
|
|
|
|
serverKey: serverKey,
|
|
|
|
odScoreAtt: odScoreAtt,
|
|
|
|
odScoreDef: odScoreDef,
|
|
|
|
odScoreTotal: odScoreTotal,
|
|
|
|
points: points,
|
|
|
|
dominance: dominance,
|
|
|
|
deletedAt: deletedAt,
|
2024-02-12 07:07:15 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
//nolint:gocyclo
|
2024-02-12 07:07:15 +00:00
|
|
|
func decodeTribeCursor(encoded string) (TribeCursor, error) {
|
|
|
|
m, err := decodeCursor(encoded)
|
|
|
|
if err != nil {
|
|
|
|
return TribeCursor{}, err
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
id, err := m.int("id")
|
|
|
|
if err != nil {
|
|
|
|
return TribeCursor{}, ErrInvalidCursor
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
2024-02-12 07:07:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return TribeCursor{}, ErrInvalidCursor
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
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,
|
|
|
|
)
|
2024-02-12 07:07:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return TribeCursor{}, ErrInvalidCursor
|
|
|
|
}
|
|
|
|
|
|
|
|
return tc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc TribeCursor) ID() int {
|
|
|
|
return tc.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc TribeCursor) ServerKey() string {
|
|
|
|
return tc.serverKey
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
func (tc TribeCursor) IsZero() bool {
|
|
|
|
return tc == TribeCursor{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc TribeCursor) Encode() string {
|
|
|
|
if tc.IsZero() {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2024-02-19 07:17:38 +00:00
|
|
|
return encodeCursor([]keyValuePair{
|
|
|
|
{"id", tc.id},
|
2024-02-12 07:07:15 +00:00
|
|
|
{"serverKey", tc.serverKey},
|
2024-02-19 07:17:38 +00:00
|
|
|
{"odScoreAtt", tc.odScoreAtt},
|
|
|
|
{"odScoreDef", tc.odScoreDef},
|
|
|
|
{"odScoreTotal", tc.odScoreTotal},
|
|
|
|
{"points", tc.points},
|
|
|
|
{"dominance", tc.dominance},
|
|
|
|
{"deletedAt", tc.deletedAt},
|
2024-02-12 07:07:15 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
type ListTribesParams struct {
|
|
|
|
ids []int
|
|
|
|
serverKeys []string
|
2024-02-20 06:50:16 +00:00
|
|
|
tags []string
|
2023-12-28 10:56:59 +00:00
|
|
|
deleted NullBool
|
|
|
|
sort []TribeSort
|
2024-02-12 07:07:15 +00:00
|
|
|
cursor TribeCursor
|
2023-12-28 10:56:59 +00:00
|
|
|
limit int
|
|
|
|
}
|
|
|
|
|
2024-01-27 08:37:12 +00:00
|
|
|
const (
|
2024-03-11 07:16:29 +00:00
|
|
|
TribeListMaxLimit = 500
|
2024-01-27 08:37:12 +00:00
|
|
|
listTribesParamsModelName = "ListTribesParams"
|
|
|
|
)
|
2023-12-28 10:56:59 +00:00
|
|
|
|
|
|
|
func NewListTribesParams() ListTribesParams {
|
|
|
|
return ListTribesParams{
|
|
|
|
sort: []TribeSort{
|
|
|
|
TribeSortServerKeyASC,
|
|
|
|
TribeSortIDASC,
|
|
|
|
},
|
|
|
|
limit: TribeListMaxLimit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) IDs() []int {
|
|
|
|
return params.ids
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetIDs(ids []int) error {
|
|
|
|
for i, id := range ids {
|
2024-02-28 06:59:10 +00:00
|
|
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
2023-12-28 10:56:59 +00:00
|
|
|
return SliceElementValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "ids",
|
|
|
|
Index: i,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params.ids = ids
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) ServerKeys() []string {
|
|
|
|
return params.serverKeys
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetServerKeys(serverKeys []string) error {
|
2024-03-02 08:49:42 +00:00
|
|
|
for i, sk := range serverKeys {
|
|
|
|
if err := validateServerKey(sk); err != nil {
|
|
|
|
return SliceElementValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "serverKeys",
|
|
|
|
Index: i,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
params.serverKeys = serverKeys
|
2024-03-02 08:49:42 +00:00
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-20 06:50:16 +00:00
|
|
|
func (params *ListTribesParams) Tags() []string {
|
|
|
|
return params.tags
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2024-03-06 07:20:10 +00:00
|
|
|
tribeTagsMinLength = 0
|
2024-02-20 06:50:16 +00:00
|
|
|
tribeTagsMaxLength = 100
|
|
|
|
)
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetTags(tags []string) error {
|
|
|
|
if err := validateSliceLen(tags, tribeTagsMinLength, tribeTagsMaxLength); err != nil {
|
|
|
|
return ValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "tags",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params.tags = tags
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
func (params *ListTribesParams) Deleted() NullBool {
|
|
|
|
return params.deleted
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetDeleted(deleted NullBool) error {
|
|
|
|
params.deleted = deleted
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) Sort() []TribeSort {
|
|
|
|
return params.sort
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
tribeSortMinLength = 1
|
2024-02-19 07:17:38 +00:00
|
|
|
tribeSortMaxLength = 3
|
2023-12-28 10:56:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
2024-03-02 08:49:42 +00:00
|
|
|
if err := validateSort(sort, tribeSortMinLength, tribeSortMaxLength); err != nil {
|
2023-12-28 10:56:59 +00:00
|
|
|
return ValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "sort",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params.sort = sort
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-20 06:34:44 +00:00
|
|
|
func (params *ListTribesParams) PrependSortString(sort []string) error {
|
|
|
|
if err := validateSliceLen(sort, tribeSortMinLength, max(tribeSortMaxLength-len(params.sort), 0)); err != nil {
|
|
|
|
return ValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "sort",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 08:49:42 +00:00
|
|
|
toPrepend := make([]TribeSort, 0, len(sort))
|
|
|
|
|
|
|
|
for i, s := range sort {
|
2024-03-15 06:49:55 +00:00
|
|
|
converted, err := newSortFromString(
|
|
|
|
s,
|
|
|
|
TribeSortODScoreAttASC,
|
|
|
|
TribeSortODScoreAttDESC,
|
|
|
|
TribeSortODScoreDefASC,
|
|
|
|
TribeSortODScoreDefDESC,
|
|
|
|
TribeSortODScoreTotalASC,
|
|
|
|
TribeSortODScoreTotalDESC,
|
|
|
|
TribeSortPointsASC,
|
|
|
|
TribeSortPointsDESC,
|
|
|
|
TribeSortDominanceASC,
|
|
|
|
TribeSortDominanceDESC,
|
|
|
|
TribeSortDeletedAtASC,
|
|
|
|
TribeSortDeletedAtDESC,
|
|
|
|
)
|
2024-02-20 06:34:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return SliceElementValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "sort",
|
|
|
|
Index: i,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
2024-03-02 08:49:42 +00:00
|
|
|
toPrepend = append(toPrepend, converted)
|
2024-02-20 06:34:44 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 08:49:42 +00:00
|
|
|
return params.SetSort(append(toPrepend, params.sort...))
|
2024-02-20 06:34:44 +00:00
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
func (params *ListTribesParams) Cursor() TribeCursor {
|
|
|
|
return params.cursor
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetCursor(cursor TribeCursor) error {
|
|
|
|
params.cursor = cursor
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetEncodedCursor(encoded string) error {
|
|
|
|
decoded, err := decodeTribeCursor(encoded)
|
|
|
|
if err != nil {
|
|
|
|
return ValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "cursor",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params.cursor = decoded
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:56:59 +00:00
|
|
|
func (params *ListTribesParams) Limit() int {
|
|
|
|
return params.limit
|
|
|
|
}
|
|
|
|
|
|
|
|
func (params *ListTribesParams) SetLimit(limit int) error {
|
|
|
|
if err := validateIntInRange(limit, 1, TribeListMaxLimit); err != nil {
|
|
|
|
return ValidationError{
|
|
|
|
Model: listTribesParamsModelName,
|
|
|
|
Field: "limit",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
params.limit = limit
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
type ListTribesResult struct {
|
|
|
|
tribes Tribes
|
|
|
|
self TribeCursor
|
|
|
|
next TribeCursor
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
const listTribesResultModelName = "ListTribesResult"
|
|
|
|
|
|
|
|
func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) {
|
|
|
|
var err error
|
|
|
|
res := ListTribesResult{
|
|
|
|
tribes: tribes,
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tribes) > 0 {
|
2024-03-06 06:35:26 +00:00
|
|
|
res.self, err = tribes[0].ToCursor()
|
2024-02-12 07:07:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return ListTribesResult{}, ValidationError{
|
|
|
|
Model: listTribesResultModelName,
|
|
|
|
Field: "self",
|
|
|
|
Err: err,
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
if !next.IsZero() {
|
2024-03-06 06:35:26 +00:00
|
|
|
res.next, err = next.ToCursor()
|
2024-02-12 07:07:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return ListTribesResult{}, ValidationError{
|
|
|
|
Model: listTribesResultModelName,
|
|
|
|
Field: "next",
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-28 10:56:59 +00:00
|
|
|
|
2024-02-12 07:07:15 +00:00
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (res ListTribesResult) Tribes() Tribes {
|
|
|
|
return res.tribes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (res ListTribesResult) Self() TribeCursor {
|
|
|
|
return res.self
|
|
|
|
}
|
|
|
|
|
|
|
|
func (res ListTribesResult) Next() TribeCursor {
|
|
|
|
return res.next
|
2023-12-28 10:56:59 +00:00
|
|
|
}
|
2024-02-20 07:14:35 +00:00
|
|
|
|
|
|
|
type TribeNotFoundError struct {
|
|
|
|
ID int
|
|
|
|
ServerKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ ErrorWithParams = TribeNotFoundError{}
|
|
|
|
|
|
|
|
func (e TribeNotFoundError) Error() string {
|
|
|
|
return fmt.Sprintf("tribe with id %d and server key %s not found", e.ID, e.ServerKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e TribeNotFoundError) Type() ErrorType {
|
|
|
|
return ErrorTypeNotFound
|
|
|
|
}
|
|
|
|
|
2024-03-18 07:01:47 +00:00
|
|
|
const errorCodeTribeNotFound = "tribe-not-found"
|
|
|
|
|
2024-02-20 07:14:35 +00:00
|
|
|
func (e TribeNotFoundError) Code() string {
|
2024-03-18 07:01:47 +00:00
|
|
|
return errorCodeTribeNotFound
|
2024-02-20 07:14:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e TribeNotFoundError) Params() map[string]any {
|
|
|
|
return map[string]any{
|
|
|
|
"ID": e.ID,
|
|
|
|
"ServerKey": e.ServerKey,
|
|
|
|
}
|
|
|
|
}
|