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 } type TribeChanges []TribeChange 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.ID() == 0 && 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 { ss := []TribeChangeSort{s, s2} slices.Sort(ss) // ASC is always an odd number, DESC is always an even number return (ss[0]%2 == 1 && ss[0] == ss[1]-1) || ss[0] == ss[1] } //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 ListTribeChangesParams struct { serverKeys []string sort []TribeChangeSort limit int offset int } const ( TribeChangeListMaxLimit = 200 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) Sort() []TribeChangeSort { return params.sort } const ( tribeChangeSortMinLength = 1 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) 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) Offset() int { return params.offset } func (params *ListTribeChangesParams) SetOffset(offset int) error { if err := validateIntInRange(offset, 0, math.MaxInt); err != nil { return ValidationError{ Model: listTribeChangesParamsModelName, Field: "offset", Err: err, } } params.offset = offset return nil }