Compare commits

..

23 Commits

Author SHA1 Message Date
847f5d6b85 chore(deps): update module github.com/getkin/kin-openapi to v0.125.0 (#57)
Some checks failed
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline failed
Reviewed-on: #57
Co-authored-by: Renovate <renovate@dwysokinski.me>
Co-committed-by: Renovate <renovate@dwysokinski.me>
2024-06-12 04:26:44 +00:00
86d78c0f0c feat: generic version of MinGreaterEqualError/MaxLessEqualError (#56)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #56
2024-06-10 13:52:59 +00:00
51a6270812 feat: tribe - sort by most points (#55)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
Reviewed-on: #55
2024-06-02 11:51:38 +00:00
4db3fee03f feat: player - sort by most points (#54)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #54
2024-06-02 11:33:50 +00:00
cf3eb9053d
chore: bump golangci-lint to v1.59 & alpine docker tag to v3.20
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-06-02 12:37:43 +02:00
9d5987f74c feat: new endpoint GET /api/v2/versions/{versionCode}/servers/{serverKey}/snapshots (#52)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
Reviewed-on: #52
2024-05-13 05:12:05 +00:00
4ca2ea9361
chore: update prod Dockerfile
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
2024-05-12 09:47:46 +02:00
6e2c54679e
feat: postgres - set application name
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
2024-05-11 17:30:06 +02:00
7de443c652
fix: server_snapshots - incorrect num_inactive_tribes
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-05-11 17:23:40 +02:00
c6d7ad8965
chore: update prod/Dockerfile
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
2024-05-10 07:04:36 +02:00
98361f3ae0 fix: ci/cd - test step timeouts (#51)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #51
2024-05-10 04:57:35 +00:00
08a7f0c504 feat: server snapshots (#49)
Some checks failed
ci/woodpecker/push/govulncheck Pipeline failed
ci/woodpecker/push/test Pipeline failed
Reviewed-on: #49
2024-05-09 07:49:04 +00:00
e3fa23d0c4 chore(deps): update module github.com/brianvoe/gofakeit/v7 to v7.0.3 (#48)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #48
Co-authored-by: Renovate <renovate@dwysokinski.me>
Co-committed-by: Renovate <renovate@dwysokinski.me>
2024-05-08 04:31:00 +00:00
f8c9bdb321 feat: add more stats to the Server model (#47)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #47
2024-05-07 06:15:37 +00:00
4d6c1c8982 refactor: server - rename num_tribes and num_players (#46)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/tag/deployment Pipeline was successful
Reviewed-on: #46
2024-05-06 04:50:24 +00:00
1c1758ce7d refactor: server - rename *_updated_at columns (#45)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
Reviewed-on: #45
2024-05-04 05:26:22 +00:00
d05d7aaacb
chore: bump golangci-lint to v1.58.0
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-05-04 06:57:03 +02:00
68b715306d
refactor: versionModelName should be const, not var
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
2024-04-09 07:51:20 +02:00
21e530aa0f
refactor: cmd_serve - rename flag variables
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-04-08 07:12:08 +02:00
be824acaec
refactor: runConsumer - minor changes to the initialization/cleanup sequence
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-04-08 07:08:11 +02:00
3fe539db19
refactor: move logger.Debug call to waitForShutdownSignal
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2024-04-08 06:41:51 +02:00
24b8ffd32c chore(deps): update module github.com/getkin/kin-openapi to v0.124.0 (#42)
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Reviewed-on: #42
Co-authored-by: Renovate <renovate@dwysokinski.me>
Co-committed-by: Renovate <renovate@dwysokinski.me>
2024-04-07 04:09:36 +00:00
03e0e4149b
fix: incorrect appName
All checks were successful
ci/woodpecker/push/govulncheck Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/cron/govulncheck Pipeline was successful
2024-04-06 07:22:49 +02:00
122 changed files with 7593 additions and 1511 deletions

View File

@ -57,10 +57,12 @@ linters:
- inamedparam
- sloglint
- revive
- gomnd
- mnd
- forbidigo
- copyloopvar
- intrange
- fatcontext
- canonicalheader
linters-settings:
gocyclo:
@ -123,7 +125,7 @@ linters-settings:
enable-all: true
sloglint:
attr-only: true
gomnd:
mnd:
ignored-functions:
- strconv.FormatInt
- strconv.ParseInt

View File

@ -6,7 +6,7 @@ repos:
stages: [commit-msg]
additional_dependencies: ["@commitlint/config-conventional"]
- repo: https://github.com/golangci/golangci-lint
rev: v1.57.2
rev: v1.59.0
hooks:
- id: golangci-lint
- repo: https://github.com/hadolint/hadolint

View File

@ -39,10 +39,10 @@ steps:
TESTS_RABBITMQ_CONNECTION_STRING:
amqp://twhelp:twhelp@rmq:5672/
commands:
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- go test -parallel 2 -race -coverprofile=coverage.txt -covermode=atomic ./...
lint:
image: golangci/golangci-lint:v1.57
image: golangci/golangci-lint:v1.59
pull: true
depends_on:
- generate

View File

@ -16,7 +16,7 @@ install-git-hooks:
.PHONY: install-golangci-lint
install-golangci-lint:
@echo "Installing github.com/golangci/golangci-lint..."
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.57.2
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.59.0
.PHONY: install-oapi-codegen
install-oapi-codegen:

View File

@ -471,6 +471,25 @@ paths:
$ref: "#/components/responses/ListTribeChangesResponse"
default:
$ref: "#/components/responses/ErrorResponse"
/v2/versions/{versionCode}/servers/{serverKey}/snapshots:
get:
operationId: listServerServerSnapshots
tags:
- versions
- servers
- snapshots
description: List the given server's snapshots
parameters:
- $ref: "#/components/parameters/VersionCodePathParam"
- $ref: "#/components/parameters/ServerKeyPathParam"
- $ref: "#/components/parameters/CursorQueryParam"
- $ref: "#/components/parameters/LimitQueryParam"
- $ref: "#/components/parameters/ServerSnapshotSortQueryParam"
responses:
200:
$ref: "#/components/responses/ListServerSnapshotsResponse"
default:
$ref: "#/components/responses/ErrorResponse"
/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/snapshots:
get:
operationId: listTribeTribeSnapshots
@ -584,7 +603,11 @@ components:
- open
- url
- numPlayers
- numActivePlayers
- numInactivePlayers
- numTribes
- numActiveTribes
- numInactiveTribes
- numVillages
- numBarbarianVillages
- numBonusVillages
@ -601,11 +624,21 @@ components:
example: https://en138.tribalwars.net
numPlayers:
type: integer
description: numActivePlayers+numInactivePlayers
numActivePlayers:
type: integer
numInactivePlayers:
type: integer
playerDataSyncedAt:
type: string
format: date-time
numTribes:
type: integer
description: numActiveTribes+numInactiveTribes
numActiveTribes:
type: integer
numInactiveTribes:
type: integer
tribeDataSyncedAt:
type: string
format: date-time
@ -1580,6 +1613,52 @@ components:
createdAt:
type: string
format: date-time
ServerSnapshot:
type: object
required:
- id
- server
- numPlayers
- numActivePlayers
- numInactivePlayers
- numTribes
- numActiveTribes
- numInactiveTribes
- numVillages
- numBarbarianVillages
- numBonusVillages
- numPlayerVillages
- date
properties:
id:
$ref: "#/components/schemas/IntId"
server:
$ref: "#/components/schemas/ServerMeta"
numPlayers:
type: integer
description: numActivePlayers+numInactivePlayers
numActivePlayers:
type: integer
numInactivePlayers:
type: integer
numTribes:
type: integer
description: numActiveTribes+numInactiveTribes
numActiveTribes:
type: integer
numInactiveTribes:
type: integer
numVillages:
type: integer
numBarbarianVillages:
type: integer
numBonusVillages:
type: integer
numPlayerVillages:
type: integer
date:
type: string
format: date
TribeSnapshot:
type: object
required:
@ -1661,7 +1740,7 @@ components:
x-go-type-skip-optional-pointer: true
allOf:
- $ref: "#/components/schemas/CursorString"
PaginationResponse:
Pagination:
type: object
properties:
cursor:
@ -1715,6 +1794,8 @@ components:
- odScoreTotal:DESC
- points:ASC
- points:DESC
- mostPoints:ASC
- mostPoints:DESC
- dominance:ASC
- dominance:DESC
- deletedAt:ASC
@ -1755,6 +1836,8 @@ components:
- odScoreTotal:DESC
- points:ASC
- points:DESC
- mostPoints:ASC
- mostPoints:DESC
- deletedAt:ASC
- deletedAt:DESC
maxItems: 2
@ -1826,6 +1909,20 @@ components:
- date:ASC
- date:DESC
maxItems: 1
ServerSnapshotSortQueryParam:
name: sort
in: query
description: Order matters!
schema:
type: array
default:
- date:ASC
items:
type: string
enum:
- date:ASC
- date:DESC
maxItems: 1
PlayerSnapshotSortQueryParam:
name: sort
in: query
@ -1891,7 +1988,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -1917,7 +2014,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -1976,7 +2073,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2002,7 +2099,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2017,7 +2114,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2043,7 +2140,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2069,7 +2166,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2084,7 +2181,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2093,13 +2190,28 @@ components:
type: array
items:
$ref: "#/components/schemas/TribeChange"
ListServerSnapshotsResponse:
description: ""
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
properties:
data:
type: array
items:
$ref: "#/components/schemas/ServerSnapshot"
ListTribeSnapshotsResponse:
description: ""
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data
@ -2114,7 +2226,7 @@ components:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PaginationResponse"
- $ref: "#/components/schemas/Pagination"
- type: object
required:
- data

View File

@ -1,9 +1,9 @@
FROM --platform=$BUILDPLATFORM alpine:3.19
FROM alpine:3.20
LABEL maintainer="contact@twhelp.app"
ARG TARGETOS="linux"
ARG TARGETARCH="amd64"
ARG CI_COMMIT_TAG="v3.0.0"
ARG TARGETOS
ARG TARGETARCH
ARG CI_COMMIT_TAG
RUN apk --no-cache add ca-certificates tzdata wget && \
wget --progress=dot:giga -O /usr/bin/twhelp https://gitea.dwysokinski.me/twhelp/core/releases/download/${CI_COMMIT_TAG}/core_${CI_COMMIT_TAG##v}_${TARGETOS}_${TARGETARCH} && \

View File

@ -23,30 +23,30 @@ var (
}
dbFlagMaxIdleConns = &cli.IntFlag{
Name: "db.maxIdleConns",
Value: 2, //nolint:gomnd
Value: 2, //nolint:mnd
EnvVars: []string{"DB_MAX_IDLE_CONNS"},
Usage: "https://pkg.go.dev/database/sql#DB.SetMaxIdleConns",
}
dbFlagMaxOpenConns = &cli.IntFlag{
Name: "db.maxOpenConns",
Value: runtime.NumCPU() * 4, //nolint:gomnd
Value: runtime.NumCPU() * 4, //nolint:mnd
EnvVars: []string{"DB_MAX_OPEN_CONNS"},
Usage: "https://pkg.go.dev/database/sql#DB.SetMaxOpenConns",
}
dbFlagConnMaxLifetime = &cli.DurationFlag{
Name: "db.connMaxLifetime",
Value: 30 * time.Minute, //nolint:gomnd
Value: 30 * time.Minute, //nolint:mnd
EnvVars: []string{"DB_CONN_MAX_LIFETIME"},
Usage: "https://pkg.go.dev/database/sql#DB.SetConnMaxLifetime",
}
dbFlagReadTimeout = &cli.DurationFlag{
Name: "db.readTimeout",
Value: 10 * time.Second, //nolint:gomnd
Value: 10 * time.Second, //nolint:mnd
EnvVars: []string{"DB_READ_TIMEOUT"},
}
dbFlagWriteTimeout = &cli.DurationFlag{
Name: "db.writeTimeout",
Value: 5 * time.Second, //nolint:gomnd
Value: 5 * time.Second, //nolint:mnd
EnvVars: []string{"DB_WRITE_TIMEOUT"},
}
dbFlags = []cli.Flag{
@ -59,8 +59,9 @@ var (
}
)
func newBunDBFromFlags(c *cli.Context) (*bun.DB, error) {
func newBunDBFromFlags(c *cli.Context, applicationName string) (*bun.DB, error) {
return newBunDB(bundDBConfig{
applicationName: applicationName,
connectionString: c.String(dbFlagConnectionString.Name),
maxOpenConns: c.Int(dbFlagMaxOpenConns.Name),
maxIdleConns: c.Int(dbFlagMaxIdleConns.Name),
@ -72,6 +73,7 @@ func newBunDBFromFlags(c *cli.Context) (*bun.DB, error) {
type bundDBConfig struct {
connectionString string
applicationName string
maxOpenConns int
maxIdleConns int
connMaxLifetime time.Duration
@ -99,6 +101,7 @@ func newSQLDB(cfg bundDBConfig) *sql.DB {
pgdriver.WithDSN(cfg.connectionString),
pgdriver.WithReadTimeout(cfg.readTimeout),
pgdriver.WithWriteTimeout(cfg.writeTimeout),
pgdriver.WithApplicationName(cfg.applicationName),
))
db.SetMaxOpenConns(cfg.maxOpenConns)
db.SetMaxIdleConns(cfg.maxIdleConns)

View File

@ -18,6 +18,7 @@ import (
"github.com/ThreeDotsLabs/watermill-amqp/v2/pkg/amqp"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/ThreeDotsLabs/watermill/message/router/middleware"
"github.com/ettle/strcase"
"github.com/uptrace/bun"
"github.com/urfave/cli/v2"
)
@ -49,18 +50,32 @@ var cmdConsumer = &cli.Command{
c.String(rmqFlagTopicSyncServersCmd.Name),
c.String(rmqFlagTopicServerSyncedEvent.Name),
)
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
publisher,
marshaler,
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
"",
)
twSvc, err := newTWServiceFromFlags(c)
if err != nil {
return err
}
serverSvc := app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher)
serverSnapshotSvc := app.NewServerSnapshotService(
adapter.NewServerSnapshotBunRepository(db),
serverSvc,
serverSnapshotPublisher,
)
consumer := port.NewServerWatermillConsumer(
app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher),
serverSvc,
serverSnapshotSvc,
subscriber,
logger,
marshaler,
c.String(rmqFlagTopicSyncServersCmd.Name),
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
c.String(rmqFlagTopicServerSyncedEvent.Name),
c.String(rmqFlagTopicTribesSyncedEvent.Name),
c.String(rmqFlagTopicPlayersSyncedEvent.Name),
@ -299,18 +314,9 @@ type registerConsumerHandlersFunc func(
//nolint:gocyclo
func runConsumer(c *cli.Context, name string, registerHandlers registerConsumerHandlersFunc) error {
ctx, cancel := context.WithCancel(c.Context)
defer cancel()
logger := loggerFromCtx(ctx)
logger := loggerFromCtx(c.Context)
watermillLogger := newWatermillLogger(logger)
var wg sync.WaitGroup
defer func() {
// it's required for the graceful shutdown
wg.Wait()
}()
amqpConn, err := newAMQPConnectionFromFlags(c, watermillLogger)
if err != nil {
return err
@ -341,19 +347,12 @@ func runConsumer(c *cli.Context, name string, registerHandlers registerConsumerH
}
}()
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, strcase.ToSnake(name))
if err != nil {
return err
}
defer closeBunDB(bunDB, logger)
healthObserver := healthfile.LiveObserver(health.New(), "/tmp/live")
defer func() {
if closeErr := healthObserver.Close(); closeErr != nil {
logger.Warn("couldn't close health observer", slog.Any("error", closeErr))
}
}()
router, err := newWatermillRouter(watermillLogger)
if err != nil {
return err
@ -371,6 +370,22 @@ func runConsumer(c *cli.Context, name string, registerHandlers registerConsumerH
return err
}
ctx, cancel := context.WithCancel(c.Context)
defer cancel()
var wg sync.WaitGroup
defer func() {
// it's required for the graceful shutdown
wg.Wait()
}()
healthObserver := healthfile.LiveObserver(health.New(), "/tmp/live")
defer func() {
if closeErr := healthObserver.Close(); closeErr != nil {
logger.Warn("couldn't close health observer", slog.Any("error", closeErr))
}
}()
wg.Add(1)
go func() {
defer wg.Done()
@ -391,7 +406,7 @@ func runConsumer(c *cli.Context, name string, registerHandlers registerConsumerH
logger.Info("consumer is up and running", slog.String("name", name))
}
waitForShutdownSignal(ctx)
waitForShutdownSignal(ctx, logger)
if closeErr := router.Close(); closeErr != nil {
logger.Warn("couldn't close router", slog.Any("error", err))

View File

@ -45,7 +45,7 @@ var cmdDB = &cli.Command{
logger := loggerFromCtx(c.Context)
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "migrations")
if err != nil {
return err
}
@ -94,7 +94,7 @@ var cmdDB = &cli.Command{
logger := loggerFromCtx(c.Context)
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "migrations")
if err != nil {
return err
}

View File

@ -47,7 +47,7 @@ var (
}
}()
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "data_sync_job")
if err != nil {
return err
}
@ -103,7 +103,7 @@ var (
}
}()
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "ennoblement_sync_job")
if err != nil {
return err
}
@ -165,19 +165,24 @@ var (
}
}()
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "snapshot_creation_job")
if err != nil {
return err
}
defer closeBunDB(bunDB, logger)
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
publisher,
newWatermillMarshaler(),
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
"",
)
tribeSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
publisher,
newWatermillMarshaler(),
c.String(rmqFlagTopicCreateTribeSnapshotsCmd.Name),
c.String(rmqFlagTopicTribeSnapshotsCreatedEvent.Name),
)
playerSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
publisher,
newWatermillMarshaler(),
@ -187,7 +192,13 @@ var (
versionSvc := app.NewVersionService(adapter.NewVersionBunRepository(bunDB))
serverSvc := app.NewServerService(adapter.NewServerBunRepository(bunDB), nil, nil)
snapshotSvc := app.NewSnapshotService(versionSvc, serverSvc, tribeSnapshotPublisher, playerSnapshotPublisher)
snapshotSvc := app.NewSnapshotService(
versionSvc,
serverSvc,
serverSnapshotPublisher,
tribeSnapshotPublisher,
playerSnapshotPublisher,
)
shutdownSignalCtx, stop := newShutdownSignalContext(c.Context)
defer stop()
@ -231,7 +242,7 @@ var (
}
}()
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "cleanup_job")
if err != nil {
return err
}

View File

@ -23,66 +23,66 @@ import (
)
var (
apiServerPortFlag = &cli.UintFlag{
apiServerFlagPort = &cli.UintFlag{
Name: "api.port",
EnvVars: []string{"API_PORT"},
Value: 9234, //nolint:gomnd
Value: 9234, //nolint:mnd
}
apiServerHandlerTimeoutFlag = &cli.DurationFlag{
apiServerFlagHandlerTimeout = &cli.DurationFlag{
Name: "api.handlerTimeout",
EnvVars: []string{"API_HANDLER_TIMEOUT"},
Value: 5 * time.Second, //nolint:gomnd
Value: 5 * time.Second, //nolint:mnd
Usage: "https://pkg.go.dev/net/http#TimeoutHandler",
}
apiServerReadTimeoutFlag = &cli.DurationFlag{
apiServerFlagReadTimeout = &cli.DurationFlag{
Name: "api.readTimeout",
EnvVars: []string{"API_READ_TIMEOUT"},
Value: 5 * time.Second, //nolint:gomnd
Value: 5 * time.Second, //nolint:mnd
}
apiServerReadHeaderTimeoutFlag = &cli.DurationFlag{
apiServerFlagReadHeaderTimeout = &cli.DurationFlag{
Name: "api.readHeaderTimeout",
EnvVars: []string{"API_READ_HEADER_TIMEOUT"},
Value: time.Second,
}
apiServerWriteTimeoutFlag = &cli.DurationFlag{
apiServerFlagWriteTimeout = &cli.DurationFlag{
Name: "api.writeTimeout",
EnvVars: []string{"API_WRITE_TIMEOUT"},
Value: 10 * time.Second, //nolint:gomnd
Value: 10 * time.Second, //nolint:mnd
}
apiServerIdleTimeoutFlag = &cli.DurationFlag{
apiServerFlagIdleTimeout = &cli.DurationFlag{
Name: "api.idleTimeout",
EnvVars: []string{"API_IDLE_TIMEOUT"},
Value: 180 * time.Second, //nolint:gomnd
Value: 180 * time.Second, //nolint:mnd
}
apiServerShutdownTimeoutFlag = &cli.DurationFlag{
apiServerFlagShutdownTimeout = &cli.DurationFlag{
Name: "api.shutdownTimeout",
EnvVars: []string{"API_SHUTDOWN_TIMEOUT"},
Value: 10 * time.Second, //nolint:gomnd
Value: 10 * time.Second, //nolint:mnd
}
apiServerOpenAPIEnabledFlag = &cli.BoolFlag{
apiServerFlagOpenAPIEnabled = &cli.BoolFlag{
Name: "api.openApi.enabled",
EnvVars: []string{"API_OPENAPI_ENABLED"},
Value: true,
}
apiServerOpenAPISwaggerEnabledFlag = &cli.BoolFlag{
apiServerFlagOpenAPISwaggerEnabled = &cli.BoolFlag{
Name: "api.openApi.swaggerEnabled",
EnvVars: []string{"API_OPENAPI_SWAGGER_ENABLED"},
Value: true,
}
apiServerOpenAPIServersFlag = &cli.StringSliceFlag{
apiServerFlagOpenAPIServers = &cli.StringSliceFlag{
Name: "api.openApi.servers",
EnvVars: []string{"API_OPENAPI_SERVERS"},
}
apiServerFlags = []cli.Flag{
apiServerPortFlag,
apiServerHandlerTimeoutFlag,
apiServerReadTimeoutFlag,
apiServerReadHeaderTimeoutFlag,
apiServerWriteTimeoutFlag,
apiServerIdleTimeoutFlag,
apiServerOpenAPIEnabledFlag,
apiServerOpenAPISwaggerEnabledFlag,
apiServerOpenAPIServersFlag,
apiServerFlagPort,
apiServerFlagHandlerTimeout,
apiServerFlagReadTimeout,
apiServerFlagReadHeaderTimeout,
apiServerFlagWriteTimeout,
apiServerFlagIdleTimeout,
apiServerFlagOpenAPIEnabled,
apiServerFlagOpenAPISwaggerEnabled,
apiServerFlagOpenAPIServers,
}
)
@ -99,7 +99,7 @@ var cmdServe = &cli.Command{
logger := loggerFromCtx(c.Context)
// deps
bunDB, err := newBunDBFromFlags(c)
bunDB, err := newBunDBFromFlags(c, "api")
if err != nil {
return err
}
@ -113,6 +113,7 @@ var cmdServe = &cli.Command{
villageRepo := adapter.NewVillageBunRepository(bunDB)
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
serverSnapshotRepo := adapter.NewServerSnapshotBunRepository(bunDB)
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(bunDB)
playerSnapshotRepo := adapter.NewPlayerSnapshotBunRepository(bunDB)
@ -124,6 +125,7 @@ var cmdServe = &cli.Command{
playerSvc := app.NewPlayerService(playerRepo, tribeChangeSvc, nil, nil)
villageSvc := app.NewVillageService(villageRepo, nil, nil)
ennoblementSvc := app.NewEnnoblementService(ennoblementRepo, nil, nil)
serverSnapshotSvc := app.NewServerSnapshotService(serverSnapshotRepo, serverSvc, nil)
tribeSnapshotSvc := app.NewTribeSnapshotService(tribeSnapshotRepo, tribeSvc, nil)
playerSnapshotSvc := app.NewPlayerSnapshotService(playerSnapshotRepo, playerSvc, nil)
@ -132,11 +134,11 @@ var cmdServe = &cli.Command{
server, err := newHTTPServer(
httpServerConfig{
port: c.Uint(apiServerPortFlag.Name),
readTimeout: c.Duration(apiServerReadTimeoutFlag.Name),
readHeaderTimeout: c.Duration(apiServerReadHeaderTimeoutFlag.Name),
writeTimeout: c.Duration(apiServerWriteTimeoutFlag.Name),
idleTimeout: c.Duration(apiServerIdleTimeoutFlag.Name),
port: c.Uint(apiServerFlagPort.Name),
readTimeout: c.Duration(apiServerFlagReadTimeout.Name),
readHeaderTimeout: c.Duration(apiServerFlagReadHeaderTimeout.Name),
writeTimeout: c.Duration(apiServerFlagWriteTimeout.Name),
idleTimeout: c.Duration(apiServerFlagIdleTimeout.Name),
},
func(r chi.Router) error {
oapiCfg, oapiCfgErr := newOpenAPIConfigFromFlags(c)
@ -160,6 +162,7 @@ var cmdServe = &cli.Command{
villageSvc,
ennoblementSvc,
tribeChangeSvc,
serverSnapshotSvc,
tribeSnapshotSvc,
playerSnapshotSvc,
port.WithOpenAPIConfig(oapiCfg),
@ -175,11 +178,9 @@ var cmdServe = &cli.Command{
idleConnsClosed := make(chan struct{})
go func() {
waitForShutdownSignal(c.Context)
waitForShutdownSignal(c.Context, logger)
logger.Debug("received shutdown signal")
ctx, cancel := context.WithTimeout(c.Context, c.Duration(apiServerShutdownTimeoutFlag.Name))
ctx, cancel := context.WithTimeout(c.Context, c.Duration(apiServerFlagShutdownTimeout.Name))
defer cancel()
if err := server.Shutdown(ctx); err != nil {
@ -202,7 +203,7 @@ var cmdServe = &cli.Command{
}
func newOpenAPIConfigFromFlags(c *cli.Context) (port.OpenAPIConfig, error) {
rawURLs := c.StringSlice(apiServerOpenAPIServersFlag.Name)
rawURLs := c.StringSlice(apiServerFlagOpenAPIServers.Name)
servers := make([]port.OpenAPIConfigServer, 0, len(rawURLs))
@ -218,8 +219,8 @@ func newOpenAPIConfigFromFlags(c *cli.Context) (port.OpenAPIConfig, error) {
}
return port.OpenAPIConfig{
Enabled: c.Bool(apiServerOpenAPIEnabledFlag.Name),
SwaggerEnabled: c.Bool(apiServerOpenAPISwaggerEnabledFlag.Name),
Enabled: c.Bool(apiServerFlagOpenAPIEnabled.Name),
SwaggerEnabled: c.Bool(apiServerFlagOpenAPISwaggerEnabled.Name),
BasePath: apiBasePath,
Servers: servers,
}, nil
@ -239,7 +240,7 @@ func newAPIMiddlewares(c *cli.Context, logger *slog.Logger) chi.Middlewares {
})),
middleware.Recoverer,
func(next http.Handler) http.Handler {
return http.TimeoutHandler(next, c.Duration(apiServerHandlerTimeoutFlag.Name), "Timeout")
return http.TimeoutHandler(next, c.Duration(apiServerFlagHandlerTimeout.Name), "Timeout")
},
}
}

View File

@ -5,7 +5,7 @@ import (
"os"
)
const appName = "wendia"
const appName = "twhelp"
// this flag will be set by the build flags
var version = "development"

View File

@ -31,6 +31,11 @@ var (
Value: "tribes.event.synced",
EnvVars: []string{"RABBITMQ_TOPIC_TRIBES_SYNCED_EVENT"},
}
rmqFlagTopicCreateServerSnapshotCmd = &cli.StringFlag{
Name: "rabbitmq.topic.createServerSnapshotCmd",
Value: "servers.cmd.create_snapshot",
EnvVars: []string{"RABBITMQ_TOPIC_CREATE_SERVER_SNAPSHOT_CMD"},
}
rmqFlagTopicCreateTribeSnapshotsCmd = &cli.StringFlag{
Name: "rabbitmq.topic.createTribeSnapshotsCmd",
Value: "tribes.cmd.create_snapshots",
@ -79,6 +84,7 @@ var (
rmqFlags = []cli.Flag{
rmqFlagConnectionString,
rmqFlagTopicSyncServersCmd,
rmqFlagTopicCreateServerSnapshotCmd,
rmqFlagTopicServerSyncedEvent,
rmqFlagTopicTribesSyncedEvent,
rmqFlagTopicCreateTribeSnapshotsCmd,

View File

@ -20,7 +20,7 @@ var (
}
twSvcFlagTimeout = &cli.DurationFlag{
Name: "tw.timeout",
Value: 10 * time.Second, //nolint:gomnd
Value: 10 * time.Second, //nolint:mnd
EnvVars: []string{"TW_TIMEOUT"},
Usage: "https://pkg.go.dev/net/http#Client.Timeout",
}

View File

@ -2,6 +2,7 @@ package main
import (
"context"
"log/slog"
"os"
"os/signal"
"syscall"
@ -15,8 +16,9 @@ func newShutdownSignalContext(parent context.Context) (context.Context, context.
return signal.NotifyContext(parent, shutdownSignals...)
}
func waitForShutdownSignal(ctx context.Context) {
func waitForShutdownSignal(ctx context.Context, logger *slog.Logger) {
ctx, cancel := newShutdownSignalContext(ctx)
defer cancel()
<-ctx.Done()
logger.Debug("received shutdown signal")
}

10
go.mod
View File

@ -6,11 +6,11 @@ require (
gitea.dwysokinski.me/Kichiyaki/chiclientip v0.1.0
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1
github.com/brianvoe/gofakeit/v7 v7.0.2
github.com/brianvoe/gofakeit/v7 v7.0.3
github.com/cenkalti/backoff/v4 v4.3.0
github.com/elliotchance/phpserialize v1.4.0
github.com/ettle/strcase v0.2.0
github.com/getkin/kin-openapi v0.123.0
github.com/getkin/kin-openapi v0.125.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/render v1.0.3
github.com/google/go-cmp v0.6.0
@ -26,7 +26,7 @@ require (
github.com/uptrace/bun/driver/pgdriver v1.2.1
github.com/uptrace/bun/driver/sqliteshim v1.2.1
github.com/uptrace/bun/extra/bundebug v1.2.1
github.com/urfave/cli/v2 v2.27.1
github.com/urfave/cli/v2 v2.27.2
go.uber.org/automaxprocs v1.5.3
golang.org/x/sync v0.7.0
gopkg.in/yaml.v3 v3.0.1
@ -40,7 +40,7 @@ require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
@ -86,7 +86,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sys v0.18.0 // indirect

20
go.sum
View File

@ -17,8 +17,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/brianvoe/gofakeit/v7 v7.0.2 h1:jzYT7Ge3RDHw7J1CM1kwu0OQywV9vbf2qSGxBS72TCY=
github.com/brianvoe/gofakeit/v7 v7.0.2/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/brianvoe/gofakeit/v7 v7.0.3 h1:tGCt+eYfhTMWE1ko5G2EO1f/yE44yNpIwUb4h32O0wo=
github.com/brianvoe/gofakeit/v7 v7.0.3/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@ -30,8 +30,8 @@ github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvA
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
@ -55,8 +55,8 @@ github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt65fXno=
github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
@ -206,8 +206,8 @@ github.com/uptrace/bun/driver/sqliteshim v1.2.1/go.mod h1:oJtOPSCDdDHgNw/0jwIGr+
github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus=
github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
@ -220,8 +220,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=

View File

@ -39,6 +39,7 @@ func (pub *PlayerWatermillPublisher) EventSynced(
VersionCode: p.VersionCode(),
ServerURL: p.ServerURL(),
NumPlayers: p.NumPlayers(),
NumActivePlayers: p.NumActivePlayers(),
})
if err != nil {
return fmt.Errorf("%s: couldn't marshal PlayersSyncedEventPayload: %w", p.ServerKey(), err)

View File

@ -39,6 +39,7 @@ func (pub *TribeWatermillPublisher) EventSynced(
VersionCode: p.VersionCode(),
ServerURL: p.ServerURL(),
NumTribes: p.NumTribes(),
NumActiveTribes: p.NumActiveTribes(),
})
if err != nil {
return fmt.Errorf("%s: couldn't marshal TribesSyncedEventPayload: %w", p.ServerKey(), err)

View File

@ -111,7 +111,7 @@ func (repo *EnnoblementBunRepository) ListWithRelations(
func (repo *EnnoblementBunRepository) Delete(ctx context.Context, serverKey string, createdAtLTE time.Time) error {
if _, err := repo.db.NewDelete().
Model(&bunmodel.Ennoblement{}).
Model((*bunmodel.Ennoblement)(nil)).
Where("server_key = ?", serverKey).
Where("created_at <= ?", createdAtLTE).
Returning("NULL").

View File

@ -131,6 +131,17 @@ func (repo *PlayerBunRepository) ListWithRelations(
return domain.NewListPlayersWithRelationsResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *PlayerBunRepository) Count(ctx context.Context, params domain.CountPlayersParams) (int, error) {
cnt, err := repo.db.NewSelect().
Model((*bunmodel.Player)(nil)).
Apply(countPlayersParamsApplier{params: params}.apply).
Count(ctx)
if err != nil {
return 0, fmt.Errorf("couldnt count players: %w", err)
}
return cnt, nil
}
func (repo *PlayerBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
if len(ids) == 0 {
return nil
@ -269,6 +280,9 @@ func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
case domain.PlayerSortPointsASC,
domain.PlayerSortPointsDESC:
el.value = cursor.Points()
case domain.PlayerSortMostPointsASC,
domain.PlayerSortMostPointsDESC:
el.value = cursor.MostPoints()
case domain.PlayerSortDeletedAtASC,
domain.PlayerSortDeletedAtDESC:
el.value = cursor.DeletedAt()
@ -315,6 +329,10 @@ func (a listPlayersParamsApplier) sortToColumnAndDirection(
return "player.points", sortDirectionASC, nil
case domain.PlayerSortPointsDESC:
return "player.points", sortDirectionDESC, nil
case domain.PlayerSortMostPointsASC:
return "player.most_points", sortDirectionASC, nil
case domain.PlayerSortMostPointsDESC:
return "player.most_points", sortDirectionDESC, nil
case domain.PlayerSortDeletedAtASC:
return "COALESCE(player.deleted_at, '0001-01-01 00:00:00+00:00')", sortDirectionASC, nil
case domain.PlayerSortDeletedAtDESC:
@ -323,3 +341,15 @@ func (a listPlayersParamsApplier) sortToColumnAndDirection(
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
}
}
type countPlayersParamsApplier struct {
params domain.CountPlayersParams
}
func (a countPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("player.server_key IN (?)", bun.In(serverKeys))
}
return q
}

View File

@ -109,7 +109,7 @@ func (repo *PlayerSnapshotBunRepository) ListWithRelations(
func (repo *PlayerSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
if _, err := repo.db.NewDelete().
Model(&bunmodel.PlayerSnapshot{}).
Model((*bunmodel.PlayerSnapshot)(nil)).
Where("server_key = ?", serverKey).
Where("date <= ?", dateLTE).
Returning("NULL").

View File

@ -130,18 +130,32 @@ func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
q = q.Set("num_tribes = ?", numTribes.V)
}
if numActiveTribes := a.params.NumActiveTribes(); numActiveTribes.Valid {
q = q.Set("num_active_tribes = ?", numActiveTribes.V)
}
if numInactiveTribes := a.params.NumInactiveTribes(); numInactiveTribes.Valid {
q = q.Set("num_inactive_tribes = ?", numInactiveTribes.V)
}
if tribeDataSyncedAt := a.params.TribeDataSyncedAt(); tribeDataSyncedAt.Valid {
// TODO: rename this column to tribe_data_synced_at
q = q.Set("tribe_data_updated_at = ?", tribeDataSyncedAt.V)
q = q.Set("tribe_data_synced_at = ?", tribeDataSyncedAt.V)
}
if numPlayers := a.params.NumPlayers(); numPlayers.Valid {
q = q.Set("num_players = ?", numPlayers.V)
}
if numActivePlayers := a.params.NumActivePlayers(); numActivePlayers.Valid {
q = q.Set("num_active_players = ?", numActivePlayers.V)
}
if numInactivePlayers := a.params.NumInactivePlayers(); numInactivePlayers.Valid {
q = q.Set("num_inactive_players = ?", numInactivePlayers.V)
}
if playerDataSyncedAt := a.params.PlayerDataSyncedAt(); playerDataSyncedAt.Valid {
// TODO: rename this column to player_data_synced_at
q = q.Set("player_data_updated_at = ?", playerDataSyncedAt.V)
q = q.Set("player_data_synced_at = ?", playerDataSyncedAt.V)
}
if numVillages := a.params.NumVillages(); numVillages.Valid {
@ -161,13 +175,15 @@ func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
}
if villageDataSyncedAt := a.params.VillageDataSyncedAt(); villageDataSyncedAt.Valid {
// TODO: rename this column to village_data_synced_at
q = q.Set("village_data_updated_at = ?", villageDataSyncedAt.V)
q = q.Set("village_data_synced_at = ?", villageDataSyncedAt.V)
}
if ennoblementDataSyncedAt := a.params.EnnoblementDataSyncedAt(); ennoblementDataSyncedAt.Valid {
// TODO: rename this column to ennoblement_data_synced_at
q = q.Set("ennoblement_data_updated_at = ?", ennoblementDataSyncedAt.V)
q = q.Set("ennoblement_data_synced_at = ?", ennoblementDataSyncedAt.V)
}
if snapshotCreatedAt := a.params.SnapshotCreatedAt(); snapshotCreatedAt.Valid {
q = q.Set("snapshot_created_at = ?", snapshotCreatedAt.V)
}
if tribeSnapshotsCreatedAt := a.params.TribeSnapshotsCreatedAt(); tribeSnapshotsCreatedAt.Valid {
@ -216,6 +232,13 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
)
}
if snapshotCreatedAt := a.params.SnapshotCreatedAtLT(); snapshotCreatedAt.Valid {
q = q.Where(
"server.snapshot_created_at < ? OR server.snapshot_created_at is null",
snapshotCreatedAt.V,
)
}
for _, s := range a.params.Sort() {
column, dir, err := a.sortToColumnAndDirection(s)
if err != nil {

View File

@ -0,0 +1,206 @@
package adapter
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/bun/bunmodel"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"github.com/uptrace/bun"
)
type ServerSnapshotBunRepository struct {
db bun.IDB
}
func NewServerSnapshotBunRepository(db bun.IDB) *ServerSnapshotBunRepository {
return &ServerSnapshotBunRepository{db: db}
}
func (repo *ServerSnapshotBunRepository) Create(
ctx context.Context,
params ...domain.CreateServerSnapshotParams,
) error {
if len(params) == 0 {
return nil
}
now := time.Now()
snapshots := make(bunmodel.ServerSnapshots, 0, len(params))
for _, p := range params {
snapshots = append(snapshots, bunmodel.ServerSnapshot{
ServerKey: p.ServerKey(),
NumPlayers: p.NumPlayers(),
NumActivePlayers: p.NumActivePlayers(),
NumInactivePlayers: p.NumInactivePlayers(),
NumTribes: p.NumTribes(),
NumActiveTribes: p.NumActiveTribes(),
NumInactiveTribes: p.NumInactiveTribes(),
NumVillages: p.NumVillages(),
NumPlayerVillages: p.NumPlayerVillages(),
NumBarbarianVillages: p.NumBarbarianVillages(),
NumBonusVillages: p.NumBonusVillages(),
Date: p.Date(),
CreatedAt: now,
})
}
if _, err := repo.db.NewInsert().
Model(&snapshots).
Ignore().
Returning("").
Exec(ctx); err != nil {
return fmt.Errorf("something went wrong while inserting server snapshots into the db: %w", err)
}
return nil
}
func (repo *ServerSnapshotBunRepository) List(
ctx context.Context,
params domain.ListServerSnapshotsParams,
) (domain.ListServerSnapshotsResult, error) {
var serverSnapshots bunmodel.ServerSnapshots
if err := repo.db.NewSelect().
Model(&serverSnapshots).
Apply(listServerSnapshotsParamsApplier{params: params}.apply).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return domain.ListServerSnapshotsResult{}, fmt.Errorf("couldn't select server snapshots from the db: %w", err)
}
converted, err := serverSnapshots.ToDomain()
if err != nil {
return domain.ListServerSnapshotsResult{}, err
}
return domain.NewListServerSnapshotsResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *ServerSnapshotBunRepository) ListWithRelations(
ctx context.Context,
params domain.ListServerSnapshotsParams,
) (domain.ListServerSnapshotsWithRelationsResult, error) {
var serverSnapshots bunmodel.ServerSnapshots
if err := repo.db.NewSelect().
Model(&serverSnapshots).
Apply(listServerSnapshotsParamsApplier{params: params}.apply).
Relation("Server", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column(bunmodel.ServerMetaColumns...)
}).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return domain.ListServerSnapshotsWithRelationsResult{}, fmt.Errorf(
"couldn't select server snapshots from the db: %w",
err,
)
}
converted, err := serverSnapshots.ToDomainWithRelations()
if err != nil {
return domain.ListServerSnapshotsWithRelationsResult{}, err
}
return domain.NewListServerSnapshotsWithRelationsResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *ServerSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
if _, err := repo.db.NewDelete().
Model((*bunmodel.ServerSnapshot)(nil)).
Where("server_key = ?", serverKey).
Where("date <= ?", dateLTE).
Returning("NULL").
Exec(ctx); err != nil {
return fmt.Errorf("couldn't delete server snapshots: %w", err)
}
return nil
}
type listServerSnapshotsParamsApplier struct {
params domain.ListServerSnapshotsParams
}
func (a listServerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("ss.server_key IN (?)", bun.In(serverKeys))
}
for _, s := range a.params.Sort() {
column, dir, err := a.sortToColumnAndDirection(s)
if err != nil {
return q.Err(err)
}
q.OrderExpr("? ?", column, dir.Bun())
}
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
}
func (a listServerSnapshotsParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
cursor := a.params.Cursor()
if cursor.IsZero() {
return q
}
sort := a.params.Sort()
cursorApplier := cursorPaginationApplier{
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
}
for _, s := range sort {
var err error
var el cursorPaginationApplierDataElement
el.column, el.direction, err = a.sortToColumnAndDirection(s)
if err != nil {
return q.Err(err)
}
switch s {
case domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortIDDESC:
el.value = cursor.ID()
el.unique = true
case domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortServerKeyDESC:
el.value = cursor.ServerKey()
case domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateDESC:
el.value = cursor.Date()
default:
return q.Err(fmt.Errorf("%s: %w", s.String(), errInvalidSortValue))
}
cursorApplier.data = append(cursorApplier.data, el)
}
return q.Apply(cursorApplier.apply)
}
func (a listServerSnapshotsParamsApplier) sortToColumnAndDirection(
s domain.ServerSnapshotSort,
) (bun.Safe, sortDirection, error) {
switch s {
case domain.ServerSnapshotSortDateASC:
return "ss.date", sortDirectionASC, nil
case domain.ServerSnapshotSortDateDESC:
return "ss.date", sortDirectionDESC, nil
case domain.ServerSnapshotSortIDASC:
return "ss.id", sortDirectionASC, nil
case domain.ServerSnapshotSortIDDESC:
return "ss.id", sortDirectionDESC, nil
case domain.ServerSnapshotSortServerKeyASC:
return "ss.server_key", sortDirectionASC, nil
case domain.ServerSnapshotSortServerKeyDESC:
return "ss.server_key", sortDirectionDESC, nil
default:
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
}
}

View File

@ -0,0 +1,29 @@
package adapter_test
import (
"testing"
"gitea.dwysokinski.me/twhelp/core/internal/bun/buntest"
)
func TestServerSnapshotBunRepository_Postgres(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("skipping long-running test")
}
testServerSnapshotRepository(t, func(t *testing.T) repositories {
t.Helper()
return newBunDBRepositories(t, postgres.NewDB(t))
})
}
func TestServerSnapshotBunRepository_SQLite(t *testing.T) {
t.Parallel()
testServerSnapshotRepository(t, func(t *testing.T) repositories {
t.Helper()
return newBunDBRepositories(t, buntest.NewSQLiteDB(t))
})
}

View File

@ -132,6 +132,17 @@ func (repo *TribeBunRepository) List(
return domain.NewListTribesResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *TribeBunRepository) Count(ctx context.Context, params domain.CountTribesParams) (int, error) {
cnt, err := repo.db.NewSelect().
Model((*bunmodel.Tribe)(nil)).
Apply(countTribesParamsApplier{params: params}.apply).
Count(ctx)
if err != nil {
return 0, fmt.Errorf("couldnt count tribes: %w", err)
}
return cnt, nil
}
func (repo *TribeBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
if len(ids) == 0 {
return nil
@ -230,6 +241,9 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
case domain.TribeSortPointsASC,
domain.TribeSortPointsDESC:
el.value = cursor.Points()
case domain.TribeSortMostPointsASC,
domain.TribeSortMostPointsDESC:
el.value = cursor.MostPoints()
case domain.TribeSortDominanceASC,
domain.TribeSortDominanceDESC:
el.value = cursor.Dominance()
@ -275,6 +289,10 @@ func (a listTribesParamsApplier) sortToColumnAndDirection(
return "tribe.points", sortDirectionASC, nil
case domain.TribeSortPointsDESC:
return "tribe.points", sortDirectionDESC, nil
case domain.TribeSortMostPointsASC:
return "tribe.most_points", sortDirectionASC, nil
case domain.TribeSortMostPointsDESC:
return "tribe.most_points", sortDirectionDESC, nil
case domain.TribeSortDominanceASC:
return "tribe.dominance", sortDirectionASC, nil
case domain.TribeSortDominanceDESC:
@ -287,3 +305,15 @@ func (a listTribesParamsApplier) sortToColumnAndDirection(
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
}
}
type countTribesParamsApplier struct {
params domain.CountTribesParams
}
func (a countTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("tribe.server_key IN (?)", bun.In(serverKeys))
}
return q
}

View File

@ -105,7 +105,7 @@ func (repo *TribeSnapshotBunRepository) ListWithRelations(
func (repo *TribeSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
if _, err := repo.db.NewDelete().
Model(&bunmodel.TribeSnapshot{}).
Model((*bunmodel.TribeSnapshot)(nil)).
Where("server_key = ?", serverKey).
Where("date <= ?", dateLTE).
Returning("NULL").

View File

@ -71,12 +71,14 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
key := fmt.Sprintf("%s-%d-%s", p.ServerKey(), p.PlayerID(), p.Date().Format(dateFormat))
for i, ps := range playerSnapshots {
if ps.ServerKey() == p.ServerKey() && ps.PlayerID() == p.PlayerID() && ps.Date().Equal(p.Date()) {
//nolint:lll
if ps.ServerKey() == p.ServerKey() && ps.PlayerID() == p.PlayerID() && ps.Date().Format(dateFormat) == p.Date().Format(dateFormat) {
m[key] = append(m[key], i)
}
}
}
assert.NotEmpty(t, m)
for key, indexes := range m {
assert.Len(t, indexes, 1, key)
}

View File

@ -276,6 +276,56 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}))
},
},
{
name: "OK: sort=[mostPoints ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortMostPointsASC,
domain.PlayerSortServerKeyASC,
domain.PlayerSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints()),
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[mostPoints DESC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortMostPointsDESC,
domain.PlayerSortServerKeyASC,
domain.PlayerSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints())*-1,
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
@ -664,6 +714,78 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}
})
t.Run("Count", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
tests := []struct {
name string
params func(t *testing.T) domain.CountPlayersParams
assertResult func(t *testing.T, params domain.CountPlayersParams, count int)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.CountPlayersParams {
t.Helper()
return domain.NewCountPlayersParams()
},
assertResult: func(t *testing.T, _ domain.CountPlayersParams, count int) {
t.Helper()
res, err := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, err)
assert.Len(t, res.Players(), count)
},
},
{
name: "OK: serverKeys",
params: func(t *testing.T) domain.CountPlayersParams {
t.Helper()
res, err := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, err)
params := domain.NewCountPlayersParams()
require.NoError(t, params.SetServerKeys([]string{res.Players()[0].ServerKey()}))
return params
},
assertResult: func(t *testing.T, params domain.CountPlayersParams, count int) {
t.Helper()
listParams := domain.NewListPlayersParams()
require.NoError(t, listParams.SetServerKeys(params.ServerKeys()))
res, err := repos.player.List(ctx, listParams)
require.NoError(t, err)
assert.Len(t, res.Players(), count)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assertError := tt.assertError
if assertError == nil {
assertError = func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
}
}
params := tt.params(t)
cnt, err := repos.player.Count(ctx, params)
assertError(t, err)
tt.assertResult(t, params, cnt)
})
}
})
t.Run("Delete", func(t *testing.T) {
t.Parallel()

View File

@ -0,0 +1,453 @@
package adapter_test
import (
"cmp"
"context"
"slices"
"testing"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testServerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
t.Helper()
ctx := context.Background()
t.Run("Create", func(t *testing.T) {
t.Parallel()
const dateFormat = "2006-01-02"
repos := newRepos(t)
assertCreated := func(t *testing.T, params domain.CreateServerSnapshotParams) {
t.Helper()
require.NotEmpty(t, params)
listParams := domain.NewListServerSnapshotsParams()
require.NoError(t, listParams.SetServerKeys([]string{params.ServerKey()}))
res, err := repos.serverSnapshot.List(ctx, listParams)
serverSnapshots := res.ServerSnapshots()
require.NoError(t, err)
date := params.Date().Format(dateFormat)
idx := slices.IndexFunc(serverSnapshots, func(ss domain.ServerSnapshot) bool {
return ss.ServerKey() == params.ServerKey() &&
ss.Date().Format(dateFormat) == date
})
require.GreaterOrEqual(t, idx, 0)
serverSnapshot := serverSnapshots[idx]
assert.Equal(t, params.ServerKey(), serverSnapshot.ServerKey())
assert.Equal(t, params.NumPlayers(), serverSnapshot.NumPlayers())
assert.Equal(t, params.NumActivePlayers(), serverSnapshot.NumActivePlayers())
assert.Equal(t, params.NumInactivePlayers(), serverSnapshot.NumInactivePlayers())
assert.Equal(t, params.NumTribes(), serverSnapshot.NumTribes())
assert.Equal(t, params.NumActiveTribes(), serverSnapshot.NumActiveTribes())
assert.Equal(t, params.NumInactiveTribes(), serverSnapshot.NumInactiveTribes())
assert.Equal(t, params.NumVillages(), serverSnapshot.NumVillages())
assert.Equal(t, params.NumPlayerVillages(), serverSnapshot.NumPlayerVillages())
assert.Equal(t, params.NumBarbarianVillages(), serverSnapshot.NumBarbarianVillages())
assert.Equal(t, params.NumBonusVillages(), serverSnapshot.NumBonusVillages())
assert.Equal(t, date, serverSnapshot.Date().Format(dateFormat))
assert.WithinDuration(t, time.Now(), serverSnapshot.CreatedAt(), time.Minute)
}
assertNoDuplicates := func(t *testing.T, params domain.CreateServerSnapshotParams) {
t.Helper()
listParams := domain.NewListServerSnapshotsParams()
require.NoError(t, listParams.SetServerKeys([]string{params.ServerKey()}))
res, err := repos.serverSnapshot.List(ctx, listParams)
require.NoError(t, err)
serverSnapshots := res.ServerSnapshots()
var indexes []int
for i, ss := range serverSnapshots {
if ss.ServerKey() == params.ServerKey() && ss.Date().Format(dateFormat) == params.Date().Format(dateFormat) {
indexes = append(indexes, i)
}
}
assert.Len(t, indexes, 1)
}
t.Run("OK", func(t *testing.T) {
t.Parallel()
listServersParams := domain.NewListServersParams()
require.NoError(t, listServersParams.SetOpen(domain.NullBool{
V: true,
Valid: true,
}))
require.NoError(t, listServersParams.SetLimit(1))
res, err := repos.server.List(ctx, listServersParams)
require.NoError(t, err)
servers := res.Servers()
require.NotEmpty(t, servers)
date := time.Now()
createParams, err := domain.NewCreateServerSnapshotParams(servers[0], date)
require.NoError(t, err)
require.NoError(t, repos.serverSnapshot.Create(ctx, createParams))
assertCreated(t, createParams)
require.NoError(t, repos.serverSnapshot.Create(ctx, createParams))
assertNoDuplicates(t, createParams)
})
t.Run("OK: len(params) == 0", func(t *testing.T) {
t.Parallel()
require.NoError(t, repos.serverSnapshot.Create(ctx))
})
})
t.Run("List & ListWithRelations", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
tests := []struct {
name string
params func(t *testing.T) domain.ListServerSnapshotsParams
assertResult func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
return domain.NewListServerSnapshotsParams()
},
assertResult: func(
t *testing.T,
_ domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
a.Date().Compare(b.Date()),
cmp.Compare(a.ID(), b.ID()),
)
}))
assert.False(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
},
},
{
name: "OK: sort=[serverKey DESC, date DESC, id DESC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyDESC,
domain.ServerSnapshotSortDateDESC,
domain.ServerSnapshotSortIDDESC,
}))
return params
},
assertResult: func(
t *testing.T,
_ domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
a.Date().Compare(b.Date()),
cmp.Compare(a.ID(), b.ID()),
) * -1
}))
},
},
{
name: "OK: sort=[id ASC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
}))
return params
},
assertResult: func(
t *testing.T,
_ domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Compare(a.ID(), b.ID())
}))
},
},
{
name: "OK: sort=[id DESC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDDESC,
}))
return params
},
assertResult: func(
t *testing.T,
_ domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Compare(a.ID(), b.ID()) * -1
}))
},
},
{
name: "OK: serverKeys",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
res, err := repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
require.NotEmpty(t, res.ServerSnapshots())
randServerSnapshot := res.ServerSnapshots()[0]
require.NoError(t, params.SetServerKeys([]string{randServerSnapshot.ServerKey()}))
return params
},
assertResult: func(
t *testing.T,
params domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
serverKeys := params.ServerKeys()
serverSnapshots := res.ServerSnapshots()
assert.NotZero(t, serverSnapshots)
for _, ss := range serverSnapshots {
assert.True(t, slices.Contains(serverKeys, ss.ServerKey()))
}
},
},
{
name: "OK: cursor serverKeys sort=[id ASC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
res, err := repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.ServerSnapshots()), 2)
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC}))
require.NoError(t, params.SetServerKeys([]string{res.ServerSnapshots()[1].ServerKey()}))
cursor, err := res.ServerSnapshots()[1].ToCursor()
require.NoError(t, err)
require.NoError(t, params.SetCursor(cursor))
return params
},
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
t.Helper()
serverKeys := params.ServerKeys()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
for _, ss := range serverSnapshots {
assert.GreaterOrEqual(t, ss.ID(), params.Cursor().ID())
assert.True(t, slices.Contains(serverKeys, ss.ServerKey()))
}
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Compare(a.ID(), b.ID())
}))
},
},
{
name: "OK: cursor sort=[serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortIDASC,
}))
res, err := repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.ServerSnapshots()), 2)
cursor, err := res.ServerSnapshots()[1].ToCursor()
require.NoError(t, err)
require.NoError(t, params.SetCursor(cursor))
return params
},
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
assert.GreaterOrEqual(t, serverSnapshots[0].ID(), params.Cursor().ID())
for _, ss := range serverSnapshots {
assert.GreaterOrEqual(t, ss.ServerKey(), params.Cursor().ServerKey())
}
},
},
{
name: "OK: cursor sort=[serverKey DESC, id DESC]",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyDESC,
domain.ServerSnapshotSortIDDESC,
}))
res, err := repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.ServerSnapshots()), 2)
cursor, err := res.ServerSnapshots()[1].ToCursor()
require.NoError(t, err)
require.NoError(t, params.SetCursor(cursor))
return params
},
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
t.Helper()
serverSnapshots := res.ServerSnapshots()
assert.NotEmpty(t, serverSnapshots)
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
) * -1
}))
assert.LessOrEqual(t, serverSnapshots[0].ID(), params.Cursor().ID())
for _, ss := range serverSnapshots {
assert.LessOrEqual(t, ss.ServerKey(), params.Cursor().ServerKey())
}
},
},
{
name: "OK: limit=2",
params: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetLimit(2))
return params
},
assertResult: func(
t *testing.T,
params domain.ListServerSnapshotsParams,
res domain.ListServerSnapshotsResult,
) {
t.Helper()
assert.Len(t, res.ServerSnapshots(), params.Limit())
assert.False(t, res.Self().IsZero())
assert.False(t, res.Next().IsZero())
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assertError := tt.assertError
if assertError == nil {
assertError = func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
}
}
params := tt.params(t)
res, err := repos.serverSnapshot.List(ctx, params)
assertError(t, err)
tt.assertResult(t, params, res)
resWithRelations, err := repos.serverSnapshot.ListWithRelations(ctx, params)
assertError(t, err)
require.Len(t, resWithRelations.ServerSnapshots(), len(res.ServerSnapshots()))
for i, ss := range resWithRelations.ServerSnapshots() {
assert.Equal(t, res.ServerSnapshots()[i], ss.ServerSnapshot())
assert.Equal(t, ss.ServerSnapshot().ServerKey(), ss.Server().Key())
}
})
}
})
t.Run("Delete", func(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortIDASC,
}))
res, err := repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
require.NotEmpty(t, res.ServerSnapshots())
randSnapshot := res.ServerSnapshots()[0]
require.NoError(t, repos.serverSnapshot.Delete(ctx, randSnapshot.ServerKey(), randSnapshot.Date()))
require.NoError(t, params.SetServerKeys([]string{randSnapshot.ServerKey()}))
res, err = repos.serverSnapshot.List(ctx, params)
require.NoError(t, err)
assert.NotEmpty(t, res.ServerSnapshots())
for _, ss := range res.ServerSnapshots() {
assert.True(t, ss.Date().After(randSnapshot.Date()))
}
})
})
}

