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)
|
||||
|
||||
choiceSvc := service.NewChoice(client)
|
||||
groupSvc := service.NewGroup(groupRepo, client, cfg.MaxGroupsPerServer)
|
||||
groupSvc := service.NewGroup(
|
||||
groupRepo,
|
||||
client,
|
||||
logger,
|
||||
cfg.MaxGroupsPerServer,
|
||||
)
|
||||
monitorSvc := service.NewMonitor(
|
||||
monitorRepo,
|
||||
groupRepo,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user