Skip to content

Commit

Permalink
✨ Extract Websites from Ticker
Browse files Browse the repository at this point in the history
  • Loading branch information
0x46616c6b committed Jan 23, 2025
1 parent cb5f5d7 commit 227275c
Show file tree
Hide file tree
Showing 15 changed files with 442 additions and 42 deletions.
2 changes: 2 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func API(config config.Config, store storage.Storage, log *logrus.Logger) *gin.E
admin.GET(`/tickers/:tickerID`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.GetTicker)
admin.POST(`/tickers`, user.NeedAdmin(), handler.PostTicker)
admin.PUT(`/tickers/:tickerID`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.PutTicker)
admin.DELETE(`/tickers/:tickerID/websites/:domain`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.DeleteTickerWebsite)
admin.POST(`/tickers/:tickerID/websites`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.PostTickerWebsite)
admin.PUT(`/tickers/:tickerID/telegram`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.PutTickerTelegram)
admin.DELETE(`/tickers/:tickerID/telegram`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.DeleteTickerTelegram)
admin.PUT(`/tickers/:tickerID/mastodon`, ticker.PrefetchTicker(store, storage.WithPreload()), handler.PutTickerMastodon)
Expand Down
2 changes: 1 addition & 1 deletion internal/api/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (s *InitTestSuite) TestGetInit() {

s.Equal(http.StatusOK, s.w.Code)
s.Equal(`{"data":{"settings":{"refreshInterval":10000},"ticker":null},"status":"success","error":{}}`, s.w.Body.String())
s.store.AssertNotCalled(s.T(), "FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything)
s.store.AssertNotCalled(s.T(), "FindTickerByDomain", "", mock.Anything)
s.store.AssertExpectations(s.T())
})

