package domain import ( "cmp" "fmt" "math" "net/url" "slices" "time" ) const ( villageNameMinLength = 1 villageNameMaxLength = 150 villageContinentMinLength = 1 villageContinentMaxLength = 5 ) type Village struct { id int serverKey string name string points int x int y int continent string bonus int playerID int profileURL *url.URL createdAt time.Time } const villageModelName = "Village" // UnmarshalVillageFromDatabase unmarshals Village from the database. // // It should be used only for unmarshalling from the database! // You can't use UnmarshalVillageFromDatabase as constructor - It may put domain into the invalid state! func UnmarshalVillageFromDatabase( id int, serverKey string, name string, points int, x int, y int, continent string, bonus int, playerID int, rawProfileURL string, createdAt time.Time, ) (Village, error) { if err := validateIntInRange(id, 1, math.MaxInt); err != nil { return Village{}, ValidationError{ Model: villageModelName, Field: "id", Err: err, } } if err := validateServerKey(serverKey); err != nil { return Village{}, ValidationError{ Model: villageModelName, Field: "serverKey", Err: err, } } profileURL, err := parseURL(rawProfileURL) if err != nil { return Village{}, ValidationError{ Model: villageModelName, Field: "profileURL", Err: err, } } return Village{ id: id, serverKey: serverKey, name: name, points: points, x: x, y: y, continent: continent, bonus: bonus, playerID: playerID, profileURL: profileURL, createdAt: createdAt, }, nil } func (v Village) ID() int { return v.id } func (v Village) ServerKey() string { return v.serverKey } func (v Village) Name() string { return v.name } func (v Village) Points() int { return v.points } func (v Village) X() int { return v.x } func (v Village) Y() int { return v.y } func (v Village) Continent() string { return v.continent } func (v Village) Bonus() int { return v.bonus } func (v Village) PlayerID() int { return v.playerID } func (v Village) ProfileURL() *url.URL { return v.profileURL } func (v Village) CreatedAt() time.Time { return v.createdAt } func (v Village) Base() BaseVillage { return BaseVillage{ id: v.id, name: v.name, points: v.points, x: v.x, y: v.y, continent: v.continent, bonus: v.bonus, playerID: v.playerID, profileURL: v.profileURL, } } type Villages []Village // Delete finds all villages with the given serverKey that are not in the given slice with active villages // and returns their ids. Both slices must be sorted in ascending order by ID // + if vs contains villages from different servers. they must be sorted in ascending order by server key. func (vs Villages) Delete(serverKey string, active BaseVillages) []int { // villages are deleted now and then, there is no point in prereallocating these slices //nolint:prealloc var toDelete []int for _, v := range vs { if v.ServerKey() != serverKey { continue } _, found := slices.BinarySearchFunc(active, v, func(a BaseVillage, b Village) int { return cmp.Compare(a.ID(), b.ID()) }) if found { continue } toDelete = append(toDelete, v.ID()) } return toDelete } type CreateVillageParams struct { base BaseVillage serverKey string } const createVillageParamsModelName = "CreateVillageParams" func NewCreateVillageParams(serverKey string, villages BaseVillages) ([]CreateVillageParams, error) { if err := validateServerKey(serverKey); err != nil { return nil, ValidationError{ Model: createVillageParamsModelName, Field: "serverKey", Err: err, } } params := make([]CreateVillageParams, 0, len(villages)) for i, v := range villages { if v.IsZero() { return nil, fmt.Errorf("villages[%d] is an empty struct", i) } params = append(params, CreateVillageParams{ base: v, serverKey: serverKey, }) } return params, nil } func (params CreateVillageParams) Base() BaseVillage { return params.base } func (params CreateVillageParams) ServerKey() string { return params.serverKey } type VillageSort uint8 const ( VillageSortIDASC VillageSort = iota + 1 VillageSortIDDESC VillageSortServerKeyASC VillageSortServerKeyDESC ) // IsInConflict returns true if two sorts can't be used together (e.g. VillageSortIDASC and VillageSortIDDESC). func (s VillageSort) IsInConflict(s2 VillageSort) bool { ss := []VillageSort{s, s2} slices.Sort(ss) // ASC is always an odd number, DESC is always an even number return (ss[0]%2 == 1 && ss[0] == ss[1]-1) || ss[0] == ss[1] } //nolint:gocyclo func (s VillageSort) String() string { switch s { case VillageSortIDASC: return "id:ASC" case VillageSortIDDESC: return "id:DESC" case VillageSortServerKeyASC: return "serverKey:ASC" case VillageSortServerKeyDESC: return "serverKey:DESC" default: return "unknown village sort" } } type ListVillagesParams struct { ids []int idGT NullInt serverKeys []string sort []VillageSort limit int offset int } const ( VillageListMaxLimit = 500 listVillagesParamsModelName = "ListVillagesParams" ) func NewListVillagesParams() ListVillagesParams { return ListVillagesParams{ sort: []VillageSort{ VillageSortServerKeyASC, VillageSortIDASC, }, limit: VillageListMaxLimit, } } func (params *ListVillagesParams) IDs() []int { return params.ids } func (params *ListVillagesParams) SetIDs(ids []int) error { for i, id := range ids { if err := validateIntInRange(id, 1, math.MaxInt); err != nil { return SliceElementValidationError{ Model: listVillagesParamsModelName, Field: "ids", Index: i, Err: err, } } } params.ids = ids return nil } func (params *ListVillagesParams) IDGT() NullInt { return params.idGT } func (params *ListVillagesParams) SetIDGT(idGT NullInt) error { if idGT.Valid { if err := validateIntInRange(idGT.V, 0, math.MaxInt); err != nil { return ValidationError{ Model: listVillagesParamsModelName, Field: "idGT", Err: err, } } } params.idGT = idGT return nil } func (params *ListVillagesParams) ServerKeys() []string { return params.serverKeys } func (params *ListVillagesParams) SetServerKeys(serverKeys []string) error { params.serverKeys = serverKeys return nil } func (params *ListVillagesParams) Sort() []VillageSort { return params.sort } const ( villageSortMinLength = 1 villageSortMaxLength = 2 ) func (params *ListVillagesParams) SetSort(sort []VillageSort) error { if err := validateSort(sort, villageSortMinLength, villageSortMaxLength); err != nil { return ValidationError{ Model: listVillagesParamsModelName, Field: "sort", Err: err, } } params.sort = sort return nil } func (params *ListVillagesParams) Limit() int { return params.limit } func (params *ListVillagesParams) SetLimit(limit int) error { if err := validateIntInRange(limit, 1, VillageListMaxLimit); err != nil { return ValidationError{ Model: listVillagesParamsModelName, Field: "limit", Err: err, } } params.limit = limit return nil } func (params *ListVillagesParams) Offset() int { return params.offset } func (params *ListVillagesParams) SetOffset(offset int) error { if err := validateIntInRange(offset, 0, math.MaxInt); err != nil { return ValidationError{ Model: listVillagesParamsModelName, Field: "offset", Err: err, } } params.offset = offset return nil }