feat: tribe consumer (#11)

Reviewed-on: twhelp/corev3#11
This commit is contained in:
Dawid Wysokiński 2023-12-27 08:07:40 +00:00
parent 206eed966e
commit b5b699ca49
37 changed files with 1671 additions and 263 deletions

View File

@ -64,6 +64,42 @@ var cmdConsumer = &cli.Command{
) )
consumer.Register(router) consumer.Register(router)
return nil
},
)
},
},
{
Name: "tribe",
Usage: "Run the worker responsible for consuming tribe-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
"TribeConsumer",
func(
c *cli.Context,
router *message.Router,
logger watermill.LoggerAdapter,
publisher *amqp.Publisher,
subscriber *amqp.Subscriber,
marshaler watermillmsg.Marshaler,
db *bun.DB,
) error {
twSvc, err := newTWServiceFromFlags(c)
if err != nil {
return err
}
consumer := port.NewTribeWatermillConsumer(
app.NewTribeService(twSvc),
subscriber,
logger,
marshaler,
c.String(rmqFlagTopicServerSyncedEvent.Name),
)
consumer.Register(router)
return nil return nil
}, },
) )

View File

@ -3,6 +3,7 @@ package adapter
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/tw" "gitea.dwysokinski.me/twhelp/corev3/internal/tw"
@ -16,7 +17,7 @@ func NewTWHTTP(client *tw.Client) *TWHTTP {
return &TWHTTP{client: client} return &TWHTTP{client: client}
} }
func (t *TWHTTP) GetOpenServers(ctx context.Context, baseURL string) (domain.BaseServers, error) { func (t *TWHTTP) GetOpenServers(ctx context.Context, baseURL *url.URL) (domain.BaseServers, error) {
servers, err := t.client.GetOpenServers(ctx, baseURL) servers, err := t.client.GetOpenServers(ctx, baseURL)
if err != nil { if err != nil {
return nil, err return nil, err
@ -40,7 +41,7 @@ func (t *TWHTTP) convertServersToDomain(servers []tw.Server) (domain.BaseServers
return res, nil return res, nil
} }
func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL string) (domain.ServerConfig, error) { func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL *url.URL) (domain.ServerConfig, error) {
cfg, err := t.client.GetServerConfig(ctx, baseURL) cfg, err := t.client.GetServerConfig(ctx, baseURL)
if err != nil { if err != nil {
return domain.ServerConfig{}, err return domain.ServerConfig{}, err
@ -92,7 +93,7 @@ func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL string) (domain.Se
return res, nil return res, nil
} }
func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitInfo, error) { func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL *url.URL) (domain.UnitInfo, error) {
info, err := t.client.GetUnitInfo(ctx, baseURL) info, err := t.client.GetUnitInfo(ctx, baseURL)
if err != nil { if err != nil {
return domain.UnitInfo{}, err return domain.UnitInfo{}, err
@ -120,7 +121,7 @@ func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitIn
return res, nil return res, nil
} }
func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL string) (domain.BuildingInfo, error) { func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (domain.BuildingInfo, error) {
info, err := t.client.GetBuildingInfo(ctx, baseURL) info, err := t.client.GetBuildingInfo(ctx, baseURL)
if err != nil { if err != nil {
return domain.BuildingInfo{}, err return domain.BuildingInfo{}, err
@ -151,3 +152,52 @@ func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL string) (domain.Bu
return res, nil return res, nil
} }
func (t *TWHTTP) GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error) {
tribes, err := t.client.GetTribes(ctx, baseURL)
if err != nil {
return nil, err
}
return t.convertTribesToDomain(tribes)
}
func (t *TWHTTP) convertTribesToDomain(tribes []tw.Tribe) (domain.BaseTribes, error) {
res := make(domain.BaseTribes, 0, len(tribes))
for _, tr := range tribes {
od, err := domain.NewOpponentsDefeated(
tr.OpponentsDefeated.RankAtt,
tr.OpponentsDefeated.ScoreAtt,
tr.OpponentsDefeated.RankDef,
tr.OpponentsDefeated.ScoreDef,
tr.OpponentsDefeated.RankSup,
tr.OpponentsDefeated.ScoreSup,
tr.OpponentsDefeated.RankTotal,
tr.OpponentsDefeated.ScoreTotal,
)
if err != nil {
return nil, fmt.Errorf("couldn't construct domain.OpponentsDefeated: %w", err)
}
converted, err := domain.NewBaseTribe(
tr.ID,
tr.Name,
tr.Tag,
tr.NumMembers,
tr.NumVillages,
tr.Points,
tr.AllPoints,
tr.Rank,
od,
tr.ProfileURL,
)
if err != nil {
return nil, fmt.Errorf("couldn't construct domain.BaseTribe: %w", err)
}
res = append(res, converted)
}
return res, nil
}

View File

@ -36,7 +36,7 @@ func (pub *ServerWatermillPublisher) CmdSync(ctx context.Context, payloads ...do
for _, p := range payloads { for _, p := range payloads {
msg, err := pub.marshaler.Marshal(ctx, watermillmsg.SyncServersCmdPayload{ msg, err := pub.marshaler.Marshal(ctx, watermillmsg.SyncServersCmdPayload{
VersionCode: p.VersionCode(), VersionCode: p.VersionCode(),
URL: p.URL().String(), URL: p.URL(),
}) })
if err != nil { if err != nil {
return fmt.Errorf("%s: couldn't marshal SyncServersCmdPayload: %w", p.VersionCode(), err) return fmt.Errorf("%s: couldn't marshal SyncServersCmdPayload: %w", p.VersionCode(), err)
@ -62,7 +62,7 @@ func (pub *ServerWatermillPublisher) EventSynced(
msg, err := pub.marshaler.Marshal(ctx, watermillmsg.ServerSyncedEventPayload{ msg, err := pub.marshaler.Marshal(ctx, watermillmsg.ServerSyncedEventPayload{
Key: p.Key(), Key: p.Key(),
VersionCode: p.VersionCode(), VersionCode: p.VersionCode(),
URL: p.URL().String(), URL: p.URL(),
}) })
if err != nil { if err != nil {
return fmt.Errorf("%s: couldn't marshal ServerSyncedEventPayload: %w", p.Key(), err) return fmt.Errorf("%s: couldn't marshal ServerSyncedEventPayload: %w", p.Key(), err)

View File

@ -26,7 +26,7 @@ func NewServerService(repo ServerRepository, twSvc TWService, publisher ServerPu
func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCmdPayload) error { func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCmdPayload) error {
versionCode := payload.VersionCode() versionCode := payload.VersionCode()
openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL().String()) openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL())
if err != nil { if err != nil {
return fmt.Errorf("couldn't get open servers for version code %s: %w", versionCode, err) return fmt.Errorf("couldn't get open servers for version code %s: %w", versionCode, err)
} }
@ -138,19 +138,19 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
} }
func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error { func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
url := payload.URL().String() u := payload.URL()
cfg, err := svc.twSvc.GetServerConfig(ctx, url) cfg, err := svc.twSvc.GetServerConfig(ctx, u)
if err != nil { if err != nil {
return fmt.Errorf("couldn't get server config for server %s: %w", payload.Key(), err) return fmt.Errorf("couldn't get server config for server %s: %w", payload.Key(), err)
} }
buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, url) buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, u)
if err != nil { if err != nil {
return fmt.Errorf("couldn't get building info for server %s: %w", payload.Key(), err) return fmt.Errorf("couldn't get building info for server %s: %w", payload.Key(), err)
} }
unitInfo, err := svc.twSvc.GetUnitInfo(ctx, url) unitInfo, err := svc.twSvc.GetUnitInfo(ctx, u)
if err != nil { if err != nil {
return fmt.Errorf("couldn't get unit info for server %s: %w", payload.Key(), err) return fmt.Errorf("couldn't get unit info for server %s: %w", payload.Key(), err)
} }

View File

@ -0,0 +1,27 @@
package app
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
)
type TribeService struct {
twSvc TWService
}
func NewTribeService(twSvc TWService) *TribeService {
return &TribeService{twSvc: twSvc}
}
func (svc *TribeService) Sync(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
tribes, err := svc.twSvc.GetTribes(ctx, payload.URL())
if err != nil {
return fmt.Errorf("couldn't get tribes for server %s: %w", payload.Key(), err)
}
fmt.Println(payload.URL(), len(tribes))
return nil
}

View File

@ -2,13 +2,15 @@ package app
import ( import (
"context" "context"
"net/url"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
) )
type TWService interface { type TWService interface {
GetOpenServers(ctx context.Context, baseURL string) (domain.BaseServers, error) GetOpenServers(ctx context.Context, baseURL *url.URL) (domain.BaseServers, error)
GetServerConfig(ctx context.Context, baseURL string) (domain.ServerConfig, error) GetServerConfig(ctx context.Context, baseURL *url.URL) (domain.ServerConfig, error)
GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitInfo, error) GetUnitInfo(ctx context.Context, baseURL *url.URL) (domain.UnitInfo, error)
GetBuildingInfo(ctx context.Context, baseURL string) (domain.BuildingInfo, error) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (domain.BuildingInfo, error)
GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error)
} }

View File

@ -11,19 +11,22 @@ type BaseServer struct {
open bool open bool
} }
func NewBaseServer(key, rawURL string, open bool) (BaseServer, error) { const baseServerModelName = "BaseServer"
func NewBaseServer(key string, u *url.URL, open bool) (BaseServer, error) {
if err := validateServerKey(key); err != nil { if err := validateServerKey(key); err != nil {
return BaseServer{}, ValidationError{ return BaseServer{}, ValidationError{
Err: err, Model: baseServerModelName,
Field: "key", Field: "key",
Err: err,
} }
} }
u, err := parseURL(rawURL) if u == nil {
if err != nil {
return BaseServer{}, ValidationError{ return BaseServer{}, ValidationError{
Err: err, Model: baseServerModelName,
Field: "url", Field: "url",
Err: ErrNotNil,
} }
} }

View File

@ -1,6 +1,7 @@
package domain_test package domain_test
import ( import (
"net/url"
"slices" "slices"
"testing" "testing"
@ -17,7 +18,7 @@ func TestNewBaseServer(t *testing.T) {
type args struct { type args struct {
key string key string
url string url *url.URL
open bool open bool
} }
@ -32,20 +33,21 @@ func TestNewBaseServer(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
key: validBaseServer.Key(), key: validBaseServer.Key(),
url: validBaseServer.URL().String(), url: validBaseServer.URL(),
open: validBaseServer.Open(), open: validBaseServer.Open(),
}, },
}, },
{ {
name: "ERR: invalid URL", name: "ERR: url can't be nil",
args: args{ args: args{
key: validBaseServer.Key(), key: validBaseServer.Key(),
url: " ", url: nil,
open: validBaseServer.Open(), open: validBaseServer.Open(),
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Err: domain.InvalidURLError{URL: " "}, Model: "BaseServer",
Field: "url", Field: "url",
Err: domain.ErrNotNil,
}, },
}, },
} }
@ -55,12 +57,13 @@ func TestNewBaseServer(t *testing.T) {
name: serverKeyTest.name, name: serverKeyTest.name,
args: args{ args: args{
key: serverKeyTest.key, key: serverKeyTest.key,
url: validBaseServer.URL().String(), url: validBaseServer.URL(),
open: validBaseServer.Open(), open: validBaseServer.Open(),
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Err: serverKeyTest.expectedErr, Model: "BaseServer",
Field: "key", Field: "key",
Err: serverKeyTest.expectedErr,
}, },
}) })
} }
@ -77,7 +80,7 @@ func TestNewBaseServer(t *testing.T) {
return return
} }
assert.Equal(t, tt.args.key, res.Key()) assert.Equal(t, tt.args.key, res.Key())
assert.Equal(t, tt.args.url, res.URL().String()) assert.Equal(t, tt.args.url, res.URL())
assert.Equal(t, tt.args.open, res.Open()) assert.Equal(t, tt.args.open, res.Open())
}) })
} }

