feat: auto clean up old groups
This commit is contained in:
parent
2c6dcda547
commit
12cc652c56
|
@ -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,
|
||||||
|
|
|
@ -130,6 +130,18 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) DeleteMany(ctx context.Context, id ...string) error {
|
||||||
|
_, err := g.db.NewDelete().
|
||||||
|
Model(&model.Group{}).
|
||||||
|
Returning("NULL").
|
||||||
|
Where("id IN (?)", id).
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,9 +105,16 @@ func (u UpdateGroupParams) IsZero() bool {
|
||||||
!u.Barbarians.Valid
|
!u.Barbarians.Valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListGroupsParamsTWServer struct {
|
||||||
|
VersionCode string
|
||||||
|
ServerKey string
|
||||||
|
}
|
||||||
|
|
||||||
type ListGroupsParams struct {
|
type ListGroupsParams struct {
|
||||||
ServerIDs []string
|
ServerIDs []string // DC server IDs
|
||||||
EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null
|
Servers []ListGroupsParamsTWServer // version codes + tw server keys
|
||||||
|
EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null
|
||||||
|
CreatedAtLTE time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupLimitReachedError struct {
|
type GroupLimitReachedError struct {
|
||||||
|
|
|
@ -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,88 @@ 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.cleanUpOldWithDisabledNotifications(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.cleanUpOldWithClosedTWServers(ctx); err != nil {
|
||||||
return domain.ServerIsClosedError{
|
return err
|
||||||
VersionCode: versionCode,
|
}
|
||||||
Key: serverKey,
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) cleanUpOldWithDisabledNotifications(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) cleanUpOldWithClosedTWServers(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
|
||||||
|
}
|
||||||
|
|
||||||
|
params := domain.ListGroupsParams{
|
||||||
|
Servers: make([]domain.ListGroupsParamsTWServer, 0, len(servers)),
|
||||||
|
}
|
||||||
|
for _, s := range servers {
|
||||||
|
params.Servers = append(params.Servers, domain.ListGroupsParamsTWServer{
|
||||||
|
VersionCode: v.Code,
|
||||||
|
ServerKey: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user