feat: auto clean up old groups (#38)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

Reviewed-on: #38
This commit is contained in:
Dawid Wysokiński 2022-10-31 05:52:20 +00:00
parent 2c6dcda547
commit 5e99f68a91
12 changed files with 532 additions and 38 deletions

View File

@ -47,7 +47,12 @@ func New() *cli.Command {
monitorRepo := bundb.NewMonitor(db) monitorRepo := bundb.NewMonitor(db)
choiceSvc := service.NewChoice(client) choiceSvc := service.NewChoice(client)
groupSvc := service.NewGroup(groupRepo, client, cfg.MaxGroupsPerServer) groupSvc := service.NewGroup(
groupRepo,
client,
logger,
cfg.MaxGroupsPerServer,
)
monitorSvc := service.NewMonitor( monitorSvc := service.NewMonitor(
monitorRepo, monitorRepo,
groupRepo, groupRepo,

View File

@ -130,6 +130,22 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
return nil return nil
} }
func (g *Group) DeleteMany(ctx context.Context, ids ...string) error {
if len(ids) == 0 {
return nil
}
_, err := g.db.NewDelete().
Model(&model.Group{}).
Returning("NULL").
Where("id IN (?)", bun.In(ids)).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("couldn't delete groups: %w", err)
}
return nil
}
type updateGroupsParamsApplier struct { type updateGroupsParamsApplier struct {
params domain.UpdateGroupParams params domain.UpdateGroupParams
} }
@ -182,5 +198,17 @@ func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
} }
} }
if l.params.VersionCode.Valid {
q = q.Where("version_code = ?", l.params.VersionCode.String)
}
if l.params.ServerKeys != nil {
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
}
if !l.params.CreatedAtLTE.IsZero() {
q = q.Where("created_at <= ?", l.params.CreatedAtLTE)
}
return q return q
} }

View File

