core/internal/domain/tribe.go

503 lines
9.8 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 that are not in the given slice with active tribes and returns their ids.
// Both slices must be sorted in ascending order by ID.
func (ts Tribes) Delete(active BaseTribes) []int {
//nolint:prealloc
var toDelete []int
for _, t := range ts {
_, 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
}