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"}}
-
-
-
-{{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,
}
}