core/internal/domain/server.go

958 lines
21 KiB
Go

package domain
import (
"fmt"
"math"
"net/url"
"slices"
"time"
)
type Server struct {
key string
versionCode string
url *url.URL
open bool
special bool
numPlayers int
numTribes int
numVillages int
numPlayerVillages int
numBarbarianVillages int
numBonusVillages int
config ServerConfig
buildingInfo BuildingInfo
unitInfo UnitInfo
createdAt time.Time
playerDataSyncedAt time.Time
playerSnapshotsCreatedAt time.Time
tribeDataSyncedAt time.Time
tribeSnapshotsCreatedAt time.Time
villageDataSyncedAt time.Time
ennoblementDataSyncedAt time.Time
}
const serverModelName = "Server"
// UnmarshalServerFromDatabase unmarshals Server from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalServerFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalServerFromDatabase(
key string,
versionCode string,
rawURL string,
open bool,
special bool,
numPlayers int,
numTribes int,
numVillages int,
numPlayerVillages int,
numBarbarianVillages int,
numBonusVillages int,
config ServerConfig,
buildingInfo BuildingInfo,
unitInfo UnitInfo,
createdAt time.Time,
playerDataSyncedAt time.Time,
playerSnapshotsCreatedAt time.Time,
tribeDataSyncedAt time.Time,
tribeSnapshotsCreatedAt time.Time,
villageDataSyncedAt time.Time,
ennoblementDataSyncedAt time.Time,
) (Server, error) {
if key == "" {
return Server{}, ValidationError{
Model: serverModelName,
Field: "key",
Err: ErrRequired,
}
}
if versionCode == "" {
return Server{}, ValidationError{
Model: serverModelName,
Field: "versionCode",
Err: ErrRequired,
}
}
u, err := parseURL(rawURL)
if err != nil {
return Server{}, ValidationError{
Model: serverModelName,
Field: "url",
Err: err,
}
}
return Server{
key: key,
url: u,
open: open,
special: special,
numPlayers: numPlayers,
numTribes: numTribes,
numVillages: numVillages,
numPlayerVillages: numPlayerVillages,
numBarbarianVillages: numBarbarianVillages,
numBonusVillages: numBonusVillages,
config: config,
buildingInfo: buildingInfo,
unitInfo: unitInfo,
createdAt: createdAt,
playerDataSyncedAt: playerDataSyncedAt,
playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
tribeDataSyncedAt: tribeDataSyncedAt,
tribeSnapshotsCreatedAt: tribeSnapshotsCreatedAt,
villageDataSyncedAt: villageDataSyncedAt,
ennoblementDataSyncedAt: ennoblementDataSyncedAt,
versionCode: versionCode,
}, nil
}
func (s Server) Key() string {
return s.key
}
func (s Server) VersionCode() string {
return s.versionCode
}
func (s Server) URL() *url.URL {
return s.url
}
func (s Server) Open() bool {
return s.open
}
func (s Server) Special() bool {
return s.special
}
func (s Server) NumPlayers() int {
return s.numPlayers
}
func (s Server) NumTribes() int {
return s.numTribes
}
func (s Server) NumVillages() int {
return s.numVillages
}
func (s Server) NumPlayerVillages() int {
return s.numPlayerVillages
}
func (s Server) NumBarbarianVillages() int {
return s.numBarbarianVillages
}
func (s Server) NumBonusVillages() int {
return s.numBonusVillages
}
func (s Server) Config() ServerConfig {
return s.config
}
func (s Server) BuildingInfo() BuildingInfo {
return s.buildingInfo
}
func (s Server) UnitInfo() UnitInfo {
return s.unitInfo
}
func (s Server) CreatedAt() time.Time {
return s.createdAt
}
func (s Server) PlayerDataSyncedAt() time.Time {
return s.playerDataSyncedAt
}
func (s Server) PlayerSnapshotsCreatedAt() time.Time {
return s.playerSnapshotsCreatedAt
}
func (s Server) TribeDataSyncedAt() time.Time {
return s.tribeDataSyncedAt
}
func (s Server) TribeSnapshotsCreatedAt() time.Time {
return s.tribeSnapshotsCreatedAt
}
func (s Server) VillageDataSyncedAt() time.Time {
return s.villageDataSyncedAt
}
func (s Server) EnnoblementDataSyncedAt() time.Time {
return s.ennoblementDataSyncedAt
}
func (s Server) ToCursor() (ServerCursor, error) {
return NewServerCursor(s.key, s.open)
}
func (s Server) Meta() ServerMeta {
return ServerMeta{
key: s.key,
url: s.url,
open: s.open,
}
}
func (s Server) Base() BaseServer {
return BaseServer{
key: s.key,
url: s.url,
open: s.open,
}
}
func (s Server) canBeClosed(open BaseServers) bool {
return s.open && !slices.ContainsFunc(open, func(openServer BaseServer) bool {
return openServer.Key() == s.key && openServer.Open() == s.open
})
}
func (s Server) canCleanUpData() bool {
return !s.open && !s.special
}
func (s Server) IsZero() bool {
return s == Server{}
}
type Servers []Server
// Close finds all servers with Server.Open returning true that are not in the given slice with open servers
// and then converts them to BaseServers with the corrected open value.
func (ss Servers) Close(open BaseServers) (BaseServers, error) {
res := make(BaseServers, 0, len(ss))
for _, s := range ss {
if !s.canBeClosed(open) {
continue
}
base, err := NewBaseServer(s.Key(), s.URL(), false)
if err != nil {
return nil, fmt.Errorf("couldn't construct BaseServer for server with key '%s': %w", s.Key(), err)
}
res = append(res, base)
}
return res, nil
}
// CleanUpData finds all servers for which old data (ennoblements, snapshots etc.) can be deleted.
func (ss Servers) CleanUpData() ([]CleanUpDataCmdPayload, error) {
res := make([]CleanUpDataCmdPayload, 0, len(ss))
for _, s := range ss {
if !s.canCleanUpData() {
continue
}
payloadServer, err := NewCleanUpDataCmdPayloadServer(
s.Key(),
s.VersionCode(),
s.Open(),
s.Special(),
s.PlayerDataSyncedAt(),
s.PlayerSnapshotsCreatedAt(),
s.TribeDataSyncedAt(),
s.TribeSnapshotsCreatedAt(),
s.VillageDataSyncedAt(),
s.EnnoblementDataSyncedAt(),
)
if err != nil {
return nil, fmt.Errorf("couldn't construct CleanUpDataCmdPayloadServer for server with key '%s': %w", s.Key(), err)
}
payload, err := NewCleanUpDataCmdPayload(payloadServer)
if err != nil {
return nil, fmt.Errorf("couldn't construct CleanUpDataCmdPayload for server with key '%s': %w", s.Key(), err)
}
res = append(res, payload)
}
return res, nil
}
type ServerMeta struct {
key string
url *url.URL
open bool
}
const serverMetaModelName = "ServerMeta"
// UnmarshalServerMetaFromDatabase unmarshals ServerMeta from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalServerMetaFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalServerMetaFromDatabase(key string, rawURL string, open bool) (ServerMeta, error) {
if key == "" {
return ServerMeta{}, ValidationError{
Model: serverModelName,
Field: "key",
Err: ErrRequired,
}
}
u, err := parseURL(rawURL)
if err != nil {
return ServerMeta{}, ValidationError{
Model: serverMetaModelName,
Field: "url",
Err: err,
}
}
return ServerMeta{key: key, url: u, open: open}, nil
}
func (s ServerMeta) Key() string {
return s.key
}
func (s ServerMeta) URL() *url.URL {
return s.url
}
func (s ServerMeta) Open() bool {
return s.open
}
type CreateServerParams struct {
base BaseServer
versionCode string
}
const createServerParamsModelName = "CreateServerParams"
func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) {
if err := validateVersionCode(versionCode); err != nil {
return nil, ValidationError{
Model: createServerParamsModelName,
Field: "versionCode",
Err: err,
}
}
res := make([]CreateServerParams, 0, len(servers))
for i, s := range servers {
if s.IsZero() {
return nil, fmt.Errorf("servers[%d] is an empty struct", i)
}
res = append(res, CreateServerParams{
base: s,
versionCode: versionCode,
})
}
return res, nil
}
func (params CreateServerParams) Base() BaseServer {
return params.base
}
func (params CreateServerParams) VersionCode() string {
return params.versionCode
}
type UpdateServerParams struct {
config NullServerConfig
buildingInfo NullBuildingInfo
unitInfo NullUnitInfo
numTribes NullInt
tribeDataSyncedAt NullTime
numPlayers NullInt
playerDataSyncedAt NullTime
numVillages NullInt
numPlayerVillages NullInt
numBarbarianVillages NullInt
numBonusVillages NullInt
villageDataSyncedAt NullTime
ennoblementDataSyncedAt NullTime
tribeSnapshotsCreatedAt NullTime
playerSnapshotsCreatedAt NullTime
}
const updateServerParamsModelName = "UpdateServerParams"
func (params *UpdateServerParams) Config() NullServerConfig {
return params.config
}
func (params *UpdateServerParams) SetConfig(config NullServerConfig) error {
params.config = config
return nil
}
func (params *UpdateServerParams) BuildingInfo() NullBuildingInfo {
return params.buildingInfo
}
func (params *UpdateServerParams) SetBuildingInfo(buildingInfo NullBuildingInfo) error {
params.buildingInfo = buildingInfo
return nil
}
func (params *UpdateServerParams) UnitInfo() NullUnitInfo {
return params.unitInfo
}
func (params *UpdateServerParams) SetUnitInfo(unitInfo NullUnitInfo) error {
params.unitInfo = unitInfo
return nil
}
func (params *UpdateServerParams) NumTribes() NullInt {
return params.numTribes
}
func (params *UpdateServerParams) SetNumTribes(numTribes NullInt) error {
if numTribes.Valid {
if err := validateIntInRange(numTribes.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numTribes",
Err: err,
}
}
}
params.numTribes = numTribes
return nil
}
func (params *UpdateServerParams) TribeDataSyncedAt() NullTime {
return params.tribeDataSyncedAt
}
func (params *UpdateServerParams) SetTribeDataSyncedAt(tribeDataSyncedAt NullTime) error {
params.tribeDataSyncedAt = tribeDataSyncedAt
return nil
}
func (params *UpdateServerParams) NumPlayers() NullInt {
return params.numPlayers
}
func (params *UpdateServerParams) SetNumPlayers(numPlayers NullInt) error {
if numPlayers.Valid {
if err := validateIntInRange(numPlayers.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numPlayers",
Err: err,
}
}
}
params.numPlayers = numPlayers
return nil
}
func (params *UpdateServerParams) PlayerDataSyncedAt() NullTime {
return params.playerDataSyncedAt
}
func (params *UpdateServerParams) SetPlayerDataSyncedAt(playerDataSyncedAt NullTime) error {
params.playerDataSyncedAt = playerDataSyncedAt
return nil
}
func (params *UpdateServerParams) NumVillages() NullInt {
return params.numVillages
}
func (params *UpdateServerParams) SetNumVillages(numVillages NullInt) error {
if numVillages.Valid {
if err := validateIntInRange(numVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numVillages",
Err: err,
}
}
}
params.numVillages = numVillages
return nil
}
func (params *UpdateServerParams) NumPlayerVillages() NullInt {
return params.numPlayerVillages
}
func (params *UpdateServerParams) SetNumPlayerVillages(numPlayerVillages NullInt) error {
if numPlayerVillages.Valid {
if err := validateIntInRange(numPlayerVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numPlayerVillages",
Err: err,
}
}
}
params.numPlayerVillages = numPlayerVillages
return nil
}
func (params *UpdateServerParams) NumBarbarianVillages() NullInt {
return params.numBarbarianVillages
}
func (params *UpdateServerParams) SetNumBarbarianVillages(numBarbarianVillages NullInt) error {
if numBarbarianVillages.Valid {
if err := validateIntInRange(numBarbarianVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numBarbarianVillages",
Err: err,
}
}
}
params.numBarbarianVillages = numBarbarianVillages
return nil
}
func (params *UpdateServerParams) NumBonusVillages() NullInt {
return params.numBonusVillages
}
func (params *UpdateServerParams) SetNumBonusVillages(numBonusVillages NullInt) error {
if numBonusVillages.Valid {
if err := validateIntInRange(numBonusVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numBonusVillages",
Err: err,
}
}
}
params.numBonusVillages = numBonusVillages
return nil
}
func (params *UpdateServerParams) VillageDataSyncedAt() NullTime {
return params.villageDataSyncedAt
}
func (params *UpdateServerParams) SetVillageDataSyncedAt(villageDataSyncedAt NullTime) error {
params.villageDataSyncedAt = villageDataSyncedAt
return nil
}
func (params *UpdateServerParams) EnnoblementDataSyncedAt() NullTime {
return params.ennoblementDataSyncedAt
}
func (params *UpdateServerParams) SetEnnoblementDataSyncedAt(ennoblementDataSyncedAt NullTime) error {
params.ennoblementDataSyncedAt = ennoblementDataSyncedAt
return nil
}
func (params *UpdateServerParams) TribeSnapshotsCreatedAt() NullTime {
return params.tribeSnapshotsCreatedAt
}
func (params *UpdateServerParams) SetTribeSnapshotsCreatedAt(tribeSnapshotsCreatedAt NullTime) error {
params.tribeSnapshotsCreatedAt = tribeSnapshotsCreatedAt
return nil
}
func (params *UpdateServerParams) PlayerSnapshotsCreatedAt() NullTime {
return params.playerSnapshotsCreatedAt
}
func (params *UpdateServerParams) SetPlayerSnapshotsCreatedAt(playerSnapshotsCreatedAt NullTime) error {
params.playerSnapshotsCreatedAt = playerSnapshotsCreatedAt
return nil
}
//nolint:gocyclo
func (params *UpdateServerParams) IsZero() bool {
return !params.config.Valid &&
!params.buildingInfo.Valid &&
!params.unitInfo.Valid &&
!params.numTribes.Valid &&
!params.tribeDataSyncedAt.Valid &&
!params.numPlayers.Valid &&
!params.playerDataSyncedAt.Valid &&
!params.numVillages.Valid &&
!params.numPlayerVillages.Valid &&
!params.numBarbarianVillages.Valid &&
!params.numBonusVillages.Valid &&
!params.villageDataSyncedAt.Valid &&
!params.ennoblementDataSyncedAt.Valid &&
!params.tribeSnapshotsCreatedAt.Valid &&
!params.playerSnapshotsCreatedAt.Valid
}
type ServerSort uint8
const (
ServerSortKeyASC ServerSort = iota + 1
ServerSortKeyDESC
ServerSortOpenASC
ServerSortOpenDESC
)
// IsInConflict returns true if two sorts can't be used together (e.g. ServerSortKeyASC and ServerSortKeyDESC).
func (s ServerSort) IsInConflict(s2 ServerSort) bool {
return isSortInConflict(s, s2)
}
func (s ServerSort) String() string {
switch s {
case ServerSortKeyASC:
return "key:ASC"
case ServerSortKeyDESC:
return "key:DESC"
case ServerSortOpenASC:
return "open:ASC"
case ServerSortOpenDESC:
return "open:DESC"
default:
return "unknown server sort"
}
}
type ServerCursor struct {
key string
open bool
}
const serverCursorModelName = "ServerCursor"
func NewServerCursor(key string, open bool) (ServerCursor, error) {
if err := validateServerKey(key); err != nil {
return ServerCursor{}, ValidationError{
Model: serverCursorModelName,
Field: "key",
Err: err,
}
}
return ServerCursor{key: key, open: open}, nil
}
func decodeServerCursor(encoded string) (ServerCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
return ServerCursor{}, err
}
key, err := m.string("key")
if err != nil {
return ServerCursor{}, ErrInvalidCursor
}
open, err := m.bool("open")
if err != nil {
return ServerCursor{}, ErrInvalidCursor
}
vc, err := NewServerCursor(key, open)
if err != nil {
return ServerCursor{}, ErrInvalidCursor
}
return vc, nil
}
func (sc ServerCursor) Key() string {
return sc.key
}
func (sc ServerCursor) Open() bool {
return sc.open
}
func (sc ServerCursor) IsZero() bool {
return sc == ServerCursor{}
}
func (sc ServerCursor) Encode() string {
if sc.IsZero() {
return ""
}
return encodeCursor([]keyValuePair{
{"key", sc.key},
{"open", sc.open},
})
}
type ListServersParams struct {
keys []string
versionCodes []string
open NullBool
special NullBool
tribeSnapshotsCreatedAtLT NullTime
playerSnapshotsCreatedAtLT NullTime
sort []ServerSort
cursor ServerCursor
limit int
}
const (
ServerListMaxLimit = 500
listServersParamsModelName = "ListServersParams"
)
func NewListServersParams() ListServersParams {
return ListServersParams{
sort: []ServerSort{ServerSortKeyASC},
limit: ServerListMaxLimit,
special: NullBool{
V: false,
Valid: true,
},
}
}
func (params *ListServersParams) Keys() []string {
return params.keys
}
func (params *ListServersParams) SetKeys(keys []string) error {
for i, k := range keys {
if err := validateServerKey(k); err != nil {
return SliceElementValidationError{
Model: listServersParamsModelName,
Field: "keys",
Index: i,
Err: err,
}
}
}
params.keys = keys
return nil
}
func (params *ListServersParams) VersionCodes() []string {
return params.versionCodes
}
func (params *ListServersParams) SetVersionCodes(versionCodes []string) error {
for i, vc := range versionCodes {
if err := validateVersionCode(vc); err != nil {
return SliceElementValidationError{
Model: listServersParamsModelName,
Field: "versionCodes",
Index: i,
Err: err,
}
}
}
params.versionCodes = versionCodes
return nil
}
func (params *ListServersParams) Open() NullBool {
return params.open
}
func (params *ListServersParams) SetOpen(open NullBool) error {
params.open = open
return nil
}
func (params *ListServersParams) Special() NullBool {
return params.special
}
func (params *ListServersParams) SetSpecial(special NullBool) error {
params.special = special
return nil
}
func (params *ListServersParams) TribeSnapshotsCreatedAtLT() NullTime {
return params.tribeSnapshotsCreatedAtLT
}
func (params *ListServersParams) SetTribeSnapshotsCreatedAtLT(tribeSnapshotsCreatedAtLT NullTime) error {
params.tribeSnapshotsCreatedAtLT = tribeSnapshotsCreatedAtLT
return nil
}
func (params *ListServersParams) PlayerSnapshotsCreatedAtLT() NullTime {
return params.playerSnapshotsCreatedAtLT
}
func (params *ListServersParams) SetPlayerSnapshotsCreatedAtLT(playerSnapshotsCreatedAtLT NullTime) error {
params.playerSnapshotsCreatedAtLT = playerSnapshotsCreatedAtLT
return nil
}
func (params *ListServersParams) Sort() []ServerSort {
return params.sort
}
const (
serverSortMinLength = 0
serverSortMaxLength = 2
)
func (params *ListServersParams) SetSort(sort []ServerSort) error {
if err := validateSort(sort, serverSortMinLength, serverSortMaxLength); err != nil {
return ValidationError{
Model: listServersParamsModelName,
Field: "sort",
Err: err,
}
}
params.sort = sort
return nil
}
func (params *ListServersParams) Cursor() ServerCursor {
return params.cursor
}
func (params *ListServersParams) SetCursor(cursor ServerCursor) error {
params.cursor = cursor
return nil
}
func (params *ListServersParams) SetEncodedCursor(encoded string) error {
decoded, err := decodeServerCursor(encoded)
if err != nil {
return ValidationError{
Model: listServersParamsModelName,
Field: "cursor",
Err: err,
}
}
params.cursor = decoded
return nil
}
func (params *ListServersParams) Limit() int {
return params.limit
}
func (params *ListServersParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, ServerListMaxLimit); err != nil {
return ValidationError{
Model: listServersParamsModelName,
Field: "limit",
Err: err,
}
}
params.limit = limit
return nil
}
type ListServersResult struct {
servers Servers
self ServerCursor
next ServerCursor
}
const listServersResultModelName = "ListServersResult"
func NewListServersResult(servers Servers, next Server) (ListServersResult, error) {
var err error
res := ListServersResult{
servers: servers,
}
if len(servers) > 0 {
res.self, err = servers[0].ToCursor()
if err != nil {
return ListServersResult{}, ValidationError{
Model: listServersResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.ToCursor()
if err != nil {
return ListServersResult{}, ValidationError{
Model: listServersResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListServersResult) Servers() Servers {
return res.servers
}
func (res ListServersResult) Self() ServerCursor {
return res.self
}
func (res ListServersResult) Next() ServerCursor {
return res.next
}
type ServerNotFoundError struct {
Key string
}
var _ ErrorWithParams = ServerNotFoundError{}
func (e ServerNotFoundError) Error() string {
return fmt.Sprintf("server with key %s not found", e.Key)
}
func (e ServerNotFoundError) Type() ErrorType {
return ErrorTypeNotFound
}
const errorCodeServerNotFound = "server-not-found"
func (e ServerNotFoundError) Code() string {
return errorCodeServerNotFound
}
func (e ServerNotFoundError) Params() map[string]any {
return map[string]any{
"Key": e.Key,
}
}