View File

@ -0,0 +1,188 @@
package domain
import "net/url"
type BaseTribe struct {
id int
name string
tag string
numMembers int
numVillages int
points int
allPoints int
rank int
od OpponentsDefeated
profileURL *url.URL
}
const baseTribeModelName = "BaseTribe"
//nolint:gocyclo
func NewBaseTribe(
id int,
name string,
tag string,
numMembers int,
numVillages int,
points int,
allPoints int,
rank int,
od OpponentsDefeated,
profileURL *url.URL,
) (BaseTribe, error) {
if id < 1 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "id",
Err: MinGreaterEqualError{
Min: 1,
Current: id,
},
}
}
if l := len(name); l < tribeNameMinLength || l > tribeNameMaxLength {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "name",
Err: LenOutOfRangeError{
Min: tribeNameMinLength,
Max: tribeNameMaxLength,
Current: l,
},
}
}
// we convert tag to []rune to get the correct length
// e.g. for tags such as Орловы, which takes 12 bytes rather than 6
// explanation: https://golangbyexample.com/number-characters-string-golang/
if l := len([]rune(tag)); l < tribeTagMinLength || l > tribeTagMaxLength {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "tag",
Err: LenOutOfRangeError{
Min: tribeTagMinLength,
Max: tribeTagMaxLength,
Current: l,
},
}
}
if numMembers < 0 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "numMembers",
Err: MinGreaterEqualError{
Min: 0,
Current: numMembers,
},
}
}
if numVillages < 0 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "numVillages",
Err: MinGreaterEqualError{
Min: 0,
Current: numVillages,
},
}
}
if points < 0 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "points",
Err: MinGreaterEqualError{
Min: 0,
Current: points,
},
}
}
if allPoints < 0 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "allPoints",
Err: MinGreaterEqualError{
Min: 0,
Current: allPoints,
},
}
}
if rank < 0 {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "rank",
Err: MinGreaterEqualError{
Min: 0,
Current: rank,
},
}
}
if profileURL == nil {
return BaseTribe{}, ValidationError{
Model: baseTribeModelName,
Field: "profileURL",
Err: ErrNotNil,
}
}
return BaseTribe{
id: id,
name: name,
tag: tag,
numMembers: numMembers,
numVillages: numVillages,
points: points,
allPoints: allPoints,
rank: rank,
od: od,
profileURL: profileURL,
}, nil
}
func (b BaseTribe) ID() int {
return b.id
}
func (b BaseTribe) Name() string {
return b.name
}
func (b BaseTribe) Tag() string {
return b.tag
}
func (b BaseTribe) NumMembers() int {
return b.numMembers
}
func (b BaseTribe) NumVillages() int {
return b.numVillages
}
func (b BaseTribe) Points() int {
return b.points
}
func (b BaseTribe) AllPoints() int {
return b.allPoints
}
func (b BaseTribe) Rank() int {
return b.rank
}
func (b BaseTribe) OD() OpponentsDefeated {
return b.od
}
func (b BaseTribe) ProfileURL() *url.URL {
return b.profileURL
}
type BaseTribes []BaseTribe

View File

@ -0,0 +1,357 @@
package domain_test
import (
"net/url"
"testing"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewBaseTribe(t *testing.T) {
t.Parallel()
validBaseTribe := domaintest.NewBaseTribe(t)
type args struct {
id int
name string
tag string
numMembers int
numVillages int
points int
allPoints int
rank int
od domain.OpponentsDefeated
profileURL *url.URL
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
},
{
name: "OK: tag/name with non-ascii characters",
args: args{
id: validBaseTribe.ID(),
name: "Шубутные",
tag: "Орловы",
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
},
{
name: "ERR: id < 1",
args: args{
id: 0,
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "id",
Err: domain.MinGreaterEqualError{
Min: 1,
Current: 0,
},
},
},
{
name: "ERR: len(name) < 1",
args: args{
id: validBaseTribe.ID(),
name: "",
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "name",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 255,
Current: 0,
},
},
},
{
name: "ERR: len(name) > 255",
args: args{
id: validBaseTribe.ID(),
name: gofakeit.LetterN(256),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "name",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 255,
Current: 256,
},
},
},
{
name: "ERR: len(tag) < 1",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Tag(),
tag: "",
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "tag",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 10,
Current: 0,
},
},
},
{
name: "ERR: len(tag) > 10",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: gofakeit.LetterN(11),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "tag",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 10,
Current: 11,
},
},
},
{
name: "ERR: numMembers < 0",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: -1,
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "numMembers",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: numVillages < 0",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: -1,
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "numVillages",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: points < 0",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: -1,
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "points",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: allPoints < 0",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: -1,
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "allPoints",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: rank < 0",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: -1,
od: validBaseTribe.OD(),
profileURL: validBaseTribe.ProfileURL(),
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "rank",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: profileURL can't be nil",
args: args{
id: validBaseTribe.ID(),
name: validBaseTribe.Name(),
tag: validBaseTribe.Tag(),
numMembers: validBaseTribe.NumMembers(),
numVillages: validBaseTribe.NumVillages(),
points: validBaseTribe.Points(),
allPoints: validBaseTribe.AllPoints(),
rank: validBaseTribe.Rank(),
od: validBaseTribe.OD(),
profileURL: nil,
},
expectedErr: domain.ValidationError{
Model: "BaseTribe",
Field: "profileURL",
Err: domain.ErrNotNil,
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
res, err := domain.NewBaseTribe(
tt.args.id,
tt.args.name,
tt.args.tag,
tt.args.numMembers,
tt.args.numVillages,
tt.args.points,
tt.args.allPoints,
tt.args.rank,
tt.args.od,
tt.args.profileURL,
)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.id, res.ID())
assert.Equal(t, tt.args.name, res.Name())
assert.Equal(t, tt.args.tag, res.Tag())
assert.Equal(t, tt.args.numMembers, res.NumMembers())
assert.Equal(t, tt.args.numVillages, res.NumVillages())
assert.Equal(t, tt.args.points, res.Points())
assert.Equal(t, tt.args.allPoints, res.AllPoints())
assert.Equal(t, tt.args.rank, res.Rank())
assert.Equal(t, tt.args.od, res.OD())
assert.Equal(t, tt.args.profileURL, res.ProfileURL())
})
}
}

View File

@ -33,7 +33,7 @@ func NewBaseServer(tb TestingTB, opts ...func(cfg *BaseServerConfig)) domain.Bas
} }
} }
s, err := domain.NewBaseServer(cfg.Key, cfg.URL.String(), cfg.Open) s, err := domain.NewBaseServer(cfg.Key, cfg.URL, cfg.Open)
require.NoError(tb, err) require.NoError(tb, err)
return s return s

View File

