Skip to content

Commit

Permalink
Merge pull request #328 from systemli/feat/signal_group_details
Browse files Browse the repository at this point in the history
✨ Use ticker title and description for signal group details
  • Loading branch information
doobry-systemli authored Jul 7, 2024
2 parents 1b5b7ca + e2d19cc commit 16a6990
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 74 deletions.
1 change: 1 addition & 0 deletions internal/api/response/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
FormError ErrorMessage = "invalid form values"
StorageError ErrorMessage = "failed to save"
UploadsNotFound ErrorMessage = "uploads not found"
BridgeError ErrorMessage = "unable to update ticker in bridges"
MastodonError ErrorMessage = "unable to connect to mastodon"
BlueskyError ErrorMessage = "unable to connect to bluesky"
SignalGroupError ErrorMessage = "unable to connect to signal"
Expand Down
20 changes: 8 additions & 12 deletions internal/api/response/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,10 @@ type Bluesky struct {
}

type SignalGroup struct {
Active bool `json:"active"`
Connected bool `json:"connected"`
GroupID string `json:"groupID"`
GroupName string `json:"groupName"`
GroupDescription string `json:"groupDescription"`
GroupInviteLink string `json:"groupInviteLink"`
Active bool `json:"active"`
Connected bool `json:"connected"`
GroupID string `json:"groupID"`
GroupInviteLink string `json:"groupInviteLink"`
}

type Location struct {
Expand Down Expand Up @@ -108,12 +106,10 @@ func TickerResponse(t storage.Ticker, config config.Config) Ticker {
Handle: t.Bluesky.Handle,
},
SignalGroup: SignalGroup{
Active: t.SignalGroup.Active,
Connected: t.SignalGroup.Connected(),
GroupID: t.SignalGroup.GroupID,
GroupName: t.SignalGroup.GroupName,
GroupDescription: t.SignalGroup.GroupDescription,
GroupInviteLink: t.SignalGroup.GroupInviteLink,
Active: t.SignalGroup.Active,
Connected: t.SignalGroup.Connected(),
GroupID: t.SignalGroup.GroupID,
GroupInviteLink: t.SignalGroup.GroupInviteLink,
},
Location: Location{
Lat: t.Location.Lat,
Expand Down
10 changes: 3 additions & 7 deletions internal/api/response/ticker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,9 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
},
},
SignalGroup: storage.TickerSignalGroup{
Active: true,
GroupID: "example",
GroupName: "Example",
GroupDescription: "Example",
GroupInviteLink: "https://signal.group/#example",
Active: true,
GroupID: "example",
GroupInviteLink: "https://signal.group/#example",
},
Location: storage.TickerLocation{
Lat: 0.0,
Expand Down Expand Up @@ -96,8 +94,6 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
s.Equal(ticker.SignalGroup.Active, tickerResponse[0].SignalGroup.Active)
s.Equal(ticker.SignalGroup.Connected(), tickerResponse[0].SignalGroup.Connected)
s.Equal(ticker.SignalGroup.GroupID, tickerResponse[0].SignalGroup.GroupID)
s.Equal(ticker.SignalGroup.GroupName, tickerResponse[0].SignalGroup.GroupName)
s.Equal(ticker.SignalGroup.GroupDescription, tickerResponse[0].SignalGroup.GroupDescription)
s.Equal(ticker.Location.Lat, tickerResponse[0].Location.Lat)
s.Equal(ticker.Location.Lon, tickerResponse[0].Location.Lon)
}
Expand Down
23 changes: 13 additions & 10 deletions internal/api/tickers.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ func (h *handler) PutTicker(c *gin.Context) {
return
}

err = h.bridges.Update(ticker)
if err != nil {
log.WithError(err).Error("failed to update ticker in bridges")
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.BridgeError))
return
}

err = h.storage.SaveTicker(&ticker)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.StorageError))
Expand Down Expand Up @@ -304,16 +311,12 @@ func (h *handler) PutTickerSignalGroup(c *gin.Context) {
return
}

if body.GroupName != "" && body.GroupDescription != "" {
ticker.SignalGroup.GroupName = body.GroupName
ticker.SignalGroup.GroupDescription = body.GroupDescription
groupClient := signal.NewGroupClient(h.config)
err = groupClient.CreateOrUpdateGroup(&ticker.SignalGroup)
if err != nil {
log.WithError(err).Error("failed to create or update group")
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.SignalGroupError))
return
}
groupClient := signal.NewGroupClient(h.config)
err = groupClient.CreateOrUpdateGroup(&ticker)
if err != nil {
log.WithError(err).Error("failed to create or update group")
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.SignalGroupError))
return
}
ticker.SignalGroup.Active = body.Active

Expand Down
173 changes: 153 additions & 20 deletions internal/api/tickers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,44 +594,75 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
s.store.AssertExpectations(s.T())
})

s.Run("when storage returns error", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.Run("when signal-cli API call updateGroup returns error", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(500)

s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once()

h := s.handler()
h.PutTickerSignalGroup(s.ctx)

s.Equal(http.StatusBadRequest, s.w.Code)
s.True(gock.IsDone())
s.store.AssertExpectations(s.T())
})

s.Run("when storage returns ticker", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.Run("when signal-cli API call updateGroup returns no groupId", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": map[string]interface{}{
"timestamp": 1,
},
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(nil).Once()

h := s.handler()
h.PutTickerSignalGroup(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Equal(gock.IsDone(), true)
s.Equal(http.StatusBadRequest, s.w.Code)
s.True(gock.IsDone())
s.store.AssertExpectations(s.T())
})