Expand Down
2 changes: 0 additions & 2 deletions internal/api/response/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
type InitTicker struct {
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Domain string `json:"domain"`
Title string `json:"title"`
Description string `json:"description"`
Information InitTickerInformation `json:"information"`
Expand All @@ -30,7 +29,6 @@ func InitTickerResponse(ticker storage.Ticker) InitTicker {
return InitTicker{
ID: ticker.ID,
CreatedAt: ticker.CreatedAt,
Domain: ticker.Domain,
Title: ticker.Title,
Description: ticker.Description,
Information: InitTickerInformation{
Expand Down
1 change: 0 additions & 1 deletion internal/api/response/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func (s *InitTickerResponseTestSuite) TestInitTickerResponse() {

s.Equal(ticker.ID, response.ID)
s.Equal(ticker.CreatedAt, response.CreatedAt)
s.Equal(ticker.Domain, response.Domain)
s.Equal(ticker.Title, response.Title)
s.Equal(ticker.Description, response.Description)
s.Equal(ticker.Information.Author, response.Information.Author)
Expand Down
19 changes: 17 additions & 2 deletions internal/api/response/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
type Ticker struct {
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Domain string `json:"domain"`
Title string `json:"title"`
Description string `json:"description"`
Active bool `json:"active"`
Information Information `json:"information"`
Websites []Website `json:"websites"`
Telegram Telegram `json:"telegram"`
Mastodon Mastodon `json:"mastodon"`
Bluesky Bluesky `json:"bluesky"`
Expand All @@ -33,6 +33,12 @@ type Information struct {
Bluesky string `json:"bluesky"`
}

type Website struct {
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Domain string `json:"domain"`
}

type Telegram struct {
Active bool `json:"active"`
Connected bool `json:"connected"`
Expand Down Expand Up @@ -69,10 +75,18 @@ type Location struct {
}

func TickerResponse(t storage.Ticker, config config.Config) Ticker {
websites := make([]Website, 0)
for _, website := range t.Websites {
websites = append(websites, Website{
ID: website.ID,
CreatedAt: website.CreatedAt,
Domain: website.Domain,
})
}

return Ticker{
ID: t.ID,
CreatedAt: t.CreatedAt,
Domain: t.Domain,
Title: t.Title,
Description: t.Description,
Active: t.Active,
Expand All @@ -86,6 +100,7 @@ func TickerResponse(t storage.Ticker, config config.Config) Ticker {
Mastodon: t.Information.Mastodon,
Bluesky: t.Information.Bluesky,
},
Websites: websites,
Telegram: Telegram{
Active: t.Telegram.Active,
Connected: t.Telegram.Connected(),
Expand Down
8 changes: 7 additions & 1 deletion internal/api/response/ticker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
Mastodon: "https://systemli.social/@example",
Bluesky: "https://example.com",
},
Websites: []storage.TickerWebsite{
{
Domain: "example.com",
},
},
Telegram: storage.TickerTelegram{
Active: true,
ChannelName: "example",
Expand Down Expand Up @@ -69,7 +74,6 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
s.Equal(1, len(tickerResponse))
s.Equal(ticker.ID, tickerResponse[0].ID)
s.Equal(ticker.CreatedAt, tickerResponse[0].CreatedAt)
s.Equal(ticker.Domain, tickerResponse[0].Domain)
s.Equal(ticker.Title, tickerResponse[0].Title)
s.Equal(ticker.Description, tickerResponse[0].Description)
s.Equal(ticker.Active, tickerResponse[0].Active)
Expand All @@ -81,6 +85,8 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
s.Equal(ticker.Information.Telegram, tickerResponse[0].Information.Telegram)
s.Equal(ticker.Information.Mastodon, tickerResponse[0].Information.Mastodon)
s.Equal(ticker.Information.Bluesky, tickerResponse[0].Information.Bluesky)
s.Equal(1, len(ticker.Websites))
s.Equal(ticker.Websites[0].Domain, tickerResponse[0].Websites[0].Domain)
s.Equal(ticker.Telegram.Active, tickerResponse[0].Telegram.Active)
s.Equal(ticker.Telegram.Connected(), tickerResponse[0].Telegram.Connected)
s.Equal(config.Telegram.User.UserName, tickerResponse[0].Telegram.BotUsername)
Expand Down
58 changes: 51 additions & 7 deletions internal/api/tickers.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,53 @@ func (h *handler) PutTickerUsers(c *gin.Context) {
c.JSON(http.StatusOK, response.SuccessResponse(map[string]interface{}{"users": response.UsersResponse(ticker.Users)}))
}

func (h *handler) PostTickerWebsite(c *gin.Context) {
ticker, err := helper.Ticker(c)
if err != nil {
c.JSON(http.StatusNotFound, response.ErrorResponse(response.CodeDefault, response.TickerNotFound))
return
}

var body struct {
Domain string `json:"domain" binding:"required"`
}

err = c.Bind(&body)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.FormError))
return
}

err = h.storage.SaveTickerWebsite(&ticker, body.Domain)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.StorageError))
return
}

h.ClearTickerCache(&ticker)

c.JSON(http.StatusOK, response.SuccessResponse(map[string]interface{}{"ticker": response.TickerResponse(ticker, h.config)}))
}

func (h *handler) DeleteTickerWebsite(c *gin.Context) {
ticker, err := helper.Ticker(c)
if err != nil {
c.JSON(http.StatusNotFound, response.ErrorResponse(response.CodeDefault, response.TickerNotFound))
return
}

domain := c.Param("domain")
err = h.storage.DeleteTickerWebsite(&ticker, domain)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.StorageError))
return
}

h.ClearTickerCache(&ticker)

c.JSON(http.StatusOK, response.SuccessResponse(map[string]interface{}{"ticker": response.TickerResponse(ticker, h.config)}))
}

func (h *handler) PutTickerTelegram(c *gin.Context) {
ticker, err := helper.Ticker(c)
if err != nil {
Expand Down Expand Up @@ -450,16 +497,17 @@ func (h *handler) ResetTicker(c *gin.Context) {
// ClearTickerCache clears the cache for the init endpoint of a ticker
func (h *handler) ClearTickerCache(ticker *storage.Ticker) {
h.cache.Range(func(key, value interface{}) bool {
if strings.HasPrefix(key.(string), fmt.Sprintf("response:%s:/v1/init", ticker.Domain)) {
h.cache.Delete(key)
for _, website := range ticker.Websites {
if strings.HasPrefix(key.(string), fmt.Sprintf("response:%s:/v1/init", website.Domain)) {
h.cache.Delete(key)
}
}
return true
})
}

func updateTicker(t *storage.Ticker, c *gin.Context) error {
var body struct {
Domain string `json:"domain" binding:"required"`
Title string `json:"title" binding:"required"`
Description string `json:"description"`
Active bool `json:"active"`
Expand All @@ -484,10 +532,6 @@ func updateTicker(t *storage.Ticker, c *gin.Context) error {
return err
}

me, _ := helper.Me(c)
if me.IsSuperAdmin {
t.Domain = body.Domain
}
t.Title = body.Title
t.Description = body.Description
t.Active = body.Active
Expand Down
118 changes: 96 additions & 22 deletions internal/api/tickers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (s *TickerTestSuite) TestPutTicker() {

s.Run("when storage returns error", func() {
s.ctx.Set("ticker", storage.Ticker{})
body := `{"domain":"localhost","title":"title","description":"description"}`
body := `{"title":"title","description":"description"}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once()
Expand All @@ -188,28 +188,11 @@ func (s *TickerTestSuite) TestPutTicker() {
s.store.AssertExpectations(s.T())
})

s.Run("user tries to update the domain", func() {
s.ctx.Set("ticker", storage.Ticker{Domain: "localhost"})
s.cache.Set("response:localhost:/v1/init", true, time.Minute)
s.ctx.Set("me", storage.User{IsSuperAdmin: false})
body := `{"domain":"new_domain","title":"title","description":"description"}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
ticker := &storage.Ticker{Domain: "localhost", Title: "title", Description: "description"}
s.store.On("SaveTicker", ticker).Return(nil).Once()
h := s.handler()
h.PutTicker(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Nil(s.cache.Get("response:localhost:/v1/init"))
s.store.AssertExpectations(s.T())
})

s.Run("happy path", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.ctx.Set("ticker", storage.Ticker{Websites: []storage.TickerWebsite{{Domain: "localhost"}}})
s.ctx.Set("me", storage.User{IsSuperAdmin: true})
s.cache.Set("response:localhost:/v1/init", true, time.Minute)
body := `{"domain":"localhost","title":"title","description":"description"}`
body := `{"title":"title","description":"description"}`
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", 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 @@ -269,6 +252,93 @@ func (s *TickerTestSuite) TestPutTickerUsers() {
})
}

func (s *TickerTestSuite) TestPostTickerWebsite() {
s.Run("when ticker not found", func() {
h := s.handler()
h.PostTickerWebsite(s.ctx)

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

s.Run("when body is invalid", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers/1/websites", nil)
s.ctx.Request.Header.Add("Content-Type", "application/json")
h := s.handler()
h.PostTickerWebsite(s.ctx)

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

s.Run("when storage returns error", func() {
s.ctx.Set("ticker", storage.Ticker{})
body := `{"domain":"example.org"}`
s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers/1/websites", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTickerWebsite", mock.Anything, "example.org").Return(errors.New("storage error")).Once()

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

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

s.Run("when storage returns ticker", func() {
s.ctx.Set("ticker", storage.Ticker{})
body := `{"domain":"example.org"}`
s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers/1/websites", strings.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTickerWebsite", mock.Anything, "example.org").Return(nil).Once()

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

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

func (s *TickerTestSuite) TestDeleteTickerWebsite() {
s.Run("when ticker not found", func() {
h := s.handler()
h.DeleteTickerWebsite(s.ctx)

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

s.Run("when storage returns error", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.ctx.Params = gin.Params{{Key: "domain", Value: "example.org"}}
s.ctx.Request = httptest.NewRequest(http.MethodDelete, "/v1/admin/tickers/1/websites/example.org", nil)
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("DeleteTickerWebsite", mock.Anything, "example.org").Return(errors.New("storage error")).Once()

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

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

s.Run("when storage returns ticker", func() {
s.ctx.Set("ticker", storage.Ticker{})
s.ctx.Params = gin.Params{{Key: "domain", Value: "example.org"}}
s.ctx.Request = httptest.NewRequest(http.MethodDelete, "/v1/admin/tickers/1/websites/example.org", nil)
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("DeleteTickerWebsite", mock.Anything, "example.org").Return(nil).Once()

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

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

func (s *TickerTestSuite) TestPutTickerTelegram() {
s.Run("when ticker not found", func() {
h := s.handler()
Expand Down Expand Up @@ -1172,7 +1242,9 @@ func (s *TickerTestSuite) TestDeleteTicker() {
h.DeleteTicker(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Nil(s.cache.Get("response:localhost:/v1/init"))
item, found := s.cache.Get("response:localhost:/v1/init")
s.Nil(item)
s.False(found)
s.store.AssertExpectations(s.T())
})
}
Expand Down Expand Up @@ -1258,7 +1330,9 @@ func (s *TickerTestSuite) TestResetTicker() {
h.ResetTicker(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Nil(s.cache.Get("response:localhost:/v1/init"))
item, found := s.cache.Get("response:localhost:/v1/init")
s.Nil(item)
s.False(found)
s.store.AssertExpectations(s.T())
})
}
Expand Down
Loading

0 comments on commit 227275c

Please sign in to comment.