@ -0,0 +1,48 @@
package domaintest
import (
"net/url"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/require"
)
type BaseTribeConfig struct {
ID int
Tag string
OD domain.OpponentsDefeated
}
func NewBaseTribe(tb TestingTB, opts ...func(cfg *BaseTribeConfig)) domain.BaseTribe {
tb.Helper()
cfg := &BaseTribeConfig{
ID: RandID(),
Tag: RandTribeTag(),
OD: NewOpponentsDefeated(tb),
}
for _, opt := range opts {
opt(cfg)
}
u, err := url.ParseRequestURI(gofakeit.URL())
require.NoError(tb, err)
t, err := domain.NewBaseTribe(
cfg.ID,
gofakeit.LetterN(50),
cfg.Tag,
gofakeit.IntRange(1, 10000),
gofakeit.IntRange(1, 10000),
gofakeit.IntRange(1, 10000),
gofakeit.IntRange(1, 10000),
gofakeit.IntRange(1, 10000),
cfg.OD,
u,
)
require.NoError(tb, err)
return t
}

View File

@ -0,0 +1,23 @@
package domaintest
import (
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/require"
)
func NewOpponentsDefeated(tb TestingTB) domain.OpponentsDefeated {
tb.Helper()
cfg, err := domain.NewOpponentsDefeated(
gofakeit.IntRange(1, 100),
gofakeit.IntRange(100000, 1000000),
gofakeit.IntRange(1, 100),
gofakeit.IntRange(100000, 1000000),
gofakeit.IntRange(1, 100),
gofakeit.IntRange(100000, 1000000),
gofakeit.IntRange(1, 100),
gofakeit.IntRange(100000, 1000000),
)
require.NoError(tb, err)
return cfg
}

View File

@ -7,6 +7,7 @@ import (
) )
func NewServerConfig(tb TestingTB) domain.ServerConfig { func NewServerConfig(tb TestingTB) domain.ServerConfig {
tb.Helper()
cfg, err := domain.NewServerConfig( cfg, err := domain.NewServerConfig(
gofakeit.Float64Range(1, 1000), gofakeit.Float64Range(1, 1000),
gofakeit.Float64Range(1, 1000), gofakeit.Float64Range(1, 1000),

View File

@ -0,0 +1,7 @@
package domaintest
import "github.com/brianvoe/gofakeit/v6"
func RandTribeTag() string {
return gofakeit.LetterN(5)
}

View File

@ -7,6 +7,7 @@ import (
) )
func NewUnitInfo(tb TestingTB) domain.UnitInfo { func NewUnitInfo(tb TestingTB) domain.UnitInfo {
tb.Helper()
unitInfo, err := domain.NewUnitInfo( unitInfo, err := domain.NewUnitInfo(
domain.Unit{Speed: gofakeit.Float64Range(1, 25)}, domain.Unit{Speed: gofakeit.Float64Range(1, 25)},
domain.Unit{Pop: gofakeit.IntRange(1, 3000)}, domain.Unit{Pop: gofakeit.IntRange(1, 3000)},

View File

@ -0,0 +1,7 @@
package domaintest
import "github.com/brianvoe/gofakeit/v6"
func RandID() int {
return gofakeit.IntRange(1, 10000000)
}

View File

@ -4,14 +4,14 @@ type ErrorCode uint8
const ( const (
ErrorCodeUnknown ErrorCode = iota ErrorCodeUnknown ErrorCode = iota
ErrorCodeEntityNotFound ErrorCodeNotFound
ErrorCodeIncorrectInput ErrorCodeIncorrectInput
) )
func (e ErrorCode) String() string { func (e ErrorCode) String() string {
switch e { switch e {
case ErrorCodeEntityNotFound: case ErrorCodeNotFound:
return "entity-not-found" return "not-found"
case ErrorCodeIncorrectInput: case ErrorCodeIncorrectInput:
return "incorrect-input" return "incorrect-input"
case ErrorCodeUnknown: case ErrorCodeUnknown:
@ -31,3 +31,23 @@ type ErrorWithParams interface {
Error Error
Params() map[string]any Params() map[string]any
} }
type simpleError struct {
msg string
code ErrorCode
slug string
}
var _ Error = simpleError{}
func (e simpleError) Error() string {
return e.msg
}
func (e simpleError) Code() ErrorCode {
return e.code
}
func (e simpleError) Slug() string {
return e.slug
}

View File

@ -0,0 +1,156 @@
package domain
type OpponentsDefeated struct {
rankAtt int
scoreAtt int
rankDef int
scoreDef int
rankSup int
scoreSup int
rankTotal int
scoreTotal int
}
const opponentsDefeatedModelName = "OpponentsDefeated"
func NewOpponentsDefeated(
rankAtt int,
scoreAtt int,
rankDef int,
scoreDef int,
rankSup int,
scoreSup int,
rankTotal int,
scoreTotal int,
) (OpponentsDefeated, error) {
if rankAtt < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankAtt",
Err: MinGreaterEqualError{
Min: 0,
Current: rankAtt,
},
}
}
if scoreAtt < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreAtt",
Err: MinGreaterEqualError{
Min: 0,
Current: scoreAtt,
},
}
}
if rankDef < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankDef",
Err: MinGreaterEqualError{
Min: 0,
Current: rankDef,
},
}
}
if scoreDef < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreDef",
Err: MinGreaterEqualError{
Min: 0,
Current: scoreDef,
},
}
}
if rankSup < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankSup",
Err: MinGreaterEqualError{
Min: 0,
Current: rankSup,
},
}
}
if scoreSup < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreSup",
Err: MinGreaterEqualError{
Min: 0,
Current: scoreSup,
},
}
}
if rankTotal < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "rankTotal",
Err: MinGreaterEqualError{
Min: 0,
Current: rankTotal,
},
}
}
if scoreTotal < 0 {
return OpponentsDefeated{}, ValidationError{
Model: opponentsDefeatedModelName,
Field: "scoreTotal",
Err: MinGreaterEqualError{
Min: 0,
Current: scoreTotal,
},
}
}
return OpponentsDefeated{
rankAtt: rankAtt,
scoreAtt: scoreAtt,
rankDef: rankDef,
scoreDef: scoreDef,
rankSup: rankSup,
scoreSup: scoreSup,
rankTotal: rankTotal,
scoreTotal: scoreTotal,
}, nil
}
func (o OpponentsDefeated) RankAtt() int {
return o.rankAtt
}
func (o OpponentsDefeated) ScoreAtt() int {
return o.scoreAtt
}
func (o OpponentsDefeated) RankDef() int {
return o.rankDef
}
func (o OpponentsDefeated) ScoreDef() int {
return o.scoreDef
}
func (o OpponentsDefeated) RankSup() int {
return o.rankSup
}
func (o OpponentsDefeated) ScoreSup() int {
return o.scoreSup
}
func (o OpponentsDefeated) RankTotal() int {
return o.rankTotal
}
func (o OpponentsDefeated) ScoreTotal() int {
return o.scoreTotal
}

View File

@ -0,0 +1,246 @@
package domain_test
import (
"testing"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewOpponentsDefeated(t *testing.T) {
t.Parallel()
validOD := domaintest.NewOpponentsDefeated(t)
type args struct {
rankAtt int
scoreAtt int
rankDef int
scoreDef int
rankSup int
scoreSup int
rankTotal int
scoreTotal int
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
},
{
name: "ERR: rankAtt < 0",
args: args{
rankAtt: -1,
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankAtt",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: scoreAtt < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: -1,
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreAtt",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: rankDef < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: -1,
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankDef",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: scoreDef < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: -1,
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreDef",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: rankSup < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: -1,
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankSup",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: scoreSup < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: -1,
rankTotal: validOD.RankTotal(),
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreSup",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: rankTotal < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: -1,
scoreTotal: validOD.ScoreTotal(),
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "rankTotal",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: scoreTotal < 0",
args: args{
rankAtt: validOD.RankAtt(),
scoreAtt: validOD.ScoreAtt(),
rankDef: validOD.RankDef(),
scoreDef: validOD.ScoreDef(),
rankSup: validOD.RankSup(),
scoreSup: validOD.ScoreSup(),
rankTotal: validOD.RankTotal(),
scoreTotal: -1,
},
expectedErr: domain.ValidationError{
Model: "OpponentsDefeated",
Field: "scoreTotal",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
res, err := domain.NewOpponentsDefeated(
tt.args.rankAtt,
tt.args.scoreAtt,
tt.args.rankDef,
tt.args.scoreDef,
tt.args.rankSup,
tt.args.scoreSup,
tt.args.rankTotal,
tt.args.scoreTotal,
)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.rankAtt, res.RankAtt())
assert.Equal(t, tt.args.scoreAtt, res.ScoreAtt())
assert.Equal(t, tt.args.rankDef, res.RankDef())
assert.Equal(t, tt.args.scoreDef, res.ScoreDef())
assert.Equal(t, tt.args.rankSup, res.RankSup())
assert.Equal(t, tt.args.scoreSup, res.ScoreSup())
assert.Equal(t, tt.args.rankTotal, res.RankTotal())
assert.Equal(t, tt.args.scoreTotal, res.ScoreTotal())
})
}
}

View File

