Skip to content

Commit

Permalink
Merge pull request #309 from systemli/Add-ability-to-filter-and-sort-…
Browse files Browse the repository at this point in the history
…tickers

✨Add ability to filter and sort tickers
  • Loading branch information
0x46616c6b authored Apr 30, 2024
2 parents 94b321f + 88ec1de commit 803b27b
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 79 deletions.
3 changes: 2 additions & 1 deletion internal/api/tickers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ func (h *handler) GetTickers(c *gin.Context) {
return
}

tickers, err := h.storage.FindTickersByUser(me)
filter := storage.NewTickerFilter(c.Request)
tickers, err := h.storage.FindTickersByUser(me, filter)
if err != nil {
c.JSON(http.StatusNotFound, response.ErrorResponse(response.CodeDefault, response.TickerNotFound))
return
Expand Down
4 changes: 2 additions & 2 deletions internal/api/tickers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (s *TickerTestSuite) TestGetTickers() {

s.Run("when storage returns an error", func() {
s.ctx.Set("me", storage.User{IsSuperAdmin: true})
s.store.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, errors.New("storage error")).Once()
s.store.On("FindTickersByUser", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Ticker{}, errors.New("storage error")).Once()
h := s.handler()
h.GetTickers(s.ctx)

Expand All @@ -65,7 +65,7 @@ func (s *TickerTestSuite) TestGetTickers() {

s.Run("when storage returns tickers", func() {
s.ctx.Set("me", storage.User{IsSuperAdmin: true})
s.store.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, nil).Once()
s.store.On("FindTickersByUser", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Ticker{}, nil).Once()
h := s.handler()
h.GetTickers(s.ctx)

Expand Down
54 changes: 9 additions & 45 deletions internal/storage/mock_Storage.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 13 additions & 7 deletions internal/storage/sql_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,23 @@ func (s *SqlStorage) AddTickerUser(ticker *Ticker, user *User) error {
return err
}

func (s *SqlStorage) FindTickers(opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error) {
func (s *SqlStorage) FindTickersByUser(user User, filter TickerFilter, opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error) {
tickers := make([]Ticker, 0)
db := s.prepareDb(opts...)
err := db.Find(&tickers).Error

return tickers, err
}
if filter.Active != nil {
db = db.Where("active = ?", *filter.Active)
}

func (s *SqlStorage) FindTickersByUser(user User, opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error) {
tickers := make([]Ticker, 0)
db := s.prepareDb(opts...)
if filter.Domain != nil {
db = db.Where("domain LIKE ?", fmt.Sprintf("%%%s%%", *filter.Domain))
}

if filter.Title != nil {
db = db.Where("title LIKE ?", fmt.Sprintf("%%%s%%", *filter.Title))
}

db = db.Order(fmt.Sprintf("%s %s", filter.OrderBy, filter.Sort))

var err error
if user.IsSuperAdmin {
Expand Down
63 changes: 41 additions & 22 deletions internal/storage/sql_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,23 +355,6 @@ func (s *SqlStorageTestSuite) TestAddTickerUser() {
s.Equal(int64(1), count)
}

func (s *SqlStorageTestSuite) TestFindTickers() {
s.Run("when no tickers exist", func() {
tickers, err := s.store.FindTickers()
s.NoError(err)
s.Empty(tickers)
})

s.Run("when tickers exist", func() {
err := s.db.Create(&Ticker{ID: 1}).Error
s.NoError(err)

tickers, err := s.store.FindTickers()
s.NoError(err)
s.Len(tickers, 1)
})
}

func (s *SqlStorageTestSuite) TestFindTickerByID() {
s.Run("when ticker does not exist", func() {
_, err := s.store.FindTickerByID(1)
Expand Down Expand Up @@ -464,7 +447,8 @@ func (s *SqlStorageTestSuite) TestFindTickerByDomain() {

func (s *SqlStorageTestSuite) TestFindTickersByUser() {
s.Run("when no tickers exist", func() {
tickers, err := s.store.FindTickersByUser(User{ID: 1})
filter := TickerFilter{OrderBy: "id", Sort: "desc"}
tickers, err := s.store.FindTickersByUser(User{ID: 1}, filter)
s.NoError(err)
s.Empty(tickers)
})
Expand All @@ -473,27 +457,62 @@ func (s *SqlStorageTestSuite) TestFindTickersByUser() {
err := s.db.Create(&user).Error
s.NoError(err)

ticker := Ticker{Users: []User{user}}
ticker := Ticker{Users: []User{user}, Active: false, Domain: "localhost", Title: "title"}
err = s.db.Create(&ticker).Error
s.NoError(err)

s.Run("when tickers exist", func() {
tickers, err := s.store.FindTickersByUser(user)
filter := TickerFilter{OrderBy: "id", Sort: "desc"}
tickers, err := s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Len(tickers, 1)
})

s.Run("when tickers exist with preload", func() {
tickers, err := s.store.FindTickersByUser(user, WithPreload())
filter := TickerFilter{OrderBy: "id", Sort: "desc"}
tickers, err := s.store.FindTickersByUser(user, filter, WithPreload())
s.NoError(err)
s.Len(tickers, 1)
s.Len(tickers[0].Users, 1)
})

s.Run("when super admin", func() {
tickers, err := s.store.FindTickersByUser(User{IsSuperAdmin: true})
filter := TickerFilter{OrderBy: "id", Sort: "desc"}
tickers, err := s.store.FindTickersByUser(User{IsSuperAdmin: true}, filter)
s.NoError(err)
s.Len(tickers, 1)
})

s.Run("when filter is set", func() {
active := true
filter := TickerFilter{OrderBy: "id", Sort: "desc", Active: &active}
tickers, err := s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Empty(tickers)

active = false
filter = TickerFilter{OrderBy: "id", Sort: "desc", Active: &active}
tickers, err = s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Len(tickers, 1)

title := "title"
filter = TickerFilter{OrderBy: "id", Sort: "desc", Title: &title}
tickers, err = s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Len(tickers, 1)

domain := "localhost"
filter = TickerFilter{OrderBy: "id", Sort: "desc", Domain: &domain}
tickers, err = s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Len(tickers, 1)

domain = "systemli.org"
filter = TickerFilter{OrderBy: "id", Sort: "desc", Domain: &domain}
tickers, err = s.store.FindTickersByUser(user, filter)
s.NoError(err)
s.Empty(tickers)
})
}

Expand Down
3 changes: 1 addition & 2 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ type Storage interface {
DeleteTickerUsers(ticker *Ticker) error
DeleteTickerUser(ticker *Ticker, user *User) error
AddTickerUser(ticker *Ticker, user *User) error
FindTickers(opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error)
FindTickersByUser(user User, opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error)
FindTickersByUser(user User, filter TickerFilter, opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error)
FindTickerByUserAndID(user User, id int, opts ...func(*gorm.DB) *gorm.DB) (Ticker, error)
FindTickersByIDs(ids []int, opts ...func(*gorm.DB) *gorm.DB) ([]Ticker, error)
FindTickerByDomain(domain string, opts ...func(*gorm.DB) *gorm.DB) (Ticker, error)
Expand Down
52 changes: 52 additions & 0 deletions internal/storage/ticker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package storage

import (
"net/http"
"strconv"
"time"
)

Expand Down Expand Up @@ -118,3 +120,53 @@ type TickerLocation struct {
Lat float64
Lon float64
}

type TickerFilter struct {
Domain *string
Title *string
Active *bool
OrderBy string
Sort string
}

func NewTickerFilter(req *http.Request) TickerFilter {
filter := TickerFilter{
OrderBy: "id",
Sort: "asc",
}

if req == nil {
return filter
}

if req.URL.Query().Get("order_by") != "" {
opts := []string{"id", "created_at", "updated_at", "domain", "title", "active"}
for _, opt := range opts {
if req.URL.Query().Get("order_by") == opt {
filter.OrderBy = req.URL.Query().Get("order_by")
break
}
}
}
if req.URL.Query().Get("sort") != "" {
filter.Sort = req.URL.Query().Get("sort")
}

domain := req.URL.Query().Get("domain")
if domain != "" {
filter.Domain = &domain
}

title := req.URL.Query().Get("title")
if title != "" {
filter.Title = &title
}

active := req.URL.Query().Get("active")
if active != "" {
activeBool, _ := strconv.ParseBool(active)
filter.Active = &activeBool
}

return filter
}
25 changes: 25 additions & 0 deletions internal/storage/ticker_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package storage

import (
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -42,3 +43,27 @@ func TestTickerReset(t *testing.T) {
assert.Empty(t, ticker.Telegram.ChannelName)
assert.Empty(t, ticker.Location)
}

func TestNewTickerFilter(t *testing.T) {
filter := NewTickerFilter(nil)
assert.Nil(t, filter.Active)
assert.Nil(t, filter.Domain)
assert.Nil(t, filter.Title)

req := httptest.NewRequest("GET", "/", nil)
filter = NewTickerFilter(req)
assert.Nil(t, filter.Active)
assert.Nil(t, filter.Domain)
assert.Nil(t, filter.Title)

req = httptest.NewRequest("GET", "/?active=true&domain=example.org&title=Title", nil)
filter = NewTickerFilter(req)
assert.True(t, *filter.Active)
assert.Equal(t, "example.org", *filter.Domain)
assert.Equal(t, "Title", *filter.Title)

req = httptest.NewRequest("GET", "/?order_by=created_at&sort=asc", nil)
filter = NewTickerFilter(req)
assert.Equal(t, "created_at", filter.OrderBy)
assert.Equal(t, "asc", filter.Sort)
}

0 comments on commit 803b27b

Please sign in to comment.