feat: add a new command - monitor list
All checks were successful
continuous-integration/drone/pr Build is passing

This commit is contained in:
Dawid Wysokiński 2022-10-27 15:16:37 +02:00
parent de19857b34
commit 6f20736671
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
9 changed files with 209 additions and 1 deletions

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.19
require ( require (
github.com/bwmarrin/discordgo v0.26.1 github.com/bwmarrin/discordgo v0.26.1
github.com/cenkalti/backoff/v4 v4.1.3 github.com/cenkalti/backoff/v4 v4.1.3
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/ory/dockertest/v3 v3.9.1 github.com/ory/dockertest/v3 v3.9.1

3
go.sum
View File

@ -49,7 +49,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=

View File

@ -20,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)
List(ctx context.Context, groupID, serverID string) ([]domain.MonitorWithTribe, error)
Execute(ctx context.Context) ([]domain.EnnoblementNotification, error) Execute(ctx context.Context) ([]domain.EnnoblementNotification, error)
Delete(ctx context.Context, id, serverID string) error Delete(ctx context.Context, id, serverID string) error
} }

View File

@ -3,7 +3,9 @@ package discord
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
@ -57,6 +59,19 @@ func (c *monitorCommand) create(s *discordgo.Session) error {
}, },
}, },
}, },
{
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", Name: "delete",
Description: "Deletes a monitor", Description: "Deletes a monitor",
@ -90,6 +105,9 @@ func (c *monitorCommand) handle(s *discordgo.Session, i *discordgo.InteractionCr
case "create": case "create":
c.handleCreate(s, i) c.handleCreate(s, i)
return return
case "list":
c.handleList(s, i)
return
case "delete": case "delete":
c.handleDelete(s, i) c.handleDelete(s, i)
return return
@ -133,6 +151,36 @@ func (c *monitorCommand) handleCreate(s *discordgo.Session, i *discordgo.Interac
}) })
} }
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
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Type: discordgo.EmbedTypeRich,
Title: "Monitor list",
Description: buildMonitorListDescription(monitors),
Timestamp: formatTimestamp(time.Now()),
},
},
},
})
}
func (c *monitorCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *monitorCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) {
ctx := context.Background() ctx := context.Background()
@ -154,3 +202,11 @@ func (c *monitorCommand) handleDelete(s *discordgo.Session, i *discordgo.Interac
}, },
}) })
} }
func buildMonitorListDescription(monitors []domain.MonitorWithTribe) string {
description := "**ID** - **Tribe**"
for i, m := range monitors {
description += fmt.Sprintf("\n%d. %s - %s", i+1, m.ID, buildLink(m.Tribe.Tag, m.Tribe.ProfileURL))
}
return description
}

View File

@ -16,6 +16,11 @@ type Monitor struct {
CreatedAt time.Time CreatedAt time.Time
} }
type MonitorWithTribe struct {
Monitor
Tribe TribeMeta
}
type CreateMonitorParams struct { type CreateMonitorParams struct {
tribeID int64 tribeID int64
groupID string groupID string

View File

@ -103,6 +103,74 @@ func (m *Monitor) Create(ctx context.Context, groupID, serverID, tribeTag string
return monitor, nil return monitor, nil
} }
type getTribeResult struct {
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 _, monitor := range monitors {
wg.Add(1)
go func(monitor domain.Monitor) {
res := getTribeResult{
monitor: monitor,
}
res.tribe, res.err = m.client.GetTribeByID(
ctx,
group.VersionCode,
group.ServerKey,
monitor.TribeID,
)
ch <- res
}(monitor)
}
go func() {
wg.Wait()
close(ch)
}()
var firstErr error
results := make([]domain.MonitorWithTribe, 0, 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 = append(results, 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 { func (m *Monitor) Delete(ctx context.Context, id, serverID string) error {
monitor, err := m.repo.Get(ctx, id) monitor, err := m.repo.Get(ctx, id)
if err != nil { if err != nil {

View File

@ -10,6 +10,7 @@ import (
"gitea.dwysokinski.me/twhelp/dcbot/internal/service" "gitea.dwysokinski.me/twhelp/dcbot/internal/service"
"gitea.dwysokinski.me/twhelp/dcbot/internal/service/internal/mock" "gitea.dwysokinski.me/twhelp/dcbot/internal/service/internal/mock"
"gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp" "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -195,6 +196,71 @@ func TestMonitor_Create(t *testing.T) {
}) })
} }
func TestMonitor_List(t *testing.T) {
t.Parallel()
t.Run("OK", func(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, 10).
List(context.Background(), groupID, uuid.NewString())
assert.NoError(t, err)
assert.Len(t, res, len(monitors))
for _, m1 := range monitors {
var exist bool
for _, m2 := range res {
if cmp.Equal(m1, m2.Monitor) && m1.TribeID == m2.Tribe.ID {
exist = true
break
}
}
assert.True(t, exist)
}
})
}
func TestMonitor_Execute(t *testing.T) { func TestMonitor_Execute(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -12,6 +12,7 @@ import (
type TWHelpClient interface { 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)
GetTribeByID(ctx context.Context, version, server string, id int64) (twhelp.Tribe, error)
GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error) GetTribeByTag(ctx context.Context, version, server, tag string) (twhelp.Tribe, error)
ListEnnoblements( ListEnnoblements(
ctx context.Context, ctx context.Context,

View File

@ -17,6 +17,7 @@ const (
endpointListVersions = "/api/v1/versions" endpointListVersions = "/api/v1/versions"
endpointGetServer = "/api/v1/versions/%s/servers/%s" endpointGetServer = "/api/v1/versions/%s/servers/%s"
endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d"
endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s" endpointGetTribeByTag = "/api/v1/versions/%s/servers/%s/tribes/tag/%s"
endpointListEnnoblements = "/api/v1/versions/%s/servers/%s/ennoblements" endpointListEnnoblements = "/api/v1/versions/%s/servers/%s/ennoblements"
) )
@ -71,6 +72,14 @@ func (c *Client) GetServer(ctx context.Context, version, server string) (Server,
return resp.Data, nil return resp.Data, nil
} }
func (c *Client) GetTribeByID(ctx context.Context, version, server string, id int64) (Tribe, error) {
var resp getTribeResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointGetTribeByID, version, server, id), &resp); err != nil {
return Tribe{}, err
}
return resp.Data, nil
}
func (c *Client) GetTribeByTag(ctx context.Context, version, server, tag string) (Tribe, error) { func (c *Client) GetTribeByTag(ctx context.Context, version, server, tag string) (Tribe, error) {
var resp getTribeResp var resp getTribeResp
if err := c.getJSON(ctx, fmt.Sprintf(endpointGetTribeByTag, version, server, tag), &resp); err != nil { if err := c.getJSON(ctx, fmt.Sprintf(endpointGetTribeByTag, version, server, tag), &resp); err != nil {