@ -211,6 +211,44 @@ func TestGroup_List(t *testing.T) {
"abeb6c8e-70b6-445c-989f-890cd2a1f87a", "abeb6c8e-70b6-445c-989f-890cd2a1f87a",
}, },
}, },
{
name: "VersionCode=pl",
params: domain.ListGroupsParams{
VersionCode: domain.NullString{
String: "pl",
Valid: true,
},
},
expectedGroups: []string{
"d56ad37f-2637-48ea-98f8-79627f3fcc96",
"429b790e-7186-4106-b531-4cc4931ce2ba",
"abeb6c8e-70b6-445c-989f-890cd2a1f87a",
"0be82203-4ca3-4b4c-a0c8-3a70099d88f7",
"982a9765-471c-43e8-9abb-1e4ee4f03738",
},
},
{
name: "VersionCode=pl,ServerKeys=[pl180]",
params: domain.ListGroupsParams{
VersionCode: domain.NullString{
String: "pl",
Valid: true,
},
ServerKeys: []string{"pl180"},
},
expectedGroups: []string{
"982a9765-471c-43e8-9abb-1e4ee4f03738",
},
},
{
name: "CreatedAtLTE=2022-03-15T15:00:10.000Z",
params: domain.ListGroupsParams{
CreatedAtLTE: time.Date(2022, time.March, 15, 15, 00, 10, 0, time.UTC),
},
expectedGroups: []string{
"d56ad37f-2637-48ea-98f8-79627f3fcc96",
},
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -363,11 +401,53 @@ func TestGroup_Delete(t *testing.T) {
}) })
} }
func TestGroup_DeleteMany(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
groupRepo := bundb.NewGroup(db)
monitorRepo := bundb.NewMonitor(db)
group1 := getGroupFromFixture(t, fixture, "group-1-server-1")
group2 := getGroupFromFixture(t, fixture, "group-2-server-1")
ids := []string{group1.ID.String(), group2.ID.String()}
serverIDs := []string{group1.ServerID}
t.Run("OK", func(t *testing.T) {
t.Parallel()
beforeDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
ServerIDs: serverIDs,
})
assert.NoError(t, err)
assert.NoError(t, groupRepo.DeleteMany(context.Background(), ids...))
afterDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
ServerIDs: serverIDs,
})
assert.NoError(t, err)
assert.Len(t, afterDelete, len(beforeDelete)-len(ids))
// monitors should also be deleted
for _, id := range ids {
monitors, err := monitorRepo.List(context.Background(), id)
assert.NoError(t, err)
assert.Len(t, monitors, 0)
}
})
t.Run("OK: 0 ids", func(t *testing.T) {
t.Parallel()
assert.NoError(t, groupRepo.DeleteMany(context.Background()))
})
}
func getAllGroupsFromFixture(tb testing.TB, fixture *dbfixture.Fixture) []model.Group { func getAllGroupsFromFixture(tb testing.TB, fixture *dbfixture.Fixture) []model.Group {
tb.Helper() tb.Helper()
//nolint:lll //nolint:lll
ids := []string{"group-1-server-1", "group-2-server-1", "group-1-server-2", "group-2-server-2"} ids := []string{"group-1-server-1", "group-2-server-1", "group-1-server-2", "group-2-server-2", "group-3-server-1", "group-3-server-2"}
groups := make([]model.Group, 0, len(ids)) groups := make([]model.Group, 0, len(ids))
for _, id := range ids { for _, id := range ids {

View File

@ -28,6 +28,22 @@
channel_gains: 1555 channel_gains: 1555
channel_losses: 1235 channel_losses: 1235
created_at: 2022-03-19T15:03:10.000Z created_at: 2022-03-19T15:03:10.000Z
- _id: group-3-server-1
id: 982a9765-471c-43e8-9abb-1e4ee4f03738
server_id: server-3
server_key: pl180
version_code: pl
channel_gains: 1555
channel_losses: 1235
created_at: 2022-03-23T18:03:10.000Z
- _id: group-3-server-2
id: f3a0a5d9-07fe-4770-8b43-79cbe10bb310
server_id: server-3
server_key: en130
version_code: en
channel_gains: 1555
channel_losses: 1235
created_at: 2022-04-23T18:03:10.000Z
- model: Monitor - model: Monitor
rows: rows:
- _id: monitor-1-group-1-server-1 - _id: monitor-1-group-1-server-1

View File

@ -17,6 +17,7 @@ type GroupService interface {
SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.Group, error) SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.Group, error)
SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.Group, error) SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.Group, error)
SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.Group, error) SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.Group, error)
CleanUp(ctx context.Context) error
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
Delete(ctx context.Context, id, serverID string) error Delete(ctx context.Context, id, serverID string) error
} }
@ -122,14 +123,27 @@ func (b *Bot) registerCommand(cmd command) error {
} }
func (b *Bot) initCron() error { func (b *Bot) initCron() error {
_, err := b.c.AddJob("@every 1m", &executeMonitorsJob{ jobs := []struct {
svc: b.monitorSvc, spec string
s: b.s, job cron.Job
logger: b.logger, }{
}) {
spec: "@every 1m",
job: &executeMonitorsJob{svc: b.monitorSvc, s: b.s, logger: b.logger},
},
{
spec: "0 */8 * * *",
job: &cleanUpGroupsJob{svc: b.groupSvc, logger: b.logger},
},
}
for _, j := range jobs {
_, err := b.c.AddJob(j.spec, j.job)
if err != nil { if err != nil {
return err return err
} }
}
return nil return nil
} }

View File

@ -0,0 +1,30 @@
package discord
import (
"context"
"time"
"go.uber.org/zap"
)
const (
cleanUpGroupsJobTimeout = 10 * time.Minute
)
type cleanUpGroupsJob struct {
svc GroupService
logger *zap.Logger
}
func (j *cleanUpGroupsJob) Run() {
ctx, cancel := context.WithTimeout(context.Background(), cleanUpGroupsJobTimeout)
defer cancel()
start := time.Now()
if err := j.svc.CleanUp(ctx); err != nil {
j.logger.Error("something went wrong while deleting old groups", zap.Error(err))
return
}
j.logger.Info("old groups have been deleted", zap.Duration("duration", time.Since(start)))
}

View File