View File

@ -281,6 +281,26 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}
},
},
{
name: "OK: snapshotCreatedAt=" + snapshotsCreatedAtLT.Format(time.RFC3339),
params: func(t *testing.T) domain.ListServersParams {
t.Helper()
params := domain.NewListServersParams()
require.NoError(t, params.SetSnapshotCreatedAtLT(domain.NullTime{
V: snapshotsCreatedAtLT,
Valid: true,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListServersParams, res domain.ListServersResult) {
t.Helper()
servers := res.Servers()
assert.NotEmpty(t, servers)
for _, s := range servers {
assert.True(t, s.SnapshotCreatedAt().Before(snapshotsCreatedAtLT))
}
},
},
{
name: "OK: playerSnapshotsCreatedAtLt=" + snapshotsCreatedAtLT.Format(time.RFC3339),
params: func(t *testing.T) domain.ListServersParams {
@ -498,6 +518,14 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumActiveTribes(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumInactiveTribes(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetTribeDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -506,6 +534,14 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumActivePlayers(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumInactivePlayers(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetPlayerDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -534,6 +570,10 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
V: time.Now(),
Valid: true,
}))
require.NoError(t, updateParams.SetSnapshotCreatedAt(domain.NullTime{
V: time.Now(),
Valid: true,
}))
require.NoError(t, updateParams.SetTribeSnapshotsCreatedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -557,6 +597,8 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
assert.Equal(t, updateParams.UnitInfo().V, serverAfterUpdate.UnitInfo())
assert.Equal(t, updateParams.BuildingInfo().V, serverAfterUpdate.BuildingInfo())
assert.Equal(t, updateParams.NumTribes().V, serverAfterUpdate.NumTribes())
assert.Equal(t, updateParams.NumActiveTribes().V, serverAfterUpdate.NumActiveTribes())
assert.Equal(t, updateParams.NumInactiveTribes().V, serverAfterUpdate.NumInactiveTribes())
assert.WithinDuration(
t,
updateParams.TribeDataSyncedAt().V,
@ -564,6 +606,8 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
time.Minute,
)
assert.Equal(t, updateParams.NumPlayers().V, serverAfterUpdate.NumPlayers())
assert.Equal(t, updateParams.NumActivePlayers().V, serverAfterUpdate.NumActivePlayers())
assert.Equal(t, updateParams.NumInactivePlayers().V, serverAfterUpdate.NumInactivePlayers())
assert.WithinDuration(
t,
updateParams.PlayerDataSyncedAt().V,
@ -586,6 +630,12 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
serverAfterUpdate.EnnoblementDataSyncedAt(),
time.Minute,
)
assert.WithinDuration(
t,
updateParams.SnapshotCreatedAt().V,
serverAfterUpdate.SnapshotCreatedAt(),
time.Minute,
)
assert.WithinDuration(
t,
updateParams.TribeSnapshotsCreatedAt().V,

View File

@ -26,6 +26,7 @@ type tribeRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error
List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error)
Count(ctx context.Context, params domain.CountTribesParams) (int, error)
Delete(ctx context.Context, serverKey string, ids ...int) error
}
@ -33,6 +34,7 @@ type playerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error)
Count(ctx context.Context, params domain.CountPlayersParams) (int, error)
Delete(ctx context.Context, serverKey string, ids ...int) error
}
@ -65,6 +67,16 @@ type tribeChangeRepository interface {
) (domain.ListTribeChangesWithRelationsResult, error)
}
type serverSnapshotRepository interface {
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
List(ctx context.Context, params domain.ListServerSnapshotsParams) (domain.ListServerSnapshotsResult, error)
ListWithRelations(
ctx context.Context,
params domain.ListServerSnapshotsParams,
) (domain.ListServerSnapshotsWithRelationsResult, error)
Delete(ctx context.Context, serverKey string, dateLTE time.Time) error
}
type tribeSnapshotRepository interface {
Create(ctx context.Context, params ...domain.CreateTribeSnapshotParams) error
List(ctx context.Context, params domain.ListTribeSnapshotsParams) (domain.ListTribeSnapshotsResult, error)
@ -93,6 +105,7 @@ type repositories struct {
village villageRepository
ennoblement ennoblementRepository
tribeChange tribeChangeRepository
serverSnapshot serverSnapshotRepository
tribeSnapshot tribeSnapshotRepository
playerSnapshot playerSnapshotRepository
}
@ -110,6 +123,7 @@ func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
village: adapter.NewVillageBunRepository(bunDB),
ennoblement: adapter.NewEnnoblementBunRepository(bunDB),
tribeChange: adapter.NewTribeChangeBunRepository(bunDB),
serverSnapshot: adapter.NewServerSnapshotBunRepository(bunDB),
tribeSnapshot: adapter.NewTribeSnapshotBunRepository(bunDB),
playerSnapshot: adapter.NewPlayerSnapshotBunRepository(bunDB),
}

View File

@ -73,12 +73,14 @@ func testTribeSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repos
key := fmt.Sprintf("%s-%d-%s", p.ServerKey(), p.TribeID(), p.Date().Format(dateFormat))
for i, ts := range tribeSnapshots {
if ts.ServerKey() == p.ServerKey() && ts.TribeID() == p.TribeID() && ts.Date().Equal(p.Date()) {
//nolint:lll
if ts.ServerKey() == p.ServerKey() && ts.TribeID() == p.TribeID() && ts.Date().Format(dateFormat) == p.Date().Format(dateFormat) {
m[key] = append(m[key], i)
}
}
}
assert.NotEmpty(t, m)
for key, indexes := range m {
assert.Len(t, indexes, 1, key)
}

View File

@ -339,6 +339,56 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
}))
},
},
{
name: "OK: sort=[mostPoints ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListTribesParams {
t.Helper()
params := domain.NewListTribesParams()
require.NoError(t, params.SetSort([]domain.TribeSort{
domain.TribeSortMostPointsASC,
domain.TribeSortServerKeyASC,
domain.TribeSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
t.Helper()
tribes := res.Tribes()
assert.NotEmpty(t, tribes)
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints()),
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[mostPoints DESC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListTribesParams {
t.Helper()
params := domain.NewListTribesParams()
require.NoError(t, params.SetSort([]domain.TribeSort{
domain.TribeSortMostPointsDESC,
domain.TribeSortServerKeyASC,
domain.TribeSortIDASC,
}))
return params
},
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
t.Helper()
tribes := res.Tribes()
assert.NotEmpty(t, tribes)
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
return cmp.Or(
cmp.Compare(a.MostPoints(), b.MostPoints())*-1,
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
},
},
{
name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListTribesParams {
@ -718,6 +768,78 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
}
})
t.Run("Count", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
tests := []struct {
name string
params func(t *testing.T) domain.CountTribesParams
assertResult func(t *testing.T, params domain.CountTribesParams, count int)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.CountTribesParams {
t.Helper()
return domain.NewCountTribesParams()
},
assertResult: func(t *testing.T, _ domain.CountTribesParams, count int) {
t.Helper()
res, err := repos.tribe.List(ctx, domain.NewListTribesParams())
require.NoError(t, err)
assert.Len(t, res.Tribes(), count)
},
},
{
name: "OK: serverKeys",
params: func(t *testing.T) domain.CountTribesParams {
t.Helper()
res, err := repos.tribe.List(ctx, domain.NewListTribesParams())
require.NoError(t, err)
params := domain.NewCountTribesParams()
require.NoError(t, params.SetServerKeys([]string{res.Tribes()[0].ServerKey()}))
return params
},
assertResult: func(t *testing.T, params domain.CountTribesParams, count int) {
t.Helper()
listParams := domain.NewListTribesParams()
require.NoError(t, listParams.SetServerKeys(params.ServerKeys()))
res, err := repos.tribe.List(ctx, listParams)
require.NoError(t, err)
assert.Len(t, res.Tribes(), count)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assertError := tt.assertError
if assertError == nil {
assertError = func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
}
}
params := tt.params(t)
cnt, err := repos.tribe.Count(ctx, params)
assertError(t, err)
tt.assertResult(t, params, cnt)
})
}
})
t.Run("Delete", func(t *testing.T) {
t.Parallel()

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error)
Count(ctx context.Context, params domain.CountPlayersParams) (int, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
// In addition, Delete sets TribeID to null.
//
@ -51,10 +52,21 @@ func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.S
return fmt.Errorf("%s: couldn't delete players: %w", serverKey, err)
}
countParams := domain.NewCountPlayersParams()
if err = countParams.SetServerKeys([]string{serverKey}); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
numPlayers, err := svc.repo.Count(ctx, countParams)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
payload, err := domain.NewPlayersSyncedEventPayload(
serverKey,
serverURL,
serverSyncedPayload.VersionCode(),
numPlayers,
len(players),
)
if err != nil {
@ -101,7 +113,7 @@ func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey str
if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
if err := listParams.SetLimit(len(ids)); err != nil {
return err
}

View File

@ -189,6 +189,18 @@ func (svc *ServerService) UpdateNumTribes(ctx context.Context, payload domain.Tr
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumActiveTribes(domain.NullInt{
V: payload.NumActiveTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumInactiveTribes(domain.NullInt{
V: payload.NumInactiveTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetTribeDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -209,6 +221,18 @@ func (svc *ServerService) UpdateNumPlayers(ctx context.Context, payload domain.P
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumActivePlayers(domain.NullInt{
V: payload.NumActivePlayers(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumInactivePlayers(domain.NullInt{
V: payload.NumInactivePlayers(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetPlayerDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -274,6 +298,21 @@ func (svc *ServerService) UpdateEnnoblementDataSyncedAt(
return svc.repo.Update(ctx, key, updateParams)
}
func (svc *ServerService) UpdateSnapshotCreatedAt(
ctx context.Context,
key string,
) error {
var updateParams domain.UpdateServerParams
if err := updateParams.SetSnapshotCreatedAt(domain.NullTime{
V: time.Now(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
return svc.repo.Update(ctx, key, updateParams)
}
func (svc *ServerService) UpdateTribeSnapshotsCreatedAt(
ctx context.Context,
payload domain.SnapshotsCreatedEventPayload,

View File

@ -0,0 +1,72 @@
package app
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
)
type ServerSnapshotRepository interface {
// Create persists tribe snapshots in a store (e.g. Postgres).
// Duplicates are ignored.
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
ListWithRelations(
ctx context.Context,
params domain.ListServerSnapshotsParams,
) (domain.ListServerSnapshotsWithRelationsResult, error)
}
type ServerSnapshotService struct {
repo ServerSnapshotRepository
serverSvc *ServerService
pub SnapshotPublisher
}
func NewServerSnapshotService(
repo ServerSnapshotRepository,
serverSvc *ServerService,
pub SnapshotPublisher,
) *ServerSnapshotService {
return &ServerSnapshotService{repo: repo, serverSvc: serverSvc, pub: pub}
}
func (svc *ServerSnapshotService) Create(
ctx context.Context,
createSnapshotsCmdPayload domain.CreateSnapshotsCmdPayload,
) error {
versionCode := createSnapshotsCmdPayload.VersionCode()
serverKey := createSnapshotsCmdPayload.ServerKey()
date := createSnapshotsCmdPayload.Date()
server, err := svc.serverSvc.GetNormalByVersionCodeAndServerKey(ctx, versionCode, serverKey)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
if !server.Open() {
return nil
}
params, err := domain.NewCreateServerSnapshotParams(server, date)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
if err = svc.repo.Create(ctx, params); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
if err = svc.serverSvc.UpdateSnapshotCreatedAt(ctx, serverKey); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
return nil
}
func (svc *ServerSnapshotService) ListWithRelations(
ctx context.Context,
params domain.ListServerSnapshotsParams,
) (domain.ListServerSnapshotsWithRelationsResult, error) {
return svc.repo.ListWithRelations(ctx, params)
}

View File

@ -12,6 +12,7 @@ import (
type SnapshotService struct {
versionSvc *VersionService
serverSvc *ServerService
serverSnapshotPub SnapshotPublisher
tribeSnapshotPub SnapshotPublisher
playerSnapshotPub SnapshotPublisher
}
@ -19,14 +20,16 @@ type SnapshotService struct {
func NewSnapshotService(
versionSvc *VersionService,
serverSvc *ServerService,
tribeSnapshotPublisher SnapshotPublisher,
playerSnapshotPublisher SnapshotPublisher,
serverSnapshotPub SnapshotPublisher,
tribeSnapshotPub SnapshotPublisher,
playerSnapshotPub SnapshotPublisher,
) *SnapshotService {
return &SnapshotService{
versionSvc: versionSvc,
serverSvc: serverSvc,
tribeSnapshotPub: tribeSnapshotPublisher,
playerSnapshotPub: playerSnapshotPublisher,
serverSnapshotPub: serverSnapshotPub,
tribeSnapshotPub: tribeSnapshotPub,
playerSnapshotPub: playerSnapshotPub,
}
}
@ -48,6 +51,7 @@ func (svc *SnapshotService) Create(ctx context.Context) error {
date := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
if loopErr = errors.Join(
svc.publishServer(ctx, v, snapshotsCreatedAtLT, date),
svc.publishTribe(ctx, v, snapshotsCreatedAtLT, date),
svc.publishPlayer(ctx, v, snapshotsCreatedAtLT, date),
); loopErr != nil {
@ -58,23 +62,47 @@ func (svc *SnapshotService) Create(ctx context.Context) error {
return nil
}
func (svc *SnapshotService) publishServer(
ctx context.Context,
v domain.Version,
snapshotCreatedAtLT time.Time,
date time.Time,
) error {
params, err := svc.baseParams(v)
if err != nil {
return err
}
if err = params.SetSnapshotCreatedAtLT(domain.NullTime{
V: snapshotCreatedAtLT,
Valid: true,
}); err != nil {
return err
}
servers, err := svc.serverSvc.ListAll(ctx, params)
if err != nil {
return err
}
payloads, err := svc.toPayload(v, servers, date)
if err != nil {
return err
}
return svc.serverSnapshotPub.CmdCreate(ctx, payloads...)
}
func (svc *SnapshotService) publishTribe(
ctx context.Context,
v domain.Version,
snapshotsCreatedAtLT time.Time,
date time.Time,
) error {
params := domain.NewListServersParams()
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
params, err := svc.baseParams(v)
if err != nil {
return err
}
if err := params.SetOpen(domain.NullBool{
V: true,
Valid: true,
}); err != nil {
return err
}
if err := params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{
if err = params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{
V: snapshotsCreatedAtLT,
Valid: true,
}); err != nil {
@ -100,17 +128,11 @@ func (svc *SnapshotService) publishPlayer(
snapshotsCreatedAtLT time.Time,
date time.Time,
) error {
params := domain.NewListServersParams()
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
params, err := svc.baseParams(v)
if err != nil {
return err
}
if err := params.SetOpen(domain.NullBool{
V: true,
Valid: true,
}); err != nil {
return err
}
if err := params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{
if err = params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{
V: snapshotsCreatedAtLT,
Valid: true,
}); err != nil {
@ -130,6 +152,20 @@ func (svc *SnapshotService) publishPlayer(
return svc.playerSnapshotPub.CmdCreate(ctx, payloads...)
}
func (svc *SnapshotService) baseParams(v domain.Version) (domain.ListServersParams, error) {
params := domain.NewListServersParams()
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
return domain.ListServersParams{}, err
}
if err := params.SetOpen(domain.NullBool{
V: true,
Valid: true,
}); err != nil {
return domain.ListServersParams{}, nil
}
return params, nil
}
func (svc *SnapshotService) toPayload(
v domain.Version,
servers domain.Servers,

View File

@ -11,6 +11,7 @@ type TribeRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error
List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error)
Count(ctx context.Context, params domain.CountTribesParams) (int, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
//
// https://en.wiktionary.org/wiki/soft_deletion
@ -44,10 +45,21 @@ func (svc *TribeService) Sync(ctx context.Context, serverSyncedPayload domain.Se
return fmt.Errorf("%s: couldn't delete tribes: %w", serverKey, err)
}
countParams := domain.NewCountTribesParams()
if err = countParams.SetServerKeys([]string{serverKey}); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
numTribes, err := svc.repo.Count(ctx, countParams)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
payload, err := domain.NewTribesSyncedEventPayload(
serverKey,
serverURL,
serverSyncedPayload.VersionCode(),
numTribes,
len(tribes),
)
if err != nil {
@ -94,7 +106,7 @@ func (svc *TribeService) createOrUpdateChunk(ctx context.Context, serverKey stri
if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
if err := listParams.SetLimit(len(ids)); err != nil {
return err
}

View File

@ -17,7 +17,11 @@ type Server struct {
Open bool `bun:"open"`
Special bool `bun:"special"`
NumPlayers int `bun:"num_players"`
NumActivePlayers int `bun:"num_active_players"`
NumInactivePlayers int `bun:"num_inactive_players"`
NumTribes int `bun:"num_tribes"`
NumActiveTribes int `bun:"num_active_tribes"`
NumInactiveTribes int `bun:"num_inactive_tribes"`
NumVillages int `bun:"num_villages"`
NumPlayerVillages int `bun:"num_player_villages"`
NumBarbarianVillages int `bun:"num_barbarian_villages"`
@ -26,16 +30,13 @@ type Server struct {
BuildingInfo BuildingInfo `bun:"building_info"`
UnitInfo UnitInfo `bun:"unit_info"`
CreatedAt time.Time `bun:"created_at,nullzero"`
// TODO: rename this column to player_data_synced_at
PlayerDataUpdatedAt time.Time `bun:"player_data_updated_at,nullzero"`
SnapshotCreatedAt time.Time `bun:"snapshot_created_at,nullzero"`
PlayerDataUpdatedAt time.Time `bun:"player_data_synced_at,nullzero"`
PlayerSnapshotsCreatedAt time.Time `bun:"player_snapshots_created_at,nullzero"`
// TODO: rename this column to tribe_data_synced_at
TribeDataUpdatedAt time.Time `bun:"tribe_data_updated_at,nullzero"`
TribeDataUpdatedAt time.Time `bun:"tribe_data_synced_at,nullzero"`
TribeSnapshotsCreatedAt time.Time `bun:"tribe_snapshots_created_at,nullzero"`
// TODO: rename this column to village_data_synced_at
VillageDataUpdatedAt time.Time `bun:"village_data_updated_at,nullzero"`
// TODO: rename this column to ennoblement_data_synced_at
EnnoblementDataUpdatedAt time.Time `bun:"ennoblement_data_updated_at,nullzero"`
VillageDataUpdatedAt time.Time `bun:"village_data_synced_at,nullzero"`
EnnoblementDataUpdatedAt time.Time `bun:"ennoblement_data_synced_at,nullzero"`
VersionCode string `bun:"version_code,nullzero"`
}
@ -62,7 +63,11 @@ func (s Server) ToDomain() (domain.Server, error) {
s.Open,
s.Special,
s.NumPlayers,
s.NumActivePlayers,
s.NumInactivePlayers,
s.NumTribes,
s.NumActiveTribes,
s.NumInactiveTribes,
s.NumVillages,
s.NumPlayerVillages,
s.NumBarbarianVillages,
@ -71,6 +76,7 @@ func (s Server) ToDomain() (domain.Server, error) {
buildingInfo,
unitInfo,
s.CreatedAt,
s.SnapshotCreatedAt,
s.PlayerDataUpdatedAt,
s.PlayerSnapshotsCreatedAt,
s.TribeDataUpdatedAt,
@ -88,7 +94,7 @@ func (s Server) ToDomain() (domain.Server, error) {
func (s Server) ToMeta() (domain.ServerMeta, error) {
converted, err := domain.UnmarshalServerMetaFromDatabase(s.Key, s.URL, s.Open)
if err != nil {
return domain.ServerMeta{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
return domain.ServerMeta{}, fmt.Errorf("couldn't construct domain.ServerMeta (key=%s): %w", s.Key, err)
}
return converted, nil
}

View File

@ -0,0 +1,81 @@
package bunmodel
import (
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"github.com/uptrace/bun"
)
type ServerSnapshot struct {
bun.BaseModel `bun:"table:server_snapshots,alias:ss"`
ID int `bun:"id,pk,autoincrement,identity"`
ServerKey string `bun:"server_key,nullzero"`
Server Server `bun:"server,rel:belongs-to,join:server_key=key"`
NumPlayers int `bun:"num_players"`
NumActivePlayers int `bun:"num_active_players"`
NumInactivePlayers int `bun:"num_inactive_players"`
NumTribes int `bun:"num_tribes"`
NumActiveTribes int `bun:"num_active_tribes"`
NumInactiveTribes int `bun:"num_inactive_tribes"`
NumVillages int `bun:"num_villages"`
NumPlayerVillages int `bun:"num_player_villages"`
NumBarbarianVillages int `bun:"num_barbarian_villages"`
NumBonusVillages int `bun:"num_bonus_villages"`
Date time.Time `bun:"date,nullzero"`
CreatedAt time.Time `bun:"created_at,nullzero"`
}
func (ss ServerSnapshot) ToDomain() (domain.ServerSnapshot, error) {
converted, err := domain.UnmarshalServerSnapshotFromDatabase(
ss.ID,
ss.ServerKey,
ss.NumPlayers,
ss.NumActivePlayers,
ss.NumInactivePlayers,
ss.NumTribes,
ss.NumActiveTribes,
ss.NumInactiveTribes,
ss.NumVillages,
ss.NumPlayerVillages,
ss.NumBarbarianVillages,
ss.NumBonusVillages,
ss.Date,
ss.CreatedAt,
)
if err != nil {
return domain.ServerSnapshot{}, fmt.Errorf(
"couldn't construct domain.ServerSnapshot (id=%d): %w",
ss.ID,
err,
)
}
return converted, nil
}
func (ss ServerSnapshot) ToDomainWithRelations() (domain.ServerSnapshotWithRelations, error) {
converted, err := ss.ToDomain()
if err != nil {
return domain.ServerSnapshotWithRelations{}, err
}
server, err := ss.Server.ToMeta()
if err != nil {
return domain.ServerSnapshotWithRelations{}, err
}
return converted.WithRelations(server), nil
}
type ServerSnapshots []ServerSnapshot
func (sss ServerSnapshots) ToDomain() (domain.ServerSnapshots, error) {
return sliceToDomain(sss)
}
func (sss ServerSnapshots) ToDomainWithRelations() (domain.ServerSnapshotsWithRelations, error) {
return sliceToDomainWithRelations(sss)
}

View File

@ -23,6 +23,7 @@ func NewFixture(bunDB *bun.DB) *Fixture {
(*bunmodel.Village)(nil),
(*bunmodel.Ennoblement)(nil),
(*bunmodel.TribeChange)(nil),
(*bunmodel.ServerSnapshot)(nil),
(*bunmodel.TribeSnapshot)(nil),
(*bunmodel.PlayerSnapshot)(nil),
)

View File

@ -0,0 +1,44 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for oldColumn, newColumn := range getColumnsToRename20240504051104() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers RENAME COLUMN ? TO ?", bun.Safe(oldColumn), bun.Safe(newColumn))
if err != nil {
return fmt.Errorf("%s: %w", oldColumn, err)
}
}
return nil
})
}, func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for oldColumn, newColumn := range getColumnsToRename20240504051104() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers RENAME COLUMN ? TO ?", bun.Safe(newColumn), bun.Safe(oldColumn))
if err != nil {
return fmt.Errorf("%s: %w", newColumn, err)
}
}
return nil
})
})
}
func getColumnsToRename20240504051104() map[string]string {
return map[string]string{
"player_data_updated_at": "player_data_synced_at",
"tribe_data_updated_at": "tribe_data_synced_at",
"village_data_updated_at": "village_data_synced_at",
"ennoblement_data_updated_at": "ennoblement_data_synced_at",
}
}

View File

@ -0,0 +1,42 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for oldColumn, newColumn := range getColumnsToRename20240506043013() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers RENAME COLUMN ? TO ?", bun.Safe(oldColumn), bun.Safe(newColumn))
if err != nil {
return fmt.Errorf("%s: %w", oldColumn, err)
}
}
return nil
})
}, func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for oldColumn, newColumn := range getColumnsToRename20240506043013() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers RENAME COLUMN ? TO ?", bun.Safe(newColumn), bun.Safe(oldColumn))
if err != nil {
return fmt.Errorf("%s: %w", newColumn, err)
}
}
return nil
})
})
}
func getColumnsToRename20240506043013() map[string]string {
return map[string]string{
"num_players": "num_active_players",
"num_tribes": "num_active_tribes",
}
}

View File

@ -0,0 +1,44 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for _, column := range getColumnsToAdd20240506051128() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers ADD ? bigint DEFAULT 0", bun.Safe(column))
if err != nil {
return fmt.Errorf("%s: %w", column, err)
}
}
return nil
})
}, func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for _, column := range getColumnsToAdd20240506051128() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers DROP COLUMN ?", bun.Safe(column))
if err != nil {
return fmt.Errorf("%s: %w", column, err)
}
}
return nil
})
})
}
func getColumnsToAdd20240506051128() []string {
return []string{
"num_players",
"num_inactive_players",
"num_tribes",
"num_inactive_tribes",
}
}

View File

@ -0,0 +1,52 @@
package migrations
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/bun/bunmodel"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
var servers bunmodel.Servers
if err := db.NewSelect().
Model(&servers).
ExcludeColumn("snapshot_created_at").
Where("special = false").
Scan(ctx); err != nil {
return fmt.Errorf("couldn't select servers from the db: %w", err)
}
for _, s := range servers {
numPlayers, err := db.NewSelect().Model((*bunmodel.Player)(nil)).Where("server_key = ?", s.Key).Count(ctx)
if err != nil {
return fmt.Errorf("%s: numPlayers: %w", s.Key, err)
}
numTribes, err := db.NewSelect().Model((*bunmodel.Tribe)(nil)).Where("server_key = ?", s.Key).Count(ctx)
if err != nil {
return fmt.Errorf("%s: numTribes: %w", s.Key, err)
}
_, err = db.NewUpdate().
Model((*bunmodel.Server)(nil)).
Returning("NULL").
Where("key = ?", s.Key).
Set("num_players = ?", numPlayers).
Set("num_inactive_players = ?", numPlayers-s.NumActivePlayers).
Set("num_tribes = ?", numTribes).
Set("num_inactive_tribes = ?", numTribes-s.NumActiveTribes).
Exec(ctx)
if err != nil {
return fmt.Errorf("%s: %w", s.Key, err)
}
}
return nil
}, func(_ context.Context, _ *bun.DB) error {
return nil
})
}

