package domain import ( "fmt" "math" "net/url" "slices" "time" ) const ( serverKeyMinLength = 1 serverKeyMaxLength = 10 ) 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) IsZero() bool { return s == Server{} } func (s Server) Base() BaseServer { return BaseServer{ key: s.key, url: s.url, open: s.open, } } 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.Open() || slices.ContainsFunc(open, func(openServer BaseServer) bool { return openServer.Key() == s.Key() && openServer.Open() == s.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 } 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 { 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 { params.numVillages = numVillages return nil } func (params *UpdateServerParams) NumPlayerVillages() NullInt { return params.numPlayerVillages } func (params *UpdateServerParams) SetNumPlayerVillages(numPlayerVillages NullInt) error { params.numPlayerVillages = numPlayerVillages return nil } func (params *UpdateServerParams) NumBarbarianVillages() NullInt { return params.numBarbarianVillages } func (params *UpdateServerParams) SetNumBarbarianVillages(numBarbarianVillages NullInt) error { params.numBarbarianVillages = numBarbarianVillages return nil } func (params *UpdateServerParams) NumBonusVillages() NullInt { return params.numBonusVillages } func (params *UpdateServerParams) SetNumBonusVillages(numBonusVillages NullInt) error { 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.villageDataSyncedAt.Valid && !params.ennoblementDataSyncedAt.Valid && !params.tribeSnapshotsCreatedAt.Valid && !params.playerSnapshotsCreatedAt.Valid } type ServerSort uint8 const ( ServerSortKeyASC ServerSort = iota + 1 ServerSortKeyDESC ServerSortOpenASC ServerSortOpenDESC ) 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 { params.keys = keys return nil } func (params *ListServersParams) VersionCodes() []string { return params.versionCodes } func (params *ListServersParams) SetVersionCodes(versionCodes []string) error { 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 = 1 serverSortMaxLength = 2 ) func (params *ListServersParams) SetSort(sort []ServerSort) error { if err := validateSliceLen(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 = NewServerCursor(servers[0].Key(), servers[0].Open()) if err != nil { return ListServersResult{}, ValidationError{ Model: listServersResultModelName, Field: "self", Err: err, } } } if !next.IsZero() { res.next, err = NewServerCursor(next.Key(), next.Open()) 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 } func (e ServerNotFoundError) Code() string { return "server-not-found" } func (e ServerNotFoundError) Params() map[string]any { return map[string]any{ "Key": e.Key, } }