@ -106,8 +106,11 @@ func (u UpdateGroupParams) IsZero() bool {
} }
type ListGroupsParams struct { type ListGroupsParams struct {
ServerIDs []string ServerIDs []string // DC server IDs
VersionCode NullString
ServerKeys []string
EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null
CreatedAtLTE time.Time
} }
type GroupLimitReachedError struct { type GroupLimitReachedError struct {

View File

@ -4,9 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp" "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"go.uber.org/zap"
) )
//counterfeiter:generate -o internal/mock/group_repository.gen.go . GroupRepository //counterfeiter:generate -o internal/mock/group_repository.gen.go . GroupRepository
@ -16,18 +18,21 @@ type GroupRepository interface {
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
Get(ctx context.Context, id, serverID string) (domain.Group, error) Get(ctx context.Context, id, serverID string) (domain.Group, error)
Delete(ctx context.Context, id, serverID string) error Delete(ctx context.Context, id, serverID string) error
DeleteMany(ctx context.Context, id ...string) error
} }
type Group struct { type Group struct {
repo GroupRepository repo GroupRepository
client TWHelpClient client TWHelpClient
logger *zap.Logger
maxGroupsPerServer int maxGroupsPerServer int
} }
func NewGroup(repo GroupRepository, client TWHelpClient, maxGroupsPerServer int) *Group { func NewGroup(repo GroupRepository, client TWHelpClient, logger *zap.Logger, maxGroupsPerServer int) *Group {
return &Group{ return &Group{
repo: repo, repo: repo,
client: client, client: client,
logger: logger,
maxGroupsPerServer: maxGroupsPerServer, maxGroupsPerServer: maxGroupsPerServer,
} }
} }
@ -59,6 +64,29 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
return group, nil 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) {
return fmt.Errorf("TWHelpClient.GetServer: %w", err)
}
return domain.ServerDoesNotExistError{
VersionCode: versionCode,
Key: serverKey,
}
}
if !server.Open {
return domain.ServerIsClosedError{
VersionCode: versionCode,
Key: serverKey,
}
}
return nil
}
func (g *Group) SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.Group, error) { func (g *Group) SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.Group, error) {
return g.update(ctx, id, serverID, domain.UpdateGroupParams{ return g.update(ctx, id, serverID, domain.UpdateGroupParams{
ChannelGains: domain.NullString{ ChannelGains: domain.NullString{
@ -126,23 +154,97 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
return nil return nil
} }
func (g *Group) checkTWServer(ctx context.Context, versionCode, serverKey string) error { func (g *Group) CleanUp(ctx context.Context) error {
server, err := g.client.GetServer(ctx, versionCode, serverKey) if err := g.deleteAllWithDisabledNotifications(ctx); err != nil {
if err != nil { return err
var apiErr twhelp.APIError
if !errors.As(err, &apiErr) {
return fmt.Errorf("TWHelpClient.GetServer: %w", err)
}
return domain.ServerDoesNotExistError{
VersionCode: versionCode,
Key: serverKey,
}
} }
if !server.Open { if err := g.deleteAllWithClosedTWServers(ctx); err != nil {
return domain.ServerIsClosedError{ return err
VersionCode: versionCode, }
Key: serverKey,
return nil
}
func (g *Group) deleteAllWithDisabledNotifications(ctx context.Context) error {
groups, err := g.repo.List(ctx, domain.ListGroupsParams{
EnabledNotifications: domain.NullBool{
Bool: false,
Valid: true,
},
CreatedAtLTE: time.Now().Add(-24 * time.Hour),
})
if err != nil {
return fmt.Errorf("GroupRepository.List: %w", err)
}
ids := make([]string, 0, len(groups))
for _, group := range groups {
ids = append(ids, group.ID)
}
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
return fmt.Errorf("GroupRepository.DeleteMany: %w", err)
}
return nil
}
func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error {
versions, err := g.client.ListVersions(ctx)
if err != nil {
return fmt.Errorf("TWHelpClient.ListVersions: %w", err)
}
for _, v := range versions {
servers, err := g.client.ListServers(ctx, v.Code, twhelp.ListServersQueryParams{
Open: twhelp.NullBool{
Bool: false,
Valid: true,
},
})
if err != nil {
g.logger.Warn("failed to fetch closed servers", zap.Error(err), zap.String("versionCode", v.Code))
continue
}
if len(servers) == 0 {
continue
}
params := domain.ListGroupsParams{
VersionCode: domain.NullString{
String: v.Code,
Valid: true,
},
ServerKeys: make([]string, 0, len(servers)),
}
for _, s := range servers {
params.ServerKeys = append(params.ServerKeys, s.Key)
}
groups, err := g.repo.List(ctx, params)
if err != nil {
g.logger.Warn("failed to list groups", zap.Error(err), zap.String("versionCode", v.Code))
continue
}
if len(groups) == 0 {
continue
}
ids := make([]string, 0, len(groups))
for _, group := range groups {
ids = append(ids, group.ID)
}
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
g.logger.Warn(
"failed to delete groups",
zap.Error(err),
zap.String("versionCode", v.Code),
)
continue
} }
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap"
) )
func TestGroup_Create(t *testing.T) { func TestGroup_Create(t *testing.T) {
@ -57,7 +58,7 @@ func TestGroup_Create(t *testing.T) {
}, nil }, nil
}) })
g, err := service.NewGroup(repo, client, 1).Create(context.Background(), params) g, err := service.NewGroup(repo, client, zap.NewNop(), 1).Create(context.Background(), params)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, g.ID) assert.NotEmpty(t, g.ID)
assert.Equal(t, params.ServerID(), g.ServerID) assert.Equal(t, params.ServerID(), g.ServerID)
@ -78,7 +79,7 @@ func TestGroup_Create(t *testing.T) {
repo := &mock.FakeGroupRepository{} repo := &mock.FakeGroupRepository{}
repo.ListReturns(make([]domain.Group, maxGroupsPerServer), nil) repo.ListReturns(make([]domain.Group, maxGroupsPerServer), nil)
g, err := service.NewGroup(repo, nil, maxGroupsPerServer).Create(context.Background(), params) g, err := service.NewGroup(repo, nil, zap.NewNop(), maxGroupsPerServer).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.GroupLimitReachedError{ assert.ErrorIs(t, err, domain.GroupLimitReachedError{
Current: maxGroupsPerServer, Current: maxGroupsPerServer,
Limit: maxGroupsPerServer, Limit: maxGroupsPerServer,
@ -100,7 +101,7 @@ func TestGroup_Create(t *testing.T) {
} }
}) })
g, err := service.NewGroup(repo, client, 1).Create(context.Background(), params) g, err := service.NewGroup(repo, client, zap.NewNop(), 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.ServerDoesNotExistError{ assert.ErrorIs(t, err, domain.ServerDoesNotExistError{
VersionCode: params.VersionCode(), VersionCode: params.VersionCode(),
Key: params.ServerKey(), Key: params.ServerKey(),
@ -123,7 +124,7 @@ func TestGroup_Create(t *testing.T) {
}, nil }, nil
}) })
g, err := service.NewGroup(repo, client, 1).Create(context.Background(), params) g, err := service.NewGroup(repo, client, zap.NewNop(), 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.ServerIsClosedError{ assert.ErrorIs(t, err, domain.ServerIsClosedError{
VersionCode: params.VersionCode(), VersionCode: params.VersionCode(),
Key: params.ServerKey(), Key: params.ServerKey(),
@ -131,3 +132,166 @@ func TestGroup_Create(t *testing.T) {
assert.Zero(t, g) assert.Zero(t, g)
}) })
} }
func TestGroup_CleanUp(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
groupsWithDisabledNotifications := []domain.Group{
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: "",
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now().Add(-48 * time.Hour),
},
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: "",
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now().Add(-45 * time.Hour),
},
}
repo.ListReturnsOnCall(0, groupsWithDisabledNotifications, nil)
groupsPL := []domain.Group{
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now().Add(-120 * time.Hour),
},
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now().Add(-11 * time.Hour),
},
}
repo.ListReturnsOnCall(1, groupsPL, nil)
groupsUK := []domain.Group{
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "uk51",
VersionCode: "uk",
CreatedAt: time.Now().Add(-150 * time.Hour),
},
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "uk52",
VersionCode: "uk",
CreatedAt: time.Now().Add(-200 * time.Hour),
},
}
repo.ListReturnsOnCall(2, groupsUK, nil)
repo.DeleteManyReturns(nil)
client := &mock.FakeTWHelpClient{}
versions := []twhelp.Version{
{
Code: "pl",
Host: "www.plemiona.pl",
Name: "Poland",
Timezone: "Europe/Warsaw",
},
{
Code: "uk",
Host: "www.tribalwars.co.uk",
Name: "United Kingdom",
Timezone: "Europe/London",
},
{
Code: "tr",
Host: "www.klanlar.org",
Name: "Turkey",
Timezone: "Europe/Istanbul",
},
}
client.ListVersionsReturns(versions, nil)
serversPL := []twhelp.Server{
{
Key: "pl181",
URL: "https://pl181.plemiona.pl",
Open: false,
},
}
client.ListServersReturnsOnCall(0, serversPL, nil)
serversUK := []twhelp.Server{
{
Key: "uk51",
URL: "https://uk51.tribalwars.co.uk",
Open: false,
},
{
Key: "uk52",
URL: "https://uk52.tribalwars.co.uk",
Open: false,
},
}
client.ListServersReturnsOnCall(1, serversUK, nil)
client.ListServersReturnsOnCall(1, serversUK, nil)
client.ListServersReturnsOnCall(2, nil, nil) // Turkey
assert.NoError(t, service.NewGroup(repo, client, zap.NewNop(), 1).CleanUp(context.Background()))
require.Equal(t, 3, repo.ListCallCount())
require.Equal(t, 3, repo.DeleteManyCallCount())
for _, tt := range []struct {
i int
groups []domain.Group
}{
{
i: 0,
groups: groupsWithDisabledNotifications,
},
{
i: 1,
groups: groupsPL,
},
{
i: 2,
groups: groupsUK,
},
} {
_, ids := repo.DeleteManyArgsForCall(tt.i)
assert.Len(t, ids, len(tt.groups))
for i, id := range ids {
assert.Equal(t, tt.groups[i].ID, id)
}
}
require.Equal(t, 1, client.ListVersionsCallCount())
require.Equal(t, 3, client.ListServersCallCount())
})
}

