diff --git a/radio.go b/radio.go index 2ade4461..b021866e 100644 --- a/radio.go +++ b/radio.go @@ -238,7 +238,7 @@ type SongInfo struct { } type SearchService interface { - Search(ctx context.Context, query string, limit int, offset int) (*SearchResult, error) + Search(ctx context.Context, query string, limit int64, offset int64) (*SearchResult, error) Update(context.Context, ...Song) error Delete(context.Context, ...Song) error } diff --git a/storage/mariadb/search.go b/storage/mariadb/search.go index 0dd900fd..f81a90c5 100644 --- a/storage/mariadb/search.go +++ b/storage/mariadb/search.go @@ -35,7 +35,7 @@ ORDER BY score DESC; `) -func (ss SearchService) Search(ctx context.Context, search_query string, limit int, offset int) (*radio.SearchResult, error) { +func (ss SearchService) Search(ctx context.Context, search_query string, limit int64, offset int64) (*radio.SearchResult, error) { search_query, err := processQuery(search_query) if err != nil { return nil, err diff --git a/templates/default/lastplayed.tmpl b/templates/default/lastplayed.tmpl index e0847058..296c3a70 100644 --- a/templates/default/lastplayed.tmpl +++ b/templates/default/lastplayed.tmpl @@ -15,60 +15,6 @@ {{printjson .}} {{end}} -{{define "pagination"}} -
- -
- - -
-
-{{end}} - -{{define "page-link"}} -
  • - {{.Nr}} -
  • -{{end}} - {{define "song"}}
    diff --git a/templates/default/search.tmpl b/templates/default/search.tmpl index a78e8f37..f36d7843 100644 --- a/templates/default/search.tmpl +++ b/templates/default/search.tmpl @@ -8,13 +8,13 @@
    - +
    {{template "search-header"}} - {{/*template "pagination" .*/}} - {{template "search-results" .}} + {{template "search-results" .Songs}} + {{template "pagination" .Page}}
    @@ -22,15 +22,15 @@ {{define "search-results"}} {{with .}} + {{range .}}
    - {{/*range .Songs*/}} -
    {{/*.Artist*/}} Some artist
    -
    {{/*.Title*/}} Some song
    +
    {{.Artist}}
    +
    {{.Title}}
    - {{/*end*/}}
    + {{end}} {{end}} {{end}} diff --git a/website/api/php/api.go b/website/api/php/api.go index f5e00b98..81c32171 100644 --- a/website/api/php/api.go +++ b/website/api/php/api.go @@ -201,7 +201,7 @@ func (a *API) getSearch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // key from the url router, query is part of the url query := chi.URLParamFromCtx(ctx, "query") - result, err := a.search.Search(ctx, query, limit, offset) + result, err := a.search.Search(ctx, query, int64(limit), int64(offset)) if err != nil { hlog.FromRequest(r).Error().Err(err).Msg("") return diff --git a/website/main.go b/website/main.go index 44037f01..9d2c0f0b 100644 --- a/website/main.go +++ b/website/main.go @@ -8,6 +8,7 @@ import ( "github.com/R-a-dio/valkyrie/config" "github.com/R-a-dio/valkyrie/errors" + "github.com/R-a-dio/valkyrie/search" "github.com/R-a-dio/valkyrie/storage" "github.com/R-a-dio/valkyrie/templates" "github.com/R-a-dio/valkyrie/util/daypass" @@ -63,6 +64,11 @@ func Execute(ctx context.Context, cfg config.Config) error { executor := siteTemplates.Executor() // daypass generation dpass := daypass.New(ctx) + // search service + searchService, err := search.Open(ctx, cfg) + if err != nil { + return errors.E(op, err) + } r := NewRouter() @@ -145,6 +151,7 @@ func Execute(ctx context.Context, cfg config.Config) error { Manager: manager, Streamer: streamer, Storage: storage, + Search: searchService, })) // setup the http server diff --git a/website/middleware/authentication.go b/website/middleware/authentication.go index 966b61a1..64651d60 100644 --- a/website/middleware/authentication.go +++ b/website/middleware/authentication.go @@ -387,7 +387,7 @@ func RequestWithUser(r *http.Request, u *radio.User) *http.Request { // // This should ONLY be used for situations where a human cannot input the // login info somehow. Namely for icecast source clients. -func BasicAuth(us radio.UserStorage) func(http.Handler) http.Handler { +func BasicAuth(uss radio.UserStorageService) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, passwd, ok := r.BasicAuth() @@ -406,6 +406,7 @@ func BasicAuth(us radio.UserStorage) func(http.Handler) http.Handler { } } + us := uss.User(r.Context()) user, err := us.Get(username) if err != nil { hlog.FromRequest(r).Error().Err(err).Str("username", username).Msg("database error") diff --git a/website/middleware/authentication_test.go b/website/middleware/authentication_test.go index a6aa3439..c7994b6c 100644 --- a/website/middleware/authentication_test.go +++ b/website/middleware/authentication_test.go @@ -120,15 +120,19 @@ func TestBasicAuth(t *testing.T) { for _, test := range basicAuthCases { test := test t.Run(test.Name, func(t *testing.T) { - us := &mocks.UserStorageMock{ - GetFunc: func(name string) (*radio.User, error) { - if test.GetFuncRet == nil { - return nil, test.GetFuncErr + storage := &mocks.StorageServiceMock{ + UserFunc: func(contextMoqParam context.Context) radio.UserStorage { + return &mocks.UserStorageMock{ + GetFunc: func(name string) (*radio.User, error) { + if test.GetFuncRet == nil { + return nil, test.GetFuncErr + } + if test.GetFuncRet.Username != name { + return nil, errors.E(errors.UserUnknown) + } + return test.GetFuncRet, test.GetFuncErr + }, } - if test.GetFuncRet.Username != name { - return nil, errors.E(errors.UserUnknown) - } - return test.GetFuncRet, test.GetFuncErr }, } @@ -141,7 +145,7 @@ func TestBasicAuth(t *testing.T) { w := httptest.NewRecorder() r := chi.NewRouter() - r.Use(BasicAuth(us)) + r.Use(BasicAuth(storage)) r.Get("/*", func(w http.ResponseWriter, r *http.Request) { user := UserFromContext(r.Context()) assert.Equal(t, test.GetFuncRet, user) diff --git a/website/public/lastplayed.go b/website/public/lastplayed.go index 9e974d76..cf811049 100644 --- a/website/public/lastplayed.go +++ b/website/public/lastplayed.go @@ -49,7 +49,7 @@ func NewLastPlayedInput(s radio.SongStorageService, r *http.Request) (*LastPlaye Songs: songs, Page: shared.NewPagination( page, shared.PageCount(total, lastplayedSize), - "/last-played?page=%d", + r.URL, ), }, nil } diff --git a/website/public/search.go b/website/public/search.go index d50e16ba..9894a709 100644 --- a/website/public/search.go +++ b/website/public/search.go @@ -3,17 +3,46 @@ package public import ( "net/http" + radio "github.com/R-a-dio/valkyrie" + "github.com/R-a-dio/valkyrie/errors" "github.com/R-a-dio/valkyrie/website/middleware" + "github.com/R-a-dio/valkyrie/website/shared" ) +const searchPageSize = 20 + type SearchInput struct { middleware.Input + + Query string + Songs []radio.Song + Page *shared.Pagination } -func NewSearchInput(r *http.Request) SearchInput { - return SearchInput{ - Input: middleware.InputFromRequest(r), +func NewSearchInput(s radio.SearchService, r *http.Request) (*SearchInput, error) { + const op errors.Op = "website/public.NewSearchInput" + ctx := r.Context() + + page, offset, err := getPageOffset(r, searchPageSize) + if err != nil { + return nil, errors.E(op, err) + } + + query := r.FormValue("q") + result, err := s.Search(ctx, query, searchPageSize, offset) + if err != nil { + return nil, err } + + return &SearchInput{ + Input: middleware.InputFromRequest(r), + Query: query, + Songs: result.Songs, + Page: shared.NewPagination( + page, shared.PageCount(int64(result.TotalHits), searchPageSize), + r.URL, + ), + }, nil } func (SearchInput) TemplateBundle() string { @@ -21,9 +50,13 @@ func (SearchInput) TemplateBundle() string { } func (s State) GetSearch(w http.ResponseWriter, r *http.Request) { - input := NewSearchInput(r) + input, err := NewSearchInput(s.Search, r) + if err != nil { + s.errorHandler(w, r, err) + return + } - err := s.Templates.Execute(w, r, input) + err = s.Templates.Execute(w, r, input) if err != nil { s.errorHandler(w, r, err) return diff --git a/website/public/state.go b/website/public/state.go index c98c23d5..81c09e30 100644 --- a/website/public/state.go +++ b/website/public/state.go @@ -23,6 +23,7 @@ type State struct { Manager radio.ManagerService Streamer radio.StreamerService Storage radio.StorageService + Search radio.SearchService } func (s *State) errorHandler(w http.ResponseWriter, r *http.Request, err error) { diff --git a/website/shared/pagination.go b/website/shared/pagination.go index 7d3f5aef..64637ac5 100644 --- a/website/shared/pagination.go +++ b/website/shared/pagination.go @@ -1,8 +1,9 @@ package shared import ( - "fmt" "html/template" + "net/url" + "strconv" ) func PageCount(total, size int64) int64 { @@ -13,22 +14,30 @@ func PageCount(total, size int64) int64 { return full } -func NewPagination(current, total int64, urlFormat string) *Pagination { +func NewPagination(current, total int64, uri *url.URL) *Pagination { return &Pagination{ - Nr: current, - Total: total, - format: urlFormat, + Nr: current, + Total: total, + uri: uri, } } type Pagination struct { - Nr int64 - Total int64 - format string + Nr int64 + Total int64 + uri *url.URL } func (p *Pagination) URL() template.URL { - return template.URL(fmt.Sprintf(p.format, p.Nr)) + u := *p.uri + v := u.Query() + v.Set("page", strconv.FormatInt(p.Nr, 10)) + u.RawQuery = v.Encode() + return template.URL(u.RequestURI()) +} + +func (p *Pagination) BaseURL() template.URL { + return template.URL(p.uri.Path) } func (p *Pagination) createPage(page int64) *Pagination { @@ -43,9 +52,9 @@ func (p *Pagination) createPage(page int64) *Pagination { } return &Pagination{ - Nr: page, - Total: p.Total, - format: p.format, + Nr: page, + Total: p.Total, + uri: p.uri, } }