package domain import ( "fmt" "math" "time" ) 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) ToCursor() (PlayerSnapshotCursor, error) { return NewPlayerSnapshotCursor(ps.id, ps.serverKey, ps.date) } func (ps PlayerSnapshot) IsZero() bool { return ps == PlayerSnapshot{} } type PlayerSnapshots []PlayerSnapshot 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 = 1 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) 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 }