@ -8,6 +8,11 @@ import (
"time" "time"
) )
const (
serverKeyMinLength = 1
serverKeyMaxLength = 10
)
type Server struct { type Server struct {
key string key string
versionCode string versionCode string
@ -195,7 +200,7 @@ func (ss Servers) Close(open BaseServers) (BaseServers, error) {
continue continue
} }
base, err := NewBaseServer(s.Key(), s.URL().String(), false) base, err := NewBaseServer(s.Key(), s.URL(), false)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't construct BaseServer for server with key '%s': %w", s.Key(), err) return nil, fmt.Errorf("couldn't construct BaseServer for server with key '%s': %w", s.Key(), err)
} }
@ -211,11 +216,14 @@ type CreateServerParams struct {
versionCode string versionCode string
} }
const createServerParamsModelName = "CreateServerParams"
func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) { func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) {
if err := validateVersionCode(versionCode); err != nil { if err := validateVersionCode(versionCode); err != nil {
return nil, ValidationError{ return nil, ValidationError{
Err: err, Model: createServerParamsModelName,
Field: "versionCode", Field: "versionCode",
Err: err,
} }
} }
@ -304,6 +312,8 @@ type ListServersParams struct {
offset int offset int
} }
const listServersParamsModelName = "ListServersParams"
func NewListServersParams() ListServersParams { func NewListServersParams() ListServersParams {
return ListServersParams{ return ListServersParams{
sort: []ServerSort{ServerSortKeyASC}, sort: []ServerSort{ServerSortKeyASC},
@ -372,6 +382,7 @@ const (
func (params *ListServersParams) SetSort(sort []ServerSort) error { func (params *ListServersParams) SetSort(sort []ServerSort) error {
if err := validateSliceLen(sort, serverSortMinLength, serverSortMaxLength); err != nil { if err := validateSliceLen(sort, serverSortMinLength, serverSortMaxLength); err != nil {
return ValidationError{ return ValidationError{
Model: listServersParamsModelName,
Field: "sort", Field: "sort",
Err: err, Err: err,
} }
@ -389,6 +400,7 @@ func (params *ListServersParams) Limit() int {
func (params *ListServersParams) SetLimit(limit int) error { func (params *ListServersParams) SetLimit(limit int) error {
if limit <= 0 { if limit <= 0 {
return ValidationError{ return ValidationError{
Model: listServersParamsModelName,
Field: "limit", Field: "limit",
Err: MinGreaterEqualError{ Err: MinGreaterEqualError{
Min: 1, Min: 1,
@ -399,6 +411,7 @@ func (params *ListServersParams) SetLimit(limit int) error {
if limit > ServerListMaxLimit { if limit > ServerListMaxLimit {
return ValidationError{ return ValidationError{
Model: listServersParamsModelName,
Field: "limit", Field: "limit",
Err: MaxLessEqualError{ Err: MaxLessEqualError{
Max: ServerListMaxLimit, Max: ServerListMaxLimit,
@ -419,6 +432,7 @@ func (params *ListServersParams) Offset() int {
func (params *ListServersParams) SetOffset(offset int) error { func (params *ListServersParams) SetOffset(offset int) error {
if offset < 0 { if offset < 0 {
return ValidationError{ return ValidationError{
Model: listServersParamsModelName,
Field: "offset", Field: "offset",
Err: MinGreaterEqualError{ Err: MinGreaterEqualError{
Min: 0, Min: 0,
@ -443,7 +457,7 @@ func (e ServerNotFoundError) Error() string {
} }
func (e ServerNotFoundError) Code() ErrorCode { func (e ServerNotFoundError) Code() ErrorCode {
return ErrorCodeEntityNotFound return ErrorCodeNotFound
} }
func (e ServerNotFoundError) Slug() string { func (e ServerNotFoundError) Slug() string {

View File

@ -23,15 +23,6 @@ func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPay
return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil
} }
func NewSyncServersCmdPayloadWithStringURL(versionCode string, rawURL string) (SyncServersCmdPayload, error) {
u, err := parseURL(rawURL)
if err != nil {
return SyncServersCmdPayload{}, err
}
return NewSyncServersCmdPayload(versionCode, u)
}
func (p SyncServersCmdPayload) VersionCode() string { func (p SyncServersCmdPayload) VersionCode() string {
return p.versionCode return p.versionCode
} }
@ -46,7 +37,7 @@ type ServerSyncedEventPayload struct {
versionCode string versionCode string
} }
func NewServerSyncedEventPayload(key string, rawURL string, versionCode string) (ServerSyncedEventPayload, error) { func NewServerSyncedEventPayload(key string, u *url.URL, versionCode string) (ServerSyncedEventPayload, error) {
if key == "" { if key == "" {
return ServerSyncedEventPayload{}, errors.New("key can't be blank") return ServerSyncedEventPayload{}, errors.New("key can't be blank")
} }
@ -55,9 +46,8 @@ func NewServerSyncedEventPayload(key string, rawURL string, versionCode string)
return ServerSyncedEventPayload{}, errors.New("version code can't be blank") return ServerSyncedEventPayload{}, errors.New("version code can't be blank")
} }
u, err := parseURL(rawURL) if u == nil {
if err != nil { return ServerSyncedEventPayload{}, errors.New("url can't be nil")
return ServerSyncedEventPayload{}, err
} }
return ServerSyncedEventPayload{ return ServerSyncedEventPayload{

View File

@ -53,61 +53,12 @@ func TestNewSyncServersCmdPayload(t *testing.T) {
} }
} }
func TestNewSyncServersCmdPayloadWithStringURL(t *testing.T) {
t.Parallel()
type args struct {
versionCode string
url string
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
versionCode: "pl",
url: "https://plemiona.pl",
},
},
{
name: "ERR: invalid url",
args: args{
versionCode: "pl",
url: "plemiona.pl",
},
expectedErr: domain.InvalidURLError{
URL: "plemiona.pl",
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
payload, err := domain.NewSyncServersCmdPayloadWithStringURL(tt.args.versionCode, tt.args.url)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.versionCode, payload.VersionCode())
assert.Equal(t, tt.args.url, payload.URL().String())
})
}
}
func TestNewServerSyncedEventPayload(t *testing.T) { func TestNewServerSyncedEventPayload(t *testing.T) {
t.Parallel() t.Parallel()
server := domaintest.NewServer(t) server := domaintest.NewServer(t)
payload, err := domain.NewServerSyncedEventPayload(server.Key(), server.URL().String(), server.VersionCode()) payload, err := domain.NewServerSyncedEventPayload(server.Key(), server.URL(), server.VersionCode())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, server.Key(), payload.Key()) assert.Equal(t, server.Key(), payload.Key())
assert.Equal(t, server.URL(), payload.URL()) assert.Equal(t, server.URL(), payload.URL())

View File

@ -90,8 +90,9 @@ func TestNewCreateServerParams(t *testing.T) {
}, },
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Err: versionCodeTest.expectedErr, Model: "CreateServerParams",
Field: "versionCode", Field: "versionCode",
Err: versionCodeTest.expectedErr,
}, },
}) })
} }
@ -142,6 +143,7 @@ func TestListServersParams_SetSort(t *testing.T) {
sort: nil, sort: nil,
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "sort", Field: "sort",
Err: domain.LenOutOfRangeError{ Err: domain.LenOutOfRangeError{
Min: 1, Min: 1,
@ -160,6 +162,7 @@ func TestListServersParams_SetSort(t *testing.T) {
}, },
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "sort", Field: "sort",
Err: domain.LenOutOfRangeError{ Err: domain.LenOutOfRangeError{
Min: 1, Min: 1,
@ -211,6 +214,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
limit: 0, limit: 0,
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "limit", Field: "limit",
Err: domain.MinGreaterEqualError{ Err: domain.MinGreaterEqualError{
Min: 1, Min: 1,
@ -224,6 +228,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
limit: domain.ServerListMaxLimit + 1, limit: domain.ServerListMaxLimit + 1,
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "limit", Field: "limit",
Err: domain.MaxLessEqualError{ Err: domain.MaxLessEqualError{
Max: domain.ServerListMaxLimit, Max: domain.ServerListMaxLimit,
@ -274,6 +279,7 @@ func TestListServersParams_SetOffset(t *testing.T) {
offset: -1, offset: -1,
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListServersParams",
Field: "offset", Field: "offset",
Err: domain.MinGreaterEqualError{ Err: domain.MinGreaterEqualError{
Min: 0, Min: 0,

116
internal/domain/tribe.go Normal file
View File

@ -0,0 +1,116 @@
package domain
import (
"net/url"
"time"
)
const (
tribeNameMinLength = 1
tribeNameMaxLength = 255
tribeTagMinLength = 1
tribeTagMaxLength = 10
)
type Tribe struct {
id int
serverKey string
name string
tag string
numMembers int
numVillages int
points int
allPoints int
rank int
od OpponentsDefeated
profileURL *url.URL
dominance float64
bestRank int
bestRankAt time.Time
mostPoints int
mostPointsAt time.Time
mostVillages int
mostVillagesAt time.Time
createdAt time.Time
deletedAt time.Time
}
func (t Tribe) ID() int {
return t.id
}
func (t Tribe) ServerKey() string {
return t.serverKey
}
func (t Tribe) Name() string {
return t.name
}
func (t Tribe) Tag() string {
return t.tag
}
func (t Tribe) NumMembers() int {
return t.numMembers
}
func (t Tribe) NumVillages() int {
return t.numVillages
}
func (t Tribe) Points() int {
return t.points
}
func (t Tribe) AllPoints() int {
return t.allPoints
}
func (t Tribe) Rank() int {
return t.rank
}
func (t Tribe) OD() OpponentsDefeated {
return t.od
}
func (t Tribe) ProfileURL() *url.URL {
return t.profileURL
}
func (t Tribe) Dominance() float64 {
return t.dominance
}
func (t Tribe) BestRank() int {
return t.bestRank
}
func (t Tribe) BestRankAt() time.Time {
return t.bestRankAt
}
func (t Tribe) MostPoints() int {
return t.mostPoints
}
func (t Tribe) MostPointsAt() time.Time {
return t.mostPointsAt
}
func (t Tribe) MostVillages() int {
return t.mostVillages
}
func (t Tribe) MostVillagesAt() time.Time {
return t.mostVillagesAt
}
func (t Tribe) CreatedAt() time.Time {
return t.createdAt
}
func (t Tribe) DeletedAt() time.Time {
return t.deletedAt
}

View File

@ -10,7 +10,7 @@ type InvalidURLError struct {
} }
func (e InvalidURLError) Error() string { func (e InvalidURLError) Error() string {
return fmt.Sprintf("'%s': invalid URL", e.URL) return fmt.Sprintf("%s: invalid URL", e.URL)
} }
func parseURL(rawURL string) (*url.URL, error) { func parseURL(rawURL string) (*url.URL, error) {

View File

@ -8,6 +8,7 @@ import (
) )
type ValidationError struct { type ValidationError struct {
Model string
Field string Field string
Err error Err error
} }
@ -15,7 +16,20 @@ type ValidationError struct {
var _ ErrorWithParams = ValidationError{} var _ ErrorWithParams = ValidationError{}
func (e ValidationError) Error() string { func (e ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Err) prefix := e.Model
if e.Field != "" {
if len(prefix) > 0 {
prefix += "."
}
prefix += e.Field
}
if prefix != "" {
prefix += ": "
}
return prefix + e.Err.Error()
} }
func (e ValidationError) Code() ErrorCode { func (e ValidationError) Code() ErrorCode {
@ -28,22 +42,35 @@ func (e ValidationError) Code() ErrorCode {
func (e ValidationError) Slug() string { func (e ValidationError) Slug() string {
s := "validation" s := "validation"
var domainErr Error var domainErr Error
if errors.As(e.Err, &domainErr) { if errors.As(e.Err, &domainErr) {
s = domainErr.Slug() s = domainErr.Slug()
} }
return fmt.Sprintf("%s-%s", slug.Make(e.Field), s)
if e.Field != "" {
s = slug.Make(e.Field) + "-" + s
}
if e.Model != "" {
s = slug.Make(e.Model) + "-" + s
}
return s
} }
func (e ValidationError) Params() map[string]any { func (e ValidationError) Params() map[string]any {
var withParams ErrorWithParams var withParams ErrorWithParams
ok := errors.As(e.Err, &withParams) if ok := errors.As(e.Err, &withParams); !ok {
if !ok {
return nil return nil
} }
return withParams.Params() return withParams.Params()
} }
func (e ValidationError) Unwrap() error {
return e.Err
}
type MinGreaterEqualError struct { type MinGreaterEqualError struct {
Min int Min int
Current int Current int
@ -124,6 +151,12 @@ func (e LenOutOfRangeError) Params() map[string]any {
} }
} }
var ErrNotNil error = simpleError{
msg: "must not be nil",
code: ErrorCodeIncorrectInput,
slug: "not-nil",
}
func validateSliceLen[S ~[]E, E any](s S, min, max int) error { func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
if l := len(s); l > max || l < min { if l := len(s); l > max || l < min {
return LenOutOfRangeError{ return LenOutOfRangeError{
@ -136,11 +169,6 @@ func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
return nil return nil
} }
const (
serverKeyMinLength = 1
serverKeyMaxLength = 10
)
func validateServerKey(key string) error { func validateServerKey(key string) error {
if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength { if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength {
return LenOutOfRangeError{ return LenOutOfRangeError{
@ -153,11 +181,6 @@ func validateServerKey(key string) error {
return nil return nil
} }
const (
versionCodeMinLength = 2
versionCodeMaxLength = 2
)
func validateVersionCode(code string) error { func validateVersionCode(code string) error {
if l := len(code); l < versionCodeMinLength || l > versionCodeMaxLength { if l := len(code); l < versionCodeMinLength || l > versionCodeMaxLength {
return LenOutOfRangeError{ return LenOutOfRangeError{

View File

@ -5,6 +5,11 @@ import (
"net/url" "net/url"
) )
const (
versionCodeMinLength = 2
versionCodeMaxLength = 2
)
type Version struct { type Version struct {
code string code string
name string name string
@ -82,6 +87,8 @@ type ListVersionsParams struct {
sort []VersionSort sort []VersionSort
} }
const listVersionsParamsModelName = "ListVersionsParams"
func NewListVersionsParams() ListVersionsParams { func NewListVersionsParams() ListVersionsParams {
return ListVersionsParams{sort: []VersionSort{VersionSortCodeASC}} return ListVersionsParams{sort: []VersionSort{VersionSortCodeASC}}
} }
@ -98,6 +105,7 @@ func (params *ListVersionsParams) Sort() []VersionSort {
func (params *ListVersionsParams) SetSort(sort []VersionSort) error { func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil { if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil {
return ValidationError{ return ValidationError{
Model: listVersionsParamsModelName,
Field: "sort", Field: "sort",
Err: err, Err: err,
} }

View File

@ -34,6 +34,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
sort: nil, sort: nil,
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListVersionsParams",
Field: "sort", Field: "sort",
Err: domain.LenOutOfRangeError{ Err: domain.LenOutOfRangeError{
Min: 1, Min: 1,
@ -51,6 +52,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
}, },
}, },
expectedErr: domain.ValidationError{ expectedErr: domain.ValidationError{
Model: "ListVersionsParams",
Field: "sort", Field: "sort",
Err: domain.LenOutOfRangeError{ Err: domain.LenOutOfRangeError{
Min: 1, Min: 1,

View File

@ -55,7 +55,7 @@ func (c *ServerWatermillConsumer) sync(msg *message.Message) error {
return nil return nil
} }
payload, err := domain.NewSyncServersCmdPayloadWithStringURL(rawPayload.VersionCode, rawPayload.URL) payload, err := domain.NewSyncServersCmdPayload(rawPayload.VersionCode, rawPayload.URL)
if err != nil { if err != nil {
c.logger.Error("couldn't construct domain.SyncServersCmdPayload", err, watermill.LogFields{ c.logger.Error("couldn't construct domain.SyncServersCmdPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()), "handler": message.HandlerNameFromCtx(msg.Context()),

View File

@ -0,0 +1,63 @@
package port
import (
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/watermillmsg"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
)
type TribeWatermillConsumer struct {
svc *app.TribeService
subscriber message.Subscriber
logger watermill.LoggerAdapter
marshaler watermillmsg.Marshaler
eventServerSyncedTopic string
}
func NewTribeWatermillConsumer(
svc *app.TribeService,
subscriber message.Subscriber,
logger watermill.LoggerAdapter,
marshaler watermillmsg.Marshaler,
eventServerSyncedTopic string,
) *TribeWatermillConsumer {
return &TribeWatermillConsumer{
svc: svc,
subscriber: subscriber,
logger: logger,
marshaler: marshaler,
eventServerSyncedTopic: eventServerSyncedTopic,
}
}
func (c *TribeWatermillConsumer) Register(router *message.Router) {
router.AddNoPublisherHandler(
"TribeConsumer.sync",
c.eventServerSyncedTopic,
c.subscriber,
c.sync,
)
}
func (c *TribeWatermillConsumer) sync(msg *message.Message) error {
var rawPayload watermillmsg.ServerSyncedEventPayload
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.NewServerSyncedEventPayload(rawPayload.Key, rawPayload.URL, rawPayload.VersionCode)
if err != nil {
c.logger.Error("couldn't construct domain.ServerSyncedEventPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
return c.svc.Sync(msg.Context(), payload)
}

View File

@ -59,19 +59,19 @@ func NewClient(opts ...ClientOption) *Client {
} }
} }
var ErrBaseURLCantBeNil = errors.New("baseURL can't be nil")
var ( var (
ErrInvalidServerKey = errors.New("invalid server key") ErrInvalidServerKey = errors.New("invalid server key")
ErrInvalidServerURL = errors.New("invalid server URL") ErrInvalidServerURL = errors.New("invalid server URL")
) )
func (c *Client) GetOpenServers(ctx context.Context, rawBaseURL string) ([]Server, error) { func (c *Client) GetOpenServers(ctx context.Context, baseURL *url.URL) ([]Server, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return nil, ErrBaseURLCantBeNil
return nil, err
} }
u := buildURL(baseURL, endpointGetServers) resp, err := c.get(ctx, buildURL(baseURL, endpointGetServers))
resp, err := c.get(ctx, u)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,86 +79,54 @@ func (c *Client) GetOpenServers(ctx context.Context, rawBaseURL string) ([]Serve
_ = resp.Body.Close() _ = resp.Body.Close()
}() }()
b, err := io.ReadAll(resp.Body) return serverParser{r: resp.Body}.parse()
if err != nil {
return nil, fmt.Errorf("%s: couldn't read response body: %w", u, err)
}
m, err := phpserialize.UnmarshalAssociativeArray(b)
if err != nil {
return nil, fmt.Errorf("%s: couldn't unmarshal response body: %w", u, err)
}
servers := make([]Server, 0, len(m))
for key, val := range m {
keyStr, ok := key.(string)
if !ok || keyStr == "" {
return nil, fmt.Errorf("%s: parsing '%v': %w", u, key, ErrInvalidServerKey)
}
urlStr, ok := val.(string)
if !ok || urlStr == "" {
return nil, fmt.Errorf("%s: parsing '%v': %w", u, val, ErrInvalidServerURL)
}
servers = append(servers, Server{
Key: keyStr,
URL: urlStr,
})
}
return servers, nil
} }
func (c *Client) GetServerConfig(ctx context.Context, rawBaseURL string) (ServerConfig, error) { func (c *Client) GetServerConfig(ctx context.Context, baseURL *url.URL) (ServerConfig, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return ServerConfig{}, ErrBaseURLCantBeNil
return ServerConfig{}, err
} }
var cfg ServerConfig var cfg ServerConfig
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil { if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil {
return ServerConfig{}, err return ServerConfig{}, err
} }
return cfg, nil return cfg, nil
} }
func (c *Client) GetBuildingInfo(ctx context.Context, rawBaseURL string) (BuildingInfo, error) { func (c *Client) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (BuildingInfo, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return BuildingInfo{}, ErrBaseURLCantBeNil
return BuildingInfo{}, err
} }
var info BuildingInfo var info BuildingInfo
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil { if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil {
return BuildingInfo{}, err return BuildingInfo{}, err
} }
return info, nil return info, nil
} }
func (c *Client) GetUnitInfo(ctx context.Context, rawBaseURL string) (UnitInfo, error) { func (c *Client) GetUnitInfo(ctx context.Context, baseURL *url.URL) (UnitInfo, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return UnitInfo{}, ErrBaseURLCantBeNil
return UnitInfo{}, err
} }
var info UnitInfo var info UnitInfo
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil { if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil {
return UnitInfo{}, err return UnitInfo{}, err
} }
return info, nil return info, nil
} }
func (c *Client) GetTribes(ctx context.Context, rawBaseURL string) ([]Tribe, error) { func (c *Client) GetTribes(ctx context.Context, baseURL *url.URL) ([]Tribe, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return nil, ErrBaseURLCantBeNil
return nil, err
} }
od, err := c.getOD(ctx, baseURL, true) od, err := c.getOD(ctx, baseURL, true)
@ -181,10 +149,9 @@ func (c *Client) GetTribes(ctx context.Context, rawBaseURL string) ([]Tribe, err
}.parse() }.parse()
} }
func (c *Client) GetPlayers(ctx context.Context, rawBaseURL string) ([]Player, error) { func (c *Client) GetPlayers(ctx context.Context, baseURL *url.URL) ([]Player, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return nil, ErrBaseURLCantBeNil
return nil, err
} }
od, err := c.getOD(ctx, baseURL, false) od, err := c.getOD(ctx, baseURL, false)
@ -260,10 +227,9 @@ func (c *Client) getSingleODFile(ctx context.Context, u *url.URL) ([]odRecord, e
}.parse() }.parse()
} }
func (c *Client) GetVillages(ctx context.Context, rawBaseURL string) ([]Village, error) { func (c *Client) GetVillages(ctx context.Context, baseURL *url.URL) ([]Village, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return nil, ErrBaseURLCantBeNil
return nil, err
} }
resp, err := c.get(ctx, buildURL(baseURL, endpointVillages)) resp, err := c.get(ctx, buildURL(baseURL, endpointVillages))
@ -280,10 +246,9 @@ func (c *Client) GetVillages(ctx context.Context, rawBaseURL string) ([]Village,
}.parse() }.parse()
} }
func (c *Client) GetEnnoblements(ctx context.Context, rawBaseURL string, since time.Time) ([]Ennoblement, error) { func (c *Client) GetEnnoblements(ctx context.Context, baseURL *url.URL, since time.Time) ([]Ennoblement, error) {
baseURL, err := url.ParseRequestURI(rawBaseURL) if baseURL == nil {
if err != nil { return nil, ErrBaseURLCantBeNil
return nil, err
} }
u := buildURL(baseURL, endpointEnnoblements) u := buildURL(baseURL, endpointEnnoblements)
@ -311,7 +276,6 @@ func (c *Client) getXML(ctx context.Context, u *url.URL, v any) error {
return err return err
} }
defer func() { defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close() _ = resp.Body.Close()
}() }()
@ -327,7 +291,7 @@ func (c *Client) get(ctx context.Context, u *url.URL) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", urlStr, err) return nil, err
} }
// headers // headers
@ -347,6 +311,48 @@ func (c *Client) get(ctx context.Context, u *url.URL) (*http.Response, error) {
return resp, nil return resp, nil
} }
type serverParser struct {
r io.Reader
}
func (p serverParser) parse() ([]Server, error) {
b, err := io.ReadAll(p.r)
if err != nil {
return nil, fmt.Errorf("couldn't read data: %w", err)
}
m, err := phpserialize.UnmarshalAssociativeArray(b)
if err != nil {
return nil, fmt.Errorf("couldn't unmarshal response body: %w", err)
}
servers := make([]Server, 0, len(m))
for key, val := range m {
keyStr, ok := key.(string)
if !ok || keyStr == "" {
return nil, fmt.Errorf("parsing '%v': %w", key, ErrInvalidServerKey)
}
urlStr, ok := val.(string)
if !ok || urlStr == "" {
return nil, fmt.Errorf("parsing '%v': %w", val, ErrInvalidServerURL)
}
serverURL, parseErr := url.ParseRequestURI(urlStr)
if parseErr != nil {
return nil, fmt.Errorf("parsing '%v': %w", val, parseErr)
}
servers = append(servers, Server{
Key: keyStr,
URL: serverURL,
})
}
return servers, nil
}
type tribeCSVParser struct { type tribeCSVParser struct {
r io.Reader r io.Reader
baseURL *url.URL baseURL *url.URL
@ -355,8 +361,8 @@ type tribeCSVParser struct {
const fieldsPerRecordTribe = 8 const fieldsPerRecordTribe = 8
func (t tribeCSVParser) parse() ([]Tribe, error) { func (p tribeCSVParser) parse() ([]Tribe, error) {
csvR := newCSVReader(t.r, fieldsPerRecordTribe) csvR := newCSVReader(p.r, fieldsPerRecordTribe)
var tribes []Tribe var tribes []Tribe
for { for {
@ -368,7 +374,7 @@ func (t tribeCSVParser) parse() ([]Tribe, error) {
return nil, err return nil, err
} }
tribe, err := t.parseRecord(rec) tribe, err := p.parseRecord(rec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -383,7 +389,7 @@ func (t tribeCSVParser) parse() ([]Tribe, error) {
return tribes, nil return tribes, nil
} }
func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) { func (p tribeCSVParser) parseRecord(record []string) (Tribe, error) {
var err error var err error
var tribe Tribe var tribe Tribe
@ -429,9 +435,9 @@ func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) {
return Tribe{}, ParseError{Err: err, Str: record[7], Field: "Tribe.Rank"} return Tribe{}, ParseError{Err: err, Str: record[7], Field: "Tribe.Rank"}
} }
tribe.OpponentsDefeated = t.od[tribe.ID] tribe.OpponentsDefeated = p.od[tribe.ID]
tribe.ProfileURL = buildURLWithQuery(t.baseURL, endpointGame, fmt.Sprintf(queryTribeProfile, tribe.ID)).String() tribe.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryTribeProfile, tribe.ID))
return tribe, nil return tribe, nil
} }
@ -510,7 +516,7 @@ func (p playerCSVParser) parseRecord(record []string) (Player, error) {
player.OpponentsDefeated = p.od[player.ID] player.OpponentsDefeated = p.od[player.ID]
player.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryPlayerProfile, player.ID)).String() player.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryPlayerProfile, player.ID))
return player, nil return player, nil
} }
@ -522,8 +528,8 @@ type villageCSVParser struct {
const fieldsPerRecordVillage = 7 const fieldsPerRecordVillage = 7
func (v villageCSVParser) parse() ([]Village, error) { func (p villageCSVParser) parse() ([]Village, error) {
csvR := newCSVReader(v.r, fieldsPerRecordVillage) csvR := newCSVReader(p.r, fieldsPerRecordVillage)
var villages []Village var villages []Village
for { for {
@ -534,7 +540,7 @@ func (v villageCSVParser) parse() ([]Village, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
village, err := v.parseRecord(rec) village, err := p.parseRecord(rec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -548,7 +554,7 @@ func (v villageCSVParser) parse() ([]Village, error) {
return villages, nil return villages, nil
} }
func (v villageCSVParser) parseRecord(record []string) (Village, error) { func (p villageCSVParser) parseRecord(record []string) (Village, error) {
var err error var err error
var village Village var village Village
@ -589,14 +595,14 @@ func (v villageCSVParser) parseRecord(record []string) (Village, error) {
return Village{}, ParseError{Err: err, Str: record[6], Field: "Village.Bonus"} return Village{}, ParseError{Err: err, Str: record[6], Field: "Village.Bonus"}
} }
village.Continent = v.buildContinent(record, village.X, village.Y) village.Continent = p.buildContinent(record, village.X, village.Y)
village.ProfileURL = buildURLWithQuery(v.baseURL, endpointGame, fmt.Sprintf(queryVillageProfile, village.ID)).String() village.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryVillageProfile, village.ID))
return village, nil return village, nil
} }
func (v villageCSVParser) buildContinent(record []string, x, y int) string { func (p villageCSVParser) buildContinent(record []string, x, y int) string {
continent := "K" continent := "K"
switch { switch {
@ -625,8 +631,8 @@ type odRecord struct {
const fieldsPerRecordOD = 3 const fieldsPerRecordOD = 3
func (o odCSVParser) parse() ([]odRecord, error) { func (p odCSVParser) parse() ([]odRecord, error) {
csvR := newCSVReader(o.r, fieldsPerRecordOD) csvR := newCSVReader(p.r, fieldsPerRecordOD)
var odRecords []odRecord var odRecords []odRecord
for { for {
@ -638,7 +644,7 @@ func (o odCSVParser) parse() ([]odRecord, error) {
return nil, err return nil, err
} }
od, err := o.parseRecord(rec) od, err := p.parseRecord(rec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -649,7 +655,7 @@ func (o odCSVParser) parse() ([]odRecord, error) {
return odRecords, nil return odRecords, nil
} }
func (o odCSVParser) parseRecord(record []string) (odRecord, error) { func (p odCSVParser) parseRecord(record []string) (odRecord, error) {
var err error var err error
var rec odRecord var rec odRecord
@ -680,8 +686,8 @@ type ennoblementCSVParser struct {
const fieldsPerRecordEnnoblement = 7 const fieldsPerRecordEnnoblement = 7
func (e ennoblementCSVParser) parse() ([]Ennoblement, error) { func (p ennoblementCSVParser) parse() ([]Ennoblement, error) {
csvR := newCSVReader(e.r, fieldsPerRecordEnnoblement) csvR := newCSVReader(p.r, fieldsPerRecordEnnoblement)
var ennoblements []Ennoblement var ennoblements []Ennoblement
for { for {
@ -692,12 +698,12 @@ func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ennoblement, err := e.parseRecord(rec) ennoblement, err := p.parseRecord(rec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if ennoblement.CreatedAt.Before(e.since) { if ennoblement.CreatedAt.Before(p.since) {
continue continue
} }
@ -711,7 +717,7 @@ func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
return ennoblements, nil return ennoblements, nil
} }
func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) { func (p ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) {
var err error var err error
var ennoblement Ennoblement var ennoblement Ennoblement
@ -722,7 +728,7 @@ func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error)
return Ennoblement{}, ParseError{Err: err, Str: record[0], Field: "Ennoblement.VillageID"} return Ennoblement{}, ParseError{Err: err, Str: record[0], Field: "Ennoblement.VillageID"}
} }
ennoblement.CreatedAt, err = e.parseTimestamp(record[1]) ennoblement.CreatedAt, err = p.parseTimestamp(record[1])
if err != nil { if err != nil {
return Ennoblement{}, ParseError{Err: err, Str: record[1], Field: "Ennoblement.CreatedAt"} return Ennoblement{}, ParseError{Err: err, Str: record[1], Field: "Ennoblement.CreatedAt"}
} }
@ -755,7 +761,7 @@ func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error)
return ennoblement, nil return ennoblement, nil
} }
func (e ennoblementCSVParser) parseTimestamp(s string) (time.Time, error) { func (p ennoblementCSVParser) parseTimestamp(s string) (time.Time, error) {
timestamp, err := strconv.ParseInt(s, 10, 64) timestamp, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
@ -795,10 +801,8 @@ func buildURL(base *url.URL, path string) *url.URL {
} }
func buildURLWithQuery(base *url.URL, path, query string) *url.URL { func buildURLWithQuery(base *url.URL, path, query string) *url.URL {
return &url.URL{ newURL := *base
Scheme: base.Scheme, newURL.Path = path
Host: base.Host, newURL.RawQuery = query
Path: path, return &newURL
RawQuery: query,
}
} }

View File

@ -7,7 +7,9 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"os" "os"
"slices"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -52,21 +54,17 @@ func TestClient_GetOpenServers(t *testing.T) {
"pl164", "pl164",
} }
servers, err := newClient(srv.Client()).GetOpenServers(context.Background(), srv.URL) servers, err := newClient(srv.Client()).GetOpenServers(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, servers, len(expectedServerKeys)) assert.Len(t, servers, len(expectedServerKeys))
for _, key := range expectedServerKeys { for _, key := range expectedServerKeys {
found := false assert.True(t, slices.ContainsFunc(servers, func(server tw.Server) bool {
return server.Key == key && *server.URL == url.URL{
for _, server := range servers { Scheme: "https",
if server.Key == key && server.URL == fmt.Sprintf("https://%s.plemiona.pl", key) { Host: key + ".plemiona.pl",
found = true
break
} }
} }), "key '%s' not found", key)
assert.True(t, found, "key '%s' not found", key)
} }
} }
@ -82,7 +80,7 @@ func TestClient_GetServerConfig(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
cfg, err := newClient(srv.Client()).GetServerConfig(context.Background(), srv.URL) cfg, err := newClient(srv.Client()).GetServerConfig(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err) require.NoError(t, err)
assert.InEpsilon(t, 4.5, cfg.Speed, 0.01) assert.InEpsilon(t, 4.5, cfg.Speed, 0.01)
assert.InEpsilon(t, 0.5, cfg.UnitSpeed, 0.01) assert.InEpsilon(t, 0.5, cfg.UnitSpeed, 0.01)
@ -206,7 +204,7 @@ func TestClient_GetUnitInfo(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
unitInfo, err := newClient(srv.Client()).GetUnitInfo(context.Background(), srv.URL) unitInfo, err := newClient(srv.Client()).GetUnitInfo(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err) require.NoError(t, err)
assert.InEpsilon(t, 226.66666666667, unitInfo.Spear.BuildTime, 0.01) assert.InEpsilon(t, 226.66666666667, unitInfo.Spear.BuildTime, 0.01)
@ -321,7 +319,7 @@ func TestClient_GetBuildingInfo(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
buildingInfo, err := newClient(srv.Client()).GetBuildingInfo(context.Background(), srv.URL) buildingInfo, err := newClient(srv.Client()).GetBuildingInfo(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 30, buildingInfo.Main.MaxLevel) assert.Equal(t, 30, buildingInfo.Main.MaxLevel)
@ -556,7 +554,7 @@ func TestClient_GetTribes(t *testing.T) {
respODA string respODA string
respODD string respODD string
checkErr func(err error) bool checkErr func(err error) bool
expectedTribes func(url string) []tw.Tribe expectedTribes func(t *testing.T, url string) []tw.Tribe
}{ }{
{ {
name: "OK", name: "OK",
@ -564,13 +562,14 @@ func TestClient_GetTribes(t *testing.T) {
respOD: "1,1,1\n2,2,2\n3,3,3", respOD: "1,1,1\n2,2,2\n3,3,3",
respODA: "1,1,1\n2,2,2\n3,3,3", respODA: "1,1,1\n2,2,2\n3,3,3",
respODD: "1,1,1\n2,2,2\n3,3,3", respODD: "1,1,1\n2,2,2\n3,3,3",
expectedTribes: func(url string) []tw.Tribe { expectedTribes: func(t *testing.T, baseURL string) []tw.Tribe {
t.Helper()
return []tw.Tribe{ return []tw.Tribe{
{ {
ID: 1, ID: 1,
Name: "name 1", Name: "name 1",
Tag: "tag 1", Tag: "tag 1",
ProfileURL: url + "/game.php?screen=info_ally&id=1", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=1"),
NumMembers: 100, NumMembers: 100,
NumVillages: 101, NumVillages: 101,
Points: 102, Points: 102,
@ -591,7 +590,7 @@ func TestClient_GetTribes(t *testing.T) {
ID: 2, ID: 2,
Name: "name2", Name: "name2",
Tag: "tag2", Tag: "tag2",
ProfileURL: url + "/game.php?screen=info_ally&id=2", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=2"),
NumMembers: 200, NumMembers: 200,
NumVillages: 202, NumVillages: 202,
Points: 202, Points: 202,
@ -612,7 +611,7 @@ func TestClient_GetTribes(t *testing.T) {
ID: 3, ID: 3,
Name: "name3", Name: "name3",
Tag: "tag3", Tag: "tag3",
ProfileURL: url + "/game.php?screen=info_ally&id=3", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=3"),
NumMembers: 300, NumMembers: 300,
NumVillages: 303, NumVillages: 303,
Points: 302, Points: 302,
@ -633,7 +632,7 @@ func TestClient_GetTribes(t *testing.T) {
ID: 4, ID: 4,
Name: "name4", Name: "name4",
Tag: "tag4", Tag: "tag4",
ProfileURL: url + "/game.php?screen=info_ally&id=4", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=4"),
NumMembers: 400, NumMembers: 400,
NumVillages: 404, NumVillages: 404,
Points: 402, Points: 402,
@ -795,7 +794,7 @@ func TestClient_GetTribes(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
tribes, err := newClient(srv.Client()).GetTribes(context.Background(), srv.URL) tribes, err := newClient(srv.Client()).GetTribes(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool { checkErr := func(err error) bool {
return errors.Is(err, nil) return errors.Is(err, nil)
@ -806,7 +805,7 @@ func TestClient_GetTribes(t *testing.T) {
var expectedTribes []tw.Tribe var expectedTribes []tw.Tribe
if tt.expectedTribes != nil { if tt.expectedTribes != nil {
expectedTribes = tt.expectedTribes(srv.URL) expectedTribes = tt.expectedTribes(t, srv.URL)
} }
assert.True(t, checkErr(err)) assert.True(t, checkErr(err))
@ -829,7 +828,7 @@ func TestClient_GetPlayers(t *testing.T) {
respODD string respODD string
respODS string respODS string
checkErr func(err error) bool checkErr func(err error) bool
expectedPlayers func(url string) []tw.Player expectedPlayers func(t *testing.T, url string) []tw.Player
}{ }{
{ {
name: "OK", name: "OK",
@ -838,12 +837,13 @@ func TestClient_GetPlayers(t *testing.T) {
respODA: "1,1,1000\n2,2,253\n3,3,100", respODA: "1,1,1000\n2,2,253\n3,3,100",
respODD: "1,1,1002\n2,2,251\n3,3,155", respODD: "1,1,1002\n2,2,251\n3,3,155",
respODS: "1,1,1003\n2,2,250\n3,3,166", respODS: "1,1,1003\n2,2,250\n3,3,166",
expectedPlayers: func(url string) []tw.Player { expectedPlayers: func(t *testing.T, baseURL string) []tw.Player {
t.Helper()
return []tw.Player{ return []tw.Player{
{ {
ID: 1, ID: 1,
Name: "name 1", Name: "name 1",
ProfileURL: url + "/game.php?screen=info_player&id=1", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=1"),
TribeID: 123, TribeID: 123,
NumVillages: 124, NumVillages: 124,
Points: 125, Points: 125,
@ -862,7 +862,7 @@ func TestClient_GetPlayers(t *testing.T) {
{ {
ID: 2, ID: 2,
Name: "name2", Name: "name2",
ProfileURL: url + "/game.php?screen=info_player&id=2", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=2"),
TribeID: 256, TribeID: 256,
NumVillages: 257, NumVillages: 257,
Points: 258, Points: 258,
@ -881,7 +881,7 @@ func TestClient_GetPlayers(t *testing.T) {
{ {
ID: 3, ID: 3,
Name: "name3", Name: "name3",
ProfileURL: url + "/game.php?screen=info_player&id=3", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=3"),
TribeID: 356, TribeID: 356,
NumVillages: 357, NumVillages: 357,
Points: 358, Points: 358,
@ -900,7 +900,7 @@ func TestClient_GetPlayers(t *testing.T) {
{ {
ID: 4, ID: 4,
Name: "name4", Name: "name4",
ProfileURL: url + "/game.php?screen=info_player&id=4", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=4"),
TribeID: 456, TribeID: 456,
NumVillages: 457, NumVillages: 457,
Points: 458, Points: 458,
@ -1078,7 +1078,7 @@ func TestClient_GetPlayers(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
players, err := newClient(srv.Client()).GetPlayers(context.Background(), srv.URL) players, err := newClient(srv.Client()).GetPlayers(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool { checkErr := func(err error) bool {
return errors.Is(err, nil) return errors.Is(err, nil)
@ -1089,7 +1089,7 @@ func TestClient_GetPlayers(t *testing.T) {
var expectedPlayers []tw.Player var expectedPlayers []tw.Player
if tt.expectedPlayers != nil { if tt.expectedPlayers != nil {
expectedPlayers = tt.expectedPlayers(srv.URL) expectedPlayers = tt.expectedPlayers(t, srv.URL)
} }
assert.True(t, checkErr(err)) assert.True(t, checkErr(err))
@ -1108,18 +1108,19 @@ func TestClient_GetVillages(t *testing.T) {
name string name string
resp string resp string
checkErr func(err error) bool checkErr func(err error) bool
expectedVillages func(url string) []tw.Village expectedVillages func(t *testing.T, baseURL string) []tw.Village
}{ }{
{ {
name: "OK", name: "OK",
//nolint:lll //nolint:lll
resp: "123,village%201,500,501,502,503,504\n124,village 2,100,201,102,103,104\n125,village%203,1,501,502,503,504\n126,village%204,1,1,502,503,504\n127,village%205,103,1,502,503,504", resp: "123,village%201,500,501,502,503,504\n124,village 2,100,201,102,103,104\n125,village%203,1,501,502,503,504\n126,village%204,1,1,502,503,504\n127,village%205,103,1,502,503,504",
expectedVillages: func(url string) []tw.Village { expectedVillages: func(t *testing.T, baseURL string) []tw.Village {
t.Helper()
return []tw.Village{ return []tw.Village{
{ {
ID: 123, ID: 123,
Name: "village 1", Name: "village 1",
ProfileURL: url + "/game.php?screen=info_village&id=123", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=123"),
X: 500, X: 500,
Y: 501, Y: 501,
Continent: "K55", Continent: "K55",
@ -1130,7 +1131,7 @@ func TestClient_GetVillages(t *testing.T) {
{ {
ID: 124, ID: 124,
Name: "village 2", Name: "village 2",
ProfileURL: url + "/game.php?screen=info_village&id=124", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=124"),
X: 100, X: 100,
Y: 201, Y: 201,
Continent: "K21", Continent: "K21",
@ -1141,7 +1142,7 @@ func TestClient_GetVillages(t *testing.T) {
{ {
ID: 125, ID: 125,
Name: "village 3", Name: "village 3",
ProfileURL: url + "/game.php?screen=info_village&id=125", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=125"),
X: 1, X: 1,
Y: 501, Y: 501,
Continent: "K50", Continent: "K50",
@ -1152,7 +1153,7 @@ func TestClient_GetVillages(t *testing.T) {
{ {
ID: 126, ID: 126,
Name: "village 4", Name: "village 4",
ProfileURL: url + "/game.php?screen=info_village&id=126", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=126"),
X: 1, X: 1,
Y: 1, Y: 1,
Continent: "K0", Continent: "K0",
@ -1163,7 +1164,7 @@ func TestClient_GetVillages(t *testing.T) {
{ {
ID: 127, ID: 127,
Name: "village 5", Name: "village 5",
ProfileURL: url + "/game.php?screen=info_village&id=127", ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=127"),
X: 103, X: 103,
Y: 1, Y: 1,
Continent: "K1", Continent: "K1",
@ -1250,7 +1251,7 @@ func TestClient_GetVillages(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
villages, err := newClient(srv.Client()).GetVillages(context.Background(), srv.URL) villages, err := newClient(srv.Client()).GetVillages(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool { checkErr := func(err error) bool {
return errors.Is(err, nil) return errors.Is(err, nil)
@ -1261,7 +1262,7 @@ func TestClient_GetVillages(t *testing.T) {
var expectedVillages []tw.Village var expectedVillages []tw.Village
if tt.expectedVillages != nil { if tt.expectedVillages != nil {
expectedVillages = tt.expectedVillages(srv.URL) expectedVillages = tt.expectedVillages(t, srv.URL)
} }
assert.True(t, checkErr(err)) assert.True(t, checkErr(err))
@ -1420,7 +1421,7 @@ func TestClient_GetEnnoblements(t *testing.T) {
srv := httptest.NewTLSServer(mux) srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close) t.Cleanup(srv.Close)
ennoblements, err := newClient(srv.Client()).GetEnnoblements(context.Background(), srv.URL, tt.since) ennoblements, err := newClient(srv.Client()).GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
checkErr := func(err error) bool { checkErr := func(err error) bool {
return errors.Is(err, nil) return errors.Is(err, nil)
@ -1644,7 +1645,7 @@ func TestClient_GetEnnoblements(t *testing.T) {
client := newClient(srv.Client(), tt.clientOpts...) client := newClient(srv.Client(), tt.clientOpts...)
ennoblements, err := client.GetEnnoblements(context.Background(), srv.URL, tt.since) ennoblements, err := client.GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
checkErr := func(err error) bool { checkErr := func(err error) bool {
return errors.Is(err, nil) return errors.Is(err, nil)
@ -1663,6 +1664,13 @@ func TestClient_GetEnnoblements(t *testing.T) {
}) })
} }
func parseURL(tb testing.TB, rawURL string) *url.URL {
tb.Helper()
u, err := url.ParseRequestURI(rawURL)
require.NoError(tb, err)
return u
}
// user agent and http client can't be overridden via opts // user agent and http client can't be overridden via opts
func newClient(hc *http.Client, opts ...tw.ClientOption) *tw.Client { func newClient(hc *http.Client, opts ...tw.ClientOption) *tw.Client {
return tw.NewClient(append(opts, tw.WithHTTPClient(hc), tw.WithUserAgent(testUserAgent))...) return tw.NewClient(append(opts, tw.WithHTTPClient(hc), tw.WithUserAgent(testUserAgent))...)

View File

@ -1,12 +1,13 @@
package tw package tw
import ( import (
"net/url"
"time" "time"
) )
type Server struct { type Server struct {
Key string Key string
URL string URL *url.URL
} }
type OpponentsDefeated struct { type OpponentsDefeated struct {
@ -31,7 +32,7 @@ type Tribe struct {
Points int Points int
AllPoints int AllPoints int
Rank int Rank int
ProfileURL string ProfileURL *url.URL
} }
type Player struct { type Player struct {
@ -43,7 +44,7 @@ type Player struct {
Points int Points int
Rank int Rank int
TribeID int TribeID int
ProfileURL string ProfileURL *url.URL
} }
type Village struct { type Village struct {
@ -55,7 +56,7 @@ type Village struct {
Continent string Continent string
Bonus int Bonus int
PlayerID int PlayerID int
ProfileURL string ProfileURL *url.URL
} }
type Ennoblement struct { type Ennoblement struct {

View File

@ -1,12 +1,14 @@
package watermillmsg package watermillmsg
import "net/url"
type SyncServersCmdPayload struct { type SyncServersCmdPayload struct {
VersionCode string `json:"versionCode"` VersionCode string `json:"versionCode"`
URL string `json:"url"` URL *url.URL `json:"url"`
} }
type ServerSyncedEventPayload struct { type ServerSyncedEventPayload struct {
Key string `json:"key"` Key string `json:"key"`
URL string `json:"url"` URL *url.URL `json:"url"`
VersionCode string `json:"versionCode"` VersionCode string `json:"versionCode"`
} }

View File

@ -3,6 +3,7 @@ kind: Kustomization
resources: resources:
- jobs.yml - jobs.yml
- server-consumer.yml - server-consumer.yml
- tribe-consumer.yml
images: images:
- name: twhelp - name: twhelp
newName: twhelp newName: twhelp

View File

@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: twhelp-tribe-consumer-deployment
spec:
selector:
matchLabels:
app: twhelp-tribe-consumer
template:
metadata:
labels:
app: twhelp-tribe-consumer
spec:
containers:
- name: twhelp-tribe-consumer
image: twhelp
args: [consumer, tribe]
env:
- name: APP_MODE
value: development
- name: LOG_LEVEL
value: debug
- name: DB_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: twhelp-secret
key: db-connection-string
- name: RABBITMQ_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: twhelp-secret
key: rabbitmq-connection-string
livenessProbe:
exec:
command: [cat, /tmp/live]
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 300m
memory: 300Mi