508 lines
10 KiB
Go
508 lines
10 KiB
Go
package domain
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"math"
|
|
"net/url"
|
|
"slices"
|
|
"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
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
if err := validateServerKey(serverKey); err != nil {
|
|
return Tribe{}, ValidationError{
|
|
Model: tribeModelName,
|
|
Field: "serverKey",
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 {
|
|
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
|
|
}
|
|
|
|
func (t Tribe) IsDeleted() bool {
|
|
return !t.deletedAt.IsZero()
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
type Tribes []Tribe
|
|
|
|
// 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 {
|
|
//nolint:prealloc
|
|
var toDelete []int
|
|
|
|
for _, t := range ts {
|
|
if t.IsDeleted() || t.ServerKey() != serverKey {
|
|
continue
|
|
}
|
|
|
|
_, 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
|
|
}
|
|
|
|
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"
|
|
|
|
// 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.
|
|
//
|
|
//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
|
|
idx, found := slices.BinarySearchFunc(storedTribes, t, func(a Tribe, b BaseTribe) int {
|
|
if res := cmp.Compare(a.ServerKey(), serverKey); res != 0 {
|
|
return res
|
|
}
|
|
return cmp.Compare(a.ID(), b.ID())
|
|
})
|
|
if found {
|
|
old = storedTribes[idx]
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
p := CreateTribeParams{
|
|
base: t,
|
|
serverKey: serverKey,
|
|
bestRank: t.Rank(),
|
|
bestRankAt: now,
|
|
mostPoints: t.AllPoints(),
|
|
mostPointsAt: now,
|
|
mostVillages: t.NumVillages(),
|
|
mostVillagesAt: now,
|
|
}
|
|
|
|
if old.ID() > 0 {
|
|
if old.MostPoints() >= p.MostPoints() {
|
|
p.mostPoints = old.MostPoints()
|
|
p.mostPointsAt = old.MostPointsAt()
|
|
}
|
|
|
|
if old.MostVillages() >= p.MostVillages() {
|
|
p.mostVillages = old.MostVillages()
|
|
p.mostVillagesAt = old.MostVillagesAt()
|
|
}
|
|
|
|
if old.BestRank() <= p.BestRank() {
|
|
p.bestRank = old.BestRank()
|
|
p.bestRankAt = old.BestRankAt()
|
|
}
|
|
}
|
|
|
|
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
|
|
)
|
|
|
|
const TribeListMaxLimit = 200
|
|
|
|
type ListTribesParams struct {
|
|
ids []int
|
|
idGT NullInt
|
|
serverKeys []string
|
|
deleted NullBool
|
|
sort []TribeSort
|
|
limit int
|
|
offset int
|
|
}
|
|
|
|
const listTribesParamsModelName = "ListTribesParams"
|
|
|
|
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 {
|
|
if err := validateIntInRange(id, 0, math.MaxInt); err != nil {
|
|
return SliceElementValidationError{
|
|
Model: listTribesParamsModelName,
|
|
Field: "ids",
|
|
Index: i,
|
|
Err: err,
|
|
}
|
|
}
|
|
}
|
|
|
|
params.ids = ids
|
|
|
|
return nil
|
|
}
|
|
|
|
func (params *ListTribesParams) IDGT() NullInt {
|
|
return params.idGT
|
|
}
|
|
|
|
func (params *ListTribesParams) SetIDGT(idGT NullInt) error {
|
|
if idGT.Valid {
|
|
if err := validateIntInRange(idGT.Value, 0, math.MaxInt); err != nil {
|
|
return ValidationError{
|
|
Model: listTribesParamsModelName,
|
|
Field: "idGT",
|
|
Err: err,
|
|
}
|
|
}
|
|
}
|
|
|
|
params.idGT = idGT
|
|
|
|
return nil
|
|
}
|
|
|
|
func (params *ListTribesParams) ServerKeys() []string {
|
|
return params.serverKeys
|
|
}
|
|
|
|
func (params *ListTribesParams) SetServerKeys(serverKeys []string) error {
|
|
params.serverKeys = serverKeys
|
|
return nil
|
|
}
|
|
|
|
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
|
|
tribeSortMaxLength = 2
|
|
)
|
|
|
|
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
|
if err := validateSliceLen(sort, tribeSortMinLength, tribeSortMaxLength); err != nil {
|
|
return ValidationError{
|
|
Model: listTribesParamsModelName,
|
|
Field: "sort",
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
params.sort = sort
|
|
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (params *ListTribesParams) Offset() int {
|
|
return params.offset
|
|
}
|
|
|
|
func (params *ListTribesParams) SetOffset(offset int) error {
|
|
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
|
return ValidationError{
|
|
Model: listTribesParamsModelName,
|
|
Field: "offset",
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
params.offset = offset
|
|
|
|
return nil
|
|
}
|