core/internal/domain/player_snapshot.go

652 lines
14 KiB
Go

package domain
import (
"fmt"
"math"
"time"
)
const PlayerSnapshotRetentionForClosedServers = 180 * 24 * time.Hour
type PlayerSnapshot struct {
id int
playerID int
serverKey string
numVillages int
points int
rank int
tribeID int
od OpponentsDefeated
date time.Time
createdAt time.Time
}
const playerSnapshotModelName = "PlayerSnapshot"
// UnmarshalPlayerSnapshotFromDatabase unmarshals PlayerSnapshot from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalPlayerSnapshotFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalPlayerSnapshotFromDatabase(
id int,
playerID int,
serverKey string,
numVillages int,
points int,
rank int,
tribeID int,
od OpponentsDefeated,
date time.Time,
createdAt time.Time,
) (PlayerSnapshot, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return PlayerSnapshot{}, ValidationError{
Model: playerSnapshotModelName,
Field: "id",
Err: err,
}
}
if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
return PlayerSnapshot{}, ValidationError{
Model: playerSnapshotModelName,
Field: "playerID",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return PlayerSnapshot{}, ValidationError{
Model: playerSnapshotModelName,
Field: "serverKey",
Err: err,
}
}
return PlayerSnapshot{
id: id,
playerID: playerID,
serverKey: serverKey,
numVillages: numVillages,
points: points,
rank: rank,
tribeID: tribeID,
od: od,
date: date,
createdAt: createdAt,
}, nil
}
func (ps PlayerSnapshot) ID() int {
return ps.id
}
func (ps PlayerSnapshot) PlayerID() int {
return ps.playerID
}
func (ps PlayerSnapshot) ServerKey() string {
return ps.serverKey
}
func (ps PlayerSnapshot) NumVillages() int {
return ps.numVillages
}
func (ps PlayerSnapshot) Points() int {
return ps.points
}
func (ps PlayerSnapshot) Rank() int {
return ps.rank
}
func (ps PlayerSnapshot) TribeID() int {
return ps.tribeID
}
func (ps PlayerSnapshot) OD() OpponentsDefeated {
return ps.od
}
func (ps PlayerSnapshot) Date() time.Time {
return ps.date
}
func (ps PlayerSnapshot) CreatedAt() time.Time {
return ps.createdAt
}
func (ps PlayerSnapshot) WithRelations(player PlayerMeta, tribe NullTribeMeta) PlayerSnapshotWithRelations {
return PlayerSnapshotWithRelations{
snapshot: ps,
player: player,
tribe: tribe,
}
}
func (ps PlayerSnapshot) ToCursor() (PlayerSnapshotCursor, error) {
return NewPlayerSnapshotCursor(ps.id, ps.serverKey, ps.date)
}
func (ps PlayerSnapshot) IsZero() bool {
return ps == PlayerSnapshot{}
}
type PlayerSnapshots []PlayerSnapshot
type PlayerSnapshotWithRelations struct {
snapshot PlayerSnapshot
player PlayerMeta
tribe NullTribeMeta
}
func (ps PlayerSnapshotWithRelations) PlayerSnapshot() PlayerSnapshot {
return ps.snapshot
}
func (ps PlayerSnapshotWithRelations) Player() PlayerMeta {
return ps.player
}
func (ps PlayerSnapshotWithRelations) Tribe() NullTribeMeta {
return ps.tribe
}
func (ps PlayerSnapshotWithRelations) IsZero() bool {
return ps.snapshot.IsZero()
}
type PlayerSnapshotsWithRelations []PlayerSnapshotWithRelations
type CreatePlayerSnapshotParams struct {
playerID int
serverKey string
numVillages int
points int
rank int
tribeID int
od OpponentsDefeated
date time.Time
}
func NewCreatePlayerSnapshotParams(players Players, date time.Time) ([]CreatePlayerSnapshotParams, error) {
params := make([]CreatePlayerSnapshotParams, 0, len(players))
for i, p := range players {
if p.IsZero() {
return nil, fmt.Errorf("players[%d] is an empty struct", i)
}
if p.IsDeleted() {
continue
}
params = append(params, CreatePlayerSnapshotParams{
playerID: p.ID(),
serverKey: p.ServerKey(),
numVillages: p.NumVillages(),
points: p.Points(),
rank: p.Rank(),
tribeID: p.TribeID(),
od: p.OD(),
date: date,
})
}
return params, nil
}
func (params CreatePlayerSnapshotParams) PlayerID() int {
return params.playerID
}
func (params CreatePlayerSnapshotParams) ServerKey() string {
return params.serverKey
}
func (params CreatePlayerSnapshotParams) NumVillages() int {
return params.numVillages
}
func (params CreatePlayerSnapshotParams) Points() int {
return params.points
}
func (params CreatePlayerSnapshotParams) Rank() int {
return params.rank
}
func (params CreatePlayerSnapshotParams) TribeID() int {
return params.tribeID
}
func (params CreatePlayerSnapshotParams) OD() OpponentsDefeated {
return params.od
}
func (params CreatePlayerSnapshotParams) Date() time.Time {
return params.date
}
type PlayerSnapshotSort uint8
const (
PlayerSnapshotSortDateASC PlayerSnapshotSort = iota + 1
PlayerSnapshotSortDateDESC
PlayerSnapshotSortIDASC
PlayerSnapshotSortIDDESC
PlayerSnapshotSortServerKeyASC
PlayerSnapshotSortServerKeyDESC
)
// IsInConflict returns true if two sorts can't be used together
// (e.g. PlayerSnapshotSortIDASC and PlayerSnapshotSortIDDESC).
func (s PlayerSnapshotSort) IsInConflict(s2 PlayerSnapshotSort) bool {
return isSortInConflict(s, s2)
}
//nolint:gocyclo
func (s PlayerSnapshotSort) String() string {
switch s {
case PlayerSnapshotSortDateASC:
return "date:ASC"
case PlayerSnapshotSortDateDESC:
return "date:DESC"
case PlayerSnapshotSortIDASC:
return "id:ASC"
case PlayerSnapshotSortIDDESC:
return "id:DESC"
case PlayerSnapshotSortServerKeyASC:
return "serverKey:ASC"
case PlayerSnapshotSortServerKeyDESC:
return "serverKey:DESC"
default:
return "unknown player snapshot sort"
}
}
type PlayerSnapshotCursor struct {
id int
serverKey string
date time.Time
}
const playerSnapshotCursorModelName = "PlayerSnapshotCursor"
func NewPlayerSnapshotCursor(id int, serverKey string, date time.Time) (PlayerSnapshotCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return PlayerSnapshotCursor{}, ValidationError{
Model: playerSnapshotCursorModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return PlayerSnapshotCursor{}, ValidationError{
Model: playerSnapshotCursorModelName,
Field: "serverKey",
Err: err,
}
}
return PlayerSnapshotCursor{
id: id,
serverKey: serverKey,
date: date,
}, nil
}
//nolint:gocyclo
func decodePlayerSnapshotCursor(encoded string) (PlayerSnapshotCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
return PlayerSnapshotCursor{}, err
}
id, err := m.int("id")
if err != nil {
return PlayerSnapshotCursor{}, ErrInvalidCursor
}
serverKey, err := m.string("serverKey")
if err != nil {
return PlayerSnapshotCursor{}, ErrInvalidCursor
}
date, err := m.time("date")
if err != nil {
return PlayerSnapshotCursor{}, ErrInvalidCursor
}
psc, err := NewPlayerSnapshotCursor(
id,
serverKey,
date,
)
if err != nil {
return PlayerSnapshotCursor{}, ErrInvalidCursor
}
return psc, nil
}
func (psc PlayerSnapshotCursor) ID() int {
return psc.id
}
func (psc PlayerSnapshotCursor) ServerKey() string {
return psc.serverKey
}
func (psc PlayerSnapshotCursor) Date() time.Time {
return psc.date
}
func (psc PlayerSnapshotCursor) IsZero() bool {
return psc == PlayerSnapshotCursor{}
}
func (psc PlayerSnapshotCursor) Encode() string {
if psc.IsZero() {
return ""
}
return encodeCursor([]keyValuePair{
{"id", psc.id},
{"serverKey", psc.serverKey},
{"date", psc.date},
})
}
type ListPlayerSnapshotsParams struct {
serverKeys []string
playerIDs []int
sort []PlayerSnapshotSort
cursor PlayerSnapshotCursor
limit int
}
const (
PlayerSnapshotListMaxLimit = 500
listPlayerSnapshotsParamsModelName = "ListPlayerSnapshotsParams"
)
func NewListPlayerSnapshotsParams() ListPlayerSnapshotsParams {
return ListPlayerSnapshotsParams{
sort: []PlayerSnapshotSort{
PlayerSnapshotSortServerKeyASC,
PlayerSnapshotSortDateASC,
PlayerSnapshotSortIDASC,
},
limit: PlayerSnapshotListMaxLimit,
}
}
func (params *ListPlayerSnapshotsParams) ServerKeys() []string {
return params.serverKeys
}
func (params *ListPlayerSnapshotsParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
func (params *ListPlayerSnapshotsParams) PlayerIDs() []int {
return params.playerIDs
}
func (params *ListPlayerSnapshotsParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "playerIDs",
Index: i,
Err: err,
}
}
}
params.playerIDs = playerIDs
return nil
}
func (params *ListPlayerSnapshotsParams) Sort() []PlayerSnapshotSort {
return params.sort
}
const (
playerSnapshotSortMinLength = 0
playerSnapshotSortMaxLength = 3
)
func (params *ListPlayerSnapshotsParams) SetSort(sort []PlayerSnapshotSort) error {
if err := validateSort(sort, playerSnapshotSortMinLength, playerSnapshotSortMaxLength); err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
params.sort = sort
return nil
}
func (params *ListPlayerSnapshotsParams) PrependSort(sort []PlayerSnapshotSort) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(playerSnapshotSortMaxLength-len(params.sort), 0)); err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
return params.SetSort(append(sort, params.sort...))
}
func (params *ListPlayerSnapshotsParams) PrependSortString(
sort []string,
allowed []PlayerSnapshotSort,
maxLength int,
) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(min(playerSnapshotSortMaxLength-len(params.sort), maxLength), 0)); err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
toPrepend := make([]PlayerSnapshotSort, 0, len(sort))
for i, s := range sort {
converted, err := newSortFromString(s, allowed...)
if err != nil {
return SliceElementValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "sort",
Index: i,
Err: err,
}
}
toPrepend = append(toPrepend, converted)
}
return params.SetSort(append(toPrepend, params.sort...))
}
func (params *ListPlayerSnapshotsParams) Cursor() PlayerSnapshotCursor {
return params.cursor
}
func (params *ListPlayerSnapshotsParams) SetCursor(cursor PlayerSnapshotCursor) error {
params.cursor = cursor
return nil
}
func (params *ListPlayerSnapshotsParams) SetEncodedCursor(encoded string) error {
decoded, err := decodePlayerSnapshotCursor(encoded)
if err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "cursor",
Err: err,
}
}
params.cursor = decoded
return nil
}
func (params *ListPlayerSnapshotsParams) Limit() int {
return params.limit
}
func (params *ListPlayerSnapshotsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, PlayerSnapshotListMaxLimit); err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "limit",
Err: err,
}
}
params.limit = limit
return nil
}
type ListPlayerSnapshotsResult struct {
snapshots PlayerSnapshots
self PlayerSnapshotCursor
next PlayerSnapshotCursor
}
const listPlayerSnapshotsResultModelName = "ListPlayerSnapshotsResult"
func NewListPlayerSnapshotsResult(
snapshots PlayerSnapshots,
next PlayerSnapshot,
) (ListPlayerSnapshotsResult, error) {
var err error
res := ListPlayerSnapshotsResult{
snapshots: snapshots,
}
if len(snapshots) > 0 {
res.self, err = snapshots[0].ToCursor()
if err != nil {
return ListPlayerSnapshotsResult{}, ValidationError{
Model: listPlayerSnapshotsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.ToCursor()
if err != nil {
return ListPlayerSnapshotsResult{}, ValidationError{
Model: listPlayerSnapshotsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListPlayerSnapshotsResult) PlayerSnapshots() PlayerSnapshots {
return res.snapshots
}
func (res ListPlayerSnapshotsResult) Self() PlayerSnapshotCursor {
return res.self
}
func (res ListPlayerSnapshotsResult) Next() PlayerSnapshotCursor {
return res.next
}
type ListPlayerSnapshotsWithRelationsResult struct {
snapshots PlayerSnapshotsWithRelations
self PlayerSnapshotCursor
next PlayerSnapshotCursor
}
const listPlayerSnapshotsWithRelationsResultModelName = "ListPlayerSnapshotsWithRelationsResult"
func NewListPlayerSnapshotsWithRelationsResult(
snapshots PlayerSnapshotsWithRelations,
next PlayerSnapshotWithRelations,
) (ListPlayerSnapshotsWithRelationsResult, error) {
var err error
res := ListPlayerSnapshotsWithRelationsResult{
snapshots: snapshots,
}
if len(snapshots) > 0 {
res.self, err = snapshots[0].PlayerSnapshot().ToCursor()
if err != nil {
return ListPlayerSnapshotsWithRelationsResult{}, ValidationError{
Model: listPlayerSnapshotsWithRelationsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.PlayerSnapshot().ToCursor()
if err != nil {
return ListPlayerSnapshotsWithRelationsResult{}, ValidationError{
Model: listPlayerSnapshotsWithRelationsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListPlayerSnapshotsWithRelationsResult) PlayerSnapshots() PlayerSnapshotsWithRelations {
return res.snapshots
}
func (res ListPlayerSnapshotsWithRelationsResult) Self() PlayerSnapshotCursor {
return res.self
}
func (res ListPlayerSnapshotsWithRelationsResult) Next() PlayerSnapshotCursor {
return res.next
}