View File

@ -11,6 +11,11 @@ import (
//counterfeiter:generate -o internal/mock/twhelp_client.gen.go . TWHelpClient //counterfeiter:generate -o internal/mock/twhelp_client.gen.go . TWHelpClient
type TWHelpClient interface { type TWHelpClient interface {
ListVersions(ctx context.Context) ([]twhelp.Version, error) 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) GetServer(ctx context.Context, version, server string) (twhelp.Server, error)
GetTribeByID(ctx context.Context, version, server string, id int64) (twhelp.Tribe, error) GetTribeByID(ctx context.Context, version, server string, id int64) (twhelp.Tribe, error)
GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error) GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error)

View File

@ -16,6 +16,7 @@ const (
defaultTimeout = 10 * time.Second defaultTimeout = 10 * time.Second
endpointListVersions = "/api/v1/versions" endpointListVersions = "/api/v1/versions"
endpointListServers = "/api/v1/versions/%s/servers"
endpointGetServer = "/api/v1/versions/%s/servers/%s" endpointGetServer = "/api/v1/versions/%s/servers/%s"
endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d" endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d"
endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s" endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s"
@ -64,6 +65,39 @@ func (c *Client) ListVersions(ctx context.Context) ([]Version, error) {
return resp.Data, nil return resp.Data, nil
} }
type ListServersQueryParams struct {
Limit int32
Offset int32
Open NullBool
}
func (c *Client) ListServers(
ctx context.Context,
version string,
params ListServersQueryParams,
) ([]Server, error) {
q := url.Values{}
if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(params.Limit)))
}
if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(params.Offset)))
}
if params.Open.Valid {
q.Set("open", strconv.FormatBool(params.Open.Bool))
}
var resp listServersResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointListServers, version)+"?"+q.Encode(), &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
func (c *Client) GetServer(ctx context.Context, version, server string) (Server, error) { func (c *Client) GetServer(ctx context.Context, version, server string) (Server, error) {
var resp getServerResp var resp getServerResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointGetServer, version, server), &resp); err != nil { if err := c.getJSON(ctx, fmt.Sprintf(endpointGetServer, version, server), &resp); err != nil {
@ -105,22 +139,26 @@ type ListEnnoblementsQueryParams struct {
Sort []ListEnnoblementsSort Sort []ListEnnoblementsSort
} }
func (c *Client) ListEnnoblements(ctx context.Context, version, server string, queryParams ListEnnoblementsQueryParams) ([]Ennoblement, error) { func (c *Client) ListEnnoblements(
ctx context.Context,
version, server string,
params ListEnnoblementsQueryParams,
) ([]Ennoblement, error) {
q := url.Values{} q := url.Values{}
if queryParams.Limit > 0 { if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(queryParams.Limit))) q.Set("limit", strconv.Itoa(int(params.Limit)))
} }
if queryParams.Offset > 0 { if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(queryParams.Offset))) q.Set("offset", strconv.Itoa(int(params.Offset)))
} }
if !queryParams.Since.IsZero() { if !params.Since.IsZero() {
q.Set("since", queryParams.Since.Format(time.RFC3339)) q.Set("since", params.Since.Format(time.RFC3339))
} }
for _, s := range queryParams.Sort { for _, s := range params.Sort {
q.Add("sort", s.String()) q.Add("sort", s.String())
} }

View File

@ -53,6 +53,10 @@ type getServerResp struct {
Data Server `json:"data"` Data Server `json:"data"`
} }
type listServersResp struct {
Data []Server `json:"data"`
}
type Tribe struct { type Tribe struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Tag string `json:"tag"` Tag string `json:"tag"`
@ -152,3 +156,8 @@ type Ennoblement struct {
type listEnnoblementsResp struct { type listEnnoblementsResp struct {
Data []Ennoblement `json:"data"` Data []Ennoblement `json:"data"`
} }
type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not NULL
}