feat: notifications
This commit is contained in:
parent
db2026c6e4
commit
e76cb17caf
|
@ -28,6 +28,11 @@ func New() *cli.Command {
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
logger := zap.L()
|
logger := zap.L()
|
||||||
|
|
||||||
|
cfg, err := newBotConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("newBotConfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := internal.NewBunDB()
|
db, err := internal.NewBunDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("internal.NewBunDB: %w", err)
|
return fmt.Errorf("internal.NewBunDB: %w", err)
|
||||||
|
@ -43,22 +48,18 @@ func New() *cli.Command {
|
||||||
|
|
||||||
choiceSvc := service.NewChoice(client)
|
choiceSvc := service.NewChoice(client)
|
||||||
groupSvc := service.NewGroup(groupRepo, client)
|
groupSvc := service.NewGroup(groupRepo, client)
|
||||||
monitorSvc := service.NewMonitor(monitorRepo, groupRepo, client)
|
monitorSvc := service.NewMonitor(monitorRepo, groupRepo, client, cfg.EnnoblementsReqLimit)
|
||||||
|
|
||||||
cfg, err := newBotConfig()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("newBotConfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bot, err := discord.NewBot(cfg.Token, groupSvc, monitorSvc, choiceSvc)
|
bot, err := discord.NewBot(cfg.Token, groupSvc, monitorSvc, choiceSvc)
|
||||||
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() {
|
||||||
|
_ = bot.Run()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,13 +67,18 @@ func New() *cli.Command {
|
||||||
|
|
||||||
waitForSignal(c.Context)
|
waitForSignal(c.Context)
|
||||||
|
|
||||||
|
if err = bot.Close(); err != nil {
|
||||||
|
return fmt.Errorf("bot.Close: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type botConfig struct {
|
type botConfig struct {
|
||||||
Token string `envconfig:"TOKEN" required:"true"`
|
Token string `envconfig:"TOKEN" required:"true"`
|
||||||
|
EnnoblementsReqLimit int `envconfig:"ENNOBLEMENTS_REQ_LIMIT" default:"4"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBotConfig() (botConfig, error) {
|
func newBotConfig() (botConfig, error) {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -43,6 +43,7 @@ require (
|
||||||
github.com/opencontainers/runc v1.1.2 // indirect
|
github.com/opencontainers/runc v1.1.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -89,6 +89,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|
|
@ -3,9 +3,11 @@ package discord
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GroupService interface {
|
type GroupService interface {
|
||||||
|
@ -18,6 +20,7 @@ type GroupService interface {
|
||||||
|
|
||||||
type MonitorService interface {
|
type MonitorService interface {
|
||||||
Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error)
|
Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error)
|
||||||
|
Execute(ctx context.Context) error
|
||||||
Delete(ctx context.Context, id, serverID string) error
|
Delete(ctx context.Context, id, serverID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +30,7 @@ type ChoiceService interface {
|
||||||
|
|
||||||
type Bot struct {
|
type Bot struct {
|
||||||
s *discordgo.Session
|
s *discordgo.Session
|
||||||
|
c *cron.Cron
|
||||||
groupSvc GroupService
|
groupSvc GroupService
|
||||||
monitorSvc MonitorService
|
monitorSvc MonitorService
|
||||||
choiceSvc ChoiceService
|
choiceSvc ChoiceService
|
||||||
|
@ -38,19 +42,46 @@ func NewBot(token string, groupSvc GroupService, monitorSvc MonitorService, clie
|
||||||
return nil, fmt.Errorf("discordgo.New: %w", err)
|
return nil, fmt.Errorf("discordgo.New: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.Open(); err != nil {
|
b := &Bot{
|
||||||
return nil, fmt.Errorf("s.Open: %w", err)
|
s: s,
|
||||||
}
|
c: cron.New(
|
||||||
|
cron.WithLocation(time.UTC),
|
||||||
b := &Bot{s: s, groupSvc: groupSvc, monitorSvc: monitorSvc, choiceSvc: client}
|
cron.WithChain(
|
||||||
if err = b.registerCommands(); err != nil {
|
cron.SkipIfStillRunning(cron.DiscardLogger),
|
||||||
_ = s.Close()
|
),
|
||||||
return nil, fmt.Errorf("couldn't register commands: %w", err)
|
),
|
||||||
|
groupSvc: groupSvc,
|
||||||
|
monitorSvc: monitorSvc,
|
||||||
|
choiceSvc: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bot) Run() error {
|
||||||
|
if err := b.initCron(); err != nil {
|
||||||
|
return fmt.Errorf("initCron: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.s.Open(); err != nil {
|
||||||
|
return fmt.Errorf("s.Open: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.registerCommands(); err != nil {
|
||||||
|
_ = b.s.Close()
|
||||||
|
return fmt.Errorf("couldn't register commands: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.c.Run()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type command interface {
|
||||||
|
name() string
|
||||||
|
register(s *discordgo.Session) error
|
||||||
|
}
|
||||||
|
|
||||||
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},
|
||||||
|
@ -66,11 +97,6 @@ func (b *Bot) registerCommands() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type command interface {
|
|
||||||
name() string
|
|
||||||
register(s *discordgo.Session) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) registerCommand(cmd command) error {
|
func (b *Bot) registerCommand(cmd command) error {
|
||||||
if err := cmd.register(b.s); err != nil {
|
if err := cmd.register(b.s); err != nil {
|
||||||
return fmt.Errorf("couldn't register command '%s': %w", cmd.name(), err)
|
return fmt.Errorf("couldn't register command '%s': %w", cmd.name(), err)
|
||||||
|
@ -78,6 +104,15 @@ func (b *Bot) registerCommand(cmd command) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bot) initCron() error {
|
||||||
|
_, err := b.c.AddJob("@every 1m", &executeMonitorsJob{svc: b.monitorSvc})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Bot) Close() error {
|
func (b *Bot) Close() error {
|
||||||
|
<-b.c.Stop().Done()
|
||||||
return b.s.Close()
|
return b.s.Close()
|
||||||
}
|
}
|
||||||
|
|
22
internal/discord/job_execute_monitors.go
Normal file
22
internal/discord/job_execute_monitors.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
executeMonitorsJobTimeout = 2 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type executeMonitorsJob struct {
|
||||||
|
svc MonitorService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executeMonitorsJob) Run() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), executeMonitorsJobTimeout)
|
||||||
|
defer cancel()
|
||||||
|
fmt.Println("execute")
|
||||||
|
_ = e.svc.Execute(ctx)
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
||||||
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
||||||
|
@ -21,19 +23,33 @@ type MonitorRepository interface {
|
||||||
Delete(ctx context.Context, id string) error
|
Delete(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
//counterfeiter:generate -o internal/mock/group_getter.gen.go . GroupGetter
|
//counterfeiter:generate -o internal/mock/group_reader.gen.go . GroupReader
|
||||||
type GroupGetter interface {
|
type GroupReader interface {
|
||||||
Get(ctx context.Context, id, serverID string) (domain.Group, error)
|
Get(ctx context.Context, id, serverID string) (domain.Group, error)
|
||||||
|
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
repo MonitorRepository
|
repo MonitorRepository
|
||||||
client TWHelpClient
|
client TWHelpClient
|
||||||
groupSvc GroupGetter
|
groupSvc GroupReader
|
||||||
|
lastExecutionMu sync.RWMutex
|
||||||
|
lastExecution time.Time
|
||||||
|
ennoblementsReqLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(repo MonitorRepository, groupSvc GroupGetter, client TWHelpClient) *Monitor {
|
func NewMonitor(
|
||||||
return &Monitor{repo: repo, client: client, groupSvc: groupSvc}
|
repo MonitorRepository,
|
||||||
|
groupSvc GroupReader,
|
||||||
|
client TWHelpClient,
|
||||||
|
ennoblementsReqLimit int,
|
||||||
|
) *Monitor {
|
||||||
|
return &Monitor{
|
||||||
|
repo: repo,
|
||||||
|
client: client,
|
||||||
|
groupSvc: groupSvc,
|
||||||
|
ennoblementsReqLimit: ennoblementsReqLimit,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error) {
|
func (m *Monitor) Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error) {
|
||||||
|
@ -107,3 +123,110 @@ func (m *Monitor) Delete(ctx context.Context, id, serverID string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Execute(ctx context.Context) error {
|
||||||
|
groups, err := m.groupSvc.List(ctx, domain.ListGroupsParams{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GroupService.List: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ennoblements, err := m.preloadEnnoblements(ctx, groups)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range ennoblements {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listEnnoblementsResult struct {
|
||||||
|
versionCode string
|
||||||
|
serverKey string
|
||||||
|
ennoblements []twhelp.Ennoblement
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type preloadEnnoblementsResult struct {
|
||||||
|
VersionCode string
|
||||||
|
ServerKey string
|
||||||
|
Ennoblements []twhelp.Ennoblement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) preloadEnnoblements(ctx context.Context, groups []domain.Group) ([]preloadEnnoblementsResult, error) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
ch := make(chan listEnnoblementsResult)
|
||||||
|
reqLimitCh := make(chan struct{}, m.ennoblementsReqLimit)
|
||||||
|
skip := make(map[string]struct{})
|
||||||
|
|
||||||
|
m.lastExecutionMu.RLock()
|
||||||
|
since := m.lastExecution
|
||||||
|
m.lastExecutionMu.RUnlock()
|
||||||
|
if since.IsZero() {
|
||||||
|
since = time.Now().Add(-1 * time.Minute)
|
||||||
|
}
|
||||||
|
since = since.Add(1 * time.Millisecond)
|
||||||
|
|
||||||
|
for _, g := range groups {
|
||||||
|
if g.ChannelGains == "" && g.ChannelLosses == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := g.VersionCode + ":" + g.ServerKey
|
||||||
|
if _, ok := skip[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
skip[key] = struct{}{}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(g domain.Group, since time.Time) {
|
||||||
|
reqLimitCh <- struct{}{}
|
||||||
|
defer func() {
|
||||||
|
<-reqLimitCh
|
||||||
|
}()
|
||||||
|
|
||||||
|
res := listEnnoblementsResult{
|
||||||
|
versionCode: g.VersionCode,
|
||||||
|
serverKey: g.ServerKey,
|
||||||
|
}
|
||||||
|
res.ennoblements, res.err = m.client.ListEnnoblements(
|
||||||
|
ctx,
|
||||||
|
g.VersionCode,
|
||||||
|
g.ServerKey,
|
||||||
|
twhelp.ListEnnoblementsQueryParams{
|
||||||
|
Since: since,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
ch <- res
|
||||||
|
}(g, since)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
//nolint:prealloc
|
||||||
|
var results []preloadEnnoblementsResult
|
||||||
|
for res := range ch {
|
||||||
|
wg.Done()
|
||||||
|
|
||||||
|
if res.err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, preloadEnnoblementsResult{
|
||||||
|
VersionCode: res.versionCode,
|
||||||
|
ServerKey: res.serverKey,
|
||||||
|
Ennoblements: res.ennoblements,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.lastExecutionMu.Lock()
|
||||||
|
m.lastExecution = time.Now()
|
||||||
|
m.lastExecutionMu.Unlock()
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupGetter{}
|
groupSvc := &mock.FakeGroupReader{}
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
||||||
return domain.Group{
|
return domain.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
|
@ -55,7 +55,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
groupID := uuid.NewString()
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, client).
|
monitor, err := service.NewMonitor(repo, groupSvc, client, 10).
|
||||||
Create(context.Background(), groupID, uuid.NewString(), tribe.Tag)
|
Create(context.Background(), groupID, uuid.NewString(), tribe.Tag)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, monitor.ID)
|
assert.NotEmpty(t, monitor.ID)
|
||||||
|
@ -68,10 +68,10 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
groupID := uuid.NewString()
|
groupID := uuid.NewString()
|
||||||
groupSvc := &mock.FakeGroupGetter{}
|
groupSvc := &mock.FakeGroupReader{}
|
||||||
groupSvc.GetReturns(domain.Group{}, domain.GroupNotFoundError{ID: groupID})
|
groupSvc.GetReturns(domain.Group{}, domain.GroupNotFoundError{ID: groupID})
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(nil, groupSvc, nil).
|
monitor, err := service.NewMonitor(nil, groupSvc, nil, 10).
|
||||||
Create(context.Background(), groupID, uuid.NewString(), "tag")
|
Create(context.Background(), groupID, uuid.NewString(), "tag")
|
||||||
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
|
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
|
@ -87,7 +87,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
repo := &mock.FakeMonitorRepository{}
|
repo := &mock.FakeMonitorRepository{}
|
||||||
repo.ListReturns(make([]domain.Monitor, maxMonitorsPerGroup), nil)
|
repo.ListReturns(make([]domain.Monitor, maxMonitorsPerGroup), nil)
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupGetter{}
|
groupSvc := &mock.FakeGroupReader{}
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
||||||
return domain.Group{
|
return domain.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
|
@ -100,7 +100,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, nil).
|
monitor, err := service.NewMonitor(repo, groupSvc, nil, 10).
|
||||||
Create(context.Background(), uuid.NewString(), uuid.NewString(), "TAG")
|
Create(context.Background(), uuid.NewString(), uuid.NewString(), "TAG")
|
||||||
assert.ErrorIs(t, err, domain.MonitorLimitReachedError{
|
assert.ErrorIs(t, err, domain.MonitorLimitReachedError{
|
||||||
Current: maxMonitorsPerGroup,
|
Current: maxMonitorsPerGroup,
|
||||||
|
@ -126,7 +126,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupGetter{}
|
groupSvc := &mock.FakeGroupReader{}
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
||||||
return domain.Group{
|
return domain.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
|
@ -147,7 +147,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
|
|
||||||
tag := "TAG"
|
tag := "TAG"
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, client).
|
monitor, err := service.NewMonitor(repo, groupSvc, client, 10).
|
||||||
Create(context.Background(), uuid.NewString(), uuid.NewString(), tag)
|
Create(context.Background(), uuid.NewString(), uuid.NewString(), tag)
|
||||||
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
|
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
|
@ -161,7 +161,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
repo := &mock.FakeMonitorRepository{}
|
repo := &mock.FakeMonitorRepository{}
|
||||||
repo.ListReturns(nil, nil)
|
repo.ListReturns(nil, nil)
|
||||||
|
|
||||||
groupSvc := &mock.FakeGroupGetter{}
|
groupSvc := &mock.FakeGroupReader{}
|
||||||
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) {
|
||||||
return domain.Group{
|
return domain.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
|
@ -184,7 +184,7 @@ func TestMonitor_Create(t *testing.T) {
|
||||||
}
|
}
|
||||||
client.GetTribeByTagReturns(tribe, nil)
|
client.GetTribeByTagReturns(tribe, nil)
|
||||||
|
|
||||||
monitor, err := service.NewMonitor(repo, groupSvc, client).
|
monitor, err := service.NewMonitor(repo, groupSvc, client, 10).
|
||||||
Create(context.Background(), uuid.NewString(), uuid.NewString(), tribe.Tag)
|
Create(context.Background(), uuid.NewString(), uuid.NewString(), tribe.Tag)
|
||||||
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
|
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
|
||||||
Tag: tribe.Tag,
|
Tag: tribe.Tag,
|
||||||
|
|
|
@ -13,4 +13,9 @@ type TWHelpClient interface {
|
||||||
ListVersions(ctx context.Context) ([]twhelp.Version, error)
|
ListVersions(ctx context.Context) ([]twhelp.Version, error)
|
||||||
GetServer(ctx context.Context, version, server string) (twhelp.Server, error)
|
GetServer(ctx context.Context, version, server string) (twhelp.Server, error)
|
||||||
GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error)
|
GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error)
|
||||||
|
ListEnnoblements(
|
||||||
|
ctx context.Context,
|
||||||
|
version, server string,
|
||||||
|
queryParams twhelp.ListEnnoblementsQueryParams,
|
||||||
|
) ([]twhelp.Ennoblement, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,9 +15,10 @@ const (
|
||||||
defaultUserAgent = "TWHelpDCBot/development"
|
defaultUserAgent = "TWHelpDCBot/development"
|
||||||
defaultTimeout = 10 * time.Second
|
defaultTimeout = 10 * time.Second
|
||||||
|
|
||||||
endpointListVersions = "/api/v1/versions"
|
endpointListVersions = "/api/v1/versions"
|
||||||
endpointGetServer = "/api/v1/versions/%s/servers/%s"
|
endpointGetServer = "/api/v1/versions/%s/servers/%s"
|
||||||
endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s"
|
endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s"
|
||||||
|
endpointListEnnoblements = "/api/v1/versions/%s/servers/%s/ennoblements"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
@ -77,6 +79,40 @@ func (c *Client) GetTribeByTag(ctx context.Context, version, server, tag string)
|
||||||
return resp.Data, nil
|
return resp.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListEnnoblementsQueryParams struct {
|
||||||
|
Limit int32
|
||||||
|
Offset int32
|
||||||
|
Since time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListEnnoblements(
|
||||||
|
ctx context.Context,
|
||||||
|
version, server string,
|
||||||
|
queryParams ListEnnoblementsQueryParams,
|
||||||
|
) ([]Ennoblement, error) {
|
||||||
|
q := url.Values{}
|
||||||
|
|
||||||
|
if queryParams.Limit >= 0 {
|
||||||
|
q.Set("limit", strconv.Itoa(int(queryParams.Limit)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryParams.Offset >= 0 {
|
||||||
|
q.Set("offset", strconv.Itoa(int(queryParams.Offset)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !queryParams.Since.IsZero() {
|
||||||
|
q.Set("since", queryParams.Since.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp listEnnoblementsResp
|
||||||
|
|
||||||
|
if err := c.getJSON(ctx, fmt.Sprintf(endpointListEnnoblements, version, server)+"?"+q.Encode(), &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) getJSON(ctx context.Context, urlStr string, v any) error {
|
func (c *Client) getJSON(ctx context.Context, urlStr string, v any) error {
|
||||||
u, err := c.baseURL.Parse(urlStr)
|
u, err := c.baseURL.Parse(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
154
internal/twhelp/twhelp.go
Normal file
154
internal/twhelp/twhelp.go
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package twhelp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorCodeInternalServerError ErrorCode = "internal-server-error"
|
||||||
|
ErrorCodeEntityNotFound ErrorCode = "entity-not-found"
|
||||||
|
ErrorCodeValidationError ErrorCode = "validation-error"
|
||||||
|
ErrorCodeRouteNotFound ErrorCode = "route-not-found"
|
||||||
|
ErrorCodeMethodNotAllowed ErrorCode = "method-not-allowed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c ErrorCode) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIError struct {
|
||||||
|
Code ErrorCode `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e APIError) Error() string {
|
||||||
|
return e.Message + " (code=" + e.Code.String() + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorResp struct {
|
||||||
|
Error APIError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type listVersionsResp struct {
|
||||||
|
Data []Version `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Open bool `json:"open"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getServerResp struct {
|
||||||
|
Data Server `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tribe struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ProfileURL string `json:"profileUrl"`
|
||||||
|
DeletedAt time.Time `json:"deletedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getTribeResp struct {
|
||||||
|
Data Tribe `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TribeMeta struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
ProfileURL string `json:"profileUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullTribeMeta struct {
|
||||||
|
Tribe TribeMeta
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t NullTribeMeta) MarshalJSON() ([]byte, error) {
|
||||||
|
if !t.Valid {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(t.Tribe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NullTribeMeta) UnmarshalJSON(data []byte) error {
|
||||||
|
// Ignore null, like in the main JSON package.
|
||||||
|
if string(data) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &t.Tribe); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Valid = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerMeta struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ProfileURL string `json:"profileUrl"`
|
||||||
|
Tribe NullTribeMeta `json:"tribe"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullPlayerMeta struct {
|
||||||
|
Player PlayerMeta
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p NullPlayerMeta) MarshalJSON() ([]byte, error) {
|
||||||
|
if !p.Valid {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(p.Player)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NullPlayerMeta) UnmarshalJSON(data []byte) error {
|
||||||
|
// Ignore null, like in the main JSON package.
|
||||||
|
if string(data) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &p.Player); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Valid = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type VillageMeta struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
ProfileURL string `json:"profileUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ennoblement struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Village VillageMeta `json:"village"`
|
||||||
|
NewOwner NullPlayerMeta `json:"newOwner"`
|
||||||
|
OldOwner NullPlayerMeta `json:"oldOwner"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type listEnnoblementsResp struct {
|
||||||
|
Data []Ennoblement `json:"data"`
|
||||||
|
}
|
258
internal/twhelp/twhelp_test.go
Normal file
258
internal/twhelp/twhelp_test.go
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
package twhelp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNullTribeMeta_MarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tribe twhelp.NullTribeMeta
|
||||||
|
expectedJSON string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: null 1",
|
||||||
|
tribe: twhelp.NullTribeMeta{
|
||||||
|
Tribe: twhelp.TribeMeta{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
expectedJSON: "null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: null 2",
|
||||||
|
tribe: twhelp.NullTribeMeta{
|
||||||
|
Tribe: twhelp.TribeMeta{ID: 1234},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
expectedJSON: "null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: valid struct",
|
||||||
|
tribe: twhelp.NullTribeMeta{
|
||||||
|
Tribe: twhelp.TribeMeta{
|
||||||
|
ID: 997,
|
||||||
|
Name: "name 997",
|
||||||
|
Tag: "tag 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
},
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
expectedJSON: `{"id":997,"name":"name 997","tag":"tag 997","profileUrl":"profile-997"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b, err := json.Marshal(tt.tribe)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.JSONEq(t, tt.expectedJSON, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullTribeMeta_UnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
expectedTribe twhelp.NullTribeMeta
|
||||||
|
expectedJSONSyntaxError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: null",
|
||||||
|
json: "null",
|
||||||
|
expectedTribe: twhelp.NullTribeMeta{
|
||||||
|
Tribe: twhelp.TribeMeta{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: valid struct",
|
||||||
|
//nolint:lll
|
||||||
|
json: `{"id":997,"name":"name 997","tag":"tag 997","profileUrl":"profile-997"}`,
|
||||||
|
expectedTribe: twhelp.NullTribeMeta{
|
||||||
|
Tribe: twhelp.TribeMeta{
|
||||||
|
ID: 997,
|
||||||
|
Name: "name 997",
|
||||||
|
Tag: "tag 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
},
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid tribe 1",
|
||||||
|
json: "2022-07-30T14:13:12.0000005Z",
|
||||||
|
expectedTribe: twhelp.NullTribeMeta{},
|
||||||
|
expectedJSONSyntaxError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid tribe 2",
|
||||||
|
json: "hello world",
|
||||||
|
expectedTribe: twhelp.NullTribeMeta{},
|
||||||
|
expectedJSONSyntaxError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var target twhelp.NullTribeMeta
|
||||||
|
err := json.Unmarshal([]byte(tt.json), &target)
|
||||||
|
if tt.expectedJSONSyntaxError {
|
||||||
|
var syntaxError *json.SyntaxError
|
||||||
|
assert.ErrorAs(t, err, &syntaxError)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expectedTribe, target)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullPlayerMeta_MarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
player twhelp.NullPlayerMeta
|
||||||
|
expectedJSON string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: null 1",
|
||||||
|
player: twhelp.NullPlayerMeta{
|
||||||
|
Player: twhelp.PlayerMeta{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
expectedJSON: "null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: null 2",
|
||||||
|
player: twhelp.NullPlayerMeta{
|
||||||
|
Player: twhelp.PlayerMeta{ID: 1234},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
expectedJSON: "null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: valid struct",
|
||||||
|
player: twhelp.NullPlayerMeta{
|
||||||
|
Player: twhelp.PlayerMeta{
|
||||||
|
ID: 997,
|
||||||
|
Name: "name 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
Tribe: twhelp.NullTribeMeta{
|
||||||
|
Valid: true,
|
||||||
|
Tribe: twhelp.TribeMeta{
|
||||||
|
ID: 1234,
|
||||||
|
Name: "name 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
Tag: "tag 997",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
//nolint:lll
|
||||||
|
expectedJSON: `{"id":997,"name":"name 997","profileUrl":"profile-997","tribe":{"id":1234,"name":"name 997","profileUrl":"profile-997","tag":"tag 997"}}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b, err := json.Marshal(tt.player)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.JSONEq(t, tt.expectedJSON, string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullPlayerMeta_UnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
expectedPlayer twhelp.NullPlayerMeta
|
||||||
|
expectedJSONSyntaxError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: null",
|
||||||
|
json: "null",
|
||||||
|
expectedPlayer: twhelp.NullPlayerMeta{
|
||||||
|
Player: twhelp.PlayerMeta{},
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: valid struct",
|
||||||
|
//nolint:lll
|
||||||
|
json: `{"id":997,"name":"name 997","profileUrl":"profile-997","tribe":{"id":1234,"name":"name 997","profileUrl":"profile-997","tag":"tag 997"}}`,
|
||||||
|
expectedPlayer: twhelp.NullPlayerMeta{
|
||||||
|
Player: twhelp.PlayerMeta{
|
||||||
|
ID: 997,
|
||||||
|
Name: "name 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
Tribe: twhelp.NullTribeMeta{
|
||||||
|
Valid: true,
|
||||||
|
Tribe: twhelp.TribeMeta{
|
||||||
|
ID: 1234,
|
||||||
|
Name: "name 997",
|
||||||
|
ProfileURL: "profile-997",
|
||||||
|
Tag: "tag 997",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid tribe 1",
|
||||||
|
json: "2022-07-30T14:13:12.0000005Z",
|
||||||
|
expectedPlayer: twhelp.NullPlayerMeta{},
|
||||||
|
expectedJSONSyntaxError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid tribe 2",
|
||||||
|
json: "hello world",
|
||||||
|
expectedPlayer: twhelp.NullPlayerMeta{},
|
||||||
|
expectedJSONSyntaxError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var target twhelp.NullPlayerMeta
|
||||||
|
err := json.Unmarshal([]byte(tt.json), &target)
|
||||||
|
if tt.expectedJSONSyntaxError {
|
||||||
|
var syntaxError *json.SyntaxError
|
||||||
|
assert.ErrorAs(t, err, &syntaxError)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expectedPlayer, target)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
package twhelp
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ErrorCode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrorCodeInternalServerError ErrorCode = "internal-server-error"
|
|
||||||
ErrorCodeEntityNotFound ErrorCode = "entity-not-found"
|
|
||||||
ErrorCodeValidationError ErrorCode = "validation-error"
|
|
||||||
ErrorCodeRouteNotFound ErrorCode = "route-not-found"
|
|
||||||
ErrorCodeMethodNotAllowed ErrorCode = "method-not-allowed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c ErrorCode) String() string {
|
|
||||||
return string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIError struct {
|
|
||||||
Code ErrorCode `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e APIError) Error() string {
|
|
||||||
return e.Message + " (code=" + e.Code.String() + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorResp struct {
|
|
||||||
Error APIError `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Version struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Timezone string `json:"timezone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type listVersionsResp struct {
|
|
||||||
Data []Version `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Open bool `json:"open"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type getServerResp struct {
|
|
||||||
Data Server `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tribe struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
ProfileURL string `json:"profileUrl"`
|
|
||||||
DeletedAt time.Time `json:"deletedAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type getTribeResp struct {
|
|
||||||
Data Tribe `json:"data"`
|
|
||||||
}
|
|
|
@ -34,6 +34,8 @@ spec:
|
||||||
key: token
|
key: token
|
||||||
- name: TWHELP_URL
|
- name: TWHELP_URL
|
||||||
value: "https://tribalwarshelp.com"
|
value: "https://tribalwarshelp.com"
|
||||||
|
- name: BOT_ENNOBLEMENTS_REQ_LIMIT
|
||||||
|
value: "4"
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
exec:
|
||||||
command: [ "cat", "/tmp/healthy" ]
|
command: [ "cat", "/tmp/healthy" ]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user