refactor: introduce adapters (#114)
continuous-integration/drone/push Build is passing Details

Reviewed-on: #114
This commit is contained in:
Dawid Wysokiński 2023-07-03 06:23:32 +00:00
parent 4012bd0a2f
commit a8f309c299
21 changed files with 598 additions and 446 deletions

View File

@ -8,7 +8,7 @@ import (
"syscall"
"gitea.dwysokinski.me/twhelp/dcbot/cmd/dcbot/internal"
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter"
"gitea.dwysokinski.me/twhelp/dcbot/internal/discord"
"gitea.dwysokinski.me/twhelp/dcbot/internal/service"
"github.com/kelseyhightower/envconfig"
@ -41,15 +41,15 @@ func New() *cli.Command {
_ = db.Close()
}()
client, err := internal.NewTWHelpClient(c.App.Version)
twhelpService, err := internal.NewTWHelpService(c.App.Version)
if err != nil {
return fmt.Errorf("internal.NewTWHelpClient: %w", err)
return fmt.Errorf("internal.NewTWHelpService: %w", err)
}
groupRepo := bundb.NewGroup(db)
groupRepo := adapter.NewGroupBun(db)
choiceSvc := service.NewChoice(client)
groupSvc := service.NewGroup(groupRepo, client, logger, cfg.MaxGroupsPerServer, cfg.MaxMonitorsPerGroup)
choiceSvc := service.NewChoice(twhelpService)
groupSvc := service.NewGroup(groupRepo, twhelpService, logger, cfg.MaxGroupsPerServer, cfg.MaxMonitorsPerGroup)
bot, err := discord.NewBot(cfg.Token, groupSvc, choiceSvc, logger)
if err != nil {

View File

@ -6,6 +6,7 @@ import (
"net/url"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"github.com/kelseyhightower/envconfig"
"golang.org/x/time/rate"
@ -19,7 +20,7 @@ type twhelpClientConfig struct {
RateLimiterRequestBurst int `envconfig:"RATE_LIMITER_REQUEST_BURST" default:"5"`
}
func NewTWHelpClient(version string) (*twhelp.Client, error) {
func NewTWHelpService(version string) (*adapter.TWHelpHTTP, error) {
var cfg twhelpClientConfig
if err := envconfig.Process("TWHELP", &cfg); err != nil {
return nil, fmt.Errorf("envconfig.Process: %w", err)
@ -36,5 +37,5 @@ func NewTWHelpClient(version string) (*twhelp.Client, error) {
opts = append(opts, twhelp.WithRateLimiter(rate.NewLimiter(rate.Limit(cfg.RateLimiterMaxRequestsPerSecond), cfg.RateLimiterRequestBurst)))
}
return twhelp.NewClient(cfg.URL, opts...), nil
return adapter.NewTWHelpHTTP(twhelp.NewClient(cfg.URL, opts...)), nil
}

View File

@ -1,4 +1,4 @@
package bundb
package adapter
import (
"context"
@ -6,7 +6,7 @@ import (
"errors"
"fmt"
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter/internal/bunmodel"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/google/uuid"
"github.com/jackc/pgerrcode"
@ -14,16 +14,16 @@ import (
"github.com/uptrace/bun/driver/pgdriver"
)
type Group struct {
type GroupBun struct {
db *bun.DB
}
func NewGroup(db *bun.DB) *Group {
return &Group{db: db}
func NewGroupBun(db *bun.DB) *GroupBun {
return &GroupBun{db: db}
}
func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
group := model.Group{
func (g *GroupBun) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
group := bunmodel.Group{
ServerID: params.ServerID(),
VersionCode: params.VersionCode(),
ChannelGains: params.ChannelGains(),
@ -44,7 +44,7 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
return group.ToDomain(), nil
}
func (g *Group) Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
func (g *GroupBun) Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
if params.IsZero() {
return domain.GroupWithMonitors{}, domain.ErrNothingToUpdate
}
@ -53,7 +53,7 @@ func (g *Group) Update(ctx context.Context, id string, params domain.UpdateGroup
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
}
var group model.Group
var group bunmodel.Group
res, err := g.db.NewUpdate().
Model(&group).
@ -71,13 +71,13 @@ func (g *Group) Update(ctx context.Context, id string, params domain.UpdateGroup
return g.Get(ctx, id)
}
func (g *Group) AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) {
func (g *GroupBun) AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) {
parsedID, err := uuid.Parse(id)
if err != nil {
return domain.GroupWithMonitors{}, domain.GroupDoesNotExistError{ID: id}
}
monitor := model.Monitor{
monitor := bunmodel.Monitor{
GroupID: parsedID,
TribeID: tribeID,
}
@ -96,7 +96,7 @@ func (g *Group) AddMonitor(ctx context.Context, id string, tribeID int64) (domai
return g.Get(ctx, id)
}
func (g *Group) DeleteMonitors(ctx context.Context, id string, monitorIDs ...string) (domain.GroupWithMonitors, error) {
func (g *GroupBun) DeleteMonitors(ctx context.Context, id string, monitorIDs ...string) (domain.GroupWithMonitors, error) {
if _, err := uuid.Parse(id); err != nil {
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
}
@ -108,7 +108,7 @@ func (g *Group) DeleteMonitors(ctx context.Context, id string, monitorIDs ...str
}
_, err := g.db.NewDelete().
Model(&model.Monitor{}).
Model(&bunmodel.Monitor{}).
Returning("NULL").
Where("id IN (?)", bun.In(monitorIDs)).
Where("group_id = ?", id).
@ -120,8 +120,8 @@ func (g *Group) DeleteMonitors(ctx context.Context, id string, monitorIDs ...str
return g.Get(ctx, id)
}
func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) {
var groups []model.Group
func (g *GroupBun) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) {
var groups []bunmodel.Group
if err := g.db.NewSelect().
Model(&groups).
@ -142,7 +142,7 @@ func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]dom
return result, nil
}
func (g *Group) Get(ctx context.Context, id string) (domain.GroupWithMonitors, error) {
func (g *GroupBun) Get(ctx context.Context, id string) (domain.GroupWithMonitors, error) {
group, err := g.get(ctx, id, true)
if err != nil {
return domain.GroupWithMonitors{}, err
@ -150,12 +150,12 @@ func (g *Group) Get(ctx context.Context, id string) (domain.GroupWithMonitors, e
return group.ToDomain(), nil
}
func (g *Group) get(ctx context.Context, id string, withMonitors bool) (model.Group, error) {
func (g *GroupBun) get(ctx context.Context, id string, withMonitors bool) (bunmodel.Group, error) {
if _, err := uuid.Parse(id); err != nil {
return model.Group{}, domain.GroupNotFoundError{ID: id}
return bunmodel.Group{}, domain.GroupNotFoundError{ID: id}
}
var group model.Group
var group bunmodel.Group
q := g.db.NewSelect().
Model(&group).
@ -168,21 +168,21 @@ func (g *Group) get(ctx context.Context, id string, withMonitors bool) (model.Gr
if err := q.Scan(ctx); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return model.Group{}, domain.GroupNotFoundError{ID: id}
return bunmodel.Group{}, domain.GroupNotFoundError{ID: id}
}
return model.Group{}, fmt.Errorf("couldn't select group (id=%s) from the db: %w", id, err)
return bunmodel.Group{}, fmt.Errorf("couldn't select group (id=%s) from the db: %w", id, err)
}
return group, nil
}
func (g *Group) Delete(ctx context.Context, id string) error {
func (g *GroupBun) Delete(ctx context.Context, id string) error {
if _, err := uuid.Parse(id); err != nil {
return domain.GroupNotFoundError{ID: id}
}
res, err := g.db.NewDelete().
Model(&model.Group{}).
Model(&bunmodel.Group{}).
Returning("NULL").
Where("id = ?", id).
Exec(ctx)
@ -196,13 +196,13 @@ func (g *Group) Delete(ctx context.Context, id string) error {
return nil
}
func (g *Group) DeleteMany(ctx context.Context, ids ...string) error {
func (g *GroupBun) DeleteMany(ctx context.Context, ids ...string) error {
if len(ids) == 0 {
return nil
}
_, err := g.db.NewDelete().
Model(&model.Group{}).
Model(&bunmodel.Group{}).
Returning("NULL").
Where("id IN (?)", bun.In(ids)).
Exec(ctx)

View File

@ -1,11 +1,11 @@
package bundb_test
package adapter_test
import (
"context"
"testing"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
@ -20,7 +20,7 @@ func TestGroup_Create(t *testing.T) {
t.Skip("skipping long-running test")
}
repo := bundb.NewGroup(newDB(t))
repo := adapter.NewGroupBun(newBunDB(t))
t.Run("OK", func(t *testing.T) {
t.Parallel()
@ -59,9 +59,9 @@ func TestGroup_Update(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
repo := adapter.NewGroupBun(db)
group := fixture.group(t, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
@ -150,9 +150,9 @@ func TestGroup_AddMonitor(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
repo := adapter.NewGroupBun(db)
group := fixture.group(t, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
@ -223,9 +223,9 @@ func TestGroup_DeleteMonitors(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
repo := adapter.NewGroupBun(db)
t.Run("OK", func(t *testing.T) {
t.Parallel()
@ -312,9 +312,9 @@ func TestGroup_List(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
repo := adapter.NewGroupBun(db)
groups := fixture.groups(t)
allGroups := make([]string, 0, len(groups))
@ -452,9 +452,9 @@ func TestGroup_Get(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
repo := adapter.NewGroupBun(db)
group := fixture.group(t, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
@ -503,9 +503,9 @@ func TestGroup_Delete(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
groupRepo := bundb.NewGroup(db)
groupRepo := adapter.NewGroupBun(db)
group := fixture.group(t, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
@ -561,9 +561,9 @@ func TestGroup_DeleteMany(t *testing.T) {
t.Skip("skipping long-running test")
}
db := newDB(t)
db := newBunDB(t)
fixture := loadFixtures(t, db)
groupRepo := bundb.NewGroup(db)
groupRepo := adapter.NewGroupBun(db)
group1 := fixture.group(t, "group-1-server-1")
group2 := fixture.group(t, "group-2-server-1")
ids := []string{group1.ID, group2.ID}

View File

@ -1,4 +1,4 @@
package bundb_test
package adapter_test
import (
"context"
@ -14,7 +14,7 @@ import (
"time"
"unicode"
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter/internal/bunmodel"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/cenkalti/backoff/v4"
"github.com/google/uuid"
@ -33,11 +33,11 @@ var (
gooseSetUpOnce = sync.Once{}
)
func newDB(tb testing.TB) *bun.DB {
func newBunDB(tb testing.TB) *bun.DB {
tb.Helper()
if dsn, ok := os.LookupEnv("TESTS_DB_DSN"); ok {
return newDBWithDSN(tb, dsn)
return newBunDBWithDSN(tb, dsn)
}
q := url.Values{}
@ -78,10 +78,10 @@ func newDB(tb testing.TB) *bun.DB {
dsn.Host = getHostPort(tb, resource, "5432/tcp")
return newDBWithDSN(tb, dsn.String())
return newBunDBWithDSN(tb, dsn.String())
}
func newDBWithDSN(tb testing.TB, dsn string) *bun.DB {
func newBunDBWithDSN(tb testing.TB, dsn string) *bun.DB {
tb.Helper()
schema := generateSchema()
@ -141,7 +141,7 @@ func loadFixtures(tb testing.TB, bunDB *bun.DB) *bunfixture {
tb.Helper()
// ensure that models are registered
bunDB.RegisterModel(&model.Group{}, &model.Monitor{})
bunDB.RegisterModel(&bunmodel.Group{}, &bunmodel.Monitor{})
fixture := dbfixture.New(bunDB)
err := fixture.Load(context.Background(), os.DirFS("testdata"), "fixture.yml")
@ -154,7 +154,7 @@ func (f *bunfixture) group(tb testing.TB, id string) domain.GroupWithMonitors {
row, err := f.Row("Group." + id)
require.NoError(tb, err)
g, ok := row.(*model.Group)
g, ok := row.(*bunmodel.Group)
require.True(tb, ok)
return g.ToDomain()
}
@ -178,7 +178,7 @@ func (f *bunfixture) monitor(tb testing.TB, id string) domain.Monitor {
row, err := f.Row("Monitor." + id)
require.NoError(tb, err)
m, ok := row.(*model.Monitor)
m, ok := row.(*bunmodel.Monitor)
require.True(tb, ok)
return m.ToDomain()
}

View File

@ -0,0 +1,270 @@
package adapter
import (
"context"
"errors"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
)
type TWHelpHTTP struct {
client *twhelp.Client
}
func NewTWHelpHTTP(client *twhelp.Client) *TWHelpHTTP {
return &TWHelpHTTP{client: client}
}
func (t *TWHelpHTTP) ListVersions(ctx context.Context) ([]domain.TWVersion, error) {
versions, err := t.client.ListVersions(ctx)
if err != nil {
return nil, err
}
res := make([]domain.TWVersion, 0, len(versions))
for _, v := range versions {
res = append(res, t.convertVersionToDomain(v))
}
return res, nil
}
func (t *TWHelpHTTP) ListOpenServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) {
return t.listServers(ctx, version, twhelp.ListServersQueryParams{
Limit: limit,
Offset: offset,
Open: twhelp.NullBool{
Bool: true,
Valid: true,
},
})
}
func (t *TWHelpHTTP) ListClosedServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) {
return t.listServers(ctx, version, twhelp.ListServersQueryParams{
Limit: limit,
Offset: offset,
Open: twhelp.NullBool{
Bool: false,
Valid: true,
},
})
}
func (t *TWHelpHTTP) listServers(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]domain.TWServer, error) {
servers, err := t.client.ListServers(ctx, version, params)
if err != nil {
return nil, err
}
res := make([]domain.TWServer, 0, len(servers))
for _, s := range servers {
res = append(res, t.convertServerToDomain(s))
}
return res, nil
}
func (t *TWHelpHTTP) GetOpenServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) {
server, err := t.client.GetServer(ctx, versionCode, serverKey)
if err != nil {
var apiErr twhelp.APIError
if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound {
return domain.TWServer{}, err
}
return domain.TWServer{}, domain.TWServerNotFoundError{
VersionCode: versionCode,
Key: serverKey,
}
}
if !server.Open {
return domain.TWServer{}, domain.TWServerNotFoundError{
VersionCode: versionCode,
Key: serverKey,
}
}
return t.convertServerToDomain(server), nil
}
func (t *TWHelpHTTP) GetTribeByID(ctx context.Context, versionCode, serverKey string, id int64) (domain.Tribe, error) {
tribe, err := t.client.GetTribeByID(ctx, versionCode, serverKey, id)
if err != nil {
var apiErr twhelp.APIError
if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound {
return domain.Tribe{}, err
}
return domain.Tribe{}, domain.TribeIDNotFoundError{
VersionCode: versionCode,
ServerKey: serverKey,
ID: id,
}
}
return t.convertTribeToDomain(tribe), nil
}
func (t *TWHelpHTTP) GetExistingTribeByTag(ctx context.Context, versionCode, serverKey, tribeTag string) (domain.Tribe, error) {
tribes, err := t.client.ListTribes(ctx, versionCode, serverKey, twhelp.ListTribesQueryParams{
Limit: 1,
Tags: []string{tribeTag},
Deleted: twhelp.NullBool{
Valid: true,
Bool: false,
},
})
if err != nil {
return domain.Tribe{}, err
}
if len(tribes) == 0 {
return domain.Tribe{}, domain.TribeTagNotFoundError{
VersionCode: versionCode,
ServerKey: serverKey,
Tag: tribeTag,
}
}
return t.convertTribeToDomain(tribes[0]), nil
}
func (t *TWHelpHTTP) ListTribesTag(
ctx context.Context,
versionCode, serverKey string,
tribeTags []string,
offset, limit int32,
) ([]domain.Tribe, error) {
tribes, err := t.client.ListTribes(ctx, versionCode, serverKey, twhelp.ListTribesQueryParams{
Limit: limit,
Offset: offset,
Tags: tribeTags,
Deleted: twhelp.NullBool{
Valid: true,
Bool: false,
},
})
if err != nil {
return nil, err
}
res := make([]domain.Tribe, 0, len(tribes))
for _, tr := range tribes {
res = append(res, t.convertTribeToDomain(tr))
}
return res, nil
}
func (t *TWHelpHTTP) ListVillagesCoords(
ctx context.Context,
versionCode, serverKey string,
coords []string,
offset, limit int32,
) ([]domain.Village, error) {
villages, err := t.client.ListVillages(ctx, versionCode, serverKey, twhelp.ListVillagesQueryParams{
Limit: limit,
Offset: offset,
Coords: coords,
})
if err != nil {
return nil, err
}
res := make([]domain.Village, 0, len(villages))
for _, v := range villages {
res = append(res, t.convertVillageToDomain(v))
}
return res, nil
}
func (t *TWHelpHTTP) ListEnnoblementsSince(
ctx context.Context,
versionCode, serverKey string,
since time.Time,
offset, limit int32,
) ([]domain.Ennoblement, error) {
ennoblements, err := t.client.ListEnnoblements(ctx, versionCode, serverKey, twhelp.ListEnnoblementsQueryParams{
Limit: limit,
Offset: offset,
Since: since,
Sort: []twhelp.EnnoblementSort{
twhelp.EnnoblementSortCreatedAtASC,
},
})
if err != nil {
return nil, err
}
res := make([]domain.Ennoblement, 0, len(ennoblements))
for _, e := range ennoblements {
res = append(res, t.convertEnnoblementToDomain(e))
}
return res, nil
}
func (t *TWHelpHTTP) convertVersionToDomain(v twhelp.Version) domain.TWVersion {
return domain.TWVersion(v)
}
func (t *TWHelpHTTP) convertServerToDomain(s twhelp.Server) domain.TWServer {
return domain.TWServer(s)
}
func (t *TWHelpHTTP) convertTribeToDomain(tr twhelp.Tribe) domain.Tribe {
return domain.Tribe(tr)
}
func (t *TWHelpHTTP) convertTribeMetaToDomain(tr twhelp.TribeMeta) domain.TribeMeta {
return domain.TribeMeta(tr)
}
func (t *TWHelpHTTP) convertNullTribeMetaToDomain(tr twhelp.NullTribeMeta) domain.NullTribeMeta {
return domain.NullTribeMeta{
Tribe: t.convertTribeMetaToDomain(tr.Tribe),
Valid: tr.Valid,
}
}
func (t *TWHelpHTTP) convertPlayerMetaToDomain(p twhelp.PlayerMeta) domain.PlayerMeta {
return domain.PlayerMeta{
ID: p.ID,
Name: p.Name,
ProfileURL: p.ProfileURL,
Tribe: t.convertNullTribeMetaToDomain(p.Tribe),
}
}
func (t *TWHelpHTTP) convertNullPlayerMetaToDomain(p twhelp.NullPlayerMeta) domain.NullPlayerMeta {
return domain.NullPlayerMeta{
Player: t.convertPlayerMetaToDomain(p.Player),
Valid: p.Valid,
}
}
func (t *TWHelpHTTP) convertVillageToDomain(v twhelp.Village) domain.Village {
return domain.Village{
ID: v.ID,
FullName: v.FullName,
ProfileURL: v.ProfileURL,
Points: v.Points,
Player: t.convertNullPlayerMetaToDomain(v.Player),
}
}
func (t *TWHelpHTTP) convertVillageMetaToDomain(v twhelp.VillageMeta) domain.VillageMeta {
return domain.VillageMeta{
ID: v.ID,
FullName: v.FullName,
ProfileURL: v.ProfileURL,
Player: t.convertNullPlayerMetaToDomain(v.Player),
}
}
func (t *TWHelpHTTP) convertEnnoblementToDomain(e twhelp.Ennoblement) domain.Ennoblement {
return domain.Ennoblement{
ID: e.ID,
Village: t.convertVillageMetaToDomain(e.Village),
NewOwner: t.convertNullPlayerMetaToDomain(e.NewOwner),
CreatedAt: e.CreatedAt,
}
}

View File

@ -1,4 +1,4 @@
package model
package bunmodel
import (
"time"

View File

@ -1,4 +1,4 @@
package model
package bunmodel
import (
"time"

View File

@ -10,9 +10,9 @@
"err.monitor-already-exists": "The tribe has already been added to the group.",
"err.monitor-limit-reached": "The tribe limit has been reached ({{ .Current }}/{{ .Limit }}).",
"err.monitor-not-found": "Monitor (id={{ .ID }}) not found.",
"err.tw-server-does-not-exist": "The server (version code={{ .VersionCode }}, key={{ .Key }}) doesn't exist.",
"err.tw-server-is-closed": "The server (version code={{ .VersionCode }}, key={{ .Key }}) is closed.",
"err.tribe-not-found": "Tribe (tag={{ .Tag }}) not found.",
"err.tw-server-not-found": "Server (version code={{ .VersionCode }}, key={{ .Key }}) not found.",
"err.tribe-tag-not-found": "Tribe (tag={{ .Tag }}) not found.",
"err.tribe-id-not-found": "Tribe (id={{ .ID }}) not found.",
"err.tribe-does-not-exist": "The tribe (tag={{ .Tag }}) doesn't exist.",
"job.execute-monitors.embed.description": "{{ buildPlayerMarkdown .Ennoblement.NewOwner }} has taken {{ buildLink .Ennoblement.Village.FullName .Ennoblement.Village.ProfileURL }} (Old owner: {{ buildPlayerMarkdown .Ennoblement.Village.Player }}).",

View File

@ -10,9 +10,9 @@
"err.monitor-already-exists": "Plemię zostało już dodane do tej grupy.",
"err.monitor-limit-reached": "Limit plemion został osiągnięty ({{ .Current }}/{{ .Limit }}).",
"err.monitor-not-found": "Monitor (id={{ .ID }}) nie została znaleziona.",
"err.tw-server-does-not-exist": "Serwer (kod wersji={{ .VersionCode }}, klucz={{ .Key }}) nie istnieje.",
"err.tw-server-is-closed": "Serwer (kod wersji={{ .VersionCode }}, klucz={{ .Key }}) jest zamknięty.",
"err.tribe-not-found": "Plemię (skrót={{ .Tag }}) nie zostało znalezione.",
"err.tw-server-not-found": "Serwer (kod wersji={{ .VersionCode }}, klucz={{ .Key }}) nie został znaleziony.",
"err.tribe-tag-not-found": "Plemię (skrót={{ .Tag }}) nie istnieje.",
"err.tribe-id-not-found": "Plemię (id={{ .ID }}) nie istnieje.",
"err.tribe-does-not-exist": "Plemię (skrót={{ .Tag }}) nie istnieje.",
"job.execute-monitors.embed.description": "{{ buildPlayerMarkdown .Ennoblement.NewOwner }} przejął {{ buildLink .Ennoblement.Village.FullName .Ennoblement.Village.ProfileURL }} (Poprzedni właściciel: {{ buildPlayerMarkdown .Ennoblement.Village.Player }}).",

View File

@ -14,7 +14,7 @@ type Monitor struct {
type MonitorWithTribe struct {
Monitor
Tribe TribeMeta
Tribe Tribe
}
type MonitorAlreadyExistsError struct {

View File

@ -5,6 +5,27 @@ import (
"time"
)
type TWVersion struct {
Code string
Host string
Name string
Timezone string
}
type TWServer struct {
Key string
URL string
Open bool
}
type Tribe struct {
ID int64
Tag string
Name string
ProfileURL string
DeletedAt time.Time
}
type TribeMeta struct {
ID int64
Name string
@ -29,6 +50,14 @@ type NullPlayerMeta struct {
Valid bool
}
type Village struct {
ID int64
FullName string
ProfileURL string
Points int64
Player NullPlayerMeta
}
type VillageMeta struct {
ID int64
FullName string
@ -43,90 +72,72 @@ type Ennoblement struct {
CreatedAt time.Time
}
type TWServerDoesNotExistError struct {
type TWServerNotFoundError struct {
VersionCode string
Key string
}
var _ TranslatableError = TWServerDoesNotExistError{}
var _ TranslatableError = TWServerNotFoundError{}
func (e TWServerDoesNotExistError) Error() string {
return fmt.Sprintf("server (versionCode=%s,key=%s) doesn't exist", e.VersionCode, e.Key)
func (e TWServerNotFoundError) Error() string {
return fmt.Sprintf("server (versionCode=%s,key=%s) not found", e.VersionCode, e.Key)
}
func (e TWServerDoesNotExistError) Slug() string {
return "tw-server-does-not-exist"
func (e TWServerNotFoundError) Slug() string {
return "tw-server-not-found"
}
func (e TWServerDoesNotExistError) Params() map[string]any {
func (e TWServerNotFoundError) Params() map[string]any {
return map[string]any{
"VersionCode": e.VersionCode,
"Key": e.Key,
}
}
type TWServerIsClosedError struct {
type TribeTagNotFoundError struct {
VersionCode string
Key string
ServerKey string
Tag string
}
var _ TranslatableError = TWServerIsClosedError{}
var _ TranslatableError = TribeTagNotFoundError{}
func (e TWServerIsClosedError) Error() string {
return fmt.Sprintf("server (versionCode=%s,key=%s) is closed", e.VersionCode, e.Key)
func (e TribeTagNotFoundError) Error() string {
return fmt.Sprintf("tribe (versionCode=%s,serverKey=%s,tag=%s) not found", e.VersionCode, e.ServerKey, e.Tag)
}
func (e TWServerIsClosedError) Slug() string {
return "tw-server-is-closed"
func (e TribeTagNotFoundError) Slug() string {
return "tribe-tag-not-found"
}
func (e TWServerIsClosedError) Params() map[string]any {
func (e TribeTagNotFoundError) Params() map[string]any {
return map[string]any{
"VersionCode": e.VersionCode,
"Key": e.Key,
"ServerKey": e.ServerKey,
"Tag": e.Tag,
}
}
type TribeDoesNotExistError struct {
Tag string
type TribeIDNotFoundError struct {
VersionCode string
ServerKey string
ID int64
}
var _ TranslatableError = TribeDoesNotExistError{}
var _ TranslatableError = TribeIDNotFoundError{}
func (e TribeDoesNotExistError) Error() string {
return fmt.Sprintf("tribe (tag=%s) doesn't exist", e.Tag)
func (e TribeIDNotFoundError) Error() string {
return fmt.Sprintf("tribe (versionCode=%s,serverKey=%s,id=%d) not found", e.VersionCode, e.ServerKey, e.ID)
}
func (e TribeDoesNotExistError) UserError() string {
return e.Error()
func (e TribeIDNotFoundError) Slug() string {
return "tribe-id-not-found"
}
func (e TribeDoesNotExistError) Slug() string {
return "tribe-does-not-exist"
}
func (e TribeDoesNotExistError) Params() map[string]any {
func (e TribeIDNotFoundError) Params() map[string]any {
return map[string]any{
"Tag": e.Tag,
}
}
type TribeNotFoundError struct {
Tag string
}
var _ TranslatableError = TribeNotFoundError{}
func (e TribeNotFoundError) Error() string {
return fmt.Sprintf("tribe (tag=%s) not found", e.Tag)
}
func (e TribeNotFoundError) Slug() string {
return "tribe-not-found"
}
func (e TribeNotFoundError) Params() map[string]any {
return map[string]any{
"Tag": e.Tag,
"VersionCode": e.VersionCode,
"ServerKey": e.ServerKey,
"ID": e.ID,
}
}

View File

@ -2,23 +2,22 @@ package service
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
)
type Choice struct {
client TWHelpClient
twhelpSvc TWHelpService
}
func NewChoice(client TWHelpClient) *Choice {
return &Choice{client: client}
func NewChoice(twhelpSvc TWHelpService) *Choice {
return &Choice{twhelpSvc: twhelpSvc}
}
func (c *Choice) Versions(ctx context.Context) ([]domain.Choice, error) {
versions, err := c.client.ListVersions(ctx)
versions, err := c.twhelpSvc.ListVersions(ctx)
if err != nil {
return nil, fmt.Errorf("TWHelpClient.ListVersions: %w", err)
return nil, err
}
choices := make([]domain.Choice, 0, len(versions))

View File

@ -0,0 +1,22 @@
package service
import (
"context"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
)
type Coords struct {
twhelpSvc TWHelpService
}
func NewCoords(twhelpSvc TWHelpService) *Coords {
return &Coords{twhelpSvc: twhelpSvc}
}
func (c *Coords) Translate(ctx context.Context, version, server string, coords ...string) ([]domain.Village, error) {
if len(coords) == 0 {
return nil, nil
}
return c.twhelpSvc.ListVillagesCoords(ctx, version, server, coords, 0, int32(len(coords)))
}

View File

@ -2,7 +2,6 @@ package service
import (
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
)
type ennoblementNotificationBuilder struct {
@ -28,7 +27,7 @@ func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) [
ServerID: g.ServerID,
ChannelID: g.ChannelGains,
LanguageTag: g.LanguageTag,
Ennoblement: b.ennoblementToDomainModel(e),
Ennoblement: e,
})
}
@ -38,7 +37,7 @@ func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) [
ServerID: g.ServerID,
ChannelID: g.ChannelLosses,
LanguageTag: g.LanguageTag,
Ennoblement: b.ennoblementToDomainModel(e),
Ennoblement: e,
})
}
}
@ -46,7 +45,7 @@ func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) [
return notifications
}
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeGain(g domain.GroupWithMonitors, e twhelp.Ennoblement) bool {
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeGain(g domain.GroupWithMonitors, e domain.Ennoblement) bool {
if g.ChannelGains == "" {
return false
}
@ -62,7 +61,7 @@ func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeGain(g
return b.isGain(e, g.Monitors)
}
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeLoss(g domain.GroupWithMonitors, e twhelp.Ennoblement) bool {
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeLoss(g domain.GroupWithMonitors, e domain.Ennoblement) bool {
if g.ChannelLosses == "" {
return false
}
@ -74,7 +73,7 @@ func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeLoss(g
return b.isLoss(e, g.Monitors)
}
func (b ennoblementNotificationBuilder) isInternal(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
func (b ennoblementNotificationBuilder) isInternal(e domain.Ennoblement, monitors []domain.Monitor) bool {
var n, o bool
for _, m := range monitors {
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
@ -87,11 +86,11 @@ func (b ennoblementNotificationBuilder) isInternal(e twhelp.Ennoblement, monitor
return n && o
}
func (b ennoblementNotificationBuilder) isBarbarian(e twhelp.Ennoblement) bool {
func (b ennoblementNotificationBuilder) isBarbarian(e domain.Ennoblement) bool {
return !e.Village.Player.Valid
}
func (b ennoblementNotificationBuilder) isGain(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
func (b ennoblementNotificationBuilder) isGain(e domain.Ennoblement, monitors []domain.Monitor) bool {
var n bool
for _, m := range monitors {
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
@ -102,7 +101,7 @@ func (b ennoblementNotificationBuilder) isGain(e twhelp.Ennoblement, monitors []
return n && e.NewOwner.Player.ID != e.Village.Player.Player.ID
}
func (b ennoblementNotificationBuilder) isLoss(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
func (b ennoblementNotificationBuilder) isLoss(e domain.Ennoblement, monitors []domain.Monitor) bool {
var o bool
for _, m := range monitors {
if m.TribeID == e.Village.Player.Player.Tribe.Tribe.ID {
@ -112,39 +111,3 @@ func (b ennoblementNotificationBuilder) isLoss(e twhelp.Ennoblement, monitors []
}
return o && e.NewOwner.Player.ID != e.Village.Player.Player.ID
}
func (b ennoblementNotificationBuilder) ennoblementToDomainModel(e twhelp.Ennoblement) domain.Ennoblement {
return domain.Ennoblement{
ID: e.ID,
Village: domain.VillageMeta{
ID: e.Village.ID,
FullName: e.Village.FullName,
ProfileURL: e.Village.ProfileURL,
Player: domain.NullPlayerMeta{
Player: domain.PlayerMeta{
ID: e.Village.Player.Player.ID,
Name: e.Village.Player.Player.Name,
ProfileURL: e.Village.Player.Player.ProfileURL,
Tribe: domain.NullTribeMeta{
Tribe: domain.TribeMeta(e.Village.Player.Player.Tribe.Tribe),
Valid: e.Village.Player.Player.Tribe.Valid,
},
},
Valid: e.Village.Player.Valid,
},
},
NewOwner: domain.NullPlayerMeta{
Player: domain.PlayerMeta{
ID: e.NewOwner.Player.ID,
Name: e.NewOwner.Player.Name,
ProfileURL: e.NewOwner.Player.ProfileURL,
Tribe: domain.NullTribeMeta{
Tribe: domain.TribeMeta(e.NewOwner.Player.Tribe.Tribe),
Valid: e.NewOwner.Player.Tribe.Valid,
},
},
Valid: e.NewOwner.Valid,
},
CreatedAt: e.CreatedAt,
}
}

View File

@ -8,7 +8,6 @@ import (
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"go.uber.org/zap"
)
@ -26,7 +25,7 @@ type GroupRepository interface {
type Group struct {
repo GroupRepository
client TWHelpClient
twhelpSvc TWHelpService
logger *zap.Logger
maxGroupsPerServer int
maxMonitorsPerGroup int
@ -34,10 +33,10 @@ type Group struct {
ennoblementsSince map[string]time.Time // ennoblementsSince is used by listEnnoblements
}
func NewGroup(repo GroupRepository, client TWHelpClient, logger *zap.Logger, maxGroupsPerServer, maxMonitorsPerGroup int) *Group {
func NewGroup(repo GroupRepository, twhelpSvc TWHelpService, logger *zap.Logger, maxGroupsPerServer, maxMonitorsPerGroup int) *Group {
return &Group{
repo: repo,
client: client,
twhelpSvc: twhelpSvc,
logger: logger,
maxGroupsPerServer: maxGroupsPerServer,
maxMonitorsPerGroup: maxMonitorsPerGroup,
@ -59,7 +58,7 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
}
}
if err = g.checkTWServer(ctx, params.VersionCode(), params.ServerKey()); err != nil {
if _, err = g.twhelpSvc.GetOpenServer(ctx, params.VersionCode(), params.ServerKey()); err != nil {
return domain.GroupWithMonitors{}, err
}
@ -71,29 +70,6 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
return group, nil
}
func (g *Group) checkTWServer(ctx context.Context, versionCode, serverKey string) error {
server, err := g.client.GetServer(ctx, versionCode, serverKey)
if err != nil {
var apiErr twhelp.APIError
if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound {
return fmt.Errorf("TWHelpClient.GetServer: %w", err)
}
return domain.TWServerDoesNotExistError{
VersionCode: versionCode,
Key: serverKey,
}
}
if !server.Open {
return domain.TWServerIsClosedError{
VersionCode: versionCode,
Key: serverKey,
}
}
return nil
}
func (g *Group) AddTribe(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error) {
// check if group exists
groupBeforeUpdate, err := g.Get(ctx, id, serverID)
@ -112,24 +88,12 @@ func (g *Group) AddTribe(ctx context.Context, id, serverID, tribeTag string) (do
}
}
tribes, err := g.client.ListTribes(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, twhelp.ListTribesQueryParams{
Limit: 1,
Tags: []string{tribeTag},
Deleted: twhelp.NullBool{
Valid: true,
Bool: false,
},
})
tribe, err := g.twhelpSvc.GetExistingTribeByTag(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, tribeTag)
if err != nil {
return domain.GroupWithMonitors{}, fmt.Errorf("TWHelpClient.ListTribes: %w", err)
}
if len(tribes) == 0 {
return domain.GroupWithMonitors{}, domain.TribeDoesNotExistError{
Tag: tribeTag,
}
return domain.GroupWithMonitors{}, err
}
group, err := g.repo.AddMonitor(ctx, groupBeforeUpdate.ID, tribes[0].ID)
group, err := g.repo.AddMonitor(ctx, groupBeforeUpdate.ID, tribe.ID)
if err != nil {
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.AddMonitor: %w", err)
}
@ -137,6 +101,8 @@ func (g *Group) AddTribe(ctx context.Context, id, serverID, tribeTag string) (do
return group, nil
}
const listTribesLimit = 200
func (g *Group) RemoveTribe(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error) {
// check if group exists
groupBeforeUpdate, err := g.Get(ctx, id, serverID)
@ -144,14 +110,12 @@ func (g *Group) RemoveTribe(ctx context.Context, id, serverID, tribeTag string)
return domain.GroupWithMonitors{}, err
}
tribes, err := g.client.ListTribes(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, twhelp.ListTribesQueryParams{
Tags: []string{tribeTag},
})
tribes, err := g.twhelpSvc.ListTribesTag(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, []string{tribeTag}, 0, listTribesLimit)
if err != nil {
return domain.GroupWithMonitors{}, fmt.Errorf("TWHelpClient.ListTribes: %w", err)
}
if len(tribes) == 0 {
return domain.GroupWithMonitors{}, domain.TribeNotFoundError{
return domain.GroupWithMonitors{}, domain.TribeTagNotFoundError{
Tag: tribeTag,
}
}
@ -260,7 +224,7 @@ func (g *Group) Get(ctx context.Context, id, serverID string) (domain.GroupWithM
type getTribeResult struct {
index int
monitor domain.Monitor
tribe twhelp.Tribe
tribe domain.Tribe
err error
}
@ -284,7 +248,7 @@ func (g *Group) GetWithTribes(ctx context.Context, id, serverID string) (domain.
index: i,
monitor: monitor,
}
res.tribe, res.err = g.client.GetTribeByID(
res.tribe, res.err = g.twhelpSvc.GetTribeByID(
ctx,
group.VersionCode,
group.ServerKey,
@ -313,12 +277,7 @@ func (g *Group) GetWithTribes(ctx context.Context, id, serverID string) (domain.
monitors[res.index] = domain.MonitorWithTribe{
Monitor: res.monitor,
Tribe: domain.TribeMeta{
ID: res.tribe.ID,
Name: res.tribe.Name,
Tag: res.tribe.Tag,
ProfileURL: res.tribe.ProfileURL,
},
Tribe: res.tribe,
}
}
if firstErr != nil {
@ -369,12 +328,12 @@ func (g *Group) Execute(ctx context.Context) ([]domain.EnnoblementNotification,
type listEnnoblementsSingleResult struct {
versionCode string
serverKey string
ennoblements []twhelp.Ennoblement
ennoblements []domain.Ennoblement
}
type listEnnoblementsResult []listEnnoblementsSingleResult
func (r listEnnoblementsResult) find(versionCode, serverKey string) []twhelp.Ennoblement {
func (r listEnnoblementsResult) find(versionCode, serverKey string) []domain.Ennoblement {
for _, res := range r {
if res.serverKey == serverKey && res.versionCode == versionCode {
return res.ennoblements
@ -383,6 +342,8 @@ func (r listEnnoblementsResult) find(versionCode, serverKey string) []twhelp.Enn
return nil
}
const listEnnoblementsLimit = 200
func (g *Group) listEnnoblements(ctx context.Context, groups []domain.GroupWithMonitors) (listEnnoblementsResult, error) {
g.ennoblementsMu.Lock()
defer g.ennoblementsMu.Unlock()
@ -409,15 +370,7 @@ func (g *Group) listEnnoblements(ctx context.Context, groups []domain.GroupWithM
go func(group domain.GroupWithMonitors, since time.Time) {
defer wg.Done()
ennoblements, err := g.client.ListEnnoblements(
ctx,
group.VersionCode,
group.ServerKey,
twhelp.ListEnnoblementsQueryParams{
Since: since,
Sort: []twhelp.EnnoblementSort{twhelp.EnnoblementSortCreatedAtASC},
},
)
ennoblements, err := g.twhelpSvc.ListEnnoblementsSince(ctx, group.VersionCode, group.ServerKey, since, 0, listEnnoblementsLimit)
if err != nil {
g.logger.Warn(
"couldn't list ennoblements",
@ -497,19 +450,16 @@ func (g *Group) deleteAllWithDisabledNotifications(ctx context.Context) error {
return nil
}
const listServersLimit = 500
func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error {
versions, err := g.client.ListVersions(ctx)
versions, err := g.twhelpSvc.ListVersions(ctx)
if err != nil {
return fmt.Errorf("TWHelpClient.ListVersions: %w", err)
return err
}
for _, v := range versions {
servers, err := g.client.ListServers(ctx, v.Code, twhelp.ListServersQueryParams{
Open: twhelp.NullBool{
Bool: false,
Valid: true,
},
})
servers, err := g.twhelpSvc.ListClosedServers(ctx, v.Code, 0, listServersLimit)
if err != nil {
g.logger.Warn("couldn't list closed servers", zap.Error(err), zap.String("versionCode", v.Code))
continue

View File

@ -10,7 +10,6 @@ import (
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/service"
"gitea.dwysokinski.me/twhelp/dcbot/internal/service/internal/mock"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -57,9 +56,9 @@ func TestGroup_Create(t *testing.T) {
}, nil
})
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(_ context.Context, _ string, server string) (twhelp.Server, error) {
return twhelp.Server{
twhelpSvc := &mock.FakeTWHelpService{}
twhelpSvc.GetOpenServerCalls(func(_ context.Context, _ string, server string) (domain.TWServer, error) {
return domain.TWServer{
Key: server,
URL: fmt.Sprintf("https://%s.tribalwars.net", server),
Open: true,
@ -68,7 +67,7 @@ func TestGroup_Create(t *testing.T) {
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
g, err := service.NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.NoError(t, err)
assert.NotEmpty(t, g.ID)
assert.Equal(t, params.ServerID(), g.ServerID)
@ -96,53 +95,6 @@ func TestGroup_Create(t *testing.T) {
})
assert.Zero(t, g)
})
t.Run("ERR: server doesn't exist", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(_ context.Context, _ string, _ string) (twhelp.Server, error) {
return twhelp.Server{}, twhelp.APIError{
Code: twhelp.ErrorCodeEntityNotFound,
Message: "server not found",
}
})
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.TWServerDoesNotExistError{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),
})
assert.Zero(t, g)
})
t.Run("ERR: server is closed", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(ctx context.Context, _ string, server string) (twhelp.Server, error) {
return twhelp.Server{
Key: server,
URL: fmt.Sprintf("https://%s.tribalwars.net", server),
Open: false,
}, nil
})
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.TWServerIsClosedError{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),
})
assert.Zero(t, g)
})
}
func TestGroup_AddTribe(t *testing.T) {
@ -168,18 +120,18 @@ func TestGroup_AddTribe(t *testing.T) {
return group, nil
})
client := &mock.FakeTWHelpClient{}
tribe := twhelp.Tribe{
twhelpSvc := &mock.FakeTWHelpService{}
tribe := domain.Tribe{
ID: 1234,
Tag: "*TAG*",
Name: "Name",
ProfileURL: "Profile",
DeletedAt: time.Time{},
}
client.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
twhelpSvc.GetExistingTribeByTagReturns(tribe, nil)
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
AddTribe(context.Background(), group.ID, group.ServerID, tribe.Tag)
assert.NoError(t, err)
require.Len(t, g.Monitors, 1)
@ -262,31 +214,6 @@ func TestGroup_AddTribe(t *testing.T) {
})
assert.Zero(t, g)
})
t.Run("ERR: tribe doesn't exist", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
client.ListTribesReturns(nil, nil)
tribeTag := "*TAG*"
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
AddTribe(context.Background(), group.ID, group.ServerID, tribeTag)
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
Tag: tribeTag,
})
assert.Zero(t, g)
})
}
func TestGroup_RemoveTribe(t *testing.T) {
@ -334,15 +261,15 @@ func TestGroup_RemoveTribe(t *testing.T) {
}, nil
})
client := &mock.FakeTWHelpClient{}
tribe := twhelp.Tribe{
twhelpSvc := &mock.FakeTWHelpService{}
tribe := domain.Tribe{
ID: group.Monitors[0].TribeID,
Tag: "*TAG*",
}
client.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
twhelpSvc.ListTribesTagReturns([]domain.Tribe{tribe}, nil)
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
RemoveTribe(context.Background(), group.ID, group.ServerID, tribe.Tag)
assert.NoError(t, err)
assert.Len(t, g.Monitors, len(group.Monitors)-1)
@ -446,13 +373,13 @@ func TestGroup_RemoveTribe(t *testing.T) {
}, nil
})
client := &mock.FakeTWHelpClient{}
twhelpSvc := &mock.FakeTWHelpService{}
tag := "*X*"
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
RemoveTribe(context.Background(), group.ID, group.ServerID, tag)
assert.ErrorIs(t, err, domain.TribeNotFoundError{
assert.ErrorIs(t, err, domain.TribeTagNotFoundError{
Tag: tag,
})
assert.Zero(t, g)
@ -979,9 +906,9 @@ func TestGroup_GetWithTribes(t *testing.T) {
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
client.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (twhelp.Tribe, error) {
return twhelp.Tribe{
twhelpSvc := &mock.FakeTWHelpService{}
twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (domain.Tribe, error) {
return domain.Tribe{
ID: id,
Tag: fmt.Sprintf("*%d*", id),
Name: fmt.Sprintf("name %d", id),
@ -990,7 +917,7 @@ func TestGroup_GetWithTribes(t *testing.T) {
})
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
GetWithTribes(context.Background(), group.ID, group.ServerID)
assert.NoError(t, err)
assert.Equal(t, group.Group, g.Group)
@ -1078,22 +1005,22 @@ func TestGroup_GetWithTribes(t *testing.T) {
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
twhelpSvc := &mock.FakeTWHelpService{}
expectedErr := errors.New("err")
client.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (twhelp.Tribe, error) {
twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (domain.Tribe, error) {
if id == 115 {
return twhelp.Tribe{}, expectedErr
return domain.Tribe{}, expectedErr
}
<-ctx.Done()
return twhelp.Tribe{}, ctx.Err()
return domain.Tribe{}, ctx.Err()
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
GetWithTribes(ctx, group.ID, group.ServerID)
assert.ErrorIs(t, err, expectedErr)
assert.Zero(t, g)
@ -1173,8 +1100,8 @@ func TestGroup_Delete(t *testing.T) {
func TestGroup_Execute(t *testing.T) {
t.Parallel()
client := &mock.FakeTWHelpClient{}
tribes := map[string][]twhelp.TribeMeta{
twhelpSvc := &mock.FakeTWHelpService{}
tribes := map[string][]domain.TribeMeta{
"pl:pl181": {
{
ID: 1,
@ -1232,13 +1159,13 @@ func TestGroup_Execute(t *testing.T) {
},
},
}
players := map[string][]twhelp.PlayerMeta{
players := map[string][]domain.PlayerMeta{
"pl:pl181": {
{
ID: 1,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["pl:pl181"][0],
Valid: true,
},
@ -1247,7 +1174,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 2,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["pl:pl181"][1],
Valid: true,
},
@ -1256,7 +1183,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 3,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["pl:pl181"][2],
Valid: true,
},
@ -1265,7 +1192,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 4,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{},
Tribe: domain.NullTribeMeta{},
},
},
"en:en130": {
@ -1273,7 +1200,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 100,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["en:en130"][0],
Valid: true,
},
@ -1282,7 +1209,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 101,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["en:en130"][1],
Valid: true,
},
@ -1291,7 +1218,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 102,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{},
Tribe: domain.NullTribeMeta{},
},
},
"pl:pl180": {
@ -1299,7 +1226,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 200,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["pl:pl180"][0],
Valid: true,
},
@ -1310,7 +1237,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 300,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["de:de200"][0],
Valid: true,
},
@ -1319,7 +1246,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 301,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["de:de200"][0],
Valid: true,
},
@ -1328,20 +1255,20 @@ func TestGroup_Execute(t *testing.T) {
ID: 302,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: domain.NullTribeMeta{
Tribe: tribes["de:de200"][1],
Valid: true,
},
},
},
}
villages := map[string][]twhelp.VillageMeta{
villages := map[string][]domain.VillageMeta{
"pl:pl181": {
{
ID: 1,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["pl:pl181"][1],
Valid: true,
},
@ -1350,7 +1277,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 2,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1359,7 +1286,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 3,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["pl:pl181"][2],
Valid: true,
},
@ -1373,7 +1300,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 5,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1389,7 +1316,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 101,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["en:en130"][0],
Valid: true,
},
@ -1398,7 +1325,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 102,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["en:en130"][2],
Valid: true,
},
@ -1421,7 +1348,7 @@ func TestGroup_Execute(t *testing.T) {
ID: 301,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["de:de200"][1],
Valid: true,
},
@ -1430,19 +1357,19 @@ func TestGroup_Execute(t *testing.T) {
ID: 302,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: domain.NullPlayerMeta{
Player: players["de:de200"][2],
Valid: true,
},
},
},
}
ennoblements := map[string][]twhelp.Ennoblement{
ennoblements := map[string][]domain.Ennoblement{
"pl:pl181": {
{
ID: 1,
Village: villages["pl:pl181"][0],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1451,7 +1378,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 2, // self conquer, should be skipped
Village: villages["pl:pl181"][1],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1460,7 +1387,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 3, // internal, should be skipped (internals disabled)
Village: villages["pl:pl181"][2],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1469,7 +1396,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 4, // barbarian, shouldn't be skipped (barbarians enabled)
Village: villages["pl:pl181"][3],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
@ -1478,7 +1405,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 5, // disabled notifications about gains, should be skipped
Village: villages["pl:pl181"][4],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][1],
Valid: true,
},
@ -1487,7 +1414,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 6, // disabled notifications about losses, should be skipped
Village: villages["pl:pl181"][4],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl181"][3],
Valid: true,
},
@ -1498,7 +1425,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 100, // no monitor for these tribes, should be skipped
Village: villages["en:en130"][0],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["en:en130"][0],
Valid: true,
},
@ -1507,7 +1434,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 101, // no monitor for these tribes, should be skipped
Village: villages["en:en130"][1],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["en:en130"][2],
Valid: true,
},
@ -1516,7 +1443,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 102,
Village: villages["en:en130"][2],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["en:en130"][1],
Valid: true,
},
@ -1527,7 +1454,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 200, // api error, should be skipped
Village: villages["pl:pl180"][0],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["pl:pl180"][0],
Valid: true,
},
@ -1538,7 +1465,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 300, // barbarian, should be skipped (barbarians disabled)
Village: villages["de:de200"][0],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
@ -1547,7 +1474,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 301, // internal, shouldn't be skipped (internals enabled)
Village: villages["de:de200"][1],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
@ -1556,7 +1483,7 @@ func TestGroup_Execute(t *testing.T) {
{
ID: 302, // internal, shouldn't be skipped (internals enabled)
Village: villages["de:de200"][2],
NewOwner: twhelp.NullPlayerMeta{
NewOwner: domain.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
@ -1564,13 +1491,8 @@ func TestGroup_Execute(t *testing.T) {
},
},
}
client.ListEnnoblementsCalls(
func(
ctx context.Context,
version string,
server string,
_ twhelp.ListEnnoblementsQueryParams,
) ([]twhelp.Ennoblement, error) {
twhelpSvc.ListEnnoblementsSinceCalls(
func(ctx context.Context, version string, server string, _ time.Time, _ int32, _ int32) ([]domain.Ennoblement, error) {
if version == "pl" && server == "pl180" {
return nil, errors.New("random error")
}
@ -1687,7 +1609,7 @@ func TestGroup_Execute(t *testing.T) {
}
repo.ListReturns(groups, nil)
notifications, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).
notifications, err := service.NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).
Execute(context.Background())
assert.NoError(t, err)
expectedNotifications := []domain.EnnoblementNotification{
@ -1707,8 +1629,8 @@ func TestGroup_Execute(t *testing.T) {
func TestGroup_CleanUp(t *testing.T) {
t.Parallel()
client := &mock.FakeTWHelpClient{}
versions := []twhelp.Version{
twhelpSvc := &mock.FakeTWHelpService{}
versions := []domain.TWVersion{
{
Code: "pl",
Host: "www.plemiona.pl",
@ -1728,8 +1650,8 @@ func TestGroup_CleanUp(t *testing.T) {
Timezone: "Europe/Istanbul",
},
}
client.ListVersionsReturns(versions, nil)
serversByVersion := map[string][]twhelp.Server{
twhelpSvc.ListVersionsReturns(versions, nil)
serversByVersion := map[string][]domain.TWServer{
versions[0].Code: { // pl
{
Key: "pl181",
@ -1750,7 +1672,7 @@ func TestGroup_CleanUp(t *testing.T) {
},
},
}
client.ListServersCalls(func(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]twhelp.Server, error) {
twhelpSvc.ListClosedServersCalls(func(ctx context.Context, version string, _, _ int32) ([]domain.TWServer, error) {
return serversByVersion[version], nil
})
@ -1855,7 +1777,7 @@ func TestGroup_CleanUp(t *testing.T) {
return nil, errors.New("unexpected call")
})
assert.NoError(t, service.NewGroup(repo, client, zap.NewNop(), 1, 1).CleanUp(context.Background()))
assert.NoError(t, service.NewGroup(repo, twhelpSvc, zap.NewNop(), 1, 1).CleanUp(context.Background()))
require.Equal(t, 3, repo.DeleteManyCallCount())
for _, tt := range []struct {
@ -1883,43 +1805,11 @@ func TestGroup_CleanUp(t *testing.T) {
}
}
func newNotification(typ domain.EnnoblementNotificationType, serverID, channelID string, e twhelp.Ennoblement) domain.EnnoblementNotification {
func newNotification(typ domain.EnnoblementNotificationType, serverID, channelID string, e domain.Ennoblement) domain.EnnoblementNotification {
return domain.EnnoblementNotification{
Type: typ,
ServerID: serverID,
ChannelID: channelID,
Ennoblement: domain.Ennoblement{
ID: e.ID,
Village: domain.VillageMeta{
ID: e.Village.ID,
FullName: e.Village.FullName,
ProfileURL: e.Village.ProfileURL,
Player: domain.NullPlayerMeta{
Player: domain.PlayerMeta{
ID: e.Village.Player.Player.ID,
Name: e.Village.Player.Player.Name,
ProfileURL: e.Village.Player.Player.ProfileURL,
Tribe: domain.NullTribeMeta{
Tribe: domain.TribeMeta(e.Village.Player.Player.Tribe.Tribe),
Valid: e.Village.Player.Player.Tribe.Valid,
},
},
Valid: e.Village.Player.Valid,
},
},
NewOwner: domain.NullPlayerMeta{
Player: domain.PlayerMeta{
ID: e.NewOwner.Player.ID,
Name: e.NewOwner.Player.Name,
ProfileURL: e.NewOwner.Player.ProfileURL,
Tribe: domain.NullTribeMeta{
Tribe: domain.TribeMeta(e.NewOwner.Player.Tribe.Tribe),
Valid: e.NewOwner.Player.Tribe.Valid,
},
},
Valid: e.NewOwner.Valid,
},
CreatedAt: e.CreatedAt,
},
Type: typ,
ServerID: serverID,
ChannelID: channelID,
Ennoblement: e,
}
}

View File

@ -2,22 +2,22 @@ package service
import (
"context"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
)
//go:generate counterfeiter -generate
//counterfeiter:generate -o internal/mock/twhelp_client.gen.go . TWHelpClient
type TWHelpClient interface {
ListVersions(ctx context.Context) ([]twhelp.Version, error)
ListServers(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]twhelp.Server, error)
GetServer(ctx context.Context, version, server string) (twhelp.Server, error)
ListTribes(ctx context.Context, version, server string, params twhelp.ListTribesQueryParams) ([]twhelp.Tribe, error)
GetTribeByID(ctx context.Context, version, server string, id int64) (twhelp.Tribe, error)
ListEnnoblements(
ctx context.Context,
version, server string,
queryParams twhelp.ListEnnoblementsQueryParams,
) ([]twhelp.Ennoblement, error)
//counterfeiter:generate -o internal/mock/twhelp_service.gen.go . TWHelpService
type TWHelpService interface {
ListVersions(ctx context.Context) ([]domain.TWVersion, error)
ListOpenServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error)
ListClosedServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error)
GetOpenServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error)
GetTribeByID(ctx context.Context, versionCode, serverKey string, id int64) (domain.Tribe, error)
GetExistingTribeByTag(ctx context.Context, versionCode, serverKey, tribeTag string) (domain.Tribe, error)
ListTribesTag(ctx context.Context, versionCode, serverKey string, tribeTags []string, offset, limit int32) ([]domain.Tribe, error)
ListVillagesCoords(ctx context.Context, versionCode, serverKey string, coords []string, offset, limit int32) ([]domain.Village, error)
ListEnnoblementsSince(ctx context.Context, versionCode, serverKey string, since time.Time, offset, limit int32) ([]domain.Ennoblement, error)
}

View File

@ -23,6 +23,7 @@ const (
endpointListTribes = "/api/v1/versions/%s/servers/%s/tribes"
endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d"
endpointListEnnoblements = "/api/v1/versions/%s/servers/%s/ennoblements"
endpointListVillages = "/api/v1/versions/%s/servers/%s/villages"
)
type Client struct {
@ -88,11 +89,11 @@ func (c *Client) ListServers(
q := url.Values{}
if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(params.Limit)))
q.Set("limit", strconv.FormatInt(int64(params.Limit), 10))
}
if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(params.Offset)))
q.Set("offset", strconv.FormatInt(int64(params.Offset), 10))
}
if params.Open.Valid {
@ -130,11 +131,11 @@ func (c *Client) ListTribes(
q := url.Values{}
if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(params.Limit)))
q.Set("limit", strconv.FormatInt(int64(params.Limit), 10))
}
if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(params.Offset)))
q.Set("offset", strconv.FormatInt(int64(params.Offset), 10))
}
if params.Deleted.Valid {
@ -188,11 +189,11 @@ func (c *Client) ListEnnoblements(
q := url.Values{}
if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(params.Limit)))
q.Set("limit", strconv.FormatInt(int64(params.Limit), 10))
}
if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(params.Offset)))
q.Set("offset", strconv.FormatInt(int64(params.Offset), 10))
}
if !params.Since.IsZero() {
@ -211,6 +212,39 @@ func (c *Client) ListEnnoblements(
return resp.Data, nil
}
type ListVillagesQueryParams struct {
Limit int32
Offset int32
Coords []string
}
func (c *Client) ListVillages(
ctx context.Context,
version, server string,
params ListVillagesQueryParams,
) ([]Village, error) {
q := url.Values{}
if params.Limit > 0 {
q.Set("limit", strconv.FormatInt(int64(params.Limit), 10))
}
if params.Offset > 0 {
q.Set("offset", strconv.FormatInt(int64(params.Offset), 10))
}
for _, co := range params.Coords {
q.Add("coords", co)
}
var resp listVillagesResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointListVillages, version, server)+"?"+q.Encode(), &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
func (c *Client) getJSON(ctx context.Context, urlStr string, v any) error {
u, err := c.baseURL.Parse(urlStr)
if err != nil {

View File

@ -165,3 +165,15 @@ type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not NULL
}
type Village struct {
ID int64 `json:"id"`
FullName string `json:"fullName"`
ProfileURL string `json:"profileUrl"`
Points int64 `json:"points"`
Player NullPlayerMeta `json:"player"`
}
type listVillagesResp struct {
Data []Village `json:"data"`
}