refactor: group & monitor refactor (#107)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #107
This commit is contained in:
parent
e6d15ac950
commit
e110807619
|
@ -47,36 +47,23 @@ func New() *cli.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
groupRepo := bundb.NewGroup(db)
|
groupRepo := bundb.NewGroup(db)
|
||||||
monitorRepo := bundb.NewMonitor(db)
|
|
||||||
|
|
||||||
choiceSvc := service.NewChoice(client)
|
choiceSvc := service.NewChoice(client)
|
||||||
groupSvc := service.NewGroup(
|
groupSvc := service.NewGroup(groupRepo, client, logger, cfg.MaxGroupsPerServer, cfg.MaxMonitorsPerGroup)
|
||||||
groupRepo,
|
|
||||||
client,
|
|
||||||
logger,
|
|
||||||
cfg.MaxGroupsPerServer,
|
|
||||||
)
|
|
||||||
monitorSvc := service.NewMonitor(
|
|
||||||
monitorRepo,
|
|
||||||
groupRepo,
|
|
||||||
client,
|
|
||||||
logger,
|
|
||||||
cfg.MaxMonitorsPerGroup,
|
|
||||||
)
|
|
||||||
|
|
||||||
bot, err := discord.NewBot(cfg.Token, groupSvc, monitorSvc, choiceSvc, logger)
|
bot, err := discord.NewBot(cfg.Token, groupSvc, choiceSvc, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("discord.NewBot: %w", err)
|
return fmt.Errorf("discord.NewBot: %w", err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = bot.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if runErr := bot.Run(); runErr != nil {
|
if runErr := bot.Run(); runErr != nil {
|
||||||
logger.Fatal("something went wrong while starting the bot", zap.Error(runErr))
|
logger.Fatal("something went wrong while starting the bot", zap.Error(runErr))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer func() {
|
|
||||||
_ = bot.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = os.WriteFile(healthyFilePath, []byte("healthy"), healthyFilePerm); err != nil {
|
if err = os.WriteFile(healthyFilePath, []byte("healthy"), healthyFilePerm); err != nil {
|
||||||
return fmt.Errorf("couldn't create file (path=%s): %w", healthyFilePath, err)
|
return fmt.Errorf("couldn't create file (path=%s): %w", healthyFilePath, err)
|
||||||
|
@ -93,7 +80,7 @@ func New() *cli.Command {
|
||||||
|
|
||||||
type botConfig struct {
|
type botConfig struct {
|
||||||
Token string `envconfig:"TOKEN" required:"true"`
|
Token string `envconfig:"TOKEN" required:"true"`
|
||||||
MaxGroupsPerServer int `envconfig:"MAX_GROUPS_PER_SERVER" default:"10"`
|
MaxGroupsPerServer int `envconfig:"MAX_GROUPS_PER_SERVER" default:"5"`
|
||||||
MaxMonitorsPerGroup int `envconfig:"MAX_MONITORS_PER_GROUP" default:"10"`
|
MaxMonitorsPerGroup int `envconfig:"MAX_MONITORS_PER_GROUP" default:"10"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,15 @@ import (
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type twhelpClientConfig struct {
|
type twhelpClientConfig struct {
|
||||||
URL *url.URL `envconfig:"URL" required:"true"`
|
URL *url.URL `envconfig:"URL" required:"true"`
|
||||||
Timeout time.Duration `envconfig:"TIMEOUT" default:"10s"`
|
Timeout time.Duration `envconfig:"TIMEOUT" default:"10s"`
|
||||||
|
RateLimiterEnabled bool `envconfig:"RATE_LIMITER_ENABLED" default:"false"`
|
||||||
|
RateLimiterMaxRequestsPerSecond float64 `envconfig:"RATE_LIMITER_MAX_REQUESTS_PER_SECOND" default:"10"`
|
||||||
|
RateLimiterRequestBurst int `envconfig:"RATE_LIMITER_REQUEST_BURST" default:"5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTWHelpClient(version string) (*twhelp.Client, error) {
|
func NewTWHelpClient(version string) (*twhelp.Client, error) {
|
||||||
|
@ -21,11 +25,16 @@ func NewTWHelpClient(version string) (*twhelp.Client, error) {
|
||||||
return nil, fmt.Errorf("envconfig.Process: %w", err)
|
return nil, fmt.Errorf("envconfig.Process: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return twhelp.NewClient(
|
opts := []twhelp.ClientOption{
|
||||||
cfg.URL,
|
|
||||||
twhelp.WithHTTPClient(&http.Client{
|
twhelp.WithHTTPClient(&http.Client{
|
||||||
Timeout: cfg.Timeout,
|
Timeout: cfg.Timeout,
|
||||||
}),
|
}),
|
||||||
twhelp.WithUserAgent("TWHelpDCBot/"+version),
|
twhelp.WithUserAgent("TWHelpDCBot/" + version),
|
||||||
), nil
|
}
|
||||||
|
|
||||||
|
if cfg.RateLimiterEnabled {
|
||||||
|
opts = append(opts, twhelp.WithRateLimiter(rate.NewLimiter(rate.Limit(cfg.RateLimiterMaxRequestsPerSecond), cfg.RateLimiterRequestBurst)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return twhelp.NewClient(cfg.URL, opts...), nil
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -18,6 +18,7 @@ require (
|
||||||
github.com/uptrace/bun/driver/pgdriver v1.1.14
|
github.com/uptrace/bun/driver/pgdriver v1.1.14
|
||||||
github.com/urfave/cli/v2 v2.25.5
|
github.com/urfave/cli/v2 v2.25.5
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
|
golang.org/x/time v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -147,6 +147,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
|
|
@ -149,7 +149,7 @@ func loadFixtures(tb testing.TB, bunDB *bun.DB) *bunfixture {
|
||||||
return &bunfixture{fixture}
|
return &bunfixture{fixture}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *bunfixture) group(tb testing.TB, id string) domain.Group {
|
func (f *bunfixture) group(tb testing.TB, id string) domain.GroupWithMonitors {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
row, err := f.Row("Group." + id)
|
row, err := f.Row("Group." + id)
|
||||||
|
@ -159,13 +159,13 @@ func (f *bunfixture) group(tb testing.TB, id string) domain.Group {
|
||||||
return g.ToDomain()
|
return g.ToDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *bunfixture) groups(tb testing.TB) []domain.Group {
|
func (f *bunfixture) groups(tb testing.TB) []domain.GroupWithMonitors {
|
||||||
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", "group-3-server-1", "group-3-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([]domain.Group, 0, len(ids))
|
groups := make([]domain.GroupWithMonitors, 0, len(ids))
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
groups = append(groups, f.group(tb, id))
|
groups = append(groups, f.group(tb, id))
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,16 @@ func (f *bunfixture) groups(tb testing.TB) []domain.Group {
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *bunfixture) monitor(tb testing.TB, id string) domain.Monitor {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
row, err := f.Row("Monitor." + id)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
m, ok := row.(*model.Monitor)
|
||||||
|
require.True(tb, ok)
|
||||||
|
return m.ToDomain()
|
||||||
|
}
|
||||||
|
|
||||||
func generateSchema() string {
|
func generateSchema() string {
|
||||||
return strings.TrimFunc(strings.ReplaceAll(uuid.NewString(), "-", "_"), unicode.IsNumber)
|
return strings.TrimFunc(strings.ReplaceAll(uuid.NewString(), "-", "_"), unicode.IsNumber)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model"
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgerrcode"
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
|
"github.com/uptrace/bun/driver/pgdriver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
|
@ -20,7 +22,7 @@ func NewGroup(db *bun.DB) *Group {
|
||||||
return &Group{db: db}
|
return &Group{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) {
|
func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
|
||||||
group := model.Group{
|
group := model.Group{
|
||||||
ServerID: params.ServerID(),
|
ServerID: params.ServerID(),
|
||||||
VersionCode: params.VersionCode(),
|
VersionCode: params.VersionCode(),
|
||||||
|
@ -35,52 +37,103 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
|
||||||
Model(&group).
|
Model(&group).
|
||||||
Returning("*").
|
Returning("*").
|
||||||
Exec(ctx); err != nil {
|
Exec(ctx); err != nil {
|
||||||
return domain.Group{}, fmt.Errorf("something went wrong while inserting group into the db: %w", err)
|
return domain.GroupWithMonitors{}, fmt.Errorf("something went wrong while inserting group into the db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return group.ToDomain(), nil
|
return group.ToDomain(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.Group, error) {
|
func (g *Group) Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
|
||||||
if params.IsZero() {
|
if params.IsZero() {
|
||||||
return domain.Group{}, domain.ErrNothingToUpdate
|
return domain.GroupWithMonitors{}, domain.ErrNothingToUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := uuid.Parse(id); err != nil {
|
if _, err := uuid.Parse(id); err != nil {
|
||||||
return domain.Group{}, domain.GroupNotFoundError{ID: id}
|
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
|
||||||
}
|
}
|
||||||
|
|
||||||
var group model.Group
|
var group model.Group
|
||||||
|
|
||||||
res, err := g.db.NewUpdate().
|
res, err := g.db.NewUpdate().
|
||||||
Model(&group).
|
Model(&group).
|
||||||
Returning("*").
|
Returning("NULL").
|
||||||
Where("id = ?", id).
|
Where("id = ?", id).
|
||||||
Where("server_id = ?", serverID).
|
|
||||||
Apply(updateGroupsParamsApplier{params}.apply).
|
Apply(updateGroupsParamsApplier{params}.apply).
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
return domain.Group{}, fmt.Errorf("couldn't update group (id=%s,serverID=%s): %w", id, serverID, err)
|
return domain.GroupWithMonitors{}, fmt.Errorf("couldn't update group (id=%s): %w", id, err)
|
||||||
}
|
}
|
||||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
if affected, _ := res.RowsAffected(); affected == 0 {
|
||||||
return domain.Group{}, domain.GroupNotFoundError{ID: id}
|
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
|
||||||
}
|
}
|
||||||
|
|
||||||
return group.ToDomain(), nil
|
return g.Get(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error) {
|
func (g *Group) 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{
|
||||||
|
GroupID: parsedID,
|
||||||
|
TribeID: tribeID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = g.db.NewInsert().
|
||||||
|
Model(&monitor).
|
||||||
|
Returning("NULL").
|
||||||
|
Exec(ctx); err != nil {
|
||||||
|
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf(
|
||||||
|
"something went wrong while inserting monitor into the db: %w",
|
||||||
|
mapAddMonitorError(err, id, tribeID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Get(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, monitorID := range monitorIDs {
|
||||||
|
if _, err := uuid.Parse(monitorID); err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, domain.MonitorNotFoundError{ID: monitorID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := g.db.NewDelete().
|
||||||
|
Model(&model.Monitor{}).
|
||||||
|
Returning("NULL").
|
||||||
|
Where("id IN (?)", bun.In(monitorIDs)).
|
||||||
|
Where("group_id = ?", id).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("couldn't delete monitors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Get(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) {
|
||||||
var groups []model.Group
|
var groups []model.Group
|
||||||
|
|
||||||
if err := g.db.NewSelect().
|
if err := g.db.NewSelect().
|
||||||
Model(&groups).
|
Model(&groups).
|
||||||
Order("created_at ASC").
|
Order("created_at ASC").
|
||||||
|
Relation("Monitors", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
return q.Order("created_at ASC")
|
||||||
|
}).
|
||||||
Apply(listGroupsParamsApplier{params}.apply).
|
Apply(listGroupsParamsApplier{params}.apply).
|
||||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, fmt.Errorf("couldn't select groups from the db: %w", err)
|
return nil, fmt.Errorf("couldn't select groups from the db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]domain.Group, 0, len(groups))
|
result := make([]domain.GroupWithMonitors, 0, len(groups))
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
result = append(result, group.ToDomain())
|
result = append(result, group.ToDomain())
|
||||||
}
|
}
|
||||||
|
@ -88,29 +141,41 @@ func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]dom
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Get(ctx context.Context, id, serverID string) (domain.Group, error) {
|
func (g *Group) Get(ctx context.Context, id string) (domain.GroupWithMonitors, error) {
|
||||||
|
group, err := g.get(ctx, id, true)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, err
|
||||||
|
}
|
||||||
|
return group.ToDomain(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) get(ctx context.Context, id string, withMonitors bool) (model.Group, error) {
|
||||||
if _, err := uuid.Parse(id); err != nil {
|
if _, err := uuid.Parse(id); err != nil {
|
||||||
return domain.Group{}, domain.GroupNotFoundError{ID: id}
|
return model.Group{}, domain.GroupNotFoundError{ID: id}
|
||||||
}
|
}
|
||||||
|
|
||||||
var group model.Group
|
var group model.Group
|
||||||
|
|
||||||
err := g.db.NewSelect().
|
q := g.db.NewSelect().
|
||||||
Model(&group).
|
Model(&group).
|
||||||
Where("id = ?", id).
|
Where("id = ?", id)
|
||||||
Where("server_id = ?", serverID).
|
if withMonitors {
|
||||||
Scan(ctx)
|
q = q.Relation("Monitors", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
if err != nil {
|
return q.Order("created_at ASC")
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
})
|
||||||
return domain.Group{}, domain.GroupNotFoundError{ID: id}
|
|
||||||
}
|
|
||||||
return domain.Group{}, fmt.Errorf("couldn't select group (id=%s,serverID=%s) from the db: %w", id, serverID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return group.ToDomain(), nil
|
if err := q.Scan(ctx); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return model.Group{}, domain.GroupNotFoundError{ID: id}
|
||||||
|
}
|
||||||
|
return model.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, serverID string) error {
|
func (g *Group) Delete(ctx context.Context, id string) error {
|
||||||
if _, err := uuid.Parse(id); err != nil {
|
if _, err := uuid.Parse(id); err != nil {
|
||||||
return domain.GroupNotFoundError{ID: id}
|
return domain.GroupNotFoundError{ID: id}
|
||||||
}
|
}
|
||||||
|
@ -119,14 +184,14 @@ func (g *Group) Delete(ctx context.Context, id, serverID string) error {
|
||||||
Model(&model.Group{}).
|
Model(&model.Group{}).
|
||||||
Returning("NULL").
|
Returning("NULL").
|
||||||
Where("id = ?", id).
|
Where("id = ?", id).
|
||||||
Where("server_id = ?", serverID).
|
|
||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
return fmt.Errorf("couldn't delete group (id=%s,serverID=%s): %w", id, serverID, err)
|
return fmt.Errorf("couldn't delete group (id=%s): %w", id, err)
|
||||||
}
|
}
|
||||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
if affected, _ := res.RowsAffected(); affected == 0 {
|
||||||
return domain.GroupNotFoundError{ID: id}
|
return domain.GroupNotFoundError{ID: id}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +248,7 @@ type listGroupsParamsApplier struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
if l.params.ServerIDs != nil {
|
if len(l.params.ServerIDs) > 0 {
|
||||||
q = q.Where("server_id IN (?)", bun.In(l.params.ServerIDs))
|
q = q.Where("server_id IN (?)", bun.In(l.params.ServerIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +267,7 @@ func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
q = q.Where("version_code = ?", l.params.VersionCode.String)
|
q = q.Where("version_code = ?", l.params.VersionCode.String)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.params.ServerKeys != nil {
|
if len(l.params.ServerKeys) > 0 {
|
||||||
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
|
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,3 +277,26 @@ func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapAddMonitorError(err error, groupID string, tribeID int64) error {
|
||||||
|
var pgError pgdriver.Error
|
||||||
|
if !errors.As(err, &pgError) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
code := pgError.Field('C')
|
||||||
|
constraint := pgError.Field('n')
|
||||||
|
switch {
|
||||||
|
case code == pgerrcode.ForeignKeyViolation && constraint == "monitors_group_id_fkey":
|
||||||
|
return domain.GroupDoesNotExistError{
|
||||||
|
ID: groupID,
|
||||||
|
}
|
||||||
|
case code == pgerrcode.UniqueViolation && constraint == "monitors_group_id_tribe_id_key":
|
||||||
|
return domain.MonitorAlreadyExistsError{
|
||||||
|
TribeID: tribeID,
|
||||||
|
GroupID: groupID,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,8 +37,7 @@ func TestGroup_Create(t *testing.T) {
|
||||||
|
|
||||||
group, err := repo.Create(context.Background(), params)
|
group, err := repo.Create(context.Background(), params)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = uuid.Parse(group.ID)
|
assert.NotZero(t, group.ID)
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, params.ServerID(), group.ServerID)
|
assert.Equal(t, params.ServerID(), group.ServerID)
|
||||||
assert.Equal(t, params.ServerKey(), group.ServerKey)
|
assert.Equal(t, params.ServerKey(), group.ServerKey)
|
||||||
assert.Equal(t, params.VersionCode(), group.VersionCode)
|
assert.Equal(t, params.VersionCode(), group.VersionCode)
|
||||||
|
@ -84,7 +83,7 @@ func TestGroup_Update(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedGroup, err := repo.Update(context.Background(), group.ID, group.ServerID, params)
|
updatedGroup, err := repo.Update(context.Background(), group.ID, params)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, params.ChannelGains.String, updatedGroup.ChannelGains)
|
assert.Equal(t, params.ChannelGains.String, updatedGroup.ChannelGains)
|
||||||
assert.Equal(t, params.ChannelLosses.String, updatedGroup.ChannelLosses)
|
assert.Equal(t, params.ChannelLosses.String, updatedGroup.ChannelLosses)
|
||||||
|
@ -95,7 +94,7 @@ func TestGroup_Update(t *testing.T) {
|
||||||
t.Run("ERR: nothing to update", func(t *testing.T) {
|
t.Run("ERR: nothing to update", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
updatedGroup, err := repo.Update(context.Background(), "", "", domain.UpdateGroupParams{})
|
updatedGroup, err := repo.Update(context.Background(), "", domain.UpdateGroupParams{})
|
||||||
assert.ErrorIs(t, err, domain.ErrNothingToUpdate)
|
assert.ErrorIs(t, err, domain.ErrNothingToUpdate)
|
||||||
assert.Zero(t, updatedGroup)
|
assert.Zero(t, updatedGroup)
|
||||||
})
|
})
|
||||||
|
@ -106,22 +105,14 @@ func TestGroup_Update(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
id string
|
id string
|
||||||
serverID string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ID - not UUID",
|
name: "ID - not UUID",
|
||||||
id: "test",
|
id: "test",
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ID - random UUID",
|
name: "ID - random UUID",
|
||||||
id: uuid.NewString(),
|
id: uuid.NewString(),
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ServerID - random UUID",
|
|
||||||
id: group.ID,
|
|
||||||
serverID: uuid.NewString(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +122,7 @@ func TestGroup_Update(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
updatedGroup, err := repo.Update(context.Background(), tt.id, tt.serverID, domain.UpdateGroupParams{
|
updatedGroup, err := repo.Update(context.Background(), tt.id, domain.UpdateGroupParams{
|
||||||
ChannelGains: domain.NullString{
|
ChannelGains: domain.NullString{
|
||||||
String: "update",
|
String: "update",
|
||||||
Valid: true,
|
Valid: true,
|
||||||
|
@ -144,6 +135,168 @@ func TestGroup_Update(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGroup_AddMonitor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping long-running test")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := newDB(t)
|
||||||
|
fixture := loadFixtures(t, db)
|
||||||
|
repo := bundb.NewGroup(db)
|
||||||
|
group := fixture.group(t, "group-1-server-1")
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var tribeID int64 = 91283718
|
||||||
|
|
||||||
|
updatedGroup, err := repo.AddMonitor(context.Background(), group.ID, tribeID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
var found bool
|
||||||
|
for _, m := range updatedGroup.Monitors {
|
||||||
|
if m.GroupID == group.ID && m.TribeID == tribeID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ERR: group doesn't exist", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ID - not UUID",
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ID - random UUID",
|
||||||
|
id: uuid.NewString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
updatedGroup, err := repo.AddMonitor(context.Background(), tt.id, 125)
|
||||||
|
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{ID: tt.id})
|
||||||
|
assert.Zero(t, updatedGroup)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ERR: monitor already exists", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
monitor := fixture.monitor(t, "monitor-2-group-1-server-1")
|
||||||
|
|
||||||
|
updatedGroup, err := repo.AddMonitor(context.Background(), monitor.GroupID, monitor.TribeID)
|
||||||
|
assert.ErrorIs(t, err, domain.MonitorAlreadyExistsError{
|
||||||
|
TribeID: monitor.TribeID,
|
||||||
|
GroupID: monitor.GroupID,
|
||||||
|
})
|
||||||
|
assert.Zero(t, updatedGroup)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroup_DeleteMonitors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping long-running test")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := newDB(t)
|
||||||
|
fixture := loadFixtures(t, db)
|
||||||
|
repo := bundb.NewGroup(db)
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
monitor := fixture.monitor(t, "monitor-1-group-1-server-1")
|
||||||
|
|
||||||
|
updatedGroup, err := repo.DeleteMonitors(context.Background(), monitor.GroupID, monitor.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
var found bool
|
||||||
|
for _, m := range updatedGroup.Monitors {
|
||||||
|
if m.ID == monitor.ID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.False(t, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ERR: group not found", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
monitor := fixture.monitor(t, "monitor-2-group-1-server-1")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "group ID - not UUID",
|
||||||
|
id: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "group ID - not UUID",
|
||||||
|
id: uuid.NewString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
updatedGroup, err := repo.DeleteMonitors(context.Background(), tt.id, monitor.ID)
|
||||||
|
assert.ErrorIs(t, err, domain.GroupNotFoundError{ID: tt.id})
|
||||||
|
assert.Zero(t, updatedGroup)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ERR: monitor not found", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
monitor := fixture.monitor(t, "monitor-2-group-1-server-1")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
monitorID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "monitor ID - not UUID",
|
||||||
|
monitorID: "id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
updatedGroup, err := repo.DeleteMonitors(context.Background(), monitor.GroupID, tt.monitorID)
|
||||||
|
assert.ErrorIs(t, err, domain.MonitorNotFoundError{ID: tt.monitorID})
|
||||||
|
assert.Zero(t, updatedGroup)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGroup_List(t *testing.T) {
|
func TestGroup_List(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -299,9 +452,9 @@ func TestGroup_Get(t *testing.T) {
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
g, err := repo.Get(context.Background(), group.ID, group.ServerID)
|
g, err := repo.Get(context.Background(), group.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, group, g)
|
assert.Equal(t, group.ID, g.ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ERR: group not found (unknown ID)", func(t *testing.T) {
|
t.Run("ERR: group not found (unknown ID)", func(t *testing.T) {
|
||||||
|
@ -310,22 +463,14 @@ func TestGroup_Get(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
id string
|
id string
|
||||||
serverID string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ID - not UUID",
|
name: "ID - not UUID",
|
||||||
id: "test",
|
id: "test",
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ID - random UUID",
|
name: "ID - random UUID",
|
||||||
id: uuid.NewString(),
|
id: uuid.NewString(),
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ServerID - random UUID",
|
|
||||||
id: group.ID,
|
|
||||||
serverID: uuid.NewString(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +480,7 @@ func TestGroup_Get(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
g, err := repo.Get(context.Background(), tt.id, tt.serverID)
|
g, err := repo.Get(context.Background(), tt.id)
|
||||||
assert.ErrorIs(t, err, domain.GroupNotFoundError{ID: tt.id})
|
assert.ErrorIs(t, err, domain.GroupNotFoundError{ID: tt.id})
|
||||||
assert.Zero(t, g)
|
assert.Zero(t, g)
|
||||||
})
|
})
|
||||||
|
@ -353,7 +498,6 @@ func TestGroup_Delete(t *testing.T) {
|
||||||
db := newDB(t)
|
db := newDB(t)
|
||||||
fixture := loadFixtures(t, db)
|
fixture := loadFixtures(t, db)
|
||||||
groupRepo := bundb.NewGroup(db)
|
groupRepo := bundb.NewGroup(db)
|
||||||
monitorRepo := bundb.NewMonitor(db)
|
|
||||||
group := fixture.group(t, "group-1-server-1")
|
group := fixture.group(t, "group-1-server-1")
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
@ -364,18 +508,13 @@ func TestGroup_Delete(t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.NoError(t, groupRepo.Delete(context.Background(), group.ID, group.ServerID))
|
assert.NoError(t, groupRepo.Delete(context.Background(), group.ID))
|
||||||
|
|
||||||
afterDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
|
afterDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
|
||||||
ServerIDs: []string{group.ServerID},
|
ServerIDs: []string{group.ServerID},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, afterDelete, len(beforeDelete)-1)
|
assert.Len(t, afterDelete, len(beforeDelete)-1)
|
||||||
|
|
||||||
// monitors should also be deleted
|
|
||||||
monitors, err := monitorRepo.List(context.Background(), group.ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, monitors, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ERR: group not found", func(t *testing.T) {
|
t.Run("ERR: group not found", func(t *testing.T) {
|
||||||
|
@ -384,22 +523,14 @@ func TestGroup_Delete(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
id string
|
id string
|
||||||
serverID string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ID - not UUID",
|
name: "ID - not UUID",
|
||||||
id: "test",
|
id: "test",
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ID - random UUID",
|
name: "ID - random UUID",
|
||||||
id: uuid.NewString(),
|
id: uuid.NewString(),
|
||||||
serverID: group.ServerID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ServerID - random UUID",
|
|
||||||
id: group.ID,
|
|
||||||
serverID: uuid.NewString(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,11 +540,7 @@ func TestGroup_Delete(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
assert.ErrorIs(
|
assert.ErrorIs(t, groupRepo.Delete(context.Background(), tt.id), domain.GroupNotFoundError{ID: tt.id})
|
||||||
t,
|
|
||||||
groupRepo.Delete(context.Background(), tt.id, tt.serverID),
|
|
||||||
domain.GroupNotFoundError{ID: tt.id},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -429,7 +556,6 @@ func TestGroup_DeleteMany(t *testing.T) {
|
||||||
db := newDB(t)
|
db := newDB(t)
|
||||||
fixture := loadFixtures(t, db)
|
fixture := loadFixtures(t, db)
|
||||||
groupRepo := bundb.NewGroup(db)
|
groupRepo := bundb.NewGroup(db)
|
||||||
monitorRepo := bundb.NewMonitor(db)
|
|
||||||
group1 := fixture.group(t, "group-1-server-1")
|
group1 := fixture.group(t, "group-1-server-1")
|
||||||
group2 := fixture.group(t, "group-2-server-1")
|
group2 := fixture.group(t, "group-2-server-1")
|
||||||
ids := []string{group1.ID, group2.ID}
|
ids := []string{group1.ID, group2.ID}
|
||||||
|
@ -450,13 +576,6 @@ func TestGroup_DeleteMany(t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, afterDelete, len(beforeDelete)-len(ids))
|
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.Run("OK: 0 ids", func(t *testing.T) {
|
||||||
|
|
|
@ -20,10 +20,16 @@ type Group struct {
|
||||||
ServerKey string `bun:"server_key,nullzero"`
|
ServerKey string `bun:"server_key,nullzero"`
|
||||||
VersionCode string `bun:"version_code,nullzero"`
|
VersionCode string `bun:"version_code,nullzero"`
|
||||||
CreatedAt time.Time `bun:"created_at,nullzero"`
|
CreatedAt time.Time `bun:"created_at,nullzero"`
|
||||||
|
Monitors []Monitor `bun:"monitors,rel:has-many,join:id=group_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Group) ToDomain() domain.Group {
|
func (g Group) ToDomain() domain.GroupWithMonitors {
|
||||||
return domain.Group{
|
monitors := make([]domain.Monitor, 0, len(g.Monitors))
|
||||||
|
for _, m := range g.Monitors {
|
||||||
|
monitors = append(monitors, m.ToDomain())
|
||||||
|
}
|
||||||
|
return domain.GroupWithMonitors{
|
||||||
|
Group: domain.Group{
|
||||||
ID: g.ID.String(),
|
ID: g.ID.String(),
|
||||||
ServerID: g.ServerID,
|
ServerID: g.ServerID,
|
||||||
ChannelGains: g.ChannelGains,
|
ChannelGains: g.ChannelGains,
|
||||||
|
@ -33,5 +39,7 @@ func (g Group) ToDomain() domain.Group {
|
||||||
ServerKey: g.ServerKey,
|
ServerKey: g.ServerKey,
|
||||||
VersionCode: g.VersionCode,
|
VersionCode: g.VersionCode,
|
||||||
CreatedAt: g.CreatedAt,
|
CreatedAt: g.CreatedAt,
|
||||||
|
},
|
||||||
|
Monitors: monitors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
package bundb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb/internal/model"
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jackc/pgerrcode"
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
"github.com/uptrace/bun/driver/pgdriver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Monitor struct {
|
|
||||||
db *bun.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMonitor(db *bun.DB) *Monitor {
|
|
||||||
return &Monitor{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Create(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error) {
|
|
||||||
groupID, err := uuid.Parse(params.GroupID())
|
|
||||||
if err != nil {
|
|
||||||
return domain.Monitor{}, domain.GroupDoesNotExistError{
|
|
||||||
ID: params.GroupID(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor := model.Monitor{
|
|
||||||
GroupID: groupID,
|
|
||||||
TribeID: params.TribeID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = m.db.NewInsert().
|
|
||||||
Model(&monitor).
|
|
||||||
Returning("*").
|
|
||||||
Exec(ctx); err != nil {
|
|
||||||
|
|
||||||
return domain.Monitor{}, fmt.Errorf(
|
|
||||||
"something went wrong while inserting monitor into the db: %w",
|
|
||||||
mapCreateMonitorError(err, params),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor.ToDomain(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) List(ctx context.Context, groupID string) ([]domain.Monitor, error) {
|
|
||||||
if _, err := uuid.Parse(groupID); err != nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var monitors []model.Monitor
|
|
||||||
|
|
||||||
if err := m.db.NewSelect().
|
|
||||||
Model(&monitors).
|
|
||||||
Order("created_at ASC").
|
|
||||||
Where("group_id = ?", groupID).
|
|
||||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, fmt.Errorf("couldn't select monitors from the db: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]domain.Monitor, 0, len(monitors))
|
|
||||||
for _, monitor := range monitors {
|
|
||||||
result = append(result, monitor.ToDomain())
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Get(ctx context.Context, id string) (domain.Monitor, error) {
|
|
||||||
if _, err := uuid.Parse(id); err != nil {
|
|
||||||
return domain.Monitor{}, domain.MonitorNotFoundError{ID: id}
|
|
||||||
}
|
|
||||||
|
|
||||||
var monitor model.Monitor
|
|
||||||
|
|
||||||
err := m.db.NewSelect().
|
|
||||||
Model(&monitor).
|
|
||||||
Where("id = ?", id).
|
|
||||||
Scan(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return domain.Monitor{}, domain.MonitorNotFoundError{ID: id}
|
|
||||||
}
|
|
||||||
return domain.Monitor{}, fmt.Errorf("couldn't select monitor from the db (id=%s): %w", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor.ToDomain(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Delete(ctx context.Context, id string) error {
|
|
||||||
if _, err := uuid.Parse(id); err != nil {
|
|
||||||
return domain.MonitorNotFoundError{ID: id}
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := m.db.NewDelete().
|
|
||||||
Model(&model.Monitor{}).
|
|
||||||
Returning("NULL").
|
|
||||||
Where("id = ?", id).
|
|
||||||
Exec(ctx)
|
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return fmt.Errorf("couldn't delete monitor (id=%s): %w", id, err)
|
|
||||||
}
|
|
||||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
|
||||||
return domain.MonitorNotFoundError{ID: id}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapCreateMonitorError(err error, params domain.CreateMonitorParams) error {
|
|
||||||
var pgError pgdriver.Error
|
|
||||||
if !errors.As(err, &pgError) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
code := pgError.Field('C')
|
|
||||||
constraint := pgError.Field('n')
|
|
||||||
switch {
|
|
||||||
case code == pgerrcode.ForeignKeyViolation && constraint == "monitors_group_id_fkey":
|
|
||||||
return domain.GroupDoesNotExistError{
|
|
||||||
ID: params.GroupID(),
|
|
||||||
}
|
|
||||||
case code == pgerrcode.UniqueViolation && constraint == "monitors_group_id_tribe_id_key":
|
|
||||||
return domain.MonitorAlreadyExistsError{
|
|
||||||
TribeID: params.TribeID(),
|
|
||||||
GroupID: params.GroupID(),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,262 +0,0 @@
|
||||||
package bundb_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/bundb"
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMonitor_Create(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping long-running test")
|
|
||||||
}
|
|
||||||
|
|
||||||
db := newDB(t)
|
|
||||||
fixture := loadFixtures(t, db)
|
|
||||||
repo := bundb.NewMonitor(db)
|
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
group := fixture.group(t, "group-1-server-1")
|
|
||||||
|
|
||||||
params, err := domain.NewCreateMonitorParams(group.ID, 1234)
|
|
||||||
require.Zero(t, err)
|
|
||||||
|
|
||||||
monitor, err := repo.Create(context.Background(), params)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_, err = uuid.Parse(monitor.ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, params.GroupID(), monitor.GroupID)
|
|
||||||
assert.Equal(t, params.TribeID(), monitor.TribeID)
|
|
||||||
assert.WithinDuration(t, time.Now(), monitor.CreatedAt, 1*time.Second)
|
|
||||||
|
|
||||||
// check unique index
|
|
||||||
monitor, err = repo.Create(context.Background(), params)
|
|
||||||
assert.ErrorIs(t, err, domain.MonitorAlreadyExistsError{
|
|
||||||
TribeID: params.TribeID(),
|
|
||||||
GroupID: params.GroupID(),
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: group doesn't exist", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("foreign key violation", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
|
||||||
params, err := domain.NewCreateMonitorParams(groupID, 1234)
|
|
||||||
require.Zero(t, err)
|
|
||||||
|
|
||||||
monitor, err := repo.Create(context.Background(), params)
|
|
||||||
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
|
|
||||||
ID: groupID,
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("not UUID", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
groupID := "592292203234328587"
|
|
||||||
params, err := domain.NewCreateMonitorParams(groupID, 1234)
|
|
||||||
require.Zero(t, err)
|
|
||||||
|
|
||||||
monitor, err := repo.Create(context.Background(), params)
|
|
||||||
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
|
|
||||||
ID: groupID,
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitor_List(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping long-running test")
|
|
||||||
}
|
|
||||||
|
|
||||||
db := newDB(t)
|
|
||||||
fixture := loadFixtures(t, db)
|
|
||||||
repo := bundb.NewMonitor(db)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
groupID string
|
|
||||||
expectedMonitors []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "group-1-server-1",
|
|
||||||
groupID: fixture.group(t, "group-1-server-1").ID,
|
|
||||||
expectedMonitors: []string{
|
|
||||||
"e3017ba8-4fba-4bb1-ac8d-e4e9477a04d2",
|
|
||||||
"89719460-58ab-46b8-8682-46f161546949",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "group-2-server-2",
|
|
||||||
groupID: fixture.group(t, "group-2-server-2").ID,
|
|
||||||
expectedMonitors: []string{
|
|
||||||
"c7d63c3d-55f6-432e-b9d8-006f8c5ab407",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "random UUID",
|
|
||||||
groupID: uuid.NewString(),
|
|
||||||
expectedMonitors: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not UUID",
|
|
||||||
groupID: "test",
|
|
||||||
expectedMonitors: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
res, err := repo.List(context.Background(), tt.groupID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, res, len(tt.expectedMonitors))
|
|
||||||
for _, id := range tt.expectedMonitors {
|
|
||||||
found := false
|
|
||||||
for _, m := range res {
|
|
||||||
if m.ID == id {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.True(t, found, "monitor (id=%s) not found", id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitor_Get(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping long-running test")
|
|
||||||
}
|
|
||||||
|
|
||||||
db := newDB(t)
|
|
||||||
fixture := loadFixtures(t, db)
|
|
||||||
repo := bundb.NewMonitor(db)
|
|
||||||
group := fixture.group(t, "group-1-server-1")
|
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
monitors, err := repo.List(context.Background(), group.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Greater(t, len(monitors), 0)
|
|
||||||
|
|
||||||
m, err := repo.Get(context.Background(), monitors[0].ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, monitors[0], m)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: monitor not found", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
id string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ID - not UUID",
|
|
||||||
id: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ID - random UUID",
|
|
||||||
id: uuid.NewString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
monitor, err := repo.Get(context.Background(), tt.id)
|
|
||||||
assert.ErrorIs(t, err, domain.MonitorNotFoundError{ID: tt.id})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitor_Delete(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping long-running test")
|
|
||||||
}
|
|
||||||
|
|
||||||
db := newDB(t)
|
|
||||||
fixture := loadFixtures(t, db)
|
|
||||||
repo := bundb.NewMonitor(db)
|
|
||||||
group := fixture.group(t, "group-1-server-1")
|
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
beforeDelete, err := repo.List(context.Background(), group.ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Greater(t, len(beforeDelete), 0)
|
|
||||||
|
|
||||||
assert.NoError(t, repo.Delete(context.Background(), beforeDelete[0].ID))
|
|
||||||
|
|
||||||
afterDelete, err := repo.List(context.Background(), group.ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, afterDelete, len(beforeDelete)-1)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: monitor not found", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
id string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ID - not UUID",
|
|
||||||
id: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ID - random UUID",
|
|
||||||
id: uuid.NewString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
assert.ErrorIs(
|
|
||||||
t,
|
|
||||||
repo.Delete(context.Background(), tt.id),
|
|
||||||
domain.MonitorNotFoundError{ID: tt.id},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -12,20 +12,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type GroupService interface {
|
type GroupService interface {
|
||||||
Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error)
|
Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error)
|
||||||
SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.Group, error)
|
AddMonitor(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error)
|
||||||
SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.Group, error)
|
DeleteMonitor(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error)
|
||||||
SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.Group, error)
|
SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error)
|
||||||
SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.Group, error)
|
SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error)
|
||||||
CleanUp(ctx context.Context) error
|
SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.GroupWithMonitors, error)
|
||||||
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
|
SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.GroupWithMonitors, error)
|
||||||
Delete(ctx context.Context, id, serverID string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type MonitorService interface {
|
|
||||||
Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error)
|
|
||||||
List(ctx context.Context, groupID, serverID string) ([]domain.MonitorWithTribe, error)
|
|
||||||
Execute(ctx context.Context) ([]domain.EnnoblementNotification, error)
|
Execute(ctx context.Context) ([]domain.EnnoblementNotification, error)
|
||||||
|
CleanUp(ctx context.Context) error
|
||||||
|
ListServer(ctx context.Context, serverID string) ([]domain.GroupWithMonitors, error)
|
||||||
|
GetWithTribes(ctx context.Context, id, serverID string) (domain.GroupWithMonitorsAndTribes, error)
|
||||||
Delete(ctx context.Context, id, serverID string) error
|
Delete(ctx context.Context, id, serverID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +34,6 @@ type Bot struct {
|
||||||
s *discordgo.Session
|
s *discordgo.Session
|
||||||
c *cron.Cron
|
c *cron.Cron
|
||||||
groupSvc GroupService
|
groupSvc GroupService
|
||||||
monitorSvc MonitorService
|
|
||||||
choiceSvc ChoiceService
|
choiceSvc ChoiceService
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
@ -45,7 +41,6 @@ type Bot struct {
|
||||||
func NewBot(
|
func NewBot(
|
||||||
token string,
|
token string,
|
||||||
groupSvc GroupService,
|
groupSvc GroupService,
|
||||||
monitorSvc MonitorService,
|
|
||||||
client ChoiceService,
|
client ChoiceService,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) (*Bot, error) {
|
) (*Bot, error) {
|
||||||
|
@ -65,7 +60,6 @@ func NewBot(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
groupSvc: groupSvc,
|
groupSvc: groupSvc,
|
||||||
monitorSvc: monitorSvc,
|
|
||||||
choiceSvc: client,
|
choiceSvc: client,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
@ -103,7 +97,6 @@ type command interface {
|
||||||
func (b *Bot) registerCommands() error {
|
func (b *Bot) registerCommands() error {
|
||||||
commands := []command{
|
commands := []command{
|
||||||
&groupCommand{groupSvc: b.groupSvc, choiceSvc: b.choiceSvc},
|
&groupCommand{groupSvc: b.groupSvc, choiceSvc: b.choiceSvc},
|
||||||
&monitorCommand{svc: b.monitorSvc},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range commands {
|
for _, c := range commands {
|
||||||
|
@ -129,7 +122,7 @@ func (b *Bot) initCron() error {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
spec: "@every 1m",
|
spec: "@every 1m",
|
||||||
job: &executeMonitorsJob{svc: b.monitorSvc, s: b.s, logger: b.logger},
|
job: &executeMonitorsJob{svc: b.groupSvc, s: b.s, logger: b.logger},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spec: "0 */8 * * *",
|
spec: "0 */8 * * *",
|
||||||
|
@ -159,7 +152,8 @@ func (b *Bot) logCommands(_ *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
cmdData := i.ApplicationCommandData()
|
cmdData := i.ApplicationCommandData()
|
||||||
cmd := cmdData.Name
|
cmd := cmdData.Name
|
||||||
options := cmdData.Options
|
options := cmdData.Options
|
||||||
for len(options) > 0 && options[0].Type == discordgo.ApplicationCommandOptionSubCommand {
|
for len(options) > 0 &&
|
||||||
|
(options[0].Type == discordgo.ApplicationCommandOptionSubCommand || options[0].Type == discordgo.ApplicationCommandOptionSubCommandGroup) {
|
||||||
cmd += " " + options[0].Name
|
cmd += " " + options[0].Name
|
||||||
options = options[0].Options
|
options = options[0].Options
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,17 @@ package discord
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tribeTagMaxLength = 10
|
||||||
|
)
|
||||||
|
|
||||||
type groupCommand struct {
|
type groupCommand struct {
|
||||||
groupSvc GroupService
|
groupSvc GroupService
|
||||||
choiceSvc ChoiceService
|
choiceSvc ChoiceService
|
||||||
|
@ -98,9 +103,69 @@ func (c *groupCommand) create(s *discordgo.Session) error {
|
||||||
Description: "Lists all created groups",
|
Description: "Lists all created groups",
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "details",
|
||||||
|
Description: "Displays group details (including tribes added to the group)",
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "group",
|
||||||
|
Description: "Group ID",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "monitor",
|
||||||
|
Description: "Manages monitors",
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommandGroup,
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
Description: "Adds a new monitor to a group",
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "group",
|
||||||
|
Description: "Group ID",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tag",
|
||||||
|
Description: "Tribe tag",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
MaxLength: tribeTagMaxLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "delete",
|
||||||
|
Description: "Deletes a monitor",
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "group",
|
||||||
|
Description: "Group ID",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tag",
|
||||||
|
Description: "Tribe tag",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
MaxLength: tribeTagMaxLength,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
Description: "Sets various properties in a group configuration",
|
Description: "Sets various properties in group configuration",
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommandGroup,
|
Type: discordgo.ApplicationCommandOptionSubCommandGroup,
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
{
|
{
|
||||||
|
@ -266,29 +331,27 @@ func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCrea
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
switch cmdData.Options[0].Name {
|
switch cmdData.Options[0].Name {
|
||||||
case "create":
|
case "create":
|
||||||
c.handleCreate(s, i)
|
c.handleCreate(ctx, s, i)
|
||||||
return
|
|
||||||
case "list":
|
case "list":
|
||||||
c.handleList(s, i)
|
c.handleList(ctx, s, i)
|
||||||
return
|
case "details":
|
||||||
|
c.handleDetails(ctx, s, i)
|
||||||
case "set":
|
case "set":
|
||||||
c.handleSet(s, i)
|
c.handleSet(ctx, s, i)
|
||||||
return
|
|
||||||
case "unset":
|
case "unset":
|
||||||
c.handleUnset(s, i)
|
c.handleUnset(ctx, s, i)
|
||||||
return
|
case "monitor":
|
||||||
|
c.handleMonitor(ctx, s, i)
|
||||||
case "delete":
|
case "delete":
|
||||||
c.handleDelete(s, i)
|
c.handleDelete(ctx, s, i)
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleCreate(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
version := ""
|
version := ""
|
||||||
server := ""
|
server := ""
|
||||||
channelGains := ""
|
channelGains := ""
|
||||||
|
@ -345,17 +408,13 @@ func (c *groupCommand) handleCreate(s *discordgo.Session, i *discordgo.Interacti
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
Content: "group has been successfully created (ID=" + group.ID + ")",
|
Content: "group has been successfully created (id=" + group.ID + ")",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleList(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
groups, err := c.groupSvc.ListServer(ctx, i.GuildID)
|
||||||
|
|
||||||
groups, err := c.groupSvc.List(ctx, domain.ListGroupsParams{
|
|
||||||
ServerIDs: []string{i.GuildID},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
@ -379,12 +438,13 @@ func (c *groupCommand) handleList(s *discordgo.Session, i *discordgo.Interaction
|
||||||
fields = append(fields, &discordgo.MessageEmbedField{
|
fields = append(fields, &discordgo.MessageEmbedField{
|
||||||
Name: g.ID,
|
Name: g.ID,
|
||||||
Value: fmt.Sprintf(
|
Value: fmt.Sprintf(
|
||||||
"**Server**: %s\n**Channel gains**: %s\n**Channel losses**: %s\n**Internals**: %s\n **Barbarians**: %s",
|
"**Server**: %s\n**Channel gains**: %s\n**Channel losses**: %s\n**Internals**: %s\n **Barbarians**: %s\n **Number of monitored monitors**: %d",
|
||||||
g.ServerKey,
|
g.ServerKey,
|
||||||
channelGains,
|
channelGains,
|
||||||
channelLosses,
|
channelLosses,
|
||||||
boolToEmoji(g.Internals),
|
boolToEmoji(g.Internals),
|
||||||
boolToEmoji(g.Barbarians),
|
boolToEmoji(g.Barbarians),
|
||||||
|
len(g.Monitors),
|
||||||
),
|
),
|
||||||
Inline: false,
|
Inline: false,
|
||||||
})
|
})
|
||||||
|
@ -405,27 +465,75 @@ func (c *groupCommand) handleList(s *discordgo.Session, i *discordgo.Interaction
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleSet(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleDetails(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
g, err := c.groupSvc.GetWithTribes(ctx, i.ApplicationCommandData().Options[0].Options[0].StringValue(), i.GuildID)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: messageFromError(err),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var builderMonitors strings.Builder
|
||||||
|
for i, m := range g.Monitors {
|
||||||
|
if i > 0 {
|
||||||
|
builderMonitors.WriteString(" ")
|
||||||
|
}
|
||||||
|
builderMonitors.WriteString(buildLink(m.Tribe.Tag, m.Tribe.ProfileURL))
|
||||||
|
}
|
||||||
|
if builderMonitors.Len() == 0 {
|
||||||
|
builderMonitors.WriteString("None")
|
||||||
|
}
|
||||||
|
|
||||||
|
channelGains := "Not set"
|
||||||
|
if g.ChannelGains != "" {
|
||||||
|
channelGains = buildChannelMention(g.ChannelGains)
|
||||||
|
}
|
||||||
|
channelLosses := "Not set"
|
||||||
|
if g.ChannelLosses != "" {
|
||||||
|
channelLosses = buildChannelMention(g.ChannelLosses)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Embeds: []*discordgo.MessageEmbed{
|
||||||
|
{
|
||||||
|
Type: discordgo.EmbedTypeRich,
|
||||||
|
Title: g.ID,
|
||||||
|
Description: fmt.Sprintf(
|
||||||
|
"**Server**: %s\n**Channel gains**: %s\n**Channel losses**: %s\n**Internals**: %s\n **Barbarians**: %s\n **Monitored tribes**: %s",
|
||||||
|
g.ServerKey,
|
||||||
|
channelGains,
|
||||||
|
channelLosses,
|
||||||
|
boolToEmoji(g.Internals),
|
||||||
|
boolToEmoji(g.Barbarians),
|
||||||
|
builderMonitors.String(),
|
||||||
|
),
|
||||||
|
Timestamp: formatTimestamp(time.Now()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
switch i.ApplicationCommandData().Options[0].Options[0].Name {
|
switch i.ApplicationCommandData().Options[0].Options[0].Name {
|
||||||
case "channel-gains":
|
case "channel-gains":
|
||||||
c.handleSetChannelGains(s, i)
|
c.handleSetChannelGains(ctx, s, i)
|
||||||
return
|
|
||||||
case "channel-losses":
|
case "channel-losses":
|
||||||
c.handleSetChannelLosses(s, i)
|
c.handleSetChannelLosses(ctx, s, i)
|
||||||
return
|
|
||||||
case "internals":
|
case "internals":
|
||||||
c.handleSetInternals(s, i)
|
c.handleSetInternals(ctx, s, i)
|
||||||
return
|
|
||||||
case "barbarians":
|
case "barbarians":
|
||||||
c.handleSetBarbarians(s, i)
|
c.handleSetBarbarians(ctx, s, i)
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleSetChannelGains(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleSetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := ""
|
group := ""
|
||||||
channel := ""
|
channel := ""
|
||||||
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
@ -459,9 +567,7 @@ func (c *groupCommand) handleSetChannelGains(s *discordgo.Session, i *discordgo.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleSetChannelLosses(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleSetChannelLosses(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := ""
|
group := ""
|
||||||
channel := ""
|
channel := ""
|
||||||
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
@ -495,9 +601,7 @@ func (c *groupCommand) handleSetChannelLosses(s *discordgo.Session, i *discordgo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleSetInternals(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleSetInternals(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := ""
|
group := ""
|
||||||
internals := false
|
internals := false
|
||||||
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
@ -531,9 +635,7 @@ func (c *groupCommand) handleSetInternals(s *discordgo.Session, i *discordgo.Int
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleSetBarbarians(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleSetBarbarians(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := ""
|
group := ""
|
||||||
barbarians := false
|
barbarians := false
|
||||||
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
@ -567,21 +669,16 @@ func (c *groupCommand) handleSetBarbarians(s *discordgo.Session, i *discordgo.In
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleUnset(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleUnset(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
switch i.ApplicationCommandData().Options[0].Options[0].Name {
|
switch i.ApplicationCommandData().Options[0].Options[0].Name {
|
||||||
case "channel-gains":
|
case "channel-gains":
|
||||||
c.handleUnsetChannelGains(s, i)
|
c.handleUnsetChannelGains(ctx, s, i)
|
||||||
return
|
|
||||||
case "channel-losses":
|
case "channel-losses":
|
||||||
c.handleUnsetChannelLosses(s, i)
|
c.handleUnsetChannelLosses(ctx, s, i)
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleUnsetChannelGains(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleUnsetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue()
|
group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue()
|
||||||
_, err := c.groupSvc.SetChannelGains(ctx, group, i.GuildID, "")
|
_, err := c.groupSvc.SetChannelGains(ctx, group, i.GuildID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -602,9 +699,7 @@ func (c *groupCommand) handleUnsetChannelGains(s *discordgo.Session, i *discordg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleUnsetChannelLosses(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleUnsetChannelLosses(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue()
|
group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue()
|
||||||
_, err := c.groupSvc.SetChannelLosses(ctx, group, i.GuildID, "")
|
_, err := c.groupSvc.SetChannelLosses(ctx, group, i.GuildID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -625,9 +720,84 @@ func (c *groupCommand) handleUnsetChannelLosses(s *discordgo.Session, i *discord
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *groupCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (c *groupCommand) handleMonitor(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
switch i.ApplicationCommandData().Options[0].Options[0].Name {
|
||||||
|
case "add":
|
||||||
|
c.handleMonitorAdd(ctx, s, i)
|
||||||
|
case "delete":
|
||||||
|
c.handleMonitorDelete(ctx, s, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *groupCommand) handleMonitorAdd(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
group := ""
|
||||||
|
tag := ""
|
||||||
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
if opt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch opt.Name {
|
||||||
|
case "group":
|
||||||
|
group = opt.StringValue()
|
||||||
|
case "tag":
|
||||||
|
tag = opt.StringValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.groupSvc.AddMonitor(ctx, group, i.GuildID, tag)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: messageFromError(err),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: "monitor has been successfully added",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *groupCommand) handleMonitorDelete(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
group := ""
|
||||||
|
tag := ""
|
||||||
|
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
|
||||||
|
if opt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch opt.Name {
|
||||||
|
case "group":
|
||||||
|
group = opt.StringValue()
|
||||||
|
case "tag":
|
||||||
|
tag = opt.StringValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.groupSvc.DeleteMonitor(ctx, group, i.GuildID, tag)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: messageFromError(err),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: "monitor has been successfully deleted",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *groupCommand) handleDelete(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
group := i.ApplicationCommandData().Options[0].Options[0].StringValue()
|
group := i.ApplicationCommandData().Options[0].Options[0].StringValue()
|
||||||
if err := c.groupSvc.Delete(ctx, group, i.GuildID); err != nil {
|
if err := c.groupSvc.Delete(ctx, group, i.GuildID); err != nil {
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
package discord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tribeTagMaxLength = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type monitorCommand struct {
|
|
||||||
svc MonitorService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) name() string {
|
|
||||||
return "monitor"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) register(s *discordgo.Session) error {
|
|
||||||
if err := c.create(s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.AddHandler(c.handle)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) create(s *discordgo.Session) error {
|
|
||||||
var perm int64 = discordgo.PermissionAdministrator
|
|
||||||
dm := false
|
|
||||||
|
|
||||||
_, err := s.ApplicationCommandCreate(s.State.User.ID, "", &discordgo.ApplicationCommand{
|
|
||||||
Name: c.name(),
|
|
||||||
Description: "Manages monitors",
|
|
||||||
DefaultMemberPermissions: &perm,
|
|
||||||
DMPermission: &dm,
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Name: "create",
|
|
||||||
Description: "Creates a new monitor",
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Name: "group",
|
|
||||||
Description: "Group ID",
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "tag",
|
|
||||||
Description: "Tribe tag",
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Required: true,
|
|
||||||
MaxLength: tribeTagMaxLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "list",
|
|
||||||
Description: "Lists all monitors associated with a particular group",
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Name: "group",
|
|
||||||
Description: "Group ID",
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "delete",
|
|
||||||
Description: "Deletes a monitor",
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Name: "monitor",
|
|
||||||
Description: "Monitor ID",
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("s.ApplicationCommandCreate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
cmdData := i.ApplicationCommandData()
|
|
||||||
|
|
||||||
if cmdData.Name != c.name() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cmdData.Options[0].Name {
|
|
||||||
case "create":
|
|
||||||
c.handleCreate(s, i)
|
|
||||||
return
|
|
||||||
case "list":
|
|
||||||
c.handleList(s, i)
|
|
||||||
return
|
|
||||||
case "delete":
|
|
||||||
c.handleDelete(s, i)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) handleCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := ""
|
|
||||||
tag := ""
|
|
||||||
for _, opt := range i.ApplicationCommandData().Options[0].Options {
|
|
||||||
if opt == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch opt.Name {
|
|
||||||
case "group":
|
|
||||||
group = opt.StringValue()
|
|
||||||
case "tag":
|
|
||||||
tag = opt.StringValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor, err := c.svc.Create(ctx, group, i.GuildID, tag)
|
|
||||||
if err != nil {
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Content: messageFromError(err),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Content: "monitor has been successfully created (ID=" + monitor.ID + ")",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) handleList(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
group := i.ApplicationCommandData().Options[0].Options[0].StringValue()
|
|
||||||
monitors, err := c.svc.List(ctx, group, i.GuildID)
|
|
||||||
if err != nil {
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Content: messageFromError(err),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := make([]*discordgo.MessageEmbedField, 0, len(monitors))
|
|
||||||
for _, m := range monitors {
|
|
||||||
fields = append(fields, &discordgo.MessageEmbedField{
|
|
||||||
Name: m.ID,
|
|
||||||
Value: fmt.Sprintf("**Tribe**: %s", buildLink(m.Tribe.Tag, m.Tribe.ProfileURL)),
|
|
||||||
Inline: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Embeds: []*discordgo.MessageEmbed{
|
|
||||||
{
|
|
||||||
Type: discordgo.EmbedTypeRich,
|
|
||||||
Title: "Monitor list",
|
|
||||||
Description: "**Group**: " + group,
|
|
||||||
Fields: fields,
|
|
||||||
Timestamp: formatTimestamp(time.Now()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *monitorCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
monitor := i.ApplicationCommandData().Options[0].Options[0].StringValue()
|
|
||||||
if err := c.svc.Delete(ctx, monitor, i.GuildID); err != nil {
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Content: messageFromError(err),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Content: "monitor has been successfully deleted",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -19,7 +19,7 @@ const (
|
||||||
|
|
||||||
type executeMonitorsJob struct {
|
type executeMonitorsJob struct {
|
||||||
s *discordgo.Session
|
s *discordgo.Session
|
||||||
svc MonitorService
|
svc GroupService
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package domain
|
||||||
type EnnoblementNotificationType uint8
|
type EnnoblementNotificationType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnnoblementNotificationTypeGain = iota
|
EnnoblementNotificationTypeGain EnnoblementNotificationType = iota
|
||||||
EnnoblementNotificationTypeLoss
|
EnnoblementNotificationTypeLoss
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,18 @@ type Group struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GroupWithMonitors struct {
|
||||||
|
Group
|
||||||
|
|
||||||
|
Monitors []Monitor
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupWithMonitorsAndTribes struct {
|
||||||
|
Group
|
||||||
|
|
||||||
|
Monitors []MonitorWithTribe
|
||||||
|
}
|
||||||
|
|
||||||
type CreateGroupParams struct {
|
type CreateGroupParams struct {
|
||||||
serverID string
|
serverID string
|
||||||
serverKey string
|
serverKey string
|
||||||
|
@ -135,7 +147,7 @@ type GroupNotFoundError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e GroupNotFoundError) Error() string {
|
func (e GroupNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("group (ID=%s) not found", e.ID)
|
return fmt.Sprintf("group (id=%s) not found", e.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e GroupNotFoundError) UserError() string {
|
func (e GroupNotFoundError) UserError() string {
|
||||||
|
@ -151,7 +163,7 @@ type GroupDoesNotExistError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e GroupDoesNotExistError) Error() string {
|
func (e GroupDoesNotExistError) Error() string {
|
||||||
return fmt.Sprintf("group (ID=%s) doesn't exist", e.ID)
|
return fmt.Sprintf("group (id=%s) doesn't exist", e.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e GroupDoesNotExistError) UserError() string {
|
func (e GroupDoesNotExistError) UserError() string {
|
||||||
|
|
|
@ -204,7 +204,7 @@ func TestGroupNotFoundError(t *testing.T) {
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("group (ID=%s) not found", err.ID), err.Error())
|
assert.Equal(t, fmt.Sprintf("group (id=%s) not found", err.ID), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
|
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ func TestGroupDoesNotExistError(t *testing.T) {
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("group (ID=%s) doesn't exist", err.ID), err.Error())
|
assert.Equal(t, fmt.Sprintf("group (id=%s) doesn't exist", err.ID), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
tribeIDMin = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
ID string
|
ID string
|
||||||
TribeID int64
|
TribeID int64
|
||||||
|
@ -21,46 +17,13 @@ type MonitorWithTribe struct {
|
||||||
Tribe TribeMeta
|
Tribe TribeMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateMonitorParams struct {
|
|
||||||
tribeID int64
|
|
||||||
groupID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCreateMonitorParams(groupID string, tribeID int64) (CreateMonitorParams, error) {
|
|
||||||
if groupID == "" {
|
|
||||||
return CreateMonitorParams{}, ValidationError{
|
|
||||||
Field: "GroupID",
|
|
||||||
Err: ErrRequired,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tribeID < tribeIDMin {
|
|
||||||
return CreateMonitorParams{}, ValidationError{
|
|
||||||
Field: "TribeID",
|
|
||||||
Err: MinError{
|
|
||||||
Min: tribeIDMin,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateMonitorParams{tribeID: tribeID, groupID: groupID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CreateMonitorParams) TribeID() int64 {
|
|
||||||
return c.tribeID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CreateMonitorParams) GroupID() string {
|
|
||||||
return c.groupID
|
|
||||||
}
|
|
||||||
|
|
||||||
type MonitorAlreadyExistsError struct {
|
type MonitorAlreadyExistsError struct {
|
||||||
TribeID int64
|
TribeID int64
|
||||||
GroupID string
|
GroupID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MonitorAlreadyExistsError) Error() string {
|
func (e MonitorAlreadyExistsError) Error() string {
|
||||||
return fmt.Sprintf("monitor (GroupID=%s,TribeID=%d) already exists", e.GroupID, e.TribeID)
|
return fmt.Sprintf("monitor (groupID=%s,tribeID=%d) already exists", e.GroupID, e.TribeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MonitorAlreadyExistsError) UserError() string {
|
func (e MonitorAlreadyExistsError) UserError() string {
|
||||||
|
@ -93,7 +56,7 @@ type MonitorNotFoundError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MonitorNotFoundError) Error() string {
|
func (e MonitorNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("monitor (ID=%s) not found", e.ID)
|
return fmt.Sprintf("monitor (id=%s) not found", e.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MonitorNotFoundError) UserError() string {
|
func (e MonitorNotFoundError) UserError() string {
|
||||||
|
|
|
@ -9,63 +9,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewCreateMonitorParams(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
tribeID int64
|
|
||||||
groupID string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "OK",
|
|
||||||
tribeID: 15,
|
|
||||||
groupID: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ERR: GroupID cannot be blank",
|
|
||||||
groupID: "",
|
|
||||||
err: domain.ValidationError{
|
|
||||||
Field: "GroupID",
|
|
||||||
Err: domain.ErrRequired,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ERR: TribeID < 1",
|
|
||||||
groupID: uuid.NewString(),
|
|
||||||
tribeID: 0,
|
|
||||||
err: domain.ValidationError{
|
|
||||||
Field: "TribeID",
|
|
||||||
Err: domain.MinError{
|
|
||||||
Min: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
res, err := domain.NewCreateMonitorParams(
|
|
||||||
tt.groupID,
|
|
||||||
tt.tribeID,
|
|
||||||
)
|
|
||||||
if tt.err != nil {
|
|
||||||
assert.ErrorIs(t, err, tt.err)
|
|
||||||
assert.Zero(t, res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.groupID, res.GroupID())
|
|
||||||
assert.Equal(t, tt.tribeID, res.TribeID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitorAlreadyExistsError(t *testing.T) {
|
func TestMonitorAlreadyExistsError(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -74,7 +17,7 @@ func TestMonitorAlreadyExistsError(t *testing.T) {
|
||||||
TribeID: 1234,
|
TribeID: 1234,
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("monitor (GroupID=%s,TribeID=%d) already exists", err.GroupID, err.TribeID), err.Error())
|
assert.Equal(t, fmt.Sprintf("monitor (groupID=%s,tribeID=%d) already exists", err.GroupID, err.TribeID), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeAlreadyExists, err.Code())
|
assert.Equal(t, domain.ErrorCodeAlreadyExists, err.Code())
|
||||||
}
|
}
|
||||||
|
@ -99,7 +42,7 @@ func TestMonitorNotFoundError(t *testing.T) {
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("monitor (ID=%s) not found", err.ID), err.Error())
|
assert.Equal(t, fmt.Sprintf("monitor (id=%s) not found", err.ID), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
|
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ type ServerDoesNotExistError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ServerDoesNotExistError) Error() string {
|
func (e ServerDoesNotExistError) Error() string {
|
||||||
return fmt.Sprintf("server (VersionCode=%s,Key=%s) doesn't exist", e.VersionCode, e.Key)
|
return fmt.Sprintf("server (versionCode=%s,key=%s) doesn't exist", e.VersionCode, e.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ServerDoesNotExistError) UserError() string {
|
func (e ServerDoesNotExistError) UserError() string {
|
||||||
|
@ -66,7 +66,7 @@ type ServerIsClosedError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ServerIsClosedError) Error() string {
|
func (e ServerIsClosedError) Error() string {
|
||||||
return fmt.Sprintf("server (VersionCode=%s,Key=%s) is closed", e.VersionCode, e.Key)
|
return fmt.Sprintf("server (versionCode=%s,key=%s) is closed", e.VersionCode, e.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ServerIsClosedError) UserError() string {
|
func (e ServerIsClosedError) UserError() string {
|
||||||
|
@ -82,7 +82,7 @@ type TribeDoesNotExistError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e TribeDoesNotExistError) Error() string {
|
func (e TribeDoesNotExistError) Error() string {
|
||||||
return fmt.Sprintf("tribe (Tag=%s) doesn't exist", e.Tag)
|
return fmt.Sprintf("tribe (tag=%s) doesn't exist", e.Tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e TribeDoesNotExistError) UserError() string {
|
func (e TribeDoesNotExistError) UserError() string {
|
||||||
|
@ -92,3 +92,19 @@ func (e TribeDoesNotExistError) UserError() string {
|
||||||
func (e TribeDoesNotExistError) Code() ErrorCode {
|
func (e TribeDoesNotExistError) Code() ErrorCode {
|
||||||
return ErrorCodeValidationError
|
return ErrorCodeValidationError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TribeNotFoundError struct {
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TribeNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("tribe (tag=%s) not found", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TribeNotFoundError) UserError() string {
|
||||||
|
return e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TribeNotFoundError) Code() ErrorCode {
|
||||||
|
return ErrorCodeEntityNotFound
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestServerDoesNotExistError(t *testing.T) {
|
||||||
Key: "pl151",
|
Key: "pl151",
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("server (VersionCode=%s,Key=%s) doesn't exist", err.VersionCode, err.Key), err.Error())
|
assert.Equal(t, fmt.Sprintf("server (versionCode=%s,key=%s) doesn't exist", err.VersionCode, err.Key), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func TestServerIsClosedError(t *testing.T) {
|
||||||
Key: "pl151",
|
Key: "pl151",
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("server (VersionCode=%s,Key=%s) is closed", err.VersionCode, err.Key), err.Error())
|
assert.Equal(t, fmt.Sprintf("server (versionCode=%s,key=%s) is closed", err.VersionCode, err.Key), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,19 @@ func TestTribeDoesNotExistError(t *testing.T) {
|
||||||
Tag: "*TAG*",
|
Tag: "*TAG*",
|
||||||
}
|
}
|
||||||
var _ domain.Error = err
|
var _ domain.Error = err
|
||||||
assert.Equal(t, fmt.Sprintf("tribe (Tag=%s) doesn't exist", err.Tag), err.Error())
|
assert.Equal(t, fmt.Sprintf("tribe (tag=%s) doesn't exist", err.Tag), err.Error())
|
||||||
assert.Equal(t, err.Error(), err.UserError())
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTribeNotFoundError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := domain.TribeNotFoundError{
|
||||||
|
Tag: "*TAG*",
|
||||||
|
}
|
||||||
|
var _ domain.Error = err
|
||||||
|
assert.Equal(t, fmt.Sprintf("tribe (tag=%s) not found", err.Tag), err.Error())
|
||||||
|
assert.Equal(t, err.Error(), err.UserError())
|
||||||
|
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
|
||||||
|
}
|
||||||
|
|
148
internal/service/ennoblement_notification_builder.go
Normal file
148
internal/service/ennoblement_notification_builder.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ennoblementNotificationBuilder struct {
|
||||||
|
groups []domain.GroupWithMonitors
|
||||||
|
ennoblements listEnnoblementsResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) build() []domain.EnnoblementNotification {
|
||||||
|
var notifications []domain.EnnoblementNotification
|
||||||
|
for _, g := range b.groups {
|
||||||
|
notifications = append(notifications, b.buildGroup(g)...)
|
||||||
|
}
|
||||||
|
return notifications
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) []domain.EnnoblementNotification {
|
||||||
|
var notifications []domain.EnnoblementNotification
|
||||||
|
|
||||||
|
for _, e := range b.ennoblements.find(g.VersionCode, g.ServerKey) {
|
||||||
|
if b.canSendEnnoblementNotificationTypeGain(g, e) {
|
||||||
|
notifications = append(notifications, domain.EnnoblementNotification{
|
||||||
|
Type: domain.EnnoblementNotificationTypeGain,
|
||||||
|
ServerID: g.ServerID,
|
||||||
|
ChannelID: g.ChannelGains,
|
||||||
|
Ennoblement: b.ennoblementToDomainModel(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.canSendEnnoblementNotificationTypeLoss(g, e) {
|
||||||
|
notifications = append(notifications, domain.EnnoblementNotification{
|
||||||
|
Type: domain.EnnoblementNotificationTypeLoss,
|
||||||
|
ServerID: g.ServerID,
|
||||||
|
ChannelID: g.ChannelLosses,
|
||||||
|
Ennoblement: b.ennoblementToDomainModel(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notifications
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeGain(g domain.GroupWithMonitors, e twhelp.Ennoblement) bool {
|
||||||
|
if g.ChannelGains == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.Barbarians && b.isBarbarian(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.Internals && b.isInternal(e, g.Monitors) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.isGain(e, g.Monitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) canSendEnnoblementNotificationTypeLoss(g domain.GroupWithMonitors, e twhelp.Ennoblement) bool {
|
||||||
|
if g.ChannelLosses == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.isInternal(e, g.Monitors) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.isLoss(e, g.Monitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) isInternal(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
||||||
|
var n, o bool
|
||||||
|
for _, m := range monitors {
|
||||||
|
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
|
||||||
|
n = true
|
||||||
|
}
|
||||||
|
if m.TribeID == e.Village.Player.Player.Tribe.Tribe.ID {
|
||||||
|
o = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n && o
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) isBarbarian(e twhelp.Ennoblement) bool {
|
||||||
|
return !e.Village.Player.Valid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) isGain(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
||||||
|
var n bool
|
||||||
|
for _, m := range monitors {
|
||||||
|
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
|
||||||
|
n = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n && e.NewOwner.Player.ID != e.Village.Player.Player.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ennoblementNotificationBuilder) isLoss(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
||||||
|
var o bool
|
||||||
|
for _, m := range monitors {
|
||||||
|
if m.TribeID == e.Village.Player.Player.Tribe.Tribe.ID {
|
||||||
|
o = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
|
@ -13,11 +14,13 @@ import (
|
||||||
|
|
||||||
//counterfeiter:generate -o internal/mock/group_repository.gen.go . GroupRepository
|
//counterfeiter:generate -o internal/mock/group_repository.gen.go . GroupRepository
|
||||||
type GroupRepository interface {
|
type GroupRepository interface {
|
||||||
Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error)
|
Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error)
|
||||||
Update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.Group, error)
|
Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error)
|
||||||
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
|
AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error)
|
||||||
Get(ctx context.Context, id, serverID string) (domain.Group, error)
|
DeleteMonitors(ctx context.Context, id string, monitorID ...string) (domain.GroupWithMonitors, error)
|
||||||
Delete(ctx context.Context, id, serverID string) error
|
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error)
|
||||||
|
Get(ctx context.Context, id string) (domain.GroupWithMonitors, error)
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
DeleteMany(ctx context.Context, id ...string) error
|
DeleteMany(ctx context.Context, id ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,39 +29,43 @@ type Group struct {
|
||||||
client TWHelpClient
|
client TWHelpClient
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
maxGroupsPerServer int
|
maxGroupsPerServer int
|
||||||
|
maxMonitorsPerGroup int
|
||||||
|
ennoblementsMu sync.Mutex // ennoblementsMu is used by listEnnoblements
|
||||||
|
ennoblementsSince map[string]time.Time // ennoblementsSince is used by listEnnoblements
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup(repo GroupRepository, client TWHelpClient, logger *zap.Logger, maxGroupsPerServer int) *Group {
|
func NewGroup(repo GroupRepository, client TWHelpClient, logger *zap.Logger, maxGroupsPerServer, maxMonitorsPerGroup int) *Group {
|
||||||
return &Group{
|
return &Group{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
client: client,
|
client: client,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
maxGroupsPerServer: maxGroupsPerServer,
|
maxGroupsPerServer: maxGroupsPerServer,
|
||||||
|
maxMonitorsPerGroup: maxMonitorsPerGroup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error) {
|
func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
|
||||||
groups, err := g.repo.List(ctx, domain.ListGroupsParams{
|
groups, err := g.repo.List(ctx, domain.ListGroupsParams{
|
||||||
ServerIDs: []string{params.ServerID()},
|
ServerIDs: []string{params.ServerID()},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Group{}, fmt.Errorf("GroupRepository.List: %w", err)
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.List: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(groups) >= g.maxGroupsPerServer {
|
if len(groups) >= g.maxGroupsPerServer {
|
||||||
return domain.Group{}, domain.GroupLimitReachedError{
|
return domain.GroupWithMonitors{}, domain.GroupLimitReachedError{
|
||||||
Current: len(groups),
|
Current: len(groups),
|
||||||
Limit: g.maxGroupsPerServer,
|
Limit: g.maxGroupsPerServer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = g.checkTWServer(ctx, params.VersionCode(), params.ServerKey()); err != nil {
|
if err = g.checkTWServer(ctx, params.VersionCode(), params.ServerKey()); err != nil {
|
||||||
return domain.Group{}, err
|
return domain.GroupWithMonitors{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
group, err := g.repo.Create(ctx, params)
|
group, err := g.repo.Create(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Group{}, fmt.Errorf("GroupRepository.Create: %w", err)
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.Create: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
|
@ -68,7 +75,7 @@ func (g *Group) checkTWServer(ctx context.Context, versionCode, serverKey string
|
||||||
server, err := g.client.GetServer(ctx, versionCode, serverKey)
|
server, err := g.client.GetServer(ctx, versionCode, serverKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var apiErr twhelp.APIError
|
var apiErr twhelp.APIError
|
||||||
if !errors.As(err, &apiErr) {
|
if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound {
|
||||||
return fmt.Errorf("TWHelpClient.GetServer: %w", err)
|
return fmt.Errorf("TWHelpClient.GetServer: %w", err)
|
||||||
}
|
}
|
||||||
return domain.ServerDoesNotExistError{
|
return domain.ServerDoesNotExistError{
|
||||||
|
@ -87,7 +94,86 @@ func (g *Group) checkTWServer(ctx context.Context, versionCode, serverKey string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.Group, error) {
|
func (g *Group) AddMonitor(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error) {
|
||||||
|
// check if group exists
|
||||||
|
groupBeforeUpdate, err := g.Get(ctx, id, serverID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, domain.GroupNotFoundError{ID: id}) {
|
||||||
|
return domain.GroupWithMonitors{}, domain.GroupDoesNotExistError{ID: id}
|
||||||
|
}
|
||||||
|
return domain.GroupWithMonitors{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check limit
|
||||||
|
if len(groupBeforeUpdate.Monitors) >= g.maxMonitorsPerGroup {
|
||||||
|
return domain.GroupWithMonitors{}, domain.MonitorLimitReachedError{
|
||||||
|
Current: len(groupBeforeUpdate.Monitors),
|
||||||
|
Limit: g.maxMonitorsPerGroup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tribes, err := g.client.ListTribes(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, twhelp.ListTribesQueryParams{
|
||||||
|
Limit: 1,
|
||||||
|
Tags: []string{tribeTag},
|
||||||
|
Deleted: twhelp.NullBool{
|
||||||
|
Valid: true,
|
||||||
|
Bool: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("TWHelpClient.ListTribes: %w", err)
|
||||||
|
}
|
||||||
|
if len(tribes) == 0 {
|
||||||
|
return domain.GroupWithMonitors{}, domain.TribeDoesNotExistError{
|
||||||
|
Tag: tribeTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := g.repo.AddMonitor(ctx, groupBeforeUpdate.ID, tribes[0].ID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.AddMonitor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) DeleteMonitor(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error) {
|
||||||
|
// check if group exists
|
||||||
|
groupBeforeUpdate, err := g.Get(ctx, id, serverID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tribes, err := g.client.ListTribes(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, twhelp.ListTribesQueryParams{
|
||||||
|
Tags: []string{tribeTag},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("TWHelpClient.ListTribes: %w", err)
|
||||||
|
}
|
||||||
|
if len(tribes) == 0 {
|
||||||
|
return domain.GroupWithMonitors{}, domain.TribeNotFoundError{
|
||||||
|
Tag: tribeTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids []string
|
||||||
|
for _, t := range tribes {
|
||||||
|
for _, m := range groupBeforeUpdate.Monitors {
|
||||||
|
if m.TribeID == t.ID {
|
||||||
|
ids = append(ids, m.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := g.repo.DeleteMonitors(ctx, groupBeforeUpdate.ID, ids...)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.DeleteMonitors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error) {
|
||||||
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
||||||
ChannelGains: domain.NullString{
|
ChannelGains: domain.NullString{
|
||||||
String: channel,
|
String: channel,
|
||||||
|
@ -96,7 +182,7 @@ func (g *Group) SetChannelGains(ctx context.Context, id, serverID, channel strin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.Group, error) {
|
func (g *Group) SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error) {
|
||||||
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
||||||
ChannelLosses: domain.NullString{
|
ChannelLosses: domain.NullString{
|
||||||
String: channel,
|
String: channel,
|
||||||
|
@ -105,7 +191,7 @@ func (g *Group) SetChannelLosses(ctx context.Context, id, serverID, channel stri
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.Group, error) {
|
func (g *Group) SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.GroupWithMonitors, error) {
|
||||||
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
||||||
Internals: domain.NullBool{
|
Internals: domain.NullBool{
|
||||||
Bool: internals,
|
Bool: internals,
|
||||||
|
@ -114,7 +200,7 @@ func (g *Group) SetInternals(ctx context.Context, id, serverID string, internals
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.Group, error) {
|
func (g *Group) SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.GroupWithMonitors, error) {
|
||||||
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
|
||||||
Barbarians: domain.NullBool{
|
Barbarians: domain.NullBool{
|
||||||
Bool: barbarians,
|
Bool: barbarians,
|
||||||
|
@ -123,37 +209,249 @@ func (g *Group) SetBarbarians(ctx context.Context, id, serverID string, barbaria
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.Group, error) {
|
func (g *Group) update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
|
||||||
group, err := g.repo.Update(ctx, id, serverID, params)
|
// check if group exists
|
||||||
if err != nil {
|
if _, err := g.Get(ctx, id, serverID); err != nil {
|
||||||
return domain.Group{}, fmt.Errorf("GroupRepository.Update: %w", err)
|
return domain.GroupWithMonitors{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group, err := g.repo.Update(ctx, id, params)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.Update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error) {
|
func (g *Group) ListServer(ctx context.Context, serverID string) ([]domain.GroupWithMonitors, error) {
|
||||||
groups, err := g.repo.List(ctx, params)
|
groups, err := g.repo.List(ctx, domain.ListGroupsParams{
|
||||||
|
ServerIDs: []string{serverID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GroupRepository.List: %w", err)
|
return nil, fmt.Errorf("GroupRepository.List: %w", err)
|
||||||
}
|
}
|
||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Get(ctx context.Context, id, serverID string) (domain.Group, error) {
|
func (g *Group) Get(ctx context.Context, id, serverID string) (domain.GroupWithMonitors, error) {
|
||||||
group, err := g.repo.Get(ctx, id, serverID)
|
group, err := g.repo.Get(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Group{}, fmt.Errorf("GroupRepository.Get: %w", err)
|
return domain.GroupWithMonitors{}, fmt.Errorf("GroupRepository.Get: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if group.ServerID != serverID {
|
||||||
|
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getTribeResult struct {
|
||||||
|
index int
|
||||||
|
monitor domain.Monitor
|
||||||
|
tribe twhelp.Tribe
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) GetWithTribes(ctx context.Context, id, serverID string) (domain.GroupWithMonitorsAndTribes, error) {
|
||||||
|
group, err := g.Get(ctx, id, serverID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.GroupWithMonitorsAndTribes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
ch := make(chan getTribeResult)
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for i, monitor := range group.Monitors {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, monitor domain.Monitor) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
res := getTribeResult{
|
||||||
|
index: i,
|
||||||
|
monitor: monitor,
|
||||||
|
}
|
||||||
|
res.tribe, res.err = g.client.GetTribeByID(
|
||||||
|
ctx,
|
||||||
|
group.VersionCode,
|
||||||
|
group.ServerKey,
|
||||||
|
monitor.TribeID,
|
||||||
|
)
|
||||||
|
ch <- res
|
||||||
|
}(i, monitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
monitors := make([]domain.MonitorWithTribe, len(group.Monitors))
|
||||||
|
for res := range ch {
|
||||||
|
if res.err != nil && firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("couldn't load tribe (monitorID=%s): %w", res.monitor.ID, res.err)
|
||||||
|
cancel()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res.err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstErr != nil {
|
||||||
|
return domain.GroupWithMonitorsAndTribes{}, firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.GroupWithMonitorsAndTribes{
|
||||||
|
Group: group.Group,
|
||||||
|
Monitors: monitors,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Group) Delete(ctx context.Context, id, serverID string) error {
|
func (g *Group) Delete(ctx context.Context, id, serverID string) error {
|
||||||
if err := g.repo.Delete(ctx, id, serverID); err != nil {
|
// check if group exists
|
||||||
|
if _, err := g.Get(ctx, id, serverID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.repo.Delete(ctx, id); err != nil {
|
||||||
return fmt.Errorf("GroupRepository.Delete: %w", err)
|
return fmt.Errorf("GroupRepository.Delete: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) Execute(ctx context.Context) ([]domain.EnnoblementNotification, error) {
|
||||||
|
groups, err := g.repo.List(ctx, domain.ListGroupsParams{
|
||||||
|
EnabledNotifications: domain.NullBool{
|
||||||
|
Bool: true,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GroupRepository.List: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := g.listEnnoblements(ctx, groups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ennoblementNotificationBuilder{
|
||||||
|
groups: groups,
|
||||||
|
ennoblements: res,
|
||||||
|
}.build(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listEnnoblementsSingleResult struct {
|
||||||
|
versionCode string
|
||||||
|
serverKey string
|
||||||
|
ennoblements []twhelp.Ennoblement
|
||||||
|
}
|
||||||
|
|
||||||
|
type listEnnoblementsResult []listEnnoblementsSingleResult
|
||||||
|
|
||||||
|
func (r listEnnoblementsResult) find(versionCode, serverKey string) []twhelp.Ennoblement {
|
||||||
|
for _, res := range r {
|
||||||
|
if res.serverKey == serverKey && res.versionCode == versionCode {
|
||||||
|
return res.ennoblements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) listEnnoblements(ctx context.Context, groups []domain.GroupWithMonitors) (listEnnoblementsResult, error) {
|
||||||
|
g.ennoblementsMu.Lock()
|
||||||
|
defer g.ennoblementsMu.Unlock()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
ch := make(chan listEnnoblementsSingleResult)
|
||||||
|
ennoblementsSince := g.ennoblementsSince
|
||||||
|
skip := make(map[string]struct{}, len(ennoblementsSince))
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
key := g.buildEnnoblementsSinceKey(group.VersionCode, group.ServerKey)
|
||||||
|
|
||||||
|
if _, ok := skip[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
skip[key] = struct{}{}
|
||||||
|
|
||||||
|
since := ennoblementsSince[key]
|
||||||
|
if since.IsZero() {
|
||||||
|
since = time.Now().Add(-1 * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
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.ListEnnoblementsSort{twhelp.ListEnnoblementsSortCreatedAtASC},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Warn(
|
||||||
|
"couldn't list ennoblements",
|
||||||
|
zap.String("versionCode", group.VersionCode),
|
||||||
|
zap.String("serverKey", group.ServerKey),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- listEnnoblementsSingleResult{
|
||||||
|
versionCode: group.VersionCode,
|
||||||
|
serverKey: group.ServerKey,
|
||||||
|
ennoblements: ennoblements,
|
||||||
|
}
|
||||||
|
}(group, since)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// reinitialize ennoblementsSince
|
||||||
|
g.ennoblementsSince = make(map[string]time.Time)
|
||||||
|
|
||||||
|
results := make(listEnnoblementsResult, 0, len(skip))
|
||||||
|
for res := range ch {
|
||||||
|
key := g.buildEnnoblementsSinceKey(res.versionCode, res.serverKey)
|
||||||
|
if l := len(res.ennoblements); l > 0 {
|
||||||
|
g.ennoblementsSince[key] = res.ennoblements[l-1].CreatedAt.Add(time.Second)
|
||||||
|
} else {
|
||||||
|
g.ennoblementsSince[key] = ennoblementsSince[key]
|
||||||
|
}
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) buildEnnoblementsSinceKey(versionCode, serverKey string) string {
|
||||||
|
return versionCode + ":" + serverKey
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Group) CleanUp(ctx context.Context) error {
|
func (g *Group) CleanUp(ctx context.Context) error {
|
||||||
if err := g.deleteAllWithDisabledNotifications(ctx); err != nil {
|
if err := g.deleteAllWithDisabledNotifications(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -175,7 +473,7 @@ func (g *Group) deleteAllWithDisabledNotifications(ctx context.Context) error {
|
||||||
CreatedAtLTE: time.Now().Add(-24 * time.Hour),
|
CreatedAtLTE: time.Now().Add(-24 * time.Hour),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GroupRepository.List: %w", err)
|
return fmt.Errorf("couldn't find groups with disabled notifications: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ids := make([]string, 0, len(groups))
|
ids := make([]string, 0, len(groups))
|
||||||
|
@ -184,7 +482,7 @@ func (g *Group) deleteAllWithDisabledNotifications(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
|
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
|
||||||
return fmt.Errorf("GroupRepository.DeleteMany: %w", err)
|
return fmt.Errorf("couldn't delete groups with disabled notifications: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -204,7 +502,7 @@ func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Warn("failed to fetch closed servers", zap.Error(err), zap.String("versionCode", v.Code))
|
g.logger.Warn("couldn't list closed servers", zap.Error(err), zap.String("versionCode", v.Code))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +523,7 @@ func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error {
|
||||||
|
|
||||||
groups, err := g.repo.List(ctx, params)
|
groups, err := g.repo.List(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Warn("failed to list groups", zap.Error(err), zap.String("versionCode", v.Code))
|
g.logger.Warn("couldn't list groups", zap.Error(err), zap.String("versionCode", v.Code))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,11 +537,7 @@ func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
|
if err = g.repo.DeleteMany(ctx, ids...); err != nil {
|
||||||
g.logger.Warn(
|
g.logger.Warn("couldn't delete groups", zap.Error(err), zap.String("versionCode", v.Code))
|
||||||
"failed to delete groups",
|
|
||||||
zap.Error(err),
|
|
||||||
zap.String("versionCode", v.Code),
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,474 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
//counterfeiter:generate -o internal/mock/monitor_repository.gen.go . MonitorRepository
|
|
||||||
type MonitorRepository interface {
|
|
||||||
Create(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error)
|
|
||||||
List(ctx context.Context, groupID string) ([]domain.Monitor, error)
|
|
||||||
Get(ctx context.Context, id string) (domain.Monitor, error)
|
|
||||||
Delete(ctx context.Context, id string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
//counterfeiter:generate -o internal/mock/group_reader.gen.go . GroupReader
|
|
||||||
type GroupReader interface {
|
|
||||||
Get(ctx context.Context, id, serverID string) (domain.Group, error)
|
|
||||||
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Monitor struct {
|
|
||||||
repo MonitorRepository
|
|
||||||
client TWHelpClient
|
|
||||||
groupSvc GroupReader
|
|
||||||
maxMonitorsPerGroup int
|
|
||||||
logger *zap.Logger
|
|
||||||
ennoblementsMu sync.Mutex // ennoblementsMu is used by Monitor.fetchEnnoblements
|
|
||||||
ennoblementsSince map[string]time.Time // ennoblementsSince is used by Monitor.fetchEnnoblements
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMonitor(
|
|
||||||
repo MonitorRepository,
|
|
||||||
groupSvc GroupReader,
|
|
||||||
client TWHelpClient,
|
|
||||||
logger *zap.Logger,
|
|
||||||
maxMonitorsPerGroup int,
|
|
||||||
) *Monitor {
|
|
||||||
return &Monitor{
|
|
||||||
repo: repo,
|
|
||||||
client: client,
|
|
||||||
groupSvc: groupSvc,
|
|
||||||
logger: logger,
|
|
||||||
maxMonitorsPerGroup: maxMonitorsPerGroup,
|
|
||||||
ennoblementsSince: make(map[string]time.Time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error) {
|
|
||||||
// check if group exists
|
|
||||||
group, err := m.groupSvc.Get(ctx, groupID, serverID)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, domain.GroupNotFoundError{ID: groupID}) {
|
|
||||||
return domain.Monitor{}, domain.GroupDoesNotExistError{ID: groupID}
|
|
||||||
}
|
|
||||||
return domain.Monitor{}, fmt.Errorf("GroupService.Get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check limit
|
|
||||||
monitors, err := m.repo.List(ctx, group.ID)
|
|
||||||
if err != nil {
|
|
||||||
return domain.Monitor{}, fmt.Errorf("MonitorRepository.List: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(monitors) >= m.maxMonitorsPerGroup {
|
|
||||||
return domain.Monitor{}, domain.MonitorLimitReachedError{
|
|
||||||
Current: len(monitors),
|
|
||||||
Limit: m.maxMonitorsPerGroup,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tribes, err := m.client.ListTribes(ctx, group.VersionCode, group.ServerKey, twhelp.ListTribesQueryParams{
|
|
||||||
Limit: 1,
|
|
||||||
Tags: []string{tribeTag},
|
|
||||||
Deleted: twhelp.NullBool{
|
|
||||||
Valid: true,
|
|
||||||
Bool: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
var apiErr twhelp.APIError
|
|
||||||
if !errors.As(err, &apiErr) {
|
|
||||||
return domain.Monitor{}, fmt.Errorf("TWHelpClient.GetServer: %w", err)
|
|
||||||
}
|
|
||||||
return domain.Monitor{}, domain.TribeDoesNotExistError{
|
|
||||||
Tag: tribeTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(tribes) == 0 {
|
|
||||||
return domain.Monitor{}, domain.TribeDoesNotExistError{
|
|
||||||
Tag: tribeTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tribe := tribes[0]
|
|
||||||
|
|
||||||
params, err := domain.NewCreateMonitorParams(group.ID, tribe.ID)
|
|
||||||
if err != nil {
|
|
||||||
return domain.Monitor{}, fmt.Errorf("domain.NewCreateMonitorParams: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor, err := m.repo.Create(ctx, params)
|
|
||||||
if err != nil {
|
|
||||||
return domain.Monitor{}, fmt.Errorf("MonitorRepository.Create: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type getTribeResult struct {
|
|
||||||
index int
|
|
||||||
monitor domain.Monitor
|
|
||||||
tribe twhelp.Tribe
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) List(ctx context.Context, groupID, serverID string) ([]domain.MonitorWithTribe, error) {
|
|
||||||
group, err := m.groupSvc.Get(ctx, groupID, serverID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GroupService.Get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
monitors, err := m.repo.List(ctx, group.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("MonitorRepository.Delete: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
ch := make(chan getTribeResult)
|
|
||||||
|
|
||||||
for i, monitor := range monitors {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int, monitor domain.Monitor) {
|
|
||||||
res := getTribeResult{
|
|
||||||
index: i,
|
|
||||||
monitor: monitor,
|
|
||||||
}
|
|
||||||
res.tribe, res.err = m.client.GetTribeByID(
|
|
||||||
ctx,
|
|
||||||
group.VersionCode,
|
|
||||||
group.ServerKey,
|
|
||||||
monitor.TribeID,
|
|
||||||
)
|
|
||||||
ch <- res
|
|
||||||
}(i, monitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
results := make([]domain.MonitorWithTribe, len(monitors))
|
|
||||||
for res := range ch {
|
|
||||||
wg.Done()
|
|
||||||
|
|
||||||
if res.err != nil && firstErr == nil {
|
|
||||||
firstErr = fmt.Errorf("couldn't load tribe (monitorID=%s): %w", res.monitor.ID, res.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
results[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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if firstErr != nil {
|
|
||||||
return nil, firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Delete(ctx context.Context, id, serverID string) error {
|
|
||||||
monitor, err := m.repo.Get(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("MonitorRepository.Get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = m.groupSvc.Get(ctx, monitor.GroupID, serverID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("GroupService.Get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = m.repo.Delete(ctx, id); err != nil {
|
|
||||||
return fmt.Errorf("MonitorRepository.Delete: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) Execute(ctx context.Context) ([]domain.EnnoblementNotification, error) {
|
|
||||||
groups, err := m.groupSvc.List(ctx, domain.ListGroupsParams{
|
|
||||||
EnabledNotifications: domain.NullBool{
|
|
||||||
Bool: true,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GroupService.List: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := m.fetchEnnoblements(ctx, groups)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:prealloc
|
|
||||||
var notifications []domain.EnnoblementNotification
|
|
||||||
for _, g := range groups {
|
|
||||||
for _, r := range res {
|
|
||||||
if g.ServerKey != r.serverKey || g.VersionCode != r.versionCode {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ns, err := m.executeGroup(ctx, g, r.ennoblements)
|
|
||||||
if err != nil {
|
|
||||||
m.logger.Warn(
|
|
||||||
"something went wrong while executing group",
|
|
||||||
zap.String("group", g.ID),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
notifications = append(notifications, ns...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) executeGroup(
|
|
||||||
ctx context.Context,
|
|
||||||
g domain.Group,
|
|
||||||
ennoblements []twhelp.Ennoblement,
|
|
||||||
) ([]domain.EnnoblementNotification, error) {
|
|
||||||
monitors, err := m.repo.List(ctx, g.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("MonitorRepository.List: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:prealloc
|
|
||||||
var notifications []domain.EnnoblementNotification
|
|
||||||
for _, e := range ennoblements {
|
|
||||||
if canSendEnnoblementNotificationTypeGain(g, e, monitors) {
|
|
||||||
notifications = append(notifications, domain.EnnoblementNotification{
|
|
||||||
Type: domain.EnnoblementNotificationTypeGain,
|
|
||||||
ServerID: g.ServerID,
|
|
||||||
ChannelID: g.ChannelGains,
|
|
||||||
Ennoblement: ennoblementToDomainModel(e),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if canSendEnnoblementNotificationTypeLoss(g, e, monitors) {
|
|
||||||
notifications = append(notifications, domain.EnnoblementNotification{
|
|
||||||
Type: domain.EnnoblementNotificationTypeLoss,
|
|
||||||
ServerID: g.ServerID,
|
|
||||||
ChannelID: g.ChannelLosses,
|
|
||||||
Ennoblement: ennoblementToDomainModel(e),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type listEnnoblementsResult struct {
|
|
||||||
versionCode string
|
|
||||||
serverKey string
|
|
||||||
ennoblements []twhelp.Ennoblement
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type fetchEnnoblementsResult struct {
|
|
||||||
versionCode string
|
|
||||||
serverKey string
|
|
||||||
ennoblements []twhelp.Ennoblement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monitor) fetchEnnoblements(ctx context.Context, groups []domain.Group) ([]fetchEnnoblementsResult, error) {
|
|
||||||
m.ennoblementsMu.Lock()
|
|
||||||
defer m.ennoblementsMu.Unlock()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
ch := make(chan listEnnoblementsResult)
|
|
||||||
ennoblementsSince := m.ennoblementsSince
|
|
||||||
skip := make(map[string]struct{}, len(ennoblementsSince))
|
|
||||||
|
|
||||||
for _, g := range groups {
|
|
||||||
key := g.VersionCode + ":" + g.ServerKey
|
|
||||||
|
|
||||||
if _, ok := skip[key]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
skip[key] = struct{}{}
|
|
||||||
|
|
||||||
since := ennoblementsSince[key]
|
|
||||||
if since.IsZero() {
|
|
||||||
since = time.Now().Add(-1 * time.Minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(g domain.Group, since time.Time) {
|
|
||||||
res := listEnnoblementsResult{
|
|
||||||
versionCode: g.VersionCode,
|
|
||||||
serverKey: g.ServerKey,
|
|
||||||
}
|
|
||||||
res.ennoblements, res.err = m.client.ListEnnoblements(
|
|
||||||
ctx,
|
|
||||||
g.VersionCode,
|
|
||||||
g.ServerKey,
|
|
||||||
twhelp.ListEnnoblementsQueryParams{
|
|
||||||
Since: since,
|
|
||||||
Sort: []twhelp.ListEnnoblementsSort{twhelp.ListEnnoblementsSortCreatedAtASC},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ch <- res
|
|
||||||
}(g, since)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// reinitialize ennoblementsSince
|
|
||||||
m.ennoblementsSince = make(map[string]time.Time)
|
|
||||||
|
|
||||||
//nolint:prealloc
|
|
||||||
var results []fetchEnnoblementsResult
|
|
||||||
for res := range ch {
|
|
||||||
wg.Done()
|
|
||||||
|
|
||||||
key := res.versionCode + ":" + res.serverKey
|
|
||||||
if l := len(res.ennoblements); l > 0 {
|
|
||||||
m.ennoblementsSince[key] = res.ennoblements[l-1].CreatedAt.Add(time.Second)
|
|
||||||
} else {
|
|
||||||
m.ennoblementsSince[key] = ennoblementsSince[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.err != nil {
|
|
||||||
m.logger.Warn(
|
|
||||||
"failed to fetch ennoblements",
|
|
||||||
zap.String("versionCode", res.versionCode),
|
|
||||||
zap.String("serverKey", res.serverKey),
|
|
||||||
zap.Error(res.err),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, fetchEnnoblementsResult{
|
|
||||||
versionCode: res.versionCode,
|
|
||||||
serverKey: res.serverKey,
|
|
||||||
ennoblements: res.ennoblements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func canSendEnnoblementNotificationTypeGain(g domain.Group, e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
|
||||||
if g.ChannelGains == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !g.Barbarians && isBarbarian(e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !g.Internals && isInternal(e, monitors) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return isGain(e, monitors)
|
|
||||||
}
|
|
||||||
|
|
||||||
func canSendEnnoblementNotificationTypeLoss(g domain.Group, e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
|
||||||
if g.ChannelLosses == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInternal(e, monitors) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return isLoss(e, monitors)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isInternal(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
|
||||||
var n, o bool
|
|
||||||
for _, m := range monitors {
|
|
||||||
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
|
|
||||||
n = true
|
|
||||||
}
|
|
||||||
if m.TribeID == e.Village.Player.Player.Tribe.Tribe.ID {
|
|
||||||
o = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n && o
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBarbarian(e twhelp.Ennoblement) bool {
|
|
||||||
return !e.Village.Player.Valid
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGain(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
|
||||||
var n bool
|
|
||||||
for _, m := range monitors {
|
|
||||||
if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID {
|
|
||||||
n = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n && e.NewOwner.Player.ID != e.Village.Player.Player.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLoss(e twhelp.Ennoblement, monitors []domain.Monitor) bool {
|
|
||||||
var o bool
|
|
||||||
for _, m := range monitors {
|
|
||||||
if m.TribeID == e.Village.Player.Player.Tribe.Tribe.ID {
|
|
||||||
o = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o && e.NewOwner.Player.ID != e.Village.Player.Player.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func 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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,811 +0,0 @@
|
||||||
package service_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMonitor_Create(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("OK", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
repo := &mock.FakeMonitorRepository{}
|
|
||||||
repo.ListReturns(nil, nil)
|
|
||||||
repo.CreateCalls(func(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error) {
|
|
||||||
return domain.Monitor{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: params.TribeID(),
|
|
||||||
GroupID: params.GroupID(),
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
|
||||||
return domain.Group{
|
|
||||||
ID: groupID,
|
|
||||||
ServerID: serverID,
|
|
||||||
ChannelGains: "",
|
|
||||||
ChannelLosses: "",
|
|
||||||
ServerKey: "pl151",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
client := &mock.FakeTWHelpClient{}
|
|
||||||
tribe := twhelp.Tribe{
|
|
||||||
ID: 150,
|
|
||||||
Tag: "*TAG*",
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: "https://pl151.plemiona.pl/game.php?screen=info_player&id=150",
|
|
||||||
DeletedAt: time.Time{},
|
|
||||||
}
|
|
||||||
client.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
|
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
|
|
||||||
Create(context.Background(), groupID, uuid.NewString(), tribe.Tag)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, monitor.ID)
|
|
||||||
assert.Equal(t, groupID, monitor.GroupID)
|
|
||||||
assert.Equal(t, tribe.ID, monitor.TribeID)
|
|
||||||
assert.NotEmpty(t, monitor.CreatedAt)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: group doesn't exist", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groupSvc.GetReturns(domain.Group{}, domain.GroupNotFoundError{ID: groupID})
|
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(nil, groupSvc, nil, zap.NewNop(), 10).
|
|
||||||
Create(context.Background(), groupID, uuid.NewString(), "tag")
|
|
||||||
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
|
|
||||||
ID: groupID,
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: monitor limit has been reached", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const maxMonitorsPerGroup = 10
|
|
||||||
|
|
||||||
repo := &mock.FakeMonitorRepository{}
|
|
||||||
repo.ListReturns(make([]domain.Monitor, maxMonitorsPerGroup), nil)
|
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
|
||||||
return domain.Group{
|
|
||||||
ID: groupID,
|
|
||||||
ServerID: serverID,
|
|
||||||
ChannelGains: "",
|
|
||||||
ChannelLosses: "",
|
|
||||||
ServerKey: "pl151",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, nil, zap.NewNop(), maxMonitorsPerGroup).
|
|
||||||
Create(context.Background(), uuid.NewString(), uuid.NewString(), "TAG")
|
|
||||||
assert.ErrorIs(t, err, domain.MonitorLimitReachedError{
|
|
||||||
Current: maxMonitorsPerGroup,
|
|
||||||
Limit: maxMonitorsPerGroup,
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ERR: tribe doesn't exist", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "err=null",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "err!=null",
|
|
||||||
err: twhelp.APIError{
|
|
||||||
Code: twhelp.ErrorCodeInternalServerError,
|
|
||||||
Message: "internal server error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
repo := &mock.FakeMonitorRepository{}
|
|
||||||
repo.ListReturns(nil, nil)
|
|
||||||
repo.CreateCalls(func(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error) {
|
|
||||||
return domain.Monitor{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: params.TribeID(),
|
|
||||||
GroupID: params.GroupID(),
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
|
||||||
return domain.Group{
|
|
||||||
ID: groupID,
|
|
||||||
ServerID: serverID,
|
|
||||||
ChannelGains: "",
|
|
||||||
ChannelLosses: "",
|
|
||||||
ServerKey: "pl151",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
client := &mock.FakeTWHelpClient{}
|
|
||||||
client.ListTribesReturns(nil, tt.err)
|
|
||||||
|
|
||||||
tag := "TAG"
|
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
|
|
||||||
Create(context.Background(), uuid.NewString(), uuid.NewString(), tag)
|
|
||||||
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
|
|
||||||
Tag: tag,
|
|
||||||
})
|
|
||||||
assert.Zero(t, monitor)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitor_List(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
|
||||||
return domain.Group{
|
|
||||||
ID: groupID,
|
|
||||||
ServerID: serverID,
|
|
||||||
ChannelGains: "",
|
|
||||||
ChannelLosses: "",
|
|
||||||
ServerKey: "pl151",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
repo := &mock.FakeMonitorRepository{}
|
|
||||||
monitors := []domain.Monitor{
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: 124,
|
|
||||||
GroupID: groupID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: 125,
|
|
||||||
GroupID: groupID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
repo.ListReturns(monitors, nil)
|
|
||||||
|
|
||||||
client := &mock.FakeTWHelpClient{}
|
|
||||||
client.GetTribeByIDCalls(func(ctx context.Context, _ string, _ string, id int64) (twhelp.Tribe, error) {
|
|
||||||
return twhelp.Tribe{
|
|
||||||
ID: id,
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
DeletedAt: time.Time{},
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
res, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
|
|
||||||
List(context.Background(), groupID, uuid.NewString())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, res, len(monitors))
|
|
||||||
for i, m := range monitors {
|
|
||||||
assert.Equal(t, m, res[i].Monitor)
|
|
||||||
assert.Equal(t, m.TribeID, res[i].Tribe.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMonitor_Execute(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
client := &mock.FakeTWHelpClient{}
|
|
||||||
tribes := map[string][]twhelp.TribeMeta{
|
|
||||||
"pl:pl181": {
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 2,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 3,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"en:en130": {
|
|
||||||
{
|
|
||||||
ID: 100,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 101,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"pl:pl180": {
|
|
||||||
{
|
|
||||||
ID: 200,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"de:de200": {
|
|
||||||
{
|
|
||||||
ID: 300,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 301,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
Tag: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
players := map[string][]twhelp.PlayerMeta{
|
|
||||||
"pl:pl181": {
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 2,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["pl:pl181"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 3,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["pl:pl181"][2],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 4,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"en:en130": {
|
|
||||||
{
|
|
||||||
ID: 100,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["en:en130"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 101,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["en:en130"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 102,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"pl:pl180": {
|
|
||||||
{
|
|
||||||
ID: 200,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["pl:pl180"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"de:de200": {
|
|
||||||
{
|
|
||||||
ID: 300,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["de:de200"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 301,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["de:de200"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 302,
|
|
||||||
Name: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Tribe: twhelp.NullTribeMeta{
|
|
||||||
Tribe: tribes["de:de200"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
villages := map[string][]twhelp.VillageMeta{
|
|
||||||
"pl:pl181": {
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 2,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 3,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][2],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 4,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 5,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"en:en130": {
|
|
||||||
{
|
|
||||||
ID: 100,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 101,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["en:en130"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 102,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["en:en130"][2],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"pl:pl180": {
|
|
||||||
{
|
|
||||||
ID: 200,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"de:de200": {
|
|
||||||
{
|
|
||||||
ID: 300,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 301,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["de:de200"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 302,
|
|
||||||
FullName: uuid.NewString(),
|
|
||||||
ProfileURL: uuid.NewString(),
|
|
||||||
Player: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["de:de200"][2],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ennoblements := map[string][]twhelp.Ennoblement{
|
|
||||||
"pl:pl181": {
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
Village: villages["pl:pl181"][0],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 2, // self conquer, should be skipped
|
|
||||||
Village: villages["pl:pl181"][1],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-4 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 3, // internal, should be skipped (internals disabled)
|
|
||||||
Village: villages["pl:pl181"][2],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-3 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 4, // barbarian, shouldn't be skipped (barbarians enabled)
|
|
||||||
Village: villages["pl:pl181"][3],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 5, // disabled notifications about gains, should be skipped
|
|
||||||
Village: villages["pl:pl181"][4],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 6, // disabled notifications about losses, should be skipped
|
|
||||||
Village: villages["pl:pl181"][4],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl181"][3],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"en:en130": {
|
|
||||||
{
|
|
||||||
ID: 100, // no monitor for these tribes, should be skipped
|
|
||||||
Village: villages["en:en130"][0],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["en:en130"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 101, // no monitor for these tribes, should be skipped
|
|
||||||
Village: villages["en:en130"][1],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["en:en130"][2],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 102,
|
|
||||||
Village: villages["en:en130"][2],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["en:en130"][1],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"pl:pl180": {
|
|
||||||
{
|
|
||||||
ID: 200, // api error, should be skipped
|
|
||||||
Village: villages["pl:pl180"][0],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["pl:pl180"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"de:de200": {
|
|
||||||
{
|
|
||||||
ID: 300, // barbarian, should be skipped (barbarians disabled)
|
|
||||||
Village: villages["de:de200"][0],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["de:de200"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 301, // internal, shouldn't be skipped (internals enabled)
|
|
||||||
Village: villages["de:de200"][1],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["de:de200"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 302, // internal, shouldn't be skipped (internals enabled)
|
|
||||||
Village: villages["de:de200"][2],
|
|
||||||
NewOwner: twhelp.NullPlayerMeta{
|
|
||||||
Player: players["de:de200"][0],
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
CreatedAt: time.Now().Add(-5 * time.Minute),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client.ListEnnoblementsCalls(
|
|
||||||
func(
|
|
||||||
ctx context.Context,
|
|
||||||
version string,
|
|
||||||
server string,
|
|
||||||
_ twhelp.ListEnnoblementsQueryParams,
|
|
||||||
) ([]twhelp.Ennoblement, error) {
|
|
||||||
if version == "pl" && server == "pl180" {
|
|
||||||
return nil, errors.New("random error")
|
|
||||||
}
|
|
||||||
return ennoblements[version+":"+server], nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupReader{}
|
|
||||||
groups := []domain.Group{
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
ServerID: uuid.NewString(),
|
|
||||||
ChannelGains: uuid.NewString(),
|
|
||||||
ChannelLosses: "",
|
|
||||||
Barbarians: true,
|
|
||||||
ServerKey: "pl181",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
ServerID: uuid.NewString(),
|
|
||||||
ChannelGains: "",
|
|
||||||
ChannelLosses: uuid.NewString(),
|
|
||||||
ServerKey: "pl181",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
ServerID: uuid.NewString(),
|
|
||||||
ChannelGains: uuid.NewString(),
|
|
||||||
ChannelLosses: uuid.NewString(),
|
|
||||||
ServerKey: "en130",
|
|
||||||
VersionCode: "en",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
ServerID: uuid.NewString(),
|
|
||||||
ChannelGains: uuid.NewString(),
|
|
||||||
ChannelLosses: uuid.NewString(),
|
|
||||||
Barbarians: true,
|
|
||||||
ServerKey: "pl180",
|
|
||||||
VersionCode: "pl",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
ServerID: uuid.NewString(),
|
|
||||||
ChannelGains: uuid.NewString(),
|
|
||||||
ChannelLosses: uuid.NewString(),
|
|
||||||
Barbarians: false,
|
|
||||||
Internals: true,
|
|
||||||
ServerKey: "de200",
|
|
||||||
VersionCode: "de",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
groupSvc.ListReturns(groups, nil)
|
|
||||||
|
|
||||||
repo := &mock.FakeMonitorRepository{}
|
|
||||||
monitors := map[string][]domain.Monitor{
|
|
||||||
groups[0].ID: {
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["pl:pl181"][0].ID,
|
|
||||||
GroupID: groups[0].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["pl:pl181"][2].ID,
|
|
||||||
GroupID: groups[0].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groups[1].ID: {
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["pl:pl181"][1].ID,
|
|
||||||
GroupID: groups[1].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groups[2].ID: {
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["en:en130"][1].ID,
|
|
||||||
GroupID: groups[2].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groups[3].ID: {
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["pl:pl180"][0].ID,
|
|
||||||
GroupID: groups[3].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groups[4].ID: {
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["de:de200"][0].ID,
|
|
||||||
GroupID: groups[4].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
TribeID: tribes["de:de200"][1].ID,
|
|
||||||
GroupID: groups[4].ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
repo.ListCalls(func(ctx context.Context, groupID string) ([]domain.Monitor, error) {
|
|
||||||
return monitors[groupID], nil
|
|
||||||
})
|
|
||||||
|
|
||||||
notifications, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
|
|
||||||
Execute(context.Background())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
expectedNotifications := []domain.EnnoblementNotification{
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeGain, groups[0].ServerID, groups[0].ChannelGains, ennoblements["pl:pl181"][0]),
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeGain, groups[0].ServerID, groups[0].ChannelGains, ennoblements["pl:pl181"][3]),
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeLoss, groups[1].ServerID, groups[1].ChannelLosses, ennoblements["pl:pl181"][0]),
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeGain, groups[2].ServerID, groups[2].ChannelGains, ennoblements["en:en130"][2]),
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeGain, groups[4].ServerID, groups[4].ChannelGains, ennoblements["de:de200"][1]),
|
|
||||||
newNotification(domain.EnnoblementNotificationTypeGain, groups[4].ServerID, groups[4].ChannelGains, ennoblements["de:de200"][2]),
|
|
||||||
}
|
|
||||||
assert.Len(t, notifications, len(expectedNotifications))
|
|
||||||
for _, n := range expectedNotifications {
|
|
||||||
assert.Contains(t, notifications, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNotification(typ domain.EnnoblementNotificationType, serverID, channelID string, e twhelp.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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -27,6 +29,7 @@ type Client struct {
|
||||||
userAgent string
|
userAgent string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
baseURL *url.URL
|
baseURL *url.URL
|
||||||
|
rateLimiter *rate.Limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOption func(c *Client)
|
type ClientOption func(c *Client)
|
||||||
|
@ -43,6 +46,12 @@ func WithUserAgent(ua string) ClientOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithRateLimiter(rl *rate.Limiter) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.rateLimiter = rl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewClient(baseURL *url.URL, opts ...ClientOption) *Client {
|
func NewClient(baseURL *url.URL, opts ...ClientOption) *Client {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
|
@ -216,6 +225,13 @@ func (c *Client) getJSON(ctx context.Context, urlStr string, v any) error {
|
||||||
// headers
|
// headers
|
||||||
req.Header.Set("User-Agent", c.userAgent)
|
req.Header.Set("User-Agent", c.userAgent)
|
||||||
|
|
||||||
|
// rate limiter
|
||||||
|
if c.rateLimiter != nil {
|
||||||
|
if err = c.rateLimiter.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("client.Do: %w", err)
|
return fmt.Errorf("client.Do: %w", err)
|
||||||
|
|
|
@ -34,8 +34,10 @@ spec:
|
||||||
key: token
|
key: token
|
||||||
- name: TWHELP_URL
|
- name: TWHELP_URL
|
||||||
value: "https://tribalwarshelp.com"
|
value: "https://tribalwarshelp.com"
|
||||||
|
- name: TWHELP_RATE_LIMITER_ENABLED
|
||||||
|
value: "false"
|
||||||
- name: BOT_MAX_GROUPS_PER_SERVER
|
- name: BOT_MAX_GROUPS_PER_SERVER
|
||||||
value: "10"
|
value: "5"
|
||||||
- name: BOT_MAX_MONITORS_PER_GROUP
|
- name: BOT_MAX_MONITORS_PER_GROUP
|
||||||
value: "10"
|
value: "10"
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
|
39
k8s/overlays/dev/bot.yml
Normal file
39
k8s/overlays/dev/bot.yml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: twhelp-dcbot-deployment
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: twhelp-dcbot
|
||||||
|
image: dcbot
|
||||||
|
env:
|
||||||
|
- name: APP_MODE
|
||||||
|
value: development
|
||||||
|
- name: DB_DSN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: twhelp-dcbot-secret
|
||||||
|
key: db-dsn
|
||||||
|
- name: DB_MAX_OPEN_CONNECTIONS
|
||||||
|
value: "10"
|
||||||
|
- name: DB_MAX_IDLE_CONNECTIONS
|
||||||
|
value: "3"
|
||||||
|
- name: BOT_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: twhelp-dcbot-secret
|
||||||
|
key: token
|
||||||
|
- name: TWHELP_URL
|
||||||
|
value: "https://tribalwarshelp.com"
|
||||||
|
- name: TWHELP_RATE_LIMITER_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: TWHELP_RATE_LIMITER_MAX_REQUESTS_PER_SECOND
|
||||||
|
value: "10"
|
||||||
|
- name: TWHELP_RATE_LIMITER_REQUEST_BURST
|
||||||
|
value: "5"
|
||||||
|
- name: BOT_MAX_GROUPS_PER_SERVER
|
||||||
|
value: "5"
|
||||||
|
- name: BOT_MAX_MONITORS_PER_GROUP
|
||||||
|
value: "10"
|
|
@ -4,3 +4,5 @@ nameSuffix: -dev
|
||||||
resources:
|
resources:
|
||||||
- secret.yml
|
- secret.yml
|
||||||
- ../../base
|
- ../../base
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- bot.yml
|
||||||
|
|
Loading…
Reference in New Issue
Block a user