View File

@ -0,0 +1,17 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE servers ADD snapshot_created_at timestamp with time zone")
return err
}, func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE servers DROP COLUMN snapshot_created_at")
return err
})
}

View File

@ -0,0 +1,37 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, `
create table if not exists server_snapshots
(
?ID_COL,
server_key varchar(100) not null
references servers,
date date not null,
created_at timestamp with time zone default CURRENT_TIMESTAMP not null,
num_players bigint default 0,
num_active_players bigint default 0,
num_inactive_players bigint default 0,
num_tribes bigint default 0,
num_active_tribes bigint default 0,
num_inactive_tribes bigint default 0,
num_villages bigint default 0,
num_player_villages bigint default 0,
num_barbarian_villages bigint default 0,
num_bonus_villages bigint default 0,
unique (server_key, date)
);
`)
return err
}, func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "drop table if exists server_snapshots cascade;")
return err
})
}

View File

@ -47,7 +47,7 @@ func Logger(logger Slogger, opts ...Option) func(next http.Handler) http.Handler
slog.String("httpRequest.ip", cfg.ipExtractor(r)),
slog.Int("httpResponse.status", status),
slog.Int("httpResponse.bytes", ww.BytesWritten()),
//nolint:gomnd
//nolint:mnd
slog.Float64("httpResponse.duration", float64(end.Sub(start).Nanoseconds())/1000000.0), // in milliseconds
)
})

