feat: auto clean up old groups

This commit is contained in:
Dawid Wysokiński 2022-10-30 06:40:16 +01:00
parent 2c6dcda547
commit 12cc652c56
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
8 changed files with 201 additions and 31 deletions

View File

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

View File

@ -130,6 +130,18 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
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 {
params domain.UpdateGroupParams
}

View File

@ -105,9 +105,16 @@ func (u UpdateGroupParams) IsZero() bool {
!u.Barbarians.Valid
}
type ListGroupsParamsTWServer struct {
VersionCode string
ServerKey string
}
type ListGroupsParams struct {
ServerIDs []string
EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null
ServerIDs []string // DC server IDs
Servers []ListGroupsParamsTWServer // version codes + tw server keys
EnabledNotifications NullBool // check if ChannelGains != null && ChannelLosses != null
CreatedAtLTE time.Time
}
type GroupLimitReachedError struct {

View File

@ -4,9 +4,11 @@ import (
"context"
"errors"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"go.uber.org/zap"
)
//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)
Get(ctx context.Context, id, serverID string) (domain.Group, error)
Delete(ctx context.Context, id, serverID string) error
DeleteMany(ctx context.Context, id ...string) error
}
type Group struct {
repo GroupRepository
client TWHelpClient
logger *zap.Logger
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{
repo: repo,
client: client,
logger: logger,
maxGroupsPerServer: maxGroupsPerServer,
}
}
@ -59,6 +64,29 @@ 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) {
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) {
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
ChannelGains: domain.NullString{
@ -126,23 +154,88 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
return 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,
}
func (g *Group) CleanUp(ctx context.Context) error {
if err := g.cleanUpOldWithDisabledNotifications(ctx); err != nil {
return err
}
if !server.Open {
return domain.ServerIsClosedError{
VersionCode: versionCode,
Key: serverKey,
if err := g.cleanUpOldWithClosedTWServers(ctx); err != nil {
return err
}
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
}
}

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
func TestGroup_Create(t *testing.T) {
@ -57,7 +58,7 @@ func TestGroup_Create(t *testing.T) {
}, 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.NotEmpty(t, g.ID)
assert.Equal(t, params.ServerID(), g.ServerID)
@ -78,7 +79,7 @@ func TestGroup_Create(t *testing.T) {
repo := &mock.FakeGroupRepository{}
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{
Current: 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{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),
@ -123,7 +124,7 @@ func TestGroup_Create(t *testing.T) {
}, 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{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),

View File

@ -11,6 +11,11 @@ import (
//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)
GetTribeByID(ctx context.Context, version, server string, id int64) (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
endpointListVersions = "/api/v1/versions"
endpointListServers = "/api/v1/versions/%s/servers"
endpointGetServer = "/api/v1/versions/%s/servers/%s"
endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d"
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
}
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) {
var resp getServerResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointGetServer, version, server), &resp); err != nil {
@ -105,22 +139,26 @@ type ListEnnoblementsQueryParams struct {
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{}
if queryParams.Limit > 0 {
q.Set("limit", strconv.Itoa(int(queryParams.Limit)))
if params.Limit > 0 {
q.Set("limit", strconv.Itoa(int(params.Limit)))
}
if queryParams.Offset > 0 {
q.Set("offset", strconv.Itoa(int(queryParams.Offset)))
if params.Offset > 0 {
q.Set("offset", strconv.Itoa(int(params.Offset)))
}
if !queryParams.Since.IsZero() {
q.Set("since", queryParams.Since.Format(time.RFC3339))
if !params.Since.IsZero() {
q.Set("since", params.Since.Format(time.RFC3339))
}
for _, s := range queryParams.Sort {
for _, s := range params.Sort {
q.Add("sort", s.String())
}

View File

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