652 lines
14 KiB
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
|
|
}
|