View File

@ -26,7 +26,7 @@ func NewBaseEnnoblement(
points int,
createdAt time.Time,
) (BaseEnnoblement, error) {
if err := validateIntInRange(villageID, 1, math.MaxInt); err != nil {
if err := validateInRange(villageID, 1, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "villageID",
@ -34,7 +34,7 @@ func NewBaseEnnoblement(
}
}
if err := validateIntInRange(newOwnerID, 0, math.MaxInt); err != nil {
if err := validateInRange(newOwnerID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "newOwnerID",
@ -42,7 +42,7 @@ func NewBaseEnnoblement(
}
}
if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
if err := validateInRange(newTribeID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "newTribeID",
@ -50,7 +50,7 @@ func NewBaseEnnoblement(
}
}
if err := validateIntInRange(oldOwnerID, 0, math.MaxInt); err != nil {
if err := validateInRange(oldOwnerID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "oldOwnerID",
@ -58,7 +58,7 @@ func NewBaseEnnoblement(
}
}
if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
if err := validateInRange(oldTribeID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "oldTribeID",
@ -66,7 +66,7 @@ func NewBaseEnnoblement(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "points",

View File

@ -56,7 +56,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "villageID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -76,7 +76,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "newOwnerID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -96,7 +96,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "newTribeID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -116,7 +116,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "oldOwnerID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -136,7 +136,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "oldTribeID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -156,7 +156,7 @@ func TestNewBaseEnnoblement(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},

View File

@ -28,7 +28,7 @@ func NewBasePlayer(
od OpponentsDefeated,
profileURL *url.URL,
) (BasePlayer, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return BasePlayer{}, ValidationError{
Model: basePlayerModelName,
Field: "id",
@ -44,7 +44,7 @@ func NewBasePlayer(
}
}
if err := validateIntInRange(numVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numVillages, 0, math.MaxInt); err != nil {
return BasePlayer{}, ValidationError{
Model: basePlayerModelName,
Field: "numVillages",
@ -52,7 +52,7 @@ func NewBasePlayer(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return BasePlayer{}, ValidationError{
Model: basePlayerModelName,
Field: "points",
@ -60,7 +60,7 @@ func NewBasePlayer(
}
}
if err := validateIntInRange(rank, 0, math.MaxInt); err != nil {
if err := validateInRange(rank, 0, math.MaxInt); err != nil {
return BasePlayer{}, ValidationError{
Model: basePlayerModelName,
Field: "rank",
@ -68,7 +68,7 @@ func NewBasePlayer(
}
}
if err := validateIntInRange(tribeID, 0, math.MaxInt); err != nil {
if err := validateInRange(tribeID, 0, math.MaxInt); err != nil {
return BasePlayer{}, ValidationError{
Model: basePlayerModelName,
Field: "tribeID",

View File

@ -60,7 +60,7 @@ func TestNewBasePlayer(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BasePlayer",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -125,7 +125,7 @@ func TestNewBasePlayer(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BasePlayer",
Field: "numVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -146,7 +146,7 @@ func TestNewBasePlayer(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BasePlayer",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -167,7 +167,7 @@ func TestNewBasePlayer(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BasePlayer",
Field: "rank",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -188,7 +188,7 @@ func TestNewBasePlayer(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BasePlayer",
Field: "tribeID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},

View File

@ -33,7 +33,7 @@ func NewBaseTribe(
od OpponentsDefeated,
profileURL *url.URL,
) (BaseTribe, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "id",
@ -60,7 +60,7 @@ func NewBaseTribe(
}
}
if err := validateIntInRange(numMembers, 0, math.MaxInt); err != nil {
if err := validateInRange(numMembers, 0, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "numMembers",
@ -68,7 +68,7 @@ func NewBaseTribe(
}
}
if err := validateIntInRange(numVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numVillages, 0, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "numVillages",
@ -76,7 +76,7 @@ func NewBaseTribe(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "points",
@ -84,7 +84,7 @@ func NewBaseTribe(
}
}
if err := validateIntInRange(allPoints, 0, math.MaxInt); err != nil {
if err := validateInRange(allPoints, 0, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "allPoints",
@ -92,7 +92,7 @@ func NewBaseTribe(
}
}
if err := validateIntInRange(rank, 0, math.MaxInt); err != nil {
if err := validateInRange(rank, 0, math.MaxInt); err != nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "rank",

View File

@ -81,7 +81,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -200,7 +200,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "numMembers",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -223,7 +223,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "numVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -246,7 +246,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -269,7 +269,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "allPoints",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -292,7 +292,7 @@ func TestNewBaseTribe(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "rank",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},

View File

@ -29,7 +29,7 @@ func NewBaseVillage(
playerID int,
profileURL *url.URL,
) (BaseVillage, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "id",
@ -45,7 +45,7 @@ func NewBaseVillage(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "points",
@ -53,7 +53,7 @@ func NewBaseVillage(
}
}
if err := validateIntInRange(x, 0, math.MaxInt); err != nil {
if err := validateInRange(x, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "x",
@ -61,7 +61,7 @@ func NewBaseVillage(
}
}
if err := validateIntInRange(y, 0, math.MaxInt); err != nil {
if err := validateInRange(y, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "y",
@ -77,7 +77,7 @@ func NewBaseVillage(
}
}
if err := validateIntInRange(bonus, 0, math.MaxInt); err != nil {
if err := validateInRange(bonus, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "bonus",
@ -85,7 +85,7 @@ func NewBaseVillage(
}
}
if err := validateIntInRange(playerID, 0, math.MaxInt); err != nil {
if err := validateInRange(playerID, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{
Model: baseVillageModelName,
Field: "playerID",

View File

@ -63,7 +63,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -131,7 +131,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -153,7 +153,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "x",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -175,7 +175,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "y",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -243,7 +243,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "bonus",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -265,7 +265,7 @@ func TestNewBaseVillage(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "BaseVillage",
Field: "playerID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},

View File

@ -17,6 +17,7 @@ type PlayerCursorConfig struct {
ODScoreSup int
ODScoreTotal int
Points int
MostPoints int
DeletedAt time.Time
}
@ -31,6 +32,7 @@ func NewPlayerCursor(tb TestingTB, opts ...func(cfg *PlayerCursorConfig)) domain
ODScoreSup: gofakeit.IntRange(0, math.MaxInt),
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
Points: gofakeit.IntRange(0, math.MaxInt),
MostPoints: gofakeit.IntRange(0, math.MaxInt),
DeletedAt: time.Time{},
}
@ -46,6 +48,7 @@ func NewPlayerCursor(tb TestingTB, opts ...func(cfg *PlayerCursorConfig)) domain
cfg.ODScoreSup,
cfg.ODScoreTotal,
cfg.Points,
cfg.MostPoints,
cfg.DeletedAt,
)
require.NoError(tb, err)

View File

@ -83,16 +83,21 @@ func NewServer(tb TestingTB, opts ...func(cfg *ServerConfig)) domain.Server {
cfg.URL.String(),
cfg.Open,
cfg.Special,
0,
0,
0,
0,
0,
0,
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
gofakeit.IntRange(0, 100000),
NewServerConfig(tb),
NewBuildingInfo(tb),
NewUnitInfo(tb),
time.Now(),
time.Now(),
cfg.PlayerDataSyncedAt,
cfg.PlayerSnapshotsCreatedAt,
cfg.TribeDataSyncedAt,

View File

@ -0,0 +1,137 @@
package domaintest
import (
"time"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"github.com/brianvoe/gofakeit/v7"
"github.com/stretchr/testify/require"
)
type ServerSnapshotCursorConfig struct {
ID int
ServerKey string
Date time.Time
}
func NewServerSnapshotCursor(tb TestingTB, opts ...func(cfg *ServerSnapshotCursorConfig)) domain.ServerSnapshotCursor {
tb.Helper()
cfg := &ServerSnapshotCursorConfig{
ID: RandID(),
ServerKey: RandServerKey(),
Date: gofakeit.Date(),
}
for _, opt := range opts {
opt(cfg)
}
ssc, err := domain.NewServerSnapshotCursor(
cfg.ID,
cfg.ServerKey,
cfg.Date,
)
require.NoError(tb, err)
return ssc
}
type ServerSnapshotConfig struct {
ID int
ServerKey string
NumPlayers int
NumActivePlayers int
NumInactivePlayers int
NumTribes int
NumActiveTribes int
NumInactiveTribes int
NumVillages int
NumPlayerVillages int
NumBarbarianVillages int
NumBonusVillages int
Date time.Time
CreatedAt time.Time
}
func NewServerSnapshot(tb TestingTB, opts ...func(cfg *ServerSnapshotConfig)) domain.ServerSnapshot {
tb.Helper()
now := time.Now()
cfg := &ServerSnapshotConfig{
ID: RandID(),
ServerKey: RandServerKey(),
NumPlayers: gofakeit.IntRange(1, 10000),
NumActivePlayers: gofakeit.IntRange(1, 10000),
NumInactivePlayers: gofakeit.IntRange(1, 10000),
NumTribes: gofakeit.IntRange(1, 10000),
NumActiveTribes: gofakeit.IntRange(1, 10000),
NumInactiveTribes: gofakeit.IntRange(1, 10000),
NumVillages: gofakeit.IntRange(1, 10000),
NumPlayerVillages: gofakeit.IntRange(1, 10000),
NumBarbarianVillages: gofakeit.IntRange(1, 10000),
NumBonusVillages: gofakeit.IntRange(1, 10000),
Date: now,
CreatedAt: now,
}
for _, opt := range opts {
opt(cfg)
}
ss, err := domain.UnmarshalServerSnapshotFromDatabase(
cfg.ID,
cfg.ServerKey,
cfg.NumPlayers,
cfg.NumActivePlayers,
cfg.NumInactivePlayers,
cfg.NumTribes,
cfg.NumActiveTribes,
cfg.NumInactiveTribes,
cfg.NumVillages,
cfg.NumPlayerVillages,
cfg.NumBarbarianVillages,
cfg.NumBonusVillages,
cfg.Date,
cfg.CreatedAt,
)
require.NoError(tb, err)
return ss
}
type ServerSnapshotWithRelationsConfig struct {
ServerSnapshotOptions []func(cfg *ServerSnapshotConfig)
ServerOptions []func(cfg *ServerConfig)
}
func NewServerSnapshotWithRelations(
tb TestingTB,
opts ...func(cfg *ServerSnapshotWithRelationsConfig),
) domain.ServerSnapshotWithRelations {
tb.Helper()
cfg := &ServerSnapshotWithRelationsConfig{}
for _, opt := range opts {
opt(cfg)
}
ss := NewServerSnapshot(tb, cfg.ServerSnapshotOptions...)
if ss.ServerKey() != "" {
cfg.ServerOptions = append([]func(cfg *ServerConfig){
func(cfg *ServerConfig) {
cfg.Key = ss.ServerKey()
},
}, cfg.ServerOptions...)
}
var tribe domain.ServerMeta
if len(cfg.ServerOptions) > 0 {
tribe = NewServer(tb, cfg.ServerOptions...).Meta()
}
return ss.WithRelations(tribe)
}

View File

@ -20,6 +20,7 @@ type TribeCursorConfig struct {
ODScoreDef int
ODScoreTotal int
Points int
MostPoints int
Dominance float64
DeletedAt time.Time
}
@ -34,6 +35,7 @@ func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.T
ODScoreDef: gofakeit.IntRange(0, math.MaxInt),
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
Points: gofakeit.IntRange(0, math.MaxInt),
MostPoints: gofakeit.IntRange(0, math.MaxInt),
Dominance: gofakeit.Float64Range(0.1, 99.9),
DeletedAt: time.Time{},
}
@ -49,6 +51,7 @@ func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.T
cfg.ODScoreDef,
cfg.ODScoreTotal,
cfg.Points,
cfg.MostPoints,
cfg.Dominance,
cfg.DeletedAt,
)

View File

@ -27,14 +27,14 @@ func NewTribeSnapshotCursor(tb TestingTB, opts ...func(cfg *TribeSnapshotCursorC
opt(cfg)
}
psc, err := domain.NewTribeSnapshotCursor(
tsc, err := domain.NewTribeSnapshotCursor(
cfg.ID,
cfg.ServerKey,
cfg.Date,
)
require.NoError(tb, err)
return psc
return tsc
}
type TribeSnapshotConfig struct {

View File

@ -6,6 +6,10 @@ import (
"github.com/stretchr/testify/require"
)
func RandVersionCode() string {
return gofakeit.LetterN(2)
}
type VersionCursorConfig struct {
Code string
}
@ -52,7 +56,3 @@ func NewVersion(tb TestingTB, opts ...func(cfg *VersionConfig)) domain.Version {
return v
}
func RandVersionCode() string {
return gofakeit.LetterN(2)
}

View File

@ -37,7 +37,7 @@ func UnmarshalEnnoblementFromDatabase(
points int,
createdAt time.Time,
) (Ennoblement, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return Ennoblement{}, ValidationError{
Model: ennoblementModelName,
Field: "id",
@ -265,7 +265,7 @@ type EnnoblementCursor struct {
const ennoblementCursorModelName = "EnnoblementCursor"
func NewEnnoblementCursor(id int, serverKey string, createdAt time.Time) (EnnoblementCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return EnnoblementCursor{}, ValidationError{
Model: ennoblementCursorModelName,
Field: "id",
@ -405,7 +405,7 @@ func (params *ListEnnoblementsParams) VillageIDs() []int {
func (params *ListEnnoblementsParams) SetVillageIDs(villageIDs []int) error {
for i, id := range villageIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listEnnoblementsParamsModelName,
Field: "villageIDs",
@ -426,7 +426,7 @@ func (params *ListEnnoblementsParams) PlayerIDs() []int {
func (params *ListEnnoblementsParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listEnnoblementsParamsModelName,
Field: "playerIDs",
@ -447,7 +447,7 @@ func (params *ListEnnoblementsParams) TribeIDs() []int {
func (params *ListEnnoblementsParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listEnnoblementsParamsModelName,
Field: "tribeIDs",
@ -579,7 +579,7 @@ func (params *ListEnnoblementsParams) Limit() int {
}
func (params *ListEnnoblementsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, EnnoblementListMaxLimit); err != nil {
if err := validateInRange(limit, 1, EnnoblementListMaxLimit); err != nil {
return ValidationError{
Model: listEnnoblementsParamsModelName,
Field: "limit",

View File

@ -141,7 +141,7 @@ func TestNewEnnoblementCursor(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "EnnoblementCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -276,7 +276,7 @@ func TestListEnnoblementsParams_SetVillageIDs(t *testing.T) {
Model: "ListEnnoblementsParams",
Field: "villageIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -336,7 +336,7 @@ func TestListEnnoblementsParams_SetPlayerIDs(t *testing.T) {
Model: "ListEnnoblementsParams",
Field: "playerIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -396,7 +396,7 @@ func TestListEnnoblementsParams_SetTribeIDs(t *testing.T) {
Model: "ListEnnoblementsParams",
Field: "tribeIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -957,7 +957,7 @@ func TestListEnnoblementsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -971,7 +971,7 @@ func TestListEnnoblementsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.EnnoblementListMaxLimit,
Current: domain.EnnoblementListMaxLimit + 1,
},

View File

@ -113,6 +113,7 @@ type TribesSyncedEventPayload struct {
serverURL *url.URL
versionCode string
numTribes int
numActiveTribes int
}
const tribesSyncedEventPayloadModelName = "TribesSyncedEventPayload"
@ -122,6 +123,7 @@ func NewTribesSyncedEventPayload(
serverURL *url.URL,
versionCode string,
numTribes int,
numActiveTribes int,
) (TribesSyncedEventPayload, error) {
if serverKey == "" {
return TribesSyncedEventPayload{}, ValidationError{
@ -147,7 +149,7 @@ func NewTribesSyncedEventPayload(
}
}
if err := validateIntInRange(numTribes, 0, math.MaxInt); err != nil {
if err := validateInRange(numTribes, 0, math.MaxInt); err != nil {
return TribesSyncedEventPayload{}, ValidationError{
Model: tribesSyncedEventPayloadModelName,
Field: "numTribes",
@ -155,11 +157,20 @@ func NewTribesSyncedEventPayload(
}
}
if err := validateInRange(numActiveTribes, 0, math.MaxInt); err != nil {
return TribesSyncedEventPayload{}, ValidationError{
Model: tribesSyncedEventPayloadModelName,
Field: "numActiveTribes",
Err: err,
}
}
return TribesSyncedEventPayload{
serverKey: serverKey,
serverURL: serverURL,
versionCode: versionCode,
numTribes: numTribes,
numActiveTribes: numActiveTribes,
}, nil
}
@ -179,11 +190,20 @@ func (p TribesSyncedEventPayload) NumTribes() int {
return p.numTribes
}
func (p TribesSyncedEventPayload) NumActiveTribes() int {
return p.numActiveTribes
}
func (p TribesSyncedEventPayload) NumInactiveTribes() int {
return p.numTribes - p.numActiveTribes
}
type PlayersSyncedEventPayload struct {
serverKey string
serverURL *url.URL
versionCode string
numPlayers int
numActivePlayers int
}
const playersSyncedEventPayloadModelName = "PlayersSyncedEventPayload"
@ -193,6 +213,7 @@ func NewPlayersSyncedEventPayload(
serverURL *url.URL,
versionCode string,
numPlayers int,
numActivePlayers int,
) (PlayersSyncedEventPayload, error) {
if serverKey == "" {
return PlayersSyncedEventPayload{}, ValidationError{
@ -218,7 +239,7 @@ func NewPlayersSyncedEventPayload(
}
}
if err := validateIntInRange(numPlayers, 0, math.MaxInt); err != nil {
if err := validateInRange(numPlayers, 0, math.MaxInt); err != nil {
return PlayersSyncedEventPayload{}, ValidationError{
Model: playersSyncedEventPayloadModelName,
Field: "numPlayers",
@ -226,11 +247,20 @@ func NewPlayersSyncedEventPayload(
}
}
if err := validateInRange(numActivePlayers, 0, math.MaxInt); err != nil {
return PlayersSyncedEventPayload{}, ValidationError{
Model: playersSyncedEventPayloadModelName,
Field: "numActivePlayers",
Err: err,
}
}
return PlayersSyncedEventPayload{
serverKey: serverKey,
serverURL: serverURL,
versionCode: versionCode,
numPlayers: numPlayers,
numActivePlayers: numActivePlayers,
}, nil
}
@ -250,6 +280,14 @@ func (p PlayersSyncedEventPayload) NumPlayers() int {
return p.numPlayers
}
func (p PlayersSyncedEventPayload) NumActivePlayers() int {
return p.numActivePlayers
}
func (p PlayersSyncedEventPayload) NumInactivePlayers() int {
return p.numPlayers - p.numActivePlayers
}
type VillagesSyncedEventPayload struct {
serverKey string
serverURL *url.URL
@ -295,7 +333,7 @@ func NewVillagesSyncedEventPayload(
}
}
if err := validateIntInRange(numVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numVillages, 0, math.MaxInt); err != nil {
return VillagesSyncedEventPayload{}, ValidationError{
Model: villagesSyncedEventPayloadModelName,
Field: "numVillages",
@ -303,7 +341,7 @@ func NewVillagesSyncedEventPayload(
}
}
if err := validateIntInRange(numPlayerVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numPlayerVillages, 0, math.MaxInt); err != nil {
return VillagesSyncedEventPayload{}, ValidationError{
Model: villagesSyncedEventPayloadModelName,
Field: "numPlayerVillages",
@ -311,7 +349,7 @@ func NewVillagesSyncedEventPayload(
}
}
if err := validateIntInRange(numBarbarianVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numBarbarianVillages, 0, math.MaxInt); err != nil {
return VillagesSyncedEventPayload{}, ValidationError{
Model: villagesSyncedEventPayloadModelName,
Field: "numBarbarianVillages",
@ -319,7 +357,7 @@ func NewVillagesSyncedEventPayload(
}
}
if err := validateIntInRange(numBonusVillages, 0, math.MaxInt); err != nil {
if err := validateInRange(numBonusVillages, 0, math.MaxInt); err != nil {
return VillagesSyncedEventPayload{}, ValidationError{
Model: villagesSyncedEventPayloadModelName,
Field: "numBonusVillages",

View File

@ -59,18 +59,22 @@ func TestNewTribesSyncedEventPayload(t *testing.T) {
server := domaintest.NewServer(t)
numTribes := gofakeit.IntRange(0, math.MaxInt)
numActiveTribes := gofakeit.IntRange(0, math.MaxInt)
payload, err := domain.NewTribesSyncedEventPayload(
server.Key(),
server.URL(),
server.VersionCode(),
numTribes,
numActiveTribes,
)
require.NoError(t, err)
assert.Equal(t, server.Key(), payload.ServerKey())
assert.Equal(t, server.URL(), payload.ServerURL())
assert.Equal(t, server.VersionCode(), payload.VersionCode())
assert.Equal(t, numTribes, payload.NumTribes())
assert.Equal(t, numActiveTribes, payload.NumActiveTribes())
assert.Equal(t, numTribes-numActiveTribes, payload.NumInactiveTribes())
}
func TestNewPlayersSyncedEventPayload(t *testing.T) {
@ -78,18 +82,22 @@ func TestNewPlayersSyncedEventPayload(t *testing.T) {
server := domaintest.NewServer(t)
numPlayers := gofakeit.IntRange(0, math.MaxInt)
numActivePlayers := gofakeit.IntRange(0, math.MaxInt)
payload, err := domain.NewPlayersSyncedEventPayload(
server.Key(),
server.URL(),
server.VersionCode(),
numPlayers,
numActivePlayers,
)
require.NoError(t, err)
assert.Equal(t, server.Key(), payload.ServerKey())
assert.Equal(t, server.URL(), payload.ServerURL())
assert.Equal(t, server.VersionCode(), payload.VersionCode())
assert.Equal(t, numPlayers, payload.NumPlayers())
assert.Equal(t, numActivePlayers, payload.NumActivePlayers())
assert.Equal(t, numPlayers-numActivePlayers, payload.NumInactivePlayers())
}
func TestNewVillagesSyncedEventPayload(t *testing.T) {

View File

@ -25,7 +25,7 @@ func NewOpponentsDefeated(
rankTotal int,
scoreTotal int,
) (OpponentsDefeated, error) {
if err := validateIntInRange(rankAtt, 0, math.MaxInt); err != nil {
if err := validateInRange(rankAtt, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankAtt",
@ -33,7 +33,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(scoreAtt, 0, math.MaxInt); err != nil {
if err := validateInRange(scoreAtt, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreAtt",
@ -41,7 +41,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(rankDef, 0, math.MaxInt); err != nil {
if err := validateInRange(rankDef, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankDef",
@ -49,7 +49,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(scoreDef, 0, math.MaxInt); err != nil {
if err := validateInRange(scoreDef, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreDef",
@ -57,7 +57,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(rankSup, 0, math.MaxInt); err != nil {
if err := validateInRange(rankSup, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankSup",
@ -65,7 +65,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(scoreSup, 0, math.MaxInt); err != nil {
if err := validateInRange(scoreSup, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreSup",
@ -73,7 +73,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(rankTotal, 0, math.MaxInt); err != nil {
if err := validateInRange(rankTotal, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankTotal",
@ -81,7 +81,7 @@ func NewOpponentsDefeated(
}
}
if err := validateIntInRange(scoreTotal, 0, math.MaxInt); err != nil {
if err := validateInRange(scoreTotal, 0, math.MaxInt); err != nil {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreTotal",

View File

@ -58,7 +58,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankAtt",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -79,7 +79,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreAtt",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -100,7 +100,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankDef",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -121,7 +121,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreDef",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -142,7 +142,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankSup",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -163,7 +163,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreSup",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -184,7 +184,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankTotal",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -205,7 +205,7 @@ func TestNewOpponentsDefeated(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreTotal",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},

View File

@ -61,7 +61,7 @@ func UnmarshalPlayerFromDatabase(
createdAt time.Time,
deletedAt time.Time,
) (Player, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return Player{}, ValidationError{
Model: playerModelName,
Field: "id",
@ -200,6 +200,7 @@ func (p Player) ToCursor() (PlayerCursor, error) {
p.od.scoreSup,
p.od.scoreTotal,
p.points,
p.mostPoints,
p.deletedAt,
)
}
@ -326,7 +327,7 @@ const playerMetaModelName = "PlayerMeta"
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalPlayerMetaFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalPlayerMetaFromDatabase(id int, name string, rawProfileURL string) (PlayerMeta, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return PlayerMeta{}, ValidationError{
Model: playerMetaModelName,
Field: "id",
@ -552,6 +553,8 @@ const (
PlayerSortODScoreTotalDESC
PlayerSortPointsASC
PlayerSortPointsDESC
PlayerSortMostPointsASC
PlayerSortMostPointsDESC
PlayerSortDeletedAtASC
PlayerSortDeletedAtDESC
)
@ -592,6 +595,10 @@ func (s PlayerSort) String() string {
return "points:ASC"
case PlayerSortPointsDESC:
return "points:DESC"
case PlayerSortMostPointsASC:
return "mostPoints:ASC"
case PlayerSortMostPointsDESC:
return "mostPoints:DESC"
case PlayerSortDeletedAtASC:
return "deletedAt:ASC"
case PlayerSortDeletedAtDESC:
@ -609,6 +616,7 @@ type PlayerCursor struct {
odScoreSup int
odScoreTotal int
points int
mostPoints int
deletedAt time.Time
}
@ -622,9 +630,10 @@ func NewPlayerCursor(
odScoreSup int,
odScoreTotal int,
points int,
mostPoints int,
deletedAt time.Time,
) (PlayerCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "id",
@ -640,7 +649,7 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(odScoreAtt, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreAtt, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreAtt",
@ -648,7 +657,7 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(odScoreDef, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreDef, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreDef",
@ -656,7 +665,7 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(odScoreSup, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreSup, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreSup",
@ -664,7 +673,7 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(odScoreTotal, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreTotal, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreTotal",
@ -672,7 +681,7 @@ func NewPlayerCursor(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "points",
@ -680,6 +689,14 @@ func NewPlayerCursor(
}
}
if err := validateInRange(mostPoints, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "mostPoints",
Err: err,
}
}
return PlayerCursor{
id: id,
serverKey: serverKey,
@ -688,6 +705,7 @@ func NewPlayerCursor(
odScoreSup: odScoreSup,
odScoreTotal: odScoreTotal,
points: points,
mostPoints: mostPoints,
deletedAt: deletedAt,
}, nil
}
@ -734,6 +752,11 @@ func decodePlayerCursor(encoded string) (PlayerCursor, error) {
return PlayerCursor{}, ErrInvalidCursor
}
mostPoints, err := m.int("mostPoints")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
deletedAt, err := m.time("deletedAt")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
@ -747,6 +770,7 @@ func decodePlayerCursor(encoded string) (PlayerCursor, error) {
odScoreSup,
odScoreTotal,
points,
mostPoints,
deletedAt,
)
if err != nil {
@ -784,6 +808,10 @@ func (pc PlayerCursor) Points() int {
return pc.points
}
func (pc PlayerCursor) MostPoints() int {
return pc.mostPoints
}
func (pc PlayerCursor) DeletedAt() time.Time {
return pc.deletedAt
}
@ -805,6 +833,7 @@ func (pc PlayerCursor) Encode() string {
{"odScoreSup", pc.odScoreSup},
{"odScoreTotal", pc.odScoreTotal},
{"points", pc.points},
{"mostPoints", pc.mostPoints},
{"deletedAt", pc.deletedAt},
})
}
@ -842,7 +871,7 @@ func (params *ListPlayersParams) IDs() []int {
func (params *ListPlayersParams) SetIDs(ids []int) error {
for i, id := range ids {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listPlayersParamsModelName,
Field: "ids",
@ -939,7 +968,7 @@ func (params *ListPlayersParams) TribeIDs() []int {
func (params *ListPlayersParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listPlayersParamsModelName,
Field: "tribeIDs",
@ -1062,7 +1091,7 @@ func (params *ListPlayersParams) Limit() int {
}
func (params *ListPlayersParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, PlayerListMaxLimit); err != nil {
if err := validateInRange(limit, 1, PlayerListMaxLimit); err != nil {
return ValidationError{
Model: listPlayersParamsModelName,
Field: "limit",
@ -1180,6 +1209,37 @@ func (res ListPlayersWithRelationsResult) Next() PlayerCursor {
return res.next
}
type CountPlayersParams struct {
serverKeys []string
}
const countPlayersParamsModelName = "CountPlayersParams"
func NewCountPlayersParams() CountPlayersParams {
return CountPlayersParams{}
}
func (params *CountPlayersParams) ServerKeys() []string {
return params.serverKeys
}
func (params *CountPlayersParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: countPlayersParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
type PlayerNotFoundError struct {
ID int
ServerKey string

View File

@ -39,7 +39,7 @@ func UnmarshalPlayerSnapshotFromDatabase(
date time.Time,
createdAt time.Time,
) (PlayerSnapshot, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return PlayerSnapshot{}, ValidationError{
Model: playerSnapshotModelName,
Field: "id",
@ -47,7 +47,7 @@ func UnmarshalPlayerSnapshotFromDatabase(
}
}
if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
if err := validateInRange(playerID, 1, math.MaxInt); err != nil {
return PlayerSnapshot{}, ValidationError{
Model: playerSnapshotModelName,
Field: "playerID",
@ -275,7 +275,7 @@ type PlayerSnapshotCursor struct {
const playerSnapshotCursorModelName = "PlayerSnapshotCursor"
func NewPlayerSnapshotCursor(id int, serverKey string, date time.Time) (PlayerSnapshotCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return PlayerSnapshotCursor{}, ValidationError{
Model: playerSnapshotCursorModelName,
Field: "id",
@ -411,7 +411,7 @@ func (params *ListPlayerSnapshotsParams) PlayerIDs() []int {
func (params *ListPlayerSnapshotsParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "playerIDs",
@ -529,7 +529,7 @@ func (params *ListPlayerSnapshotsParams) Limit() int {
}
func (params *ListPlayerSnapshotsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, PlayerSnapshotListMaxLimit); err != nil {
if err := validateInRange(limit, 1, PlayerSnapshotListMaxLimit); err != nil {
return ValidationError{
Model: listPlayerSnapshotsParamsModelName,
Field: "limit",

View File

@ -147,7 +147,7 @@ func TestNewPlayerSnapshotCursor(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "PlayerSnapshotCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -282,7 +282,7 @@ func TestListPlayerSnapshotsParams_SetPlayerIDs(t *testing.T) {
Model: "ListPlayerSnapshotsParams",
Field: "playerIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -840,7 +840,7 @@ func TestListPlayerSnapshotsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListPlayerSnapshotsParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -854,7 +854,7 @@ func TestListPlayerSnapshotsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListPlayerSnapshotsParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.PlayerSnapshotListMaxLimit,
Current: domain.PlayerSnapshotListMaxLimit + 1,
},

View File

@ -303,6 +303,13 @@ func TestPlayerSort_IsInConflict(t *testing.T) {
},
expectedRes: true,
},
{
name: "OK: mostPoints:ASC mostPoints:DESC",
args: args{
sorts: [2]domain.PlayerSort{domain.PlayerSortMostPointsASC, domain.PlayerSortMostPointsDESC},
},
expectedRes: true,
},
{
name: "OK: deletedAt:ASC deletedAt:DESC",
args: args{
@ -334,6 +341,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup int
odScoreTotal int
points int
mostPoints int
deletedAt time.Time
}
@ -354,6 +362,7 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: nil,
@ -368,12 +377,13 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -389,12 +399,13 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreAtt",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -410,12 +421,13 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreDef",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -431,12 +443,13 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: -1,
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreSup",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -452,12 +465,13 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: -1,
points: validPlayerCursor.Points(),
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreTotal",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -473,12 +487,35 @@ func TestNewPlayerCursor(t *testing.T) {
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: -1,
mostPoints: validPlayerCursor.MostPoints(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: mostPoints < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreSup: validPlayerCursor.ODScoreSup(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
mostPoints: -1,
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "mostPoints",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -518,6 +555,7 @@ func TestNewPlayerCursor(t *testing.T) {
tt.args.odScoreSup,
tt.args.odScoreTotal,
tt.args.points,
tt.args.mostPoints,
tt.args.deletedAt,
)
require.ErrorIs(t, err, tt.expectedErr)
@ -531,6 +569,7 @@ func TestNewPlayerCursor(t *testing.T) {
assert.Equal(t, tt.args.odScoreSup, pc.ODScoreSup())
assert.Equal(t, tt.args.odScoreTotal, pc.ODScoreTotal())
assert.Equal(t, tt.args.points, pc.Points())
assert.Equal(t, tt.args.mostPoints, pc.MostPoints())
assert.Equal(t, tt.args.deletedAt, pc.DeletedAt())
assert.NotEmpty(t, pc.Encode())
})
@ -574,7 +613,7 @@ func TestListPlayersParams_SetIDs(t *testing.T) {
Model: "ListPlayersParams",
Field: "ids",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -845,7 +884,7 @@ func TestListPlayersParams_SetTribeIDs(t *testing.T) {
Model: "ListPlayersParams",
Field: "tribeIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1490,7 +1529,7 @@ func TestListPlayersParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListPlayersParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1504,7 +1543,7 @@ func TestListPlayersParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListPlayersParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.PlayerListMaxLimit,
Current: domain.PlayerListMaxLimit + 1,
},
@ -1614,3 +1653,57 @@ func TestNewListPlayersWithRelationsResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
}
func TestCountPlayersParams_SetServerKeys(t *testing.T) {
t.Parallel()
type args struct {
serverKeys []string
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
serverKeys: []string{
domaintest.RandServerKey(),
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
serverKeys: []string{serverKeyTest.key},
},
expectedErr: domain.SliceElementValidationError{
Model: "CountPlayersParams",
Field: "serverKeys",
Index: 0,
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewCountPlayersParams()
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
})
}
}

View File

@ -15,7 +15,11 @@ type Server struct {
open bool
special bool
numPlayers int
numActivePlayers int
numInactivePlayers int
numTribes int
numActiveTribes int
numInactiveTribes int
numVillages int
numPlayerVillages int
numBarbarianVillages int
@ -24,6 +28,7 @@ type Server struct {
buildingInfo BuildingInfo
unitInfo UnitInfo
createdAt time.Time
snapshotCreatedAt time.Time
playerDataSyncedAt time.Time
playerSnapshotsCreatedAt time.Time
tribeDataSyncedAt time.Time
@ -45,7 +50,11 @@ func UnmarshalServerFromDatabase(
open bool,
special bool,
numPlayers int,
numActivePlayers int,
numInactivePlayers int,
numTribes int,
numActiveTribes int,
numInactiveTribes int,
numVillages int,
numPlayerVillages int,
numBarbarianVillages int,
@ -54,6 +63,7 @@ func UnmarshalServerFromDatabase(
buildingInfo BuildingInfo,
unitInfo UnitInfo,
createdAt time.Time,
snapshotCreatedAt time.Time,
playerDataSyncedAt time.Time,
playerSnapshotsCreatedAt time.Time,
tribeDataSyncedAt time.Time,
@ -88,11 +98,16 @@ func UnmarshalServerFromDatabase(
return Server{
key: key,
versionCode: versionCode,
url: u,
open: open,
special: special,
numPlayers: numPlayers,
numActivePlayers: numActivePlayers,
numInactivePlayers: numInactivePlayers,
numTribes: numTribes,
numActiveTribes: numActiveTribes,
numInactiveTribes: numInactiveTribes,
numVillages: numVillages,
numPlayerVillages: numPlayerVillages,
numBarbarianVillages: numBarbarianVillages,
@ -101,13 +116,13 @@ func UnmarshalServerFromDatabase(
buildingInfo: buildingInfo,
unitInfo: unitInfo,
createdAt: createdAt,
snapshotCreatedAt: snapshotCreatedAt,
playerDataSyncedAt: playerDataSyncedAt,
playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
tribeDataSyncedAt: tribeDataSyncedAt,
tribeSnapshotsCreatedAt: tribeSnapshotsCreatedAt,
villageDataSyncedAt: villageDataSyncedAt,
ennoblementDataSyncedAt: ennoblementDataSyncedAt,
versionCode: versionCode,
}, nil
}
@ -135,10 +150,26 @@ func (s Server) NumPlayers() int {
return s.numPlayers
}
func (s Server) NumActivePlayers() int {
return s.numActivePlayers
}
func (s Server) NumInactivePlayers() int {
return s.numInactivePlayers
}
func (s Server) NumTribes() int {
return s.numTribes
}
func (s Server) NumActiveTribes() int {
return s.numActiveTribes
}
func (s Server) NumInactiveTribes() int {
return s.numInactiveTribes
}
func (s Server) NumVillages() int {
return s.numVillages
}
@ -171,6 +202,10 @@ func (s Server) CreatedAt() time.Time {
return s.createdAt
}
func (s Server) SnapshotCreatedAt() time.Time {
return s.snapshotCreatedAt
}
func (s Server) PlayerDataSyncedAt() time.Time {
return s.playerDataSyncedAt
}
@ -378,8 +413,12 @@ type UpdateServerParams struct {
buildingInfo NullBuildingInfo
unitInfo NullUnitInfo
numTribes NullInt
numActiveTribes NullInt
numInactiveTribes NullInt
tribeDataSyncedAt NullTime
numPlayers NullInt
numActivePlayers NullInt
numInactivePlayers NullInt
playerDataSyncedAt NullTime
numVillages NullInt
numPlayerVillages NullInt
@ -387,6 +426,7 @@ type UpdateServerParams struct {
numBonusVillages NullInt
villageDataSyncedAt NullTime
ennoblementDataSyncedAt NullTime
snapshotCreatedAt NullTime
tribeSnapshotsCreatedAt NullTime
playerSnapshotsCreatedAt NullTime
}
@ -424,9 +464,9 @@ 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 {
func (params *UpdateServerParams) SetNumTribes(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numTribes",
@ -435,7 +475,47 @@ func (params *UpdateServerParams) SetNumTribes(numTribes NullInt) error {
}
}
params.numTribes = numTribes
params.numTribes = num
return nil
}
func (params *UpdateServerParams) NumActiveTribes() NullInt {
return params.numActiveTribes
}
func (params *UpdateServerParams) SetNumActiveTribes(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numActiveTribes",
Err: err,
}
}
}
params.numActiveTribes = num
return nil
}
func (params *UpdateServerParams) NumInactiveTribes() NullInt {
return params.numInactiveTribes
}
func (params *UpdateServerParams) SetNumInactiveTribes(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numInactiveTribes",
Err: err,
}
}
}
params.numInactiveTribes = num
return nil
}
@ -453,9 +533,9 @@ 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 {
func (params *UpdateServerParams) SetNumPlayers(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numPlayers",
@ -464,7 +544,47 @@ func (params *UpdateServerParams) SetNumPlayers(numPlayers NullInt) error {
}
}
params.numPlayers = numPlayers
params.numPlayers = num
return nil
}
func (params *UpdateServerParams) NumActivePlayers() NullInt {
return params.numActivePlayers
}
func (params *UpdateServerParams) SetNumActivePlayers(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numActivePlayers",
Err: err,
}
}
}
params.numActivePlayers = num
return nil
}
func (params *UpdateServerParams) NumInactivePlayers() NullInt {
return params.numInactivePlayers
}
func (params *UpdateServerParams) SetNumInactivePlayers(num NullInt) error {
if num.Valid {
if err := validateInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numInactivePlayers",
Err: err,
}
}
}
params.numInactivePlayers = num
return nil
}
@ -484,7 +604,7 @@ func (params *UpdateServerParams) NumVillages() NullInt {
func (params *UpdateServerParams) SetNumVillages(numVillages NullInt) error {
if numVillages.Valid {
if err := validateIntInRange(numVillages.V, 0, math.MaxInt); err != nil {
if err := validateInRange(numVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numVillages",
@ -504,7 +624,7 @@ func (params *UpdateServerParams) NumPlayerVillages() NullInt {
func (params *UpdateServerParams) SetNumPlayerVillages(numPlayerVillages NullInt) error {
if numPlayerVillages.Valid {
if err := validateIntInRange(numPlayerVillages.V, 0, math.MaxInt); err != nil {
if err := validateInRange(numPlayerVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numPlayerVillages",
@ -524,7 +644,7 @@ func (params *UpdateServerParams) NumBarbarianVillages() NullInt {
func (params *UpdateServerParams) SetNumBarbarianVillages(numBarbarianVillages NullInt) error {
if numBarbarianVillages.Valid {
if err := validateIntInRange(numBarbarianVillages.V, 0, math.MaxInt); err != nil {
if err := validateInRange(numBarbarianVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numBarbarianVillages",
@ -544,7 +664,7 @@ func (params *UpdateServerParams) NumBonusVillages() NullInt {
func (params *UpdateServerParams) SetNumBonusVillages(numBonusVillages NullInt) error {
if numBonusVillages.Valid {
if err := validateIntInRange(numBonusVillages.V, 0, math.MaxInt); err != nil {
if err := validateInRange(numBonusVillages.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numBonusVillages",
@ -576,6 +696,15 @@ func (params *UpdateServerParams) SetEnnoblementDataSyncedAt(ennoblementDataSync
return nil
}
func (params *UpdateServerParams) SnapshotCreatedAt() NullTime {
return params.snapshotCreatedAt
}
func (params *UpdateServerParams) SetSnapshotCreatedAt(snapshotCreatedAt NullTime) error {
params.snapshotCreatedAt = snapshotCreatedAt
return nil
}
func (params *UpdateServerParams) TribeSnapshotsCreatedAt() NullTime {
return params.tribeSnapshotsCreatedAt
}
@ -599,9 +728,9 @@ func (params *UpdateServerParams) IsZero() bool {
return !params.config.Valid &&
!params.buildingInfo.Valid &&
!params.unitInfo.Valid &&
!params.numTribes.Valid &&
!params.numActiveTribes.Valid &&
!params.tribeDataSyncedAt.Valid &&
!params.numPlayers.Valid &&
!params.numActivePlayers.Valid &&
!params.playerDataSyncedAt.Valid &&
!params.numVillages.Valid &&
!params.numPlayerVillages.Valid &&
@ -609,6 +738,7 @@ func (params *UpdateServerParams) IsZero() bool {
!params.numBonusVillages.Valid &&
!params.villageDataSyncedAt.Valid &&
!params.ennoblementDataSyncedAt.Valid &&
!params.snapshotCreatedAt.Valid &&
!params.tribeSnapshotsCreatedAt.Valid &&
!params.playerSnapshotsCreatedAt.Valid
}
@ -713,6 +843,7 @@ type ListServersParams struct {
versionCodes []string
open NullBool
special NullBool
snapshotCreatedAtLT NullTime
tribeSnapshotsCreatedAtLT NullTime
playerSnapshotsCreatedAtLT NullTime
sort []ServerSort
@ -796,6 +927,15 @@ func (params *ListServersParams) SetSpecial(special NullBool) error {
return nil
}
func (params *ListServersParams) SnapshotCreatedAtLT() NullTime {
return params.snapshotCreatedAtLT
}
func (params *ListServersParams) SetSnapshotCreatedAtLT(snapshotCreatedAtLT NullTime) error {
params.snapshotCreatedAtLT = snapshotCreatedAtLT
return nil
}
func (params *ListServersParams) TribeSnapshotsCreatedAtLT() NullTime {
return params.tribeSnapshotsCreatedAtLT
}
@ -866,7 +1006,7 @@ func (params *ListServersParams) Limit() int {
}
func (params *ListServersParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, ServerListMaxLimit); err != nil {
if err := validateInRange(limit, 1, ServerListMaxLimit); err != nil {
return ValidationError{
Model: listServersParamsModelName,
Field: "limit",

View File

@ -0,0 +1,31 @@
package domain
type ServerMap struct {
server Server
villagesCh <-chan VillageMetaWithRelations
scale float32
}
const serverMapModelName = "ServerMap"
func NewServerMap(server Server, villagesCh <-chan VillageMetaWithRelations) ServerMap {
return ServerMap{
server: server,
villagesCh: villagesCh,
scale: 1,
}
}
func (m *ServerMap) SetScale(scale float32) error {
if err := validateInRange(scale, 0, 1); err != nil {
return ValidationError{
Model: serverMapModelName,
Field: "scale",
Err: err,
}
}
m.scale = scale
return nil
}

View File

@ -0,0 +1,658 @@
package domain
import (
"errors"
"math"
"time"
)
type ServerSnapshot struct {
id int
serverKey string
numPlayers int
numActivePlayers int
numInactivePlayers int
numTribes int
numActiveTribes int
numInactiveTribes int
numVillages int
numPlayerVillages int
numBarbarianVillages int
numBonusVillages int
date time.Time
createdAt time.Time
}
const serverSnapshotModelName = "ServerSnapshot"
// UnmarshalServerSnapshotFromDatabase unmarshals ServerSnapshot from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalServerSnapshotFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalServerSnapshotFromDatabase(
id int,
serverKey string,
numPlayers int,
numActivePlayers int,
numInactivePlayers int,
numTribes int,
numActiveTribes int,
numInactiveTribes int,
numVillages int,
numPlayerVillages int,
numBarbarianVillages int,
numBonusVillages int,
date time.Time,
createdAt time.Time,
) (ServerSnapshot, error) {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return ServerSnapshot{}, ValidationError{
Model: serverSnapshotModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return ServerSnapshot{}, ValidationError{
Model: serverSnapshotModelName,
Field: "serverKey",
Err: err,
}
}
return ServerSnapshot{
id: id,
serverKey: serverKey,
numPlayers: numPlayers,
numActivePlayers: numActivePlayers,
numInactivePlayers: numInactivePlayers,
numTribes: numTribes,
numActiveTribes: numActiveTribes,
numInactiveTribes: numInactiveTribes,
numVillages: numVillages,
numPlayerVillages: numPlayerVillages,
numBarbarianVillages: numBarbarianVillages,
numBonusVillages: numBonusVillages,
date: date,
createdAt: createdAt,
}, nil
}
func (ss ServerSnapshot) ID() int {
return ss.id
}
func (ss ServerSnapshot) ServerKey() string {
return ss.serverKey
}
func (ss ServerSnapshot) NumPlayers() int {
return ss.numPlayers
}
func (ss ServerSnapshot) NumActivePlayers() int {
return ss.numActivePlayers
}
func (ss ServerSnapshot) NumInactivePlayers() int {
return ss.numInactivePlayers
}
func (ss ServerSnapshot) NumTribes() int {
return ss.numTribes
}
func (ss ServerSnapshot) NumActiveTribes() int {
return ss.numActiveTribes
}
func (ss ServerSnapshot) NumInactiveTribes() int {
return ss.numInactiveTribes
}
func (ss ServerSnapshot) NumVillages() int {
return ss.numVillages
}
func (ss ServerSnapshot) NumPlayerVillages() int {
return ss.numPlayerVillages
}
func (ss ServerSnapshot) NumBarbarianVillages() int {
return ss.numBarbarianVillages
}
func (ss ServerSnapshot) NumBonusVillages() int {
return ss.numBonusVillages
}
func (ss ServerSnapshot) Date() time.Time {
return ss.date
}
func (ss ServerSnapshot) CreatedAt() time.Time {
return ss.createdAt
}
func (ss ServerSnapshot) WithRelations(server ServerMeta) ServerSnapshotWithRelations {
return ServerSnapshotWithRelations{
snapshot: ss,
server: server,
}
}
func (ss ServerSnapshot) ToCursor() (ServerSnapshotCursor, error) {
return NewServerSnapshotCursor(ss.id, ss.serverKey, ss.date)
}
func (ss ServerSnapshot) IsZero() bool {
return ss == ServerSnapshot{}
}
type ServerSnapshots []ServerSnapshot
type ServerSnapshotWithRelations struct {
snapshot ServerSnapshot
server ServerMeta
}
func (ts ServerSnapshotWithRelations) ServerSnapshot() ServerSnapshot {
return ts.snapshot
}
func (ts ServerSnapshotWithRelations) Server() ServerMeta {
return ts.server
}
func (ts ServerSnapshotWithRelations) IsZero() bool {
return ts.snapshot.IsZero()
}
type ServerSnapshotsWithRelations []ServerSnapshotWithRelations
type CreateServerSnapshotParams struct {
serverKey string
numPlayers int
numActivePlayers int
numInactivePlayers int
numTribes int
numActiveTribes int
numInactiveTribes int
numVillages int
numPlayerVillages int
numBarbarianVillages int
numBonusVillages int
date time.Time
}
func NewCreateServerSnapshotParams(server Server, date time.Time) (CreateServerSnapshotParams, error) {
if server.IsZero() {
return CreateServerSnapshotParams{}, errors.New("given server is an empty struct")
}
if !server.Open() {
return CreateServerSnapshotParams{}, errors.New("given server is closed")
}
return CreateServerSnapshotParams{
serverKey: server.Key(),
numPlayers: server.NumPlayers(),
numActivePlayers: server.NumActivePlayers(),
numInactivePlayers: server.NumInactivePlayers(),
numTribes: server.NumTribes(),
numActiveTribes: server.NumActiveTribes(),
numInactiveTribes: server.NumInactiveTribes(),
numVillages: server.NumVillages(),
numPlayerVillages: server.NumPlayerVillages(),
numBarbarianVillages: server.NumBarbarianVillages(),
numBonusVillages: server.NumBonusVillages(),
date: date,
}, nil
}
func (params CreateServerSnapshotParams) ServerKey() string {
return params.serverKey
}
func (params CreateServerSnapshotParams) NumPlayers() int {
return params.numPlayers
}
func (params CreateServerSnapshotParams) NumActivePlayers() int {
return params.numActivePlayers
}
func (params CreateServerSnapshotParams) NumInactivePlayers() int {
return params.numInactivePlayers
}
func (params CreateServerSnapshotParams) NumTribes() int {
return params.numTribes
}
func (params CreateServerSnapshotParams) NumActiveTribes() int {
return params.numActiveTribes
}
func (params CreateServerSnapshotParams) NumInactiveTribes() int {
return params.numInactiveTribes
}
func (params CreateServerSnapshotParams) NumVillages() int {
return params.numVillages
}
func (params CreateServerSnapshotParams) NumPlayerVillages() int {
return params.numPlayerVillages
}
func (params CreateServerSnapshotParams) NumBarbarianVillages() int {
return params.numBarbarianVillages
}
func (params CreateServerSnapshotParams) NumBonusVillages() int {
return params.numBonusVillages
}
func (params CreateServerSnapshotParams) Date() time.Time {
return params.date
}
type ServerSnapshotSort uint8
const (
ServerSnapshotSortDateASC ServerSnapshotSort = iota + 1
ServerSnapshotSortDateDESC
ServerSnapshotSortIDASC
ServerSnapshotSortIDDESC
ServerSnapshotSortServerKeyASC
ServerSnapshotSortServerKeyDESC
)
// IsInConflict returns true if two sorts can't be used together
// (e.g. ServerSnapshotSortIDASC and ServerSnapshotSortIDDESC).
func (s ServerSnapshotSort) IsInConflict(s2 ServerSnapshotSort) bool {
return isSortInConflict(s, s2)
}
//nolint:gocyclo
func (s ServerSnapshotSort) String() string {
switch s {
case ServerSnapshotSortDateASC:
return "date:ASC"
case ServerSnapshotSortDateDESC:
return "date:DESC"
case ServerSnapshotSortIDASC:
return "id:ASC"
case ServerSnapshotSortIDDESC:
return "id:DESC"
case ServerSnapshotSortServerKeyASC:
return "serverKey:ASC"
case ServerSnapshotSortServerKeyDESC:
return "serverKey:DESC"
default:
return "unknown server snapshot sort"
}
}
type ServerSnapshotCursor struct {
id int
serverKey string
date time.Time
}
const serverSnapshotCursorModelName = "ServerSnapshotCursor"
func NewServerSnapshotCursor(id int, serverKey string, date time.Time) (ServerSnapshotCursor, error) {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return ServerSnapshotCursor{}, ValidationError{
Model: serverSnapshotCursorModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return ServerSnapshotCursor{}, ValidationError{
Model: serverSnapshotCursorModelName,
Field: "serverKey",
Err: err,
}
}
return ServerSnapshotCursor{
id: id,
serverKey: serverKey,
date: date,
}, nil
}
func decodeServerSnapshotCursor(encoded string) (ServerSnapshotCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
return ServerSnapshotCursor{}, err
}
id, err := m.int("id")
if err != nil {
return ServerSnapshotCursor{}, ErrInvalidCursor
}
serverKey, err := m.string("serverKey")
if err != nil {
return ServerSnapshotCursor{}, ErrInvalidCursor
}
date, err := m.time("date")
if err != nil {
return ServerSnapshotCursor{}, ErrInvalidCursor
}
tsc, err := NewServerSnapshotCursor(
id,
serverKey,
date,
)
if err != nil {
return ServerSnapshotCursor{}, ErrInvalidCursor
}
return tsc, nil
}
func (ssc ServerSnapshotCursor) ID() int {
return ssc.id
}
func (ssc ServerSnapshotCursor) ServerKey() string {
return ssc.serverKey
}
func (ssc ServerSnapshotCursor) Date() time.Time {
return ssc.date
}
func (ssc ServerSnapshotCursor) IsZero() bool {
return ssc == ServerSnapshotCursor{}
}
func (ssc ServerSnapshotCursor) Encode() string {
if ssc.IsZero() {
return ""
}
return encodeCursor([]keyValuePair{
{"id", ssc.id},
{"serverKey", ssc.serverKey},
{"date", ssc.date},
})
}
type ListServerSnapshotsParams struct {
serverKeys []string
sort []ServerSnapshotSort
cursor ServerSnapshotCursor
limit int
}
const (
ServerSnapshotListMaxLimit = 500
listServerSnapshotsParamsModelName = "ListServerSnapshotsParams"
)
func NewListServerSnapshotsParams() ListServerSnapshotsParams {
return ListServerSnapshotsParams{
sort: []ServerSnapshotSort{
ServerSnapshotSortServerKeyASC,
ServerSnapshotSortDateASC,
ServerSnapshotSortIDASC,
},
limit: ServerSnapshotListMaxLimit,
}
}
func (params *ListServerSnapshotsParams) ServerKeys() []string {
return params.serverKeys
}
func (params *ListServerSnapshotsParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
func (params *ListServerSnapshotsParams) Sort() []ServerSnapshotSort {
return params.sort
}
const (
serverSnapshotSortMinLength = 0
serverSnapshotSortMaxLength = 3
)
func (params *ListServerSnapshotsParams) SetSort(sort []ServerSnapshotSort) error {
if err := validateSort(sort, serverSnapshotSortMinLength, serverSnapshotSortMaxLength); err != nil {
return ValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
params.sort = sort
return nil
}
func (params *ListServerSnapshotsParams) PrependSort(sort []ServerSnapshotSort) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(serverSnapshotSortMaxLength-len(params.sort), 0)); err != nil {
return ValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
return params.SetSort(append(sort, params.sort...))
}
func (params *ListServerSnapshotsParams) PrependSortString(
sort []string,
allowed []ServerSnapshotSort,
maxLength int,
) error {
if len(sort) == 0 {
return nil
}
if err := validateSliceLen(sort, 0, max(min(serverSnapshotSortMaxLength-len(params.sort), maxLength), 0)); err != nil {
return ValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "sort",
Err: err,
}
}
toPrepend := make([]ServerSnapshotSort, 0, len(sort))
for i, s := range sort {
converted, err := newSortFromString(s, allowed...)
if err != nil {
return SliceElementValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "sort",
Index: i,
Err: err,
}
}
toPrepend = append(toPrepend, converted)
}
return params.SetSort(append(toPrepend, params.sort...))
}
func (params *ListServerSnapshotsParams) Cursor() ServerSnapshotCursor {
return params.cursor
}
func (params *ListServerSnapshotsParams) SetCursor(cursor ServerSnapshotCursor) error {
params.cursor = cursor
return nil
}
func (params *ListServerSnapshotsParams) SetEncodedCursor(encoded string) error {
decoded, err := decodeServerSnapshotCursor(encoded)
if err != nil {
return ValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "cursor",
Err: err,
}
}
params.cursor = decoded
return nil
}
func (params *ListServerSnapshotsParams) Limit() int {
return params.limit
}
func (params *ListServerSnapshotsParams) SetLimit(limit int) error {
if err := validateInRange(limit, 1, ServerSnapshotListMaxLimit); err != nil {
return ValidationError{
Model: listServerSnapshotsParamsModelName,
Field: "limit",
Err: err,
}
}
params.limit = limit
return nil
}
type ListServerSnapshotsResult struct {
snapshots ServerSnapshots
self ServerSnapshotCursor
next ServerSnapshotCursor
}
const listServerSnapshotsResultModelName = "ListServerSnapshotsResult"
func NewListServerSnapshotsResult(
snapshots ServerSnapshots,
next ServerSnapshot,
) (ListServerSnapshotsResult, error) {
var err error
res := ListServerSnapshotsResult{
snapshots: snapshots,
}
if len(snapshots) > 0 {
res.self, err = snapshots[0].ToCursor()
if err != nil {
return ListServerSnapshotsResult{}, ValidationError{
Model: listServerSnapshotsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.ToCursor()
if err != nil {
return ListServerSnapshotsResult{}, ValidationError{
Model: listServerSnapshotsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListServerSnapshotsResult) ServerSnapshots() ServerSnapshots {
return res.snapshots
}
func (res ListServerSnapshotsResult) Self() ServerSnapshotCursor {
return res.self
}
func (res ListServerSnapshotsResult) Next() ServerSnapshotCursor {
return res.next
}
type ListServerSnapshotsWithRelationsResult struct {
snapshots ServerSnapshotsWithRelations
self ServerSnapshotCursor
next ServerSnapshotCursor
}
const listServerSnapshotsWithRelationsResultModelName = "ListServerSnapshotsWithRelationsResult"
func NewListServerSnapshotsWithRelationsResult(
snapshots ServerSnapshotsWithRelations,
next ServerSnapshotWithRelations,
) (ListServerSnapshotsWithRelationsResult, error) {
var err error
res := ListServerSnapshotsWithRelationsResult{
snapshots: snapshots,
}
if len(snapshots) > 0 {
res.self, err = snapshots[0].ServerSnapshot().ToCursor()
if err != nil {
return ListServerSnapshotsWithRelationsResult{}, ValidationError{
Model: listServerSnapshotsWithRelationsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.ServerSnapshot().ToCursor()
if err != nil {
return ListServerSnapshotsWithRelationsResult{}, ValidationError{
Model: listServerSnapshotsWithRelationsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListServerSnapshotsWithRelationsResult) ServerSnapshots() ServerSnapshotsWithRelations {
return res.snapshots
}
func (res ListServerSnapshotsWithRelationsResult) Self() ServerSnapshotCursor {
return res.self
}
func (res ListServerSnapshotsWithRelationsResult) Next() ServerSnapshotCursor {
return res.next
}

View File

@ -0,0 +1,901 @@
package domain_test
import (
"fmt"
"testing"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"gitea.dwysokinski.me/twhelp/core/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v7"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewCreateServerSnapshotParams(t *testing.T) {
t.Parallel()
server := domaintest.NewServer(t)
date := time.Now()
params, err := domain.NewCreateServerSnapshotParams(server, date)
require.NoError(t, err)
assert.Equal(t, server.Key(), params.ServerKey())
assert.Equal(t, server.NumPlayers(), params.NumPlayers())
assert.Equal(t, server.NumActivePlayers(), params.NumActivePlayers())
assert.Equal(t, server.NumInactivePlayers(), params.NumInactivePlayers())
assert.Equal(t, server.NumTribes(), params.NumTribes())
assert.Equal(t, server.NumActiveTribes(), params.NumActiveTribes())
assert.Equal(t, server.NumInactiveTribes(), params.NumInactiveTribes())
assert.Equal(t, server.NumVillages(), params.NumVillages())
assert.Equal(t, server.NumPlayerVillages(), params.NumPlayerVillages())
assert.Equal(t, server.NumBarbarianVillages(), params.NumBarbarianVillages())
assert.Equal(t, server.NumBonusVillages(), params.NumBonusVillages())
assert.Equal(t, date, params.Date())
}
func TestServerSnapshotSort_IsInConflict(t *testing.T) {
t.Parallel()
type args struct {
sorts [2]domain.ServerSnapshotSort
}
tests := []struct {
name string
args args
expectedRes bool
}{
{
name: "OK: id:ASC serverKey:ASC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortServerKeyASC},
},
expectedRes: false,
},
{
name: "OK: id:DESC serverKey:ASC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDDESC, domain.ServerSnapshotSortServerKeyASC},
},
expectedRes: false,
},
{
name: "OK: id:ASC id:ASC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortIDASC},
},
expectedRes: true,
},
{
name: "OK: id:ASC id:DESC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortIDDESC},
},
expectedRes: true,
},
{
name: "OK: date:ASC date:DESC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortDateASC, domain.ServerSnapshotSortDateDESC},
},
expectedRes: true,
},
{
name: "OK: serverKey:DESC serverKey:ASC",
args: args{
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortServerKeyDESC, domain.ServerSnapshotSortServerKeyASC},
},
expectedRes: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tt.expectedRes, tt.args.sorts[0].IsInConflict(tt.args.sorts[1]))
})
}
}
func TestNewServerSnapshotCursor(t *testing.T) {
t.Parallel()
validServerSnapshotCursor := domaintest.NewServerSnapshotCursor(t)
type args struct {
id int
serverKey string
date time.Time
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
id: validServerSnapshotCursor.ID(),
serverKey: validServerSnapshotCursor.ServerKey(),
date: validServerSnapshotCursor.Date(),
},
expectedErr: nil,
},
{
name: "ERR: id < 1",
args: args{
id: 0,
serverKey: validServerSnapshotCursor.ServerKey(),
date: validServerSnapshotCursor.Date(),
},
expectedErr: domain.ValidationError{
Model: "ServerSnapshotCursor",
Field: "id",
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
id: validServerSnapshotCursor.ID(),
serverKey: serverKeyTest.key,
},
expectedErr: domain.ValidationError{
Model: "ServerSnapshotCursor",
Field: "serverKey",
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ssc, err := domain.NewServerSnapshotCursor(
tt.args.id,
tt.args.serverKey,
tt.args.date,
)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.id, ssc.ID())
assert.Equal(t, tt.args.serverKey, ssc.ServerKey())
assert.Equal(t, tt.args.date, ssc.Date())
assert.NotEmpty(t, ssc.Encode())
})
}
}
func TestListServerSnapshotsParams_SetServerKeys(t *testing.T) {
t.Parallel()
type args struct {
serverKeys []string
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
serverKeys: []string{
domaintest.RandServerKey(),
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
serverKeys: []string{serverKeyTest.key},
},
expectedErr: domain.SliceElementValidationError{
Model: "ListServerSnapshotsParams",
Field: "serverKeys",
Index: 0,
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListServerSnapshotsParams()
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
})
}
}
func TestListServerSnapshotsParams_SetSort(t *testing.T) {
t.Parallel()
type args struct {
sort []domain.ServerSnapshotSort
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortServerKeyASC,
},
},
},
{
name: "OK: empty slice",
args: args{
sort: nil,
},
},
{
name: "ERR: len(sort) > 3",
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortIDDESC,
},
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 0,
Max: 3,
Current: 4,
},
},
},
{
name: "ERR: conflict",
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortIDDESC,
},
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.SortConflictError{
Sort: [2]string{domain.ServerSnapshotSortIDASC.String(), domain.ServerSnapshotSortIDDESC.String()},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListServerSnapshotsParams()
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.sort, params.Sort())
})
}
}
func TestListServerSnapshotsParams_PrependSort(t *testing.T) {
t.Parallel()
defaultNewParams := func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
return domain.ListServerSnapshotsParams{}
}
type args struct {
sort []domain.ServerSnapshotSort
}
tests := []struct {
name string
newParams func(t *testing.T) domain.ListServerSnapshotsParams
args args
expectedErr error
}{
{
name: "OK",
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortDateASC,
},
},
},
{
name: "OK: custom params",
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
}))
return params
},
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
},
},
},
{
name: "OK: empty slice",
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
}))
return params
},
args: args{
sort: nil,
},
},
{
name: "ERR: custom params + len(sort) > sortMaxLength - len(sort)",
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
}))
return params
},
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateASC,
},
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 0,
Max: 1,
Current: 2,
},
},
},
{
name: "ERR: len(sort) > 3",
newParams: defaultNewParams,
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateASC,
},
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 0,
Max: 3,
Current: 4,
},
},
},
{
name: "ERR: conflict",
args: args{
sort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateDESC,
},
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.SortConflictError{
Sort: [2]string{domain.ServerSnapshotSortDateASC.String(), domain.ServerSnapshotSortDateDESC.String()},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
newParams := defaultNewParams
if tt.newParams != nil {
newParams = tt.newParams
}
params := newParams(t)
expectedSort := params.Sort()
require.ErrorIs(t, params.PrependSort(tt.args.sort), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, append(tt.args.sort, expectedSort...), params.Sort())
})
}
}
func TestListServerSnapshotsParams_PrependSortString(t *testing.T) {
t.Parallel()
defaultNewParams := func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
return domain.ListServerSnapshotsParams{}
}
defaultAllowed := []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortDateDESC,
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortIDDESC,
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortServerKeyDESC,
}
defaultMaxLength := 3
type args struct {
sort []string
allowed []domain.ServerSnapshotSort
maxLength int
}
tests := []struct {
name string
newParams func(t *testing.T) domain.ListServerSnapshotsParams
args args
expectedSort []domain.ServerSnapshotSort
expectedErr error
}{
{
name: "OK: [id:ASC, date:ASC, serverKey:ASC]",
args: args{
sort: []string{
"id:ASC",
"date:ASC",
"serverKey:ASC",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedSort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortServerKeyASC,
},
},
{
name: "OK: [id:DESC, date:DESC, serverKey:DESC]",
args: args{
sort: []string{
"id:DESC",
"date:DESC",
"serverKey:DESC",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedSort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDDESC,
domain.ServerSnapshotSortDateDESC,
domain.ServerSnapshotSortServerKeyDESC,
},
},
{
name: "OK: custom params",
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
}))
return params
},
args: args{
sort: []string{
"date:ASC",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedSort: []domain.ServerSnapshotSort{
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortIDASC,
domain.ServerSnapshotSortServerKeyASC,
},
},
{
name: "OK: empty slice",
args: args{
sort: nil,
},
},
{
name: "ERR: custom params + len(sort) > sortMaxLength - len(sort)",
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
t.Helper()
params := domain.NewListServerSnapshotsParams()
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortIDASC,
}))
return params
},
args: args{
sort: []string{
"date:ASC",
"date:DESC",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 0,
Max: 1,
Current: 2,
},
},
},
{
name: "ERR: len(sort) > maxLength",
newParams: defaultNewParams,
args: args{
sort: []string{
"serverKey:ASC",
"date:ASC",
},
allowed: defaultAllowed,
maxLength: 1,
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 0,
Max: 1,
Current: 2,
},
},
},
{
name: "ERR: unsupported sort string",
newParams: defaultNewParams,
args: args{
sort: []string{
"date:",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedErr: domain.SliceElementValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Index: 0,
Err: domain.UnsupportedSortStringError{
Sort: "date:",
},
},
},
{
name: "ERR: conflict",
args: args{
sort: []string{
"date:ASC",
"date:DESC",
},
allowed: defaultAllowed,
maxLength: defaultMaxLength,
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "sort",
Err: domain.SortConflictError{
Sort: [2]string{domain.ServerSnapshotSortDateASC.String(), domain.ServerSnapshotSortDateDESC.String()},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
newParams := defaultNewParams
if tt.newParams != nil {
newParams = tt.newParams
}
params := newParams(t)
require.ErrorIs(t, params.PrependSortString(tt.args.sort, tt.args.allowed, tt.args.maxLength), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.expectedSort, params.Sort())
})
}
}
func TestListServerSnapshotsParams_SetEncodedCursor(t *testing.T) {
t.Parallel()
validCursor := domaintest.NewServerSnapshotCursor(t)
type args struct {
cursor string
}
tests := []struct {
name string
args args
expectedCursor domain.ServerSnapshotCursor
expectedErr error
}{
{
name: "OK",
args: args{
cursor: validCursor.Encode(),
},
expectedCursor: validCursor,
},
{
name: "ERR: len(cursor) < 1",
args: args{
cursor: "",
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "cursor",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 1000,
Current: 0,
},
},
},
{
name: "ERR: len(cursor) > 1000",
args: args{
cursor: gofakeit.LetterN(1001),
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "cursor",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 1000,
Current: 1001,
},
},
},
{
name: "ERR: malformed base64",
args: args{
cursor: "112345",
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "cursor",
Err: domain.ErrInvalidCursor,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListServerSnapshotsParams()
require.ErrorIs(t, params.SetEncodedCursor(tt.args.cursor), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
})
}
}
func TestListServerSnapshotsParams_SetLimit(t *testing.T) {
t.Parallel()
type args struct {
limit int
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
limit: domain.ServerSnapshotListMaxLimit,
},
},
{
name: "ERR: limit < 1",
args: args{
limit: 0,
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "limit",
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
},
},
{
name: fmt.Sprintf("ERR: limit > %d", domain.ServerSnapshotListMaxLimit),
args: args{
limit: domain.ServerSnapshotListMaxLimit + 1,
},
expectedErr: domain.ValidationError{
Model: "ListServerSnapshotsParams",
Field: "limit",
Err: domain.MaxLessEqualError[int]{
Max: domain.ServerSnapshotListMaxLimit,
Current: domain.ServerSnapshotListMaxLimit + 1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListServerSnapshotsParams()
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.limit, params.Limit())
})
}
}
func TestNewListServerSnapshotsResult(t *testing.T) {
t.Parallel()
snapshots := domain.ServerSnapshots{
domaintest.NewServerSnapshot(t),
domaintest.NewServerSnapshot(t),
domaintest.NewServerSnapshot(t),
}
next := domaintest.NewServerSnapshot(t)
t.Run("OK: with next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsResult(snapshots, next)
require.NoError(t, err)
assert.Equal(t, snapshots, res.ServerSnapshots())
assert.Equal(t, snapshots[0].ID(), res.Self().ID())
assert.Equal(t, snapshots[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, snapshots[0].Date(), res.Self().Date())
assert.Equal(t, next.ID(), res.Next().ID())
assert.Equal(t, next.ServerKey(), res.Next().ServerKey())
assert.Equal(t, next.Date(), res.Next().Date())
})
t.Run("OK: without next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsResult(snapshots, domain.ServerSnapshot{})
require.NoError(t, err)
assert.Equal(t, snapshots, res.ServerSnapshots())
assert.Equal(t, snapshots[0].ID(), res.Self().ID())
assert.Equal(t, snapshots[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, snapshots[0].Date(), res.Self().Date())
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 snapshots", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsResult(nil, domain.ServerSnapshot{})
require.NoError(t, err)
assert.Zero(t, res.ServerSnapshots())
assert.True(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
})
}
func TestNewListServerSnapshotsWithRelationsResult(t *testing.T) {
t.Parallel()
snapshots := domain.ServerSnapshotsWithRelations{
domaintest.NewServerSnapshotWithRelations(t),
domaintest.NewServerSnapshotWithRelations(t),
domaintest.NewServerSnapshotWithRelations(t),
}
next := domaintest.NewServerSnapshotWithRelations(t)
t.Run("OK: with next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsWithRelationsResult(snapshots, next)
require.NoError(t, err)
assert.Equal(t, snapshots, res.ServerSnapshots())
assert.Equal(t, snapshots[0].ServerSnapshot().ID(), res.Self().ID())
assert.Equal(t, snapshots[0].ServerSnapshot().ServerKey(), res.Self().ServerKey())
assert.Equal(t, snapshots[0].ServerSnapshot().Date(), res.Self().Date())
assert.Equal(t, next.ServerSnapshot().ID(), res.Next().ID())
assert.Equal(t, next.ServerSnapshot().ServerKey(), res.Next().ServerKey())
assert.Equal(t, next.ServerSnapshot().Date(), res.Next().Date())
})
t.Run("OK: without next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsWithRelationsResult(snapshots, domain.ServerSnapshotWithRelations{})
require.NoError(t, err)
assert.Equal(t, snapshots, res.ServerSnapshots())
assert.Equal(t, snapshots[0].ServerSnapshot().ID(), res.Self().ID())
assert.Equal(t, snapshots[0].ServerSnapshot().ServerKey(), res.Self().ServerKey())
assert.Equal(t, snapshots[0].ServerSnapshot().Date(), res.Self().Date())
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 snapshots", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServerSnapshotsWithRelationsResult(nil, domain.ServerSnapshotWithRelations{})
require.NoError(t, err)
assert.Zero(t, res.ServerSnapshots())
assert.True(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
})
}

View File

@ -201,7 +201,7 @@ func TestUpdateServerParams_SetNumTribes(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numTribes",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -224,6 +224,132 @@ func TestUpdateServerParams_SetNumTribes(t *testing.T) {
}
}
func TestUpdateServerParams_SetNumActiveTribes(t *testing.T) {
t.Parallel()
type args struct {
numActiveTribes domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numActiveTribes: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numActiveTribes: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numActiveTribes < 0",
args: args{
numActiveTribes: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numActiveTribes",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumActiveTribes(tt.args.numActiveTribes), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numActiveTribes, params.NumActiveTribes())
})
}
}
func TestUpdateServerParams_SetNumInactiveTribes(t *testing.T) {
t.Parallel()
type args struct {
numInactiveTribes domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numInactiveTribes: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numInactiveTribes: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numInactiveTribes < 0",
args: args{
numInactiveTribes: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numInactiveTribes",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumInactiveTribes(tt.args.numInactiveTribes), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numInactiveTribes, params.NumInactiveTribes())
})
}
}
func TestUpdateServerParams_SetNumPlayers(t *testing.T) {
t.Parallel()
@ -264,7 +390,7 @@ func TestUpdateServerParams_SetNumPlayers(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numPlayers",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -287,6 +413,132 @@ func TestUpdateServerParams_SetNumPlayers(t *testing.T) {
}
}
func TestUpdateServerParams_SetNumActivePlayers(t *testing.T) {
t.Parallel()
type args struct {
numActivePlayers domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numActivePlayers: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numActivePlayers: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numActivePlayers < 0",
args: args{
numActivePlayers: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numActivePlayers",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumActivePlayers(tt.args.numActivePlayers), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numActivePlayers, params.NumActivePlayers())
})
}
}
func TestUpdateServerParams_SetNumInactivePlayers(t *testing.T) {
t.Parallel()
type args struct {
numInactivePlayers domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numInactivePlayers: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numInactivePlayers: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numInactivePlayers < 0",
args: args{
numInactivePlayers: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numInactivePlayers",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumInactivePlayers(tt.args.numInactivePlayers), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numInactivePlayers, params.NumInactivePlayers())
})
}
}
func TestUpdateServerParams_SetNumVillages(t *testing.T) {
t.Parallel()
@ -327,7 +579,7 @@ func TestUpdateServerParams_SetNumVillages(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -390,7 +642,7 @@ func TestUpdateServerParams_SetNumPlayerVillages(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numPlayerVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -453,7 +705,7 @@ func TestUpdateServerParams_SetNumBarbarianVillages(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numBarbarianVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -516,7 +768,7 @@ func TestUpdateServerParams_SetNumBonusVillages(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numBonusVillages",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -955,7 +1207,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -969,7 +1221,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.ServerListMaxLimit,
Current: domain.ServerListMaxLimit + 1,
},

View File

@ -67,7 +67,7 @@ func UnmarshalTribeFromDatabase(
createdAt time.Time,
deletedAt time.Time,
) (Tribe, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return Tribe{}, ValidationError{
Model: tribeModelName,
Field: "id",
@ -207,6 +207,7 @@ func (t Tribe) ToCursor() (TribeCursor, error) {
t.od.scoreDef,
t.od.scoreTotal,
t.points,
t.mostPoints,
t.dominance,
t.deletedAt,
)
@ -295,7 +296,7 @@ const tribeMetaModelName = "TribeMeta"
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalTribeMetaFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalTribeMetaFromDatabase(id int, name string, tag string, rawProfileURL string) (TribeMeta, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeMeta{}, ValidationError{
Model: tribeMetaModelName,
Field: "id",
@ -472,6 +473,8 @@ const (
TribeSortODScoreTotalDESC
TribeSortPointsASC
TribeSortPointsDESC
TribeSortMostPointsASC
TribeSortMostPointsDESC
TribeSortDominanceASC
TribeSortDominanceDESC
TribeSortDeletedAtASC
@ -510,6 +513,10 @@ func (s TribeSort) String() string {
return "points:ASC"
case TribeSortPointsDESC:
return "points:DESC"
case TribeSortMostPointsASC:
return "mostPoints:ASC"
case TribeSortMostPointsDESC:
return "mostPoints:DESC"
case TribeSortDominanceASC:
return "dominance:ASC"
case TribeSortDominanceDESC:
@ -530,6 +537,7 @@ type TribeCursor struct {
odScoreDef int
odScoreTotal int
points int
mostPoints int
dominance float64
deletedAt time.Time
}
@ -543,10 +551,11 @@ func NewTribeCursor(
odScoreDef int,
odScoreTotal int,
points int,
mostPoints int,
dominance float64,
deletedAt time.Time,
) (TribeCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "id",
@ -562,7 +571,7 @@ func NewTribeCursor(
}
}
if err := validateIntInRange(odScoreAtt, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreAtt, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "odScoreAtt",
@ -570,7 +579,7 @@ func NewTribeCursor(
}
}
if err := validateIntInRange(odScoreDef, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreDef, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "odScoreDef",
@ -578,7 +587,7 @@ func NewTribeCursor(
}
}
if err := validateIntInRange(odScoreTotal, 0, math.MaxInt); err != nil {
if err := validateInRange(odScoreTotal, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "odScoreTotal",
@ -586,7 +595,7 @@ func NewTribeCursor(
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
if err := validateInRange(points, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "points",
@ -594,6 +603,14 @@ func NewTribeCursor(
}
}
if err := validateInRange(mostPoints, 0, math.MaxInt); err != nil {
return TribeCursor{}, ValidationError{
Model: tribeCursorModelName,
Field: "mostPoints",
Err: err,
}
}
return TribeCursor{
id: id,
serverKey: serverKey,
@ -601,6 +618,7 @@ func NewTribeCursor(
odScoreDef: odScoreDef,
odScoreTotal: odScoreTotal,
points: points,
mostPoints: mostPoints,
dominance: dominance,
deletedAt: deletedAt,
}, nil
@ -643,6 +661,11 @@ func decodeTribeCursor(encoded string) (TribeCursor, error) {
return TribeCursor{}, ErrInvalidCursor
}
mostPoints, err := m.int("mostPoints")
if err != nil {
return TribeCursor{}, ErrInvalidCursor
}
dominance, err := m.float64("dominance")
if err != nil {
return TribeCursor{}, ErrInvalidCursor
@ -660,6 +683,7 @@ func decodeTribeCursor(encoded string) (TribeCursor, error) {
odScoreDef,
odScoreTotal,
points,
mostPoints,
dominance,
deletedAt,
)
@ -694,6 +718,10 @@ func (tc TribeCursor) Points() int {
return tc.points
}
func (tc TribeCursor) MostPoints() int {
return tc.mostPoints
}
func (tc TribeCursor) Dominance() float64 {
return tc.dominance
}
@ -718,6 +746,7 @@ func (tc TribeCursor) Encode() string {
{"odScoreDef", tc.odScoreDef},
{"odScoreTotal", tc.odScoreTotal},
{"points", tc.points},
{"mostPoints", tc.mostPoints},
{"dominance", tc.dominance},
{"deletedAt", tc.deletedAt},
})
@ -754,7 +783,7 @@ func (params *ListTribesParams) IDs() []int {
func (params *ListTribesParams) SetIDs(ids []int) error {
for i, id := range ids {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribesParamsModelName,
Field: "ids",
@ -905,7 +934,7 @@ func (params *ListTribesParams) Limit() int {
}
func (params *ListTribesParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, TribeListMaxLimit); err != nil {
if err := validateInRange(limit, 1, TribeListMaxLimit); err != nil {
return ValidationError{
Model: listTribesParamsModelName,
Field: "limit",
@ -969,6 +998,37 @@ func (res ListTribesResult) Next() TribeCursor {
return res.next
}
type CountTribesParams struct {
serverKeys []string
}
const countTribesParamsModelName = "CountTribesParams"
func NewCountTribesParams() CountTribesParams {
return CountTribesParams{}
}
func (params *CountTribesParams) ServerKeys() []string {
return params.serverKeys
}
func (params *CountTribesParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: countTribesParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
type TribeNotFoundError struct {
ID int
ServerKey string

View File

@ -31,7 +31,7 @@ func UnmarshalTribeChangeFromDatabase(
newTribeID int,
createdAt time.Time,
) (TribeChange, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeChange{}, ValidationError{
Model: tribeChangeModelName,
Field: "id",
@ -156,7 +156,7 @@ func NewCreateTribeChangeParams(
}
}
if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
if err := validateInRange(playerID, 1, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "playerID",
@ -164,7 +164,7 @@ func NewCreateTribeChangeParams(
}
}
if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
if err := validateInRange(oldTribeID, 0, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "oldTribeID",
@ -172,7 +172,7 @@ func NewCreateTribeChangeParams(
}
}
if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
if err := validateInRange(newTribeID, 0, math.MaxInt); err != nil {
return CreateTribeChangeParams{}, ValidationError{
Model: createTribeChangeParamsModelName,
Field: "newTribeID",
@ -297,7 +297,7 @@ type TribeChangeCursor struct {
const tribeChangeCursorModelName = "TribeChangeCursor"
func NewTribeChangeCursor(id int, serverKey string, createdAt time.Time) (TribeChangeCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeChangeCursor{}, ValidationError{
Model: tribeChangeCursorModelName,
Field: "id",
@ -436,7 +436,7 @@ func (params *ListTribeChangesParams) PlayerIDs() []int {
func (params *ListTribeChangesParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "playerIDs",
@ -457,7 +457,7 @@ func (params *ListTribeChangesParams) TribeIDs() []int {
func (params *ListTribeChangesParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribeChangesParamsModelName,
Field: "tribeIDs",
@ -542,7 +542,7 @@ func (params *ListTribeChangesParams) Limit() int {
}
func (params *ListTribeChangesParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, TribeChangeListMaxLimit); err != nil {
if err := validateInRange(limit, 1, TribeChangeListMaxLimit); err != nil {
return ValidationError{
Model: listTribeChangesParamsModelName,
Field: "limit",

View File

@ -53,7 +53,7 @@ func TestNewCreateTribeChangeParams(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "CreateTribeChangeParams",
Field: "playerID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -70,7 +70,7 @@ func TestNewCreateTribeChangeParams(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "CreateTribeChangeParams",
Field: "oldTribeID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -87,7 +87,7 @@ func TestNewCreateTribeChangeParams(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "CreateTribeChangeParams",
Field: "newTribeID",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -318,7 +318,7 @@ func TestNewTribeChangeCursor(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "TribeChangeCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -453,7 +453,7 @@ func TestListTribeChangesParams_SetPlayerIDs(t *testing.T) {
Model: "ListTribeChangesParams",
Field: "playerIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -513,7 +513,7 @@ func TestListTribeChangesParams_SetTribeIDs(t *testing.T) {
Model: "ListTribeChangesParams",
Field: "tribeIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1071,7 +1071,7 @@ func TestListTribeChangesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribeChangesParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1085,7 +1085,7 @@ func TestListTribeChangesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribeChangesParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.TribeChangeListMaxLimit,
Current: domain.TribeChangeListMaxLimit + 1,
},

View File

@ -43,7 +43,7 @@ func UnmarshalTribeSnapshotFromDatabase(
date time.Time,
createdAt time.Time,
) (TribeSnapshot, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeSnapshot{}, ValidationError{
Model: tribeSnapshotModelName,
Field: "id",
@ -51,7 +51,7 @@ func UnmarshalTribeSnapshotFromDatabase(
}
}
if err := validateIntInRange(tribeID, 1, math.MaxInt); err != nil {
if err := validateInRange(tribeID, 1, math.MaxInt); err != nil {
return TribeSnapshot{}, ValidationError{
Model: tribeSnapshotModelName,
Field: "tribeID",
@ -295,7 +295,7 @@ type TribeSnapshotCursor struct {
const tribeSnapshotCursorModelName = "TribeSnapshotCursor"
func NewTribeSnapshotCursor(id int, serverKey string, date time.Time) (TribeSnapshotCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return TribeSnapshotCursor{}, ValidationError{
Model: tribeSnapshotCursorModelName,
Field: "id",
@ -318,7 +318,6 @@ func NewTribeSnapshotCursor(id int, serverKey string, date time.Time) (TribeSnap
}, nil
}
//nolint:gocyclo
func decodeTribeSnapshotCursor(encoded string) (TribeSnapshotCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
@ -431,7 +430,7 @@ func (params *ListTribeSnapshotsParams) TribeIDs() []int {
func (params *ListTribeSnapshotsParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listTribeSnapshotsParamsModelName,
Field: "tribeIDs",
@ -549,7 +548,7 @@ func (params *ListTribeSnapshotsParams) Limit() int {
}
func (params *ListTribeSnapshotsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, TribeSnapshotListMaxLimit); err != nil {
if err := validateInRange(limit, 1, TribeSnapshotListMaxLimit); err != nil {
return ValidationError{
Model: listTribeSnapshotsParamsModelName,
Field: "limit",

View File

@ -149,7 +149,7 @@ func TestNewTribeSnapshotCursor(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "TribeSnapshotCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -176,7 +176,7 @@ func TestNewTribeSnapshotCursor(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
psc, err := domain.NewTribeSnapshotCursor(
tsc, err := domain.NewTribeSnapshotCursor(
tt.args.id,
tt.args.serverKey,
tt.args.date,
@ -185,10 +185,10 @@ func TestNewTribeSnapshotCursor(t *testing.T) {
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.id, psc.ID())
assert.Equal(t, tt.args.serverKey, psc.ServerKey())
assert.Equal(t, tt.args.date, psc.Date())
assert.NotEmpty(t, psc.Encode())
assert.Equal(t, tt.args.id, tsc.ID())
assert.Equal(t, tt.args.serverKey, tsc.ServerKey())
assert.Equal(t, tt.args.date, tsc.Date())
assert.NotEmpty(t, tsc.Encode())
})
}
}
@ -284,7 +284,7 @@ func TestListTribeSnapshotsParams_SetTribeIDs(t *testing.T) {
Model: "ListTribeSnapshotsParams",
Field: "tribeIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -842,7 +842,7 @@ func TestListTribeSnapshotsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribeSnapshotsParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -856,7 +856,7 @@ func TestListTribeSnapshotsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribeSnapshotsParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.TribeSnapshotListMaxLimit,
Current: domain.TribeSnapshotListMaxLimit + 1,
},

View File

@ -255,6 +255,13 @@ func TestTribeSort_IsInConflict(t *testing.T) {
},
expectedRes: true,
},
{
name: "OK: mostPoints:ASC mostPoints:DESC",
args: args{
sorts: [2]domain.TribeSort{domain.TribeSortMostPointsASC, domain.TribeSortMostPointsDESC},
},
expectedRes: true,
},
{
name: "OK: dominance:ASC dominance:DESC",
args: args{
@ -292,6 +299,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef int
odScoreTotal int
points int
mostPoints int
dominance float64
deletedAt time.Time
}
@ -312,6 +320,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
@ -326,13 +335,14 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -347,13 +357,14 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "odScoreAtt",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -368,13 +379,14 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: -1,
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "odScoreDef",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -389,13 +401,14 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: -1,
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "odScoreTotal",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -410,13 +423,36 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: -1,
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "points",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: points < 0",
args: args{
id: validTribeCursor.ID(),
serverKey: validTribeCursor.ServerKey(),
odScoreAtt: validTribeCursor.ODScoreAtt(),
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: -1,
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "TribeCursor",
Field: "mostPoints",
Err: domain.MinGreaterEqualError[int]{
Min: 0,
Current: -1,
},
@ -434,6 +470,7 @@ func TestNewTribeCursor(t *testing.T) {
odScoreDef: validTribeCursor.ODScoreDef(),
odScoreTotal: validTribeCursor.ODScoreTotal(),
points: validTribeCursor.Points(),
mostPoints: validTribeCursor.MostPoints(),
dominance: validTribeCursor.Dominance(),
deletedAt: validTribeCursor.DeletedAt(),
},
@ -456,6 +493,7 @@ func TestNewTribeCursor(t *testing.T) {
tt.args.odScoreDef,
tt.args.odScoreTotal,
tt.args.points,
tt.args.mostPoints,
tt.args.dominance,
tt.args.deletedAt,
)
@ -469,6 +507,7 @@ func TestNewTribeCursor(t *testing.T) {
assert.Equal(t, tt.args.odScoreDef, tc.ODScoreDef())
assert.Equal(t, tt.args.odScoreTotal, tc.ODScoreTotal())
assert.Equal(t, tt.args.points, tc.Points())
assert.Equal(t, tt.args.mostPoints, tc.MostPoints())
assert.InDelta(t, tt.args.dominance, tc.Dominance(), 0.001)
assert.Equal(t, tt.args.deletedAt, tc.DeletedAt())
assert.NotEmpty(t, tc.Encode())
@ -513,7 +552,7 @@ func TestListTribesParams_SetIDs(t *testing.T) {
Model: "ListTribesParams",
Field: "ids",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1125,7 +1164,7 @@ func TestListTribesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribesParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -1139,7 +1178,7 @@ func TestListTribesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListTribesParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.TribeListMaxLimit,
Current: domain.TribeListMaxLimit + 1,
},
@ -1205,3 +1244,57 @@ func TestNewListTribesResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
}
func TestCountTribesParams_SetServerKeys(t *testing.T) {
t.Parallel()
type args struct {
serverKeys []string
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
serverKeys: []string{
domaintest.RandServerKey(),
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
serverKeys: []string{serverKeyTest.key},
},
expectedErr: domain.SliceElementValidationError{
Model: "CountTribesParams",
Field: "serverKeys",
Index: 0,
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewCountTribesParams()
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
})
}
}

View File

@ -122,56 +122,56 @@ func (e SliceElementValidationError) Unwrap() error {
return e.Err
}
type MinGreaterEqualError struct {
Min int
Current int
type MinGreaterEqualError[T float32 | float64 | int] struct {
Min T
Current T
}
var _ ErrorWithParams = MinGreaterEqualError{}
var _ ErrorWithParams = MinGreaterEqualError[int]{}
func (e MinGreaterEqualError) Error() string {
return fmt.Sprintf("must be no less than %d (current: %d)", e.Min, e.Current)
func (e MinGreaterEqualError[T]) Error() string {
return fmt.Sprintf("must be no less than %v (current: %v)", e.Min, e.Current)
}
func (e MinGreaterEqualError) Type() ErrorType {
func (e MinGreaterEqualError[T]) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeMinGreaterEqual = "min-greater-equal"
func (e MinGreaterEqualError) Code() string {
func (e MinGreaterEqualError[T]) Code() string {
return errorCodeMinGreaterEqual
}
func (e MinGreaterEqualError) Params() map[string]any {
func (e MinGreaterEqualError[T]) Params() map[string]any {
return map[string]any{
"Min": e.Min,
"Current": e.Current,
}
}
type MaxLessEqualError struct {
Max int
Current int
type MaxLessEqualError[T float32 | float64 | int] struct {
Max T
Current T
}
var _ ErrorWithParams = MaxLessEqualError{}
var _ ErrorWithParams = MaxLessEqualError[int]{}
func (e MaxLessEqualError) Error() string {
return fmt.Sprintf("must be no greater than %d (current: %d)", e.Max, e.Current)
func (e MaxLessEqualError[T]) Error() string {
return fmt.Sprintf("must be no greater than %v (current: %v)", e.Max, e.Current)
}
func (e MaxLessEqualError) Type() ErrorType {
func (e MaxLessEqualError[T]) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeMaxLessEqual = "max-less-equal"
func (e MaxLessEqualError) Code() string {
func (e MaxLessEqualError[T]) Code() string {
return errorCodeMaxLessEqual
}
func (e MaxLessEqualError) Params() map[string]any {
func (e MaxLessEqualError[T]) Params() map[string]any {
return map[string]any{
"Max": e.Max,
"Current": e.Current,
@ -356,16 +356,16 @@ func validateServerKey(key string) error {
return validateStringLen(key, serverKeyMinLength, serverKeyMaxLength)
}
func validateIntInRange(current, min, max int) error {
func validateInRange[T float32 | float64 | int](current, min, max T) error {
if current < min {
return MinGreaterEqualError{
return MinGreaterEqualError[T]{
Min: min,
Current: current,
}
}
if current > max {
return MaxLessEqualError{
return MaxLessEqualError[T]{
Max: max,
Current: current,
}

View File

@ -12,7 +12,7 @@ type Version struct {
timezone string
}
var versionModelName = "Version"
const versionModelName = "Version"
// UnmarshalVersionFromDatabase unmarshals Version from the database.
//
@ -269,7 +269,7 @@ func (params *ListVersionsParams) Limit() int {
}
func (params *ListVersionsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, VersionListMaxLimit); err != nil {
if err := validateInRange(limit, 1, VersionListMaxLimit); err != nil {
return ValidationError{
Model: listVersionsParamsModelName,
Field: "limit",

View File

@ -324,7 +324,7 @@ func TestListVersionsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListVersionsParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -338,7 +338,7 @@ func TestListVersionsParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListVersionsParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.VersionListMaxLimit,
Current: domain.VersionListMaxLimit + 1,
},

View File

@ -49,7 +49,7 @@ func UnmarshalVillageFromDatabase(
rawProfileURL string,
createdAt time.Time,
) (Village, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return Village{}, ValidationError{
Model: villageModelName,
Field: "id",
@ -263,7 +263,7 @@ func UnmarshalVillageMetaFromDatabase(
continent string,
rawProfileURL string,
) (VillageMeta, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return VillageMeta{}, ValidationError{
Model: villageMetaModelName,
Field: "id",
@ -423,7 +423,7 @@ type VillageCursor struct {
const villageCursorModelName = "VillageCursor"
func NewVillageCursor(id int, serverKey string) (VillageCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return VillageCursor{}, ValidationError{
Model: villageCursorModelName,
Field: "id",
@ -528,7 +528,7 @@ func (params *ListVillagesParams) IDs() []int {
func (params *ListVillagesParams) SetIDs(ids []int) error {
for i, id := range ids {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listVillagesParamsModelName,
Field: "ids",
@ -623,7 +623,7 @@ func (params *ListVillagesParams) PlayerIDs() []int {
func (params *ListVillagesParams) SetPlayerIDs(playerIDs []int) error {
for i, id := range playerIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listVillagesParamsModelName,
Field: "playerIDs",
@ -644,7 +644,7 @@ func (params *ListVillagesParams) TribeIDs() []int {
func (params *ListVillagesParams) SetTribeIDs(tribeIDs []int) error {
for i, id := range tribeIDs {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
if err := validateInRange(id, 1, math.MaxInt); err != nil {
return SliceElementValidationError{
Model: listVillagesParamsModelName,
Field: "tribeIDs",
@ -711,7 +711,7 @@ func (params *ListVillagesParams) Limit() int {
}
func (params *ListVillagesParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, VillageListMaxLimit); err != nil {
if err := validateInRange(limit, 1, VillageListMaxLimit); err != nil {
return ValidationError{
Model: listVillagesParamsModelName,
Field: "limit",

View File

@ -181,7 +181,7 @@ func TestNewVillageCursor(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "VillageCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -260,7 +260,7 @@ func TestListVillagesParams_SetIDs(t *testing.T) {
Model: "ListVillagesParams",
Field: "ids",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -545,7 +545,7 @@ func TestListVillagesParams_SetPlayerIDs(t *testing.T) {
Model: "ListVillagesParams",
Field: "playerIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -605,7 +605,7 @@ func TestListVillagesParams_SetTribeIDs(t *testing.T) {
Model: "ListVillagesParams",
Field: "tribeIDs",
Index: 3,
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -814,7 +814,7 @@ func TestListVillagesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListVillagesParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Err: domain.MinGreaterEqualError[int]{
Min: 1,
Current: 0,
},
@ -828,7 +828,7 @@ func TestListVillagesParams_SetLimit(t *testing.T) {
expectedErr: domain.ValidationError{
Model: "ListVillagesParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Err: domain.MaxLessEqualError[int]{
Max: domain.VillageListMaxLimit,
Current: domain.VillageListMaxLimit + 1,
},

View File

@ -158,10 +158,12 @@ func TestDataSync(t *testing.T) {
ctx,
port.NewServerWatermillConsumer(
serverSvc,
nil,
serverSub,
nopLogger,
marshaler,
serverCmdSync,
"",
serverEventSynced,
tribeEventSynced,
playerEventSynced,
@ -249,7 +251,11 @@ func TestDataSync(t *testing.T) {
assert.Equal(collect, expected["Open"], actual.Open(), msg)
assert.Equal(collect, expected["VersionCode"], actual.VersionCode(), msg)
assert.EqualValues(collect, expected["NumPlayers"], actual.NumPlayers(), msg)
assert.EqualValues(collect, expected["NumActivePlayers"], actual.NumActivePlayers(), msg)
assert.EqualValues(collect, expected["NumInactivePlayers"], actual.NumInactivePlayers(), msg)
assert.EqualValues(collect, expected["NumTribes"], actual.NumTribes(), msg)
assert.EqualValues(collect, expected["NumActiveTribes"], actual.NumActiveTribes(), msg)
assert.EqualValues(collect, expected["NumInactiveTribes"], actual.NumInactiveTribes(), msg)
assert.EqualValues(collect, expected["NumVillages"], actual.NumVillages(), msg)
assert.EqualValues(collect, expected["NumPlayerVillages"], actual.NumPlayerVillages(), msg)
assert.EqualValues(collect, expected["NumBonusVillages"], actual.NumBonusVillages(), msg)
@ -257,8 +263,7 @@ func TestDataSync(t *testing.T) {
collect,
expected["NumBarbarianVillages"],
actual.NumBarbarianVillages(),
"Key=%s",
expected["Key"],
msg,
)
assert.WithinDuration(collect, time.Now(), actual.PlayerDataSyncedAt(), time.Minute, msg)
assert.WithinDuration(collect, time.Now(), actual.TribeDataSyncedAt(), time.Minute, msg)
@ -267,8 +272,7 @@ func TestDataSync(t *testing.T) {
collect,
string(marshalJSON(collect, expected["Config"])),
string(marshalJSON(collect, serverConfigToMap(actual.Config()))),
"Key=%s",
expected["Key"],
msg,
)
assert.JSONEq(
collect,

View File

@ -138,6 +138,7 @@ func TestEnnoblementSync(t *testing.T) {
ctx,
port.NewServerWatermillConsumer(
serverSvc,
nil,
serverSub,
nopLogger,
marshaler,
@ -146,6 +147,7 @@ func TestEnnoblementSync(t *testing.T) {
"",
"",
"",
"",
ennoblementEventSynced,
"",
"",

View File

@ -70,6 +70,7 @@ func TestSnapshotCreation(t *testing.T) {
)
// events/commands
serverSnapshotCmdCreate := gofakeit.UUID()
tribeSnapshotCmdCreate := gofakeit.UUID()
tribeSnapshotEventCreated := gofakeit.UUID()
playerSnapshotCmdCreate := gofakeit.UUID()
@ -80,8 +81,15 @@ func TestSnapshotCreation(t *testing.T) {
serverRepo := adapter.NewServerBunRepository(db)
tribeRepo := adapter.NewTribeBunRepository(db)
playerRepo := adapter.NewPlayerBunRepository(db)
serverSnapshotRepo := adapter.NewServerSnapshotBunRepository(db)
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(db)
playerSnapshotRepo := adapter.NewPlayerSnapshotBunRepository(db)
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
tribePub,
marshaler,
serverSnapshotCmdCreate,
"",
)
tribeSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
tribePub,
marshaler,
@ -100,19 +108,28 @@ func TestSnapshotCreation(t *testing.T) {
serverSvc := app.NewServerService(serverRepo, nil, nil)
tribeSvc := app.NewTribeService(tribeRepo, nil, nil)
playerSvc := app.NewPlayerService(playerRepo, nil, nil, nil)
serverSnapshotSvc := app.NewServerSnapshotService(serverSnapshotRepo, serverSvc, serverSnapshotPublisher)
tribeSnapshotSvc := app.NewTribeSnapshotService(tribeSnapshotRepo, tribeSvc, tribeSnapshotPublisher)
playerSnapshotSvc := app.NewPlayerSnapshotService(playerSnapshotRepo, playerSvc, playerSnapshotPublisher)
snapshotSvc := app.NewSnapshotService(versionSvc, serverSvc, tribeSnapshotPublisher, playerSnapshotPublisher)
snapshotSvc := app.NewSnapshotService(
versionSvc,
serverSvc,
serverSnapshotPublisher,
tribeSnapshotPublisher,
playerSnapshotPublisher,
)
watermilltest.RunRouterWithContext(
t,
ctx,
port.NewServerWatermillConsumer(
serverSvc,
serverSnapshotSvc,
serverSub,
nopLogger,
marshaler,
"",
serverSnapshotCmdCreate,
"",
"",
"",
@ -155,31 +172,85 @@ func TestSnapshotCreation(t *testing.T) {
assert.EventuallyWithTf(t, func(collect *assert.CollectT) {
require.NoError(collect, ctx.Err())
listParams := domain.NewListServersParams()
require.NoError(collect, listParams.SetSort([]domain.ServerSort{
listServersParams := domain.NewListServersParams()
require.NoError(collect, listServersParams.SetSort([]domain.ServerSort{
domain.ServerSortKeyASC,
}))
require.NoError(collect, listParams.SetSpecial(domain.NullBool{
require.NoError(collect, listServersParams.SetSpecial(domain.NullBool{
V: false,
Valid: true,
}))
require.NoError(collect, listParams.SetLimit(domain.ServerListMaxLimit))
require.NoError(collect, listServersParams.SetLimit(domain.ServerListMaxLimit))
var allServers domain.Servers
for {
res, err := serverRepo.List(ctx, listParams)
res, err := serverRepo.List(ctx, listServersParams)
require.NoError(collect, err)
for _, s := range res.Servers() {
assert.WithinDuration(collect, time.Now(), s.SnapshotCreatedAt(), time.Minute, s.Key())
assert.WithinDuration(collect, time.Now(), s.PlayerSnapshotsCreatedAt(), time.Minute, s.Key())
assert.WithinDuration(collect, time.Now(), s.TribeSnapshotsCreatedAt(), time.Minute, s.Key())
}
allServers = append(allServers, res.Servers()...)
if res.Next().IsZero() {
return
break
}
require.NoError(collect, listParams.SetCursor(res.Next()))
require.NoError(collect, listServersParams.SetCursor(res.Next()))
}
listSnapshotsParams := domain.NewListServerSnapshotsParams()
require.NoError(collect, listSnapshotsParams.SetSort([]domain.ServerSnapshotSort{
domain.ServerSnapshotSortServerKeyASC,
domain.ServerSnapshotSortDateASC,
domain.ServerSnapshotSortIDASC,
}))
require.NoError(collect, listSnapshotsParams.SetLimit(domain.ServerSnapshotListMaxLimit))
cnt := 0
for {
res, err := serverSnapshotRepo.List(ctx, listSnapshotsParams)
require.NoError(collect, err)
for _, ss := range res.ServerSnapshots() {
cnt++
msg := fmt.Sprintf("ServerKey=%s", ss.ServerKey())
idx := slices.IndexFunc(allServers, func(s domain.Server) bool {
return s.Key() == ss.ServerKey()
})
if !assert.GreaterOrEqual(
collect,
idx,
0,
msg,
) {
continue
}
server := allServers[idx]
assert.NotZero(collect, ss.ID(), msg)
assert.Equal(collect, server.Key(), ss.ServerKey(), msg)
assert.Equal(collect, server.NumVillages(), ss.NumVillages(), msg)
assert.WithinDuration(collect, time.Now(), ss.CreatedAt(), time.Minute, msg)
assert.WithinDuration(collect, time.Now(), ss.Date(), 24*time.Hour, msg)
}
if res.Next().IsZero() {
break
}
require.NoError(collect, listSnapshotsParams.SetCursor(res.Next()))
}
//nolint:testifylint
assert.Equal(collect, len(allServers), cnt)
}, 30*time.Second, 500*time.Millisecond, "servers")
}()

View File

@ -10,10 +10,12 @@ import (
type ServerWatermillConsumer struct {
svc *app.ServerService
snapshotSvc *app.ServerSnapshotService
subscriber message.Subscriber
logger watermill.LoggerAdapter
marshaler watermillmsg.Marshaler
cmdSyncTopic string
cmdCreateSnapshotsTopic string
eventServerSyncedTopic string
eventTribesSyncedTopic string
eventPlayersSyncedTopic string
@ -25,10 +27,12 @@ type ServerWatermillConsumer struct {
func NewServerWatermillConsumer(
svc *app.ServerService,
snapshotSvc *app.ServerSnapshotService,
subscriber message.Subscriber,
logger watermill.LoggerAdapter,
marshaler watermillmsg.Marshaler,
cmdSyncTopic string,
cmdCreateSnapshotsTopic string,
eventServerSyncedTopic string,
eventTribesSyncedTopic string,
eventPlayersSyncedTopic string,
@ -39,10 +43,12 @@ func NewServerWatermillConsumer(
) *ServerWatermillConsumer {
return &ServerWatermillConsumer{
svc: svc,
snapshotSvc: snapshotSvc,
subscriber: subscriber,
logger: logger,
marshaler: marshaler,
cmdSyncTopic: cmdSyncTopic,
cmdCreateSnapshotsTopic: cmdCreateSnapshotsTopic,
eventServerSyncedTopic: eventServerSyncedTopic,
eventTribesSyncedTopic: eventTribesSyncedTopic,
eventPlayersSyncedTopic: eventPlayersSyncedTopic,
@ -55,6 +61,12 @@ func NewServerWatermillConsumer(
func (c *ServerWatermillConsumer) Register(router *message.Router) {
router.AddNoPublisherHandler("ServerConsumer.sync", c.cmdSyncTopic, c.subscriber, c.sync)
router.AddNoPublisherHandler(
"ServerConsumer.createSnapshots",
c.cmdCreateSnapshotsTopic,
c.subscriber,
c.createSnapshots,
)
router.AddNoPublisherHandler(
"ServerConsumer.syncConfigAndInfo",
c.eventServerSyncedTopic,
@ -141,6 +153,32 @@ func (c *ServerWatermillConsumer) syncConfigAndInfo(msg *message.Message) error
return c.svc.SyncConfigAndInfo(msg.Context(), payload)
}
func (c *ServerWatermillConsumer) createSnapshots(msg *message.Message) error {
var rawPayload watermillmsg.CreateSnapshotsCmdPayload
if err := c.marshaler.Unmarshal(msg, &rawPayload); err != nil {
c.logger.Error("couldn't unmarshal payload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
payload, err := domain.NewCreateSnapshotsCmdPayload(
rawPayload.ServerKey,
rawPayload.VersionCode,
rawPayload.VersionTimezone,
rawPayload.Date,
)
if err != nil {
c.logger.Error("couldn't construct domain.CreateSnapshotsCmdPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
return c.snapshotSvc.Create(msg.Context(), payload)
}
func (c *ServerWatermillConsumer) updateNumTribes(msg *message.Message) error {
var rawPayload watermillmsg.TribesSyncedEventPayload
@ -156,6 +194,7 @@ func (c *ServerWatermillConsumer) updateNumTribes(msg *message.Message) error {
rawPayload.ServerURL,
rawPayload.VersionCode,
rawPayload.NumTribes,
rawPayload.NumActiveTribes,
)
if err != nil {
c.logger.Error("couldn't construct domain.TribesSyncedEventPayload", err, watermill.LogFields{
@ -182,6 +221,7 @@ func (c *ServerWatermillConsumer) updateNumPlayers(msg *message.Message) error {
rawPayload.ServerURL,
rawPayload.VersionCode,
rawPayload.NumPlayers,
rawPayload.NumActivePlayers,
)
if err != nil {
c.logger.Error("couldn't construct domain.PlayersSyncedEventPayload", err, watermill.LogFields{

View File

@ -20,6 +20,7 @@ type apiHTTPHandler struct {
villageSvc *app.VillageService
ennoblementSvc *app.EnnoblementService
tribeChangeSvc *app.TribeChangeService
serverSnapshotSvc *app.ServerSnapshotService
tribeSnapshotSvc *app.TribeSnapshotService
playerSnapshotSvc *app.PlayerSnapshotService
errorRenderer apiErrorRenderer
@ -40,6 +41,7 @@ func NewAPIHTTPHandler(
villageSvc *app.VillageService,
ennoblementSvc *app.EnnoblementService,
tribeChangeSvc *app.TribeChangeService,
serverSnapshotSvc *app.ServerSnapshotService,
tribeSnapshotSvc *app.TribeSnapshotService,
playerSnapshotSvc *app.PlayerSnapshotService,
opts ...APIHTTPHandlerOption,
@ -54,6 +56,7 @@ func NewAPIHTTPHandler(
villageSvc: villageSvc,
ennoblementSvc: ennoblementSvc,
tribeChangeSvc: tribeChangeSvc,
serverSnapshotSvc: serverSnapshotSvc,
tribeSnapshotSvc: tribeSnapshotSvc,
playerSnapshotSvc: playerSnapshotSvc,
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {

View File

@ -258,7 +258,7 @@ func TestListEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -294,7 +294,7 @@ func TestListEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.EnnoblementListMaxLimit,
Current: limit,
}
@ -866,7 +866,7 @@ func TestListPlayerEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -902,7 +902,7 @@ func TestListPlayerEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.EnnoblementListMaxLimit,
Current: limit,
}
@ -1532,7 +1532,7 @@ func TestListTribeEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -1568,7 +1568,7 @@ func TestListTribeEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.EnnoblementListMaxLimit,
Current: limit,
}
@ -2185,7 +2185,7 @@ func TestListVillageEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -2221,7 +2221,7 @@ func TestListVillageEnnoblements(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.EnnoblementListMaxLimit,
Current: limit,
}

View File

@ -148,7 +148,6 @@ func (re apiErrorRenderer) domainErrorToAPIError(domainErr domain.Error) apiErro
var err error = domainErr
for {
var withPath domain.ErrorWithPath
if !errors.As(err, &withPath) {
break
}

View File

@ -24,6 +24,8 @@ var apiPlayerSortAllowedValues = []domain.PlayerSort{
domain.PlayerSortODScoreTotalDESC,
domain.PlayerSortPointsASC,
domain.PlayerSortPointsDESC,
domain.PlayerSortMostPointsASC,
domain.PlayerSortMostPointsDESC,
domain.PlayerSortDeletedAtASC,
domain.PlayerSortDeletedAtDESC,
}

View File

@ -192,7 +192,7 @@ func TestListPlayerPlayerSnapshots(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -228,7 +228,7 @@ func TestListPlayerPlayerSnapshots(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.PlayerSnapshotListMaxLimit,
Current: limit,
}

View File

@ -181,6 +181,33 @@ func TestListVersionPlayers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.Server.Key, b.Server.Key),
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "OK: name",
reqModifier: func(t *testing.T, req *http.Request) {
@ -343,7 +370,7 @@ func TestListVersionPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -379,7 +406,7 @@ func TestListVersionPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.PlayerListMaxLimit,
Current: limit,
}
@ -653,7 +680,7 @@ func TestListVersionPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
id, err := strconv.Atoi(req.URL.Query().Get("id"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: id,
}
@ -1023,6 +1050,32 @@ func TestListServerPlayers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "OK: name",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1185,7 +1238,7 @@ func TestListServerPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -1221,7 +1274,7 @@ func TestListServerPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.PlayerListMaxLimit,
Current: limit,
}
@ -1495,7 +1548,7 @@ func TestListServerPlayers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
id, err := strconv.Atoi(req.URL.Query().Get("id"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: id,
}
@ -1863,6 +1916,32 @@ func TestListTribeMembers(t *testing.T) {
}))
},
},
{
name: "OK: sort=[mostPoints:DESC]",
reqModifier: func(t *testing.T, req *http.Request) {
t.Helper()
q := req.URL.Query()
q.Set("sort", "mostPoints:DESC")
req.URL.RawQuery = q.Encode()
},
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
t.Helper()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// body
body := decodeJSON[apimodel.ListPlayersWithServersResponse](t, resp.Body)
assert.Zero(t, body.Cursor.Next)
assert.NotZero(t, body.Cursor.Self)
assert.NotZero(t, body.Data)
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.PlayerWithServer) int {
return cmp.Or(
cmp.Compare(a.MostPoints, b.MostPoints)*-1,
cmp.Compare(a.Id, b.Id),
)
}))
},
},
{
name: "ERR: limit is not an integer",
reqModifier: func(t *testing.T, req *http.Request) {
@ -1909,7 +1988,7 @@ func TestListTribeMembers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: limit,
}
@ -1945,7 +2024,7 @@ func TestListTribeMembers(t *testing.T) {
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
require.NoError(t, err)
domainErr := domain.MaxLessEqualError{
domainErr := domain.MaxLessEqualError[int]{
Max: domain.PlayerListMaxLimit,
Current: limit,
}
@ -2339,7 +2418,7 @@ func TestGetPlayer(t *testing.T) {
require.Len(t, pathSegments, 8)
id, err := strconv.Atoi(pathSegments[7])
require.NoError(t, err)
domainErr := domain.MinGreaterEqualError{
domainErr := domain.MinGreaterEqualError[int]{
Min: 1,
Current: id,
}

Some files were not shown because too many files have changed in this diff Show More