core/internal/domain/tribe_change.go

713 lines
15 KiB
Go

package domain
import (
"cmp"
"fmt"
"math"
"slices"
"time"
)
type TribeChange struct {
id int
serverKey string
playerID int
oldTribeID int
newTribeID int
createdAt time.Time
}
const tribeChangeModelName = "TribeChange"
// UnmarshalTribeChangeFromDatabase unmarshals TribeChange from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalTribeChangeFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalTribeChangeFromDatabase(
id int,
serverKey string,
playerID int,
oldTribeID int,
newTribeID int,
createdAt time.Time,
) (TribeChange, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return TribeChange{}, ValidationError{
Model: tribeChangeModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return TribeChange{}, ValidationError{
Model: tribeChangeModelName,
Field: "serverKey",
Err: err,
}
}
return TribeChange{
id: id,
playerID: playerID,
oldTribeID: oldTribeID,
newTribeID: newTribeID,
serverKey: serverKey,
createdAt: createdAt,
}, nil
}
func (tc TribeChange) ID() int {
return tc.id
}
func (tc TribeChange) PlayerID() int {
return tc.playerID
}
func (tc TribeChange) OldTribeID() int {
return tc.oldTribeID
}
func (tc TribeChange) NewTribeID() int {
return tc.newTribeID
}
func (tc TribeChange) ServerKey() string {
return tc.serverKey
}
func (tc TribeChange) CreatedAt() time.Time {
return tc.createdAt
}
func (tc TribeChange) WithRelations(
player PlayerMeta,
oldTribe NullTribeMeta,
newTribe NullTribeMeta,
) TribeChangeWithRelations {
return TribeChangeWithRelations{
tribeChange: tc,
player: player,
oldTribe: oldTribe,
newTribe: newTribe,
}
}
func (tc TribeChange) ToCursor() (TribeChangeCursor, error) {
return NewTribeChangeCursor(tc.id, tc.serverKey, tc.createdAt)
}
func (tc TribeChange) IsZero() bool {
return tc == TribeChange{}
}
type TribeChanges []TribeChange
type TribeChangeWithRelations struct {
tribeChange TribeChange
player PlayerMeta
oldTribe NullTribeMeta
newTribe NullTribeMeta
}
func (tc TribeChangeWithRelations) TribeChange() TribeChange {
return tc.tribeChange
}
func (tc TribeChangeWithRelations) Player() PlayerMeta {
return tc.player
}
func (tc TribeChangeWithRelations) OldTribe() NullTribeMeta {
return tc.oldTribe
}
func (tc TribeChangeWithRelations) NewTribe() NullTribeMeta {
return tc.newTribe
}
func (tc TribeChangeWithRelations) IsZero() bool {
return tc.tribeChange.IsZero()
}
type TribeChangesWithRelations []TribeChangeWithRelations
type CreateTribeChangeParams struct {
serverKey string
playerID int
newTribeID int
oldTribeID int
}
const createTribeChangeParamsModelName = "CreateTribeChangeParams"
func NewCreateTribeChangeParams(
serverKey string,
playerID int,
oldTribeID int,
newTribeID int,
) (CreateTribeChangeParams, error) {
if err := validateServerKey(serverKey); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "serverKey",
Err: err,
}
}
if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "playerID",
Err: err,
}
}
if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "oldTribeID",
Err: err,
}
}
if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "newTribeID",
Err: err,
}
}
return CreateTribeChangeParams{
serverKey: serverKey,
playerID: playerID,
oldTribeID: oldTribeID,
newTribeID: newTribeID,
}, nil
}
// NewCreateTribeChangeParamsFromPlayers constructs a slice of CreatePlayerParams based on the given parameters.
// Both slices must be sorted in ascending order by ID
// + if storedPlayers contains players from different servers. they must be sorted in ascending order by server key.
func NewCreateTribeChangeParamsFromPlayers(
serverKey string,
players BasePlayers,
storedPlayers Players,
) ([]CreateTribeChangeParams, error) {
// tribe changes happens now and then, there is no point in prereallocating this slice
//nolint:prealloc
var params []CreateTribeChangeParams
for i, player := range players {
if player.IsZero() {
return nil, fmt.Errorf("players[%d] is an empty struct", i)
}
var old Player
idx, found := slices.BinarySearchFunc(storedPlayers, player, func(a Player, b BasePlayer) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), serverKey),
cmp.Compare(a.ID(), b.ID()),
)
})
if found {
old = storedPlayers[idx]
}
if (old.ID() > 0 && old.TribeID() == player.TribeID()) || (old.IsZero() && player.TribeID() == 0) {
continue
}
p, err := NewCreateTribeChangeParams(
serverKey,
player.ID(),
old.TribeID(),
player.TribeID(),
)
if err != nil {
return nil, err
}
params = append(params, p)
}
return params, nil
}
func (params CreateTribeChangeParams) ServerKey() string {
return params.serverKey
}
func (params CreateTribeChangeParams) PlayerID() int {
return params.playerID
}
func (params CreateTribeChangeParams) OldTribeID() int {
return params.oldTribeID
}
func (params CreateTribeChangeParams) NewTribeID() int {
return params.newTribeID
}
type TribeChangeSort uint8
const (
TribeChangeSortCreatedAtASC TribeChangeSort = iota + 1
TribeChangeSortCreatedAtDESC
TribeChangeSortIDASC
TribeChangeSortIDDESC
TribeChangeSortServerKeyASC
TribeChangeSortServerKeyDESC
)
// IsInConflict returns true if two sorts can't be used together (e.g. TribeChangeSortIDASC and TribeChangeSortIDDESC).
func (s TribeChangeSort) IsInConflict(s2 TribeChangeSort) bool {
return isSortInConflict(s, s2)
}
//nolint:gocyclo
func (s TribeChangeSort) String() string {
switch s {
case TribeChangeSortCreatedAtASC:
return "createdAt:ASC"
case TribeChangeSortCreatedAtDESC:
return "createdAt:DESC"
case TribeChangeSortIDASC:
return "id:ASC"
case TribeChangeSortIDDESC:
return "id:DESC"
case TribeChangeSortServerKeyASC:
return "serverKey:ASC"
case TribeChangeSortServerKeyDESC:
return "serverKey:DESC"
default:
return "unknown tribe change sort"
}
}
type TribeChangeCursor struct {
id int
serverKey string
createdAt time.Time
}
const tribeChangeCursorModelName = "TribeChangeCursor"
func NewTribeChangeCursor(id int, serverKey string, createdAt time.Time) (TribeChangeCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return TribeChangeCursor{}, ValidationError{
Model: tribeChangeCursorModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return TribeChangeCursor{}, ValidationError{
Model: tribeChangeCursorModelName,
Field: "serverKey",
Err: err,
}
}
return TribeChangeCursor{
id: id,
serverKey: serverKey,
createdAt: createdAt,
}, nil
}
//nolint:gocyclo
func decodeTribeChangeCursor(encoded string) (TribeChangeCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
return TribeChangeCursor{}, err
}
id, err := m.int("id")
if err != nil {
return TribeChangeCursor{}, ErrInvalidCursor
}
serverKey, err := m.string("serverKey")
if err != nil {
return TribeChangeCursor{}, ErrInvalidCursor
}
createdAt, err := m.time("createdAt")
if err != nil {
return TribeChangeCursor{}, ErrInvalidCursor
}
ec, err := NewTribeChangeCursor(
id,
serverKey,
createdAt,
)
if err != nil {
return TribeChangeCursor{}, ErrInvalidCursor
}
return ec, nil
}
func (tcc TribeChangeCursor) ID() int {
return tcc.id
}
func (tcc TribeChangeCursor) ServerKey() string {
return tcc.serverKey
}
func (tcc TribeChangeCursor) CreatedAt() time.Time {
return tcc.createdAt
}
func (tcc TribeChangeCursor) IsZero() bool {
return tcc == TribeChangeCursor{}
}
func (tcc TribeChangeCursor) Encode() string {
if tcc.IsZero() {
return ""
}
return encodeCursor([]keyValuePair{
{"id", tcc.id},
{"serverKey", tcc.serverKey},
{"createdAt", tcc.createdAt},
})
}
type ListTribeChangesParams struct {
serverKeys []string
playerIDs []int
tribeIDs []int // TribeChange.NewTribeID or TribeChange.OldTribeID
since NullTime
before NullTime
sort []TribeChangeSort
cursor TribeChangeCursor
limit int
}
const (
TribeChangeListMaxLimit = 500
listTribeChangesParamsModelName = "ListTribeChangesParams"
)
func NewListTribeChangesParams() ListTribeChangesParams {
return ListTribeChangesParams{
sort: []TribeChangeSort{
TribeChangeSortServerKeyASC,
TribeChangeSortCreatedAtASC,
TribeChangeSortIDASC,
},
limit: TribeChangeListMaxLimit,
}
}
func (params *ListTribeChangesParams) ServerKeys() []string {
return params.serverKeys
}
func (params *ListTribeChangesParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
func (params *ListTribeChangesParams) PlayerIDs() []int {
return params.playerIDs
}
func (params *ListTribeChangesParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "playerIDs",
Index: i,
Err: err,
}
}
}
params.playerIDs = playerIDs
return nil
}
func (params *ListTribeChangesParams) TribeIDs() []int {
return params.tribeIDs
}
func (params *ListTribeChangesParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "tribeIDs",
Index: i,
Err: err,
}
}
}
params.tribeIDs = tribeIDs
return nil
}
func (params *ListTribeChangesParams) Since() NullTime {
return params.since
}
func (params *ListTribeChangesParams) SetSince(since NullTime) error {
params.since = since
return nil
}
func (params *ListTribeChangesParams) Before() NullTime {
return params.before
}
func (params *ListTribeChangesParams) SetBefore(before NullTime) error {
params.before = before
return nil
}
func (params *ListTribeChangesParams) Sort() []TribeChangeSort {
return params.sort
}
const (
tribeChangeSortMinLength = 0
tribeChangeSortMaxLength = 3
)
func (params *ListTribeChangesParams) SetSort(sort []TribeChangeSort) error {
if err := validateSort(sort, tribeChangeSortMinLength, tribeChangeSortMaxLength); err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "sort",
Err: err,
}
}
params.sort = sort
return nil
}
func (params *ListTribeChangesParams) Cursor() TribeChangeCursor {
return params.cursor
}
func (params *ListTribeChangesParams) SetCursor(cursor TribeChangeCursor) error {
params.cursor = cursor
return nil
}
func (params *ListTribeChangesParams) SetEncodedCursor(encoded string) error {
decoded, err := decodeTribeChangeCursor(encoded)
if err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "cursor",
Err: err,
}
}
params.cursor = decoded
return nil
}
func (params *ListTribeChangesParams) Limit() int {
return params.limit
}
func (params *ListTribeChangesParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, TribeChangeListMaxLimit); err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "limit",
Err: err,
}
}
params.limit = limit
return nil
}
func (params *ListTribeChangesParams) PrependSort(sort []TribeChangeSort) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(tribeChangeSortMaxLength-len(params.sort), 0)); err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "sort",
Err: err,
}
}
return params.SetSort(append(sort, params.sort...))
}
func (params *ListTribeChangesParams) PrependSortString(
sort []string,
allowed []TribeChangeSort,
maxLength int,
) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(min(tribeChangeSortMaxLength-len(params.sort), maxLength), 0)); err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "sort",
Err: err,
}
}
toPrepend := make([]TribeChangeSort, 0, len(sort))
for i, s := range sort {
converted, err := newSortFromString(s, allowed...)
if err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "sort",
Index: i,
Err: err,
}
}
toPrepend = append(toPrepend, converted)
}
return params.SetSort(append(toPrepend, params.sort...))
}
type ListTribeChangesResult struct {
tribeChanges TribeChanges
self TribeChangeCursor
next TribeChangeCursor
}
const listTribeChangesResultModelName = "ListTribeChangesResult"
func NewListTribeChangesResult(tribeChanges TribeChanges, next TribeChange) (ListTribeChangesResult, error) {
var err error
res := ListTribeChangesResult{
tribeChanges: tribeChanges,
}
if len(tribeChanges) > 0 {
res.self, err = tribeChanges[0].ToCursor()
if err != nil {
return ListTribeChangesResult{}, ValidationError{
Model: listTribeChangesResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.ToCursor()
if err != nil {
return ListTribeChangesResult{}, ValidationError{
Model: listTribeChangesResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListTribeChangesResult) TribeChanges() TribeChanges {
return res.tribeChanges
}
func (res ListTribeChangesResult) Self() TribeChangeCursor {
return res.self
}
func (res ListTribeChangesResult) Next() TribeChangeCursor {
return res.next
}
type ListTribeChangesWithRelationsResult struct {
tribeChanges TribeChangesWithRelations
self TribeChangeCursor
next TribeChangeCursor
}
const listTribeChangesWithRelationsResultModelName = "ListTribeChangesWithRelationsResult"
func NewListTribeChangesWithRelationsResult(
tribeChanges TribeChangesWithRelations,
next TribeChangeWithRelations,
) (ListTribeChangesWithRelationsResult, error) {
var err error
res := ListTribeChangesWithRelationsResult{
tribeChanges: tribeChanges,
}
if len(tribeChanges) > 0 {
res.self, err = tribeChanges[0].TribeChange().ToCursor()
if err != nil {
return ListTribeChangesWithRelationsResult{}, ValidationError{
Model: listTribeChangesWithRelationsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.TribeChange().ToCursor()
if err != nil {
return ListTribeChangesWithRelationsResult{}, ValidationError{
Model: listTribeChangesWithRelationsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListTribeChangesWithRelationsResult) TribeChanges() TribeChangesWithRelations {
return res.tribeChanges
}
func (res ListTribeChangesWithRelationsResult) Self() TribeChangeCursor {
return res.self
}
func (res ListTribeChangesWithRelationsResult) Next() TribeChangeCursor {
return res.next
}