s.Run("when signal-cli API call updateGroup returns error", func() {
s.Run("when signal-cli API call listGroups returns error", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": map[string]interface{}{
"groupId": "sample-group-id",
"timestamp": 1,
},
"id": 1,
})
// listGroups
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(500)

s.ctx.Set("ticker", storage.Ticker{})
body := `{"active":true,"GroupName":"Example","GroupDescription":"Example"}`
s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")

Expand All @@ -643,7 +674,7 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
s.store.AssertExpectations(s.T())
})

s.Run("when signal-cli API call getGroups returns error", func() {
s.Run("when signal-cli API call listGroups returns no group link", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
Expand All @@ -661,10 +692,21 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(500)
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": []map[string]interface{}{
{
"id": "sample-group-id",
"name": "Example",
"description": "Example",
},
},
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{})
body := `{"active":true,"GroupName":"Example","GroupDescription":"Example"}`
s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")

Expand All @@ -676,7 +718,7 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
s.store.AssertExpectations(s.T())
})

s.Run("when enabling signal group successfully", func() {
s.Run("when storage returns error", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
Expand Down Expand Up @@ -708,8 +750,99 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{})
body := `{"active":true,"GroupName":"Example","GroupDescription":"Example"}`
s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once()

h := s.handler()
h.PutTickerSignalGroup(s.ctx)

s.Equal(http.StatusBadRequest, s.w.Code)
s.store.AssertExpectations(s.T())
})

s.Run("when storage returns ticker", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": map[string]interface{}{
"groupId": "sample-group-id",
"timestamp": 1,
},
"id": 1,
})
// listGroups
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": []map[string]interface{}{
{
"id": "sample-group-id",
"name": "Example",
"description": "Example",
"groupInviteLink": "https://signal.group/#example",
},
},
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(nil).Once()

h := s.handler()
h.PutTickerSignalGroup(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Equal(gock.IsDone(), true)
s.store.AssertExpectations(s.T())
})

s.Run("when creating signal group successfully", func() {
// updateGroup
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": map[string]interface{}{
"groupId": "sample-group-id",
"timestamp": 1,
},
"id": 1,
})
// listGroups
gock.New("https://signal-cli.example.org").
Post("/api/v1/rpc").
MatchHeader("Content-Type", "application/json").
Reply(200).
JSON(map[string]interface{}{
"jsonrpc": "2.0",
"result": []map[string]interface{}{
{
"id": "sample-group-id",
"name": "Example",
"description": "Example",
"groupInviteLink": "https://signal.group/#example",
},
},
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example"})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(nil).Once()
Expand Down Expand Up @@ -754,8 +887,8 @@ func (s *TickerTestSuite) TestPutTickerSignalGroup() {
"id": 1,
})

s.ctx.Set("ticker", storage.Ticker{})
body := `{"active":true,"GroupId":"sample-group-id","GroupName":"Example","GroupDescription":"Example"}`
s.ctx.Set("ticker", storage.Ticker{Title: "Example", Description: "Example", SignalGroup: storage.TickerSignalGroup{GroupID: "sample-group-id"}})
body := `{"active":true}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/signal_group", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(nil).Once()
Expand Down
4 changes: 4 additions & 0 deletions internal/bridge/bluesky.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type BlueskyBridge struct {
storage storage.Storage
}

func (bb *BlueskyBridge) Update(ticker storage.Ticker) error {
return nil
}

func (bb *BlueskyBridge) Send(ticker storage.Ticker, message *storage.Message) error {
if !ticker.Bluesky.Connected() || !ticker.Bluesky.Active {
return nil
Expand Down
9 changes: 9 additions & 0 deletions internal/bridge/bluesky_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import (
"github.com/systemli/ticker/internal/storage"
)

func (s *BridgeTestSuite) TestBlueskyUpdate() {
s.Run("does nothing", func() {
bridge := s.blueskyBridge(config.Config{}, &storage.MockStorage{})

err := bridge.Update(tickerWithBridges)
s.NoError(err)
})
}

func (s *BridgeTestSuite) TestBlueskySend() {
s.Run("when bluesky is inactive", func() {
bridge := s.blueskyBridge(config.Config{}, &storage.MockStorage{})
Expand Down
13 changes: 13 additions & 0 deletions internal/bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
var log = logrus.WithField("package", "bridge")

type Bridge interface {
Update(ticker storage.Ticker) error
Send(ticker storage.Ticker, message *storage.Message) error
Delete(ticker storage.Ticker, message *storage.Message) error
}
Expand All @@ -24,6 +25,18 @@ func RegisterBridges(config config.Config, storage storage.Storage) Bridges {
return Bridges{"telegram": &telegram, "mastodon": &mastodon, "bluesky": &bluesky, "signalGroup": &signalGroup}
}

func (b *Bridges) Update(ticker storage.Ticker) error {
var err error
for name, bridge := range *b {
err := bridge.Update(ticker)
if err != nil {
log.WithError(err).WithField("bridge_name", name).Error("failed to update ticker")
}
}

return err
}

func (b *Bridges) Send(ticker storage.Ticker, message *storage.Message) error {
var err error
for name, bridge := range *b {
Expand Down
Loading

0 comments on commit 16a6990

Please sign in to comment.