Files
Gists/internal/web/handlers/util.go
Webysther Sperandio 279da52899 feat: search all fields (#622)
*  feat(search): search all feature

- add Description field to Gist struct and index it
- extend SearchGistMetadata with Description and Content
- update Bleve and Meilisearch to index and search Description
- modify ParseSearchQueryStr to parse description: and content: keywords
- update templates and i18n for new search options

* Fix test

* Set content by default

Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>

* Config to define default searchable fields

Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>

---------

Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>
Co-authored-by: Thomas Miceli <tho.miceli@gmail.com>
2026-03-12 00:55:23 +08:00

157 lines
3.7 KiB
Go

package handlers
import (
"errors"
"html/template"
"net/url"
"strconv"
"strings"
"github.com/gorilla/schema"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/web/context"
)
func GetPage(ctx *context.Context) int {
page := ctx.QueryParam("page")
if page == "" {
page = "1"
}
pageInt, err := strconv.Atoi(page)
if err != nil {
pageInt = 1
}
ctx.SetData("currPage", pageInt)
return pageInt
}
type PaginationParams struct {
Page int `schema:"page,omitempty"`
Sort string `schema:"sort,omitempty"`
Order string `schema:"order,omitempty"`
Title string `schema:"title,omitempty"`
Visibility string `schema:"visibility,omitempty"`
Language string `schema:"language,omitempty"`
Topics string `schema:"topics,omitempty"`
Query string `schema:"q,omitempty"`
HasPrevious bool `schema:"-"` // Exclude from URL parameters
HasNext bool `schema:"-"`
}
var encoder = schema.NewEncoder()
func (p PaginationParams) String() string {
values := url.Values{}
err := encoder.Encode(p, values)
if err != nil {
return ""
}
if len(values) == 0 {
return ""
}
return "?" + values.Encode()
}
func (p PaginationParams) NextURL() template.URL {
p.Page++
return template.URL(p.String())
}
func (p PaginationParams) PreviousURL() template.URL {
p.Page--
return template.URL(p.String())
}
func (p PaginationParams) WithParams(pairs ...string) template.URL {
values := url.Values{}
_ = encoder.Encode(p, values)
// reset page
values.Del("page")
for i := 0; i < len(pairs); i += 2 {
values.Set(pairs[i], pairs[i+1])
}
return template.URL("?" + values.Encode())
}
func Paginate[T any](ctx *context.Context, data []*T, pageInt int, perPage int, templateDataName string, urlPage string, labels int, params *PaginationParams) error {
var paginationParams PaginationParams
if params == nil {
paginationParams = PaginationParams{}
} else {
paginationParams = *params
}
paginationParams.Page = pageInt
lenData := len(data)
if lenData == 0 && pageInt != 1 {
return errors.New("page not found")
}
if lenData > perPage {
if lenData > 1 {
data = data[:lenData-1]
}
paginationParams.HasNext = true
}
if pageInt > 1 {
paginationParams.HasPrevious = true
}
ctx.SetData("pagination", paginationParams)
switch labels {
case 1:
ctx.SetData("prevLabel", ctx.TrH("pagination.previous"))
ctx.SetData("nextLabel", ctx.TrH("pagination.next"))
case 2:
ctx.SetData("prevLabel", ctx.TrH("pagination.newer"))
ctx.SetData("nextLabel", ctx.TrH("pagination.older"))
}
ctx.SetData("urlPage", urlPage)
ctx.SetData(templateDataName, data)
return nil
}
// ParseSearchQueryStr parses a search query string and returns a map of metadata.
// The query string is split into words and each word is checked if it contains a colon (:).
// If a word contains a colon, it is split into a key-value pair and added to the metadata map.
// If a word does not contain a colon, it is added to an "all" key in the metadata map.
// The "all" key is used to search all fields in the index.
// The function returns the metadata map.
func ParseSearchQueryStr(query string) map[string]string {
words := strings.Fields(query)
metadata := make(map[string]string)
var allFieldsBuilder strings.Builder
for _, word := range words {
if strings.Contains(word, ":") {
keyValue := strings.SplitN(word, ":", 2)
if len(keyValue) == 2 {
key := keyValue[0]
value := keyValue[1]
metadata[key] = value
}
} else {
// Add to content search by default
allFieldsBuilder.WriteString(word + " ")
}
}
// Set the default search field
allContent := strings.TrimSpace(allFieldsBuilder.String())
if allContent != "" {
metadata["default"] = allContent
}
log.Debug().Msgf("Metadata: %v", metadata)
return metadata
}