Skip to content

Commit 94d0382

Browse files
committed
Refactor search query control logic.
This is a breaking change that introduces fine grained controls for the number of results that are rendered in the site view and in API responses. - New config `[api_results]` and `[site_results]` to control numbers separately. - `site_results.max_entry_relations_per_type` to control the number of definition entries rendered in the site view per type. - `api_results.max_entry_content_items` to control the number of content items rendered in the site view on multi-word `content[]` entries. This breaks configuration params from v3
1 parent 16cf246 commit 94d0382

File tree

8 files changed

+183
-133
lines changed

8 files changed

+183
-133
lines changed

cmd/dictpress/admin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func handleGetPendingEntries(c echo.Context) error {
8383
var (
8484
app = c.Get("app").(*App)
8585

86-
pg = app.resultsPg.NewFromURL(c.Request().URL.Query())
86+
pg = app.pgSite.NewFromURL(c.Request().URL.Query())
8787
)
8888

8989
// Search and compose results.

cmd/dictpress/handlers.go

Lines changed: 80 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,7 @@ import (
2020
type results struct {
2121
Entries []data.Entry `json:"entries"`
2222

23-
Query struct {
24-
Query string `json:"query"`
25-
FromLang string `json:"from_lang"`
26-
ToLang string `json:"to_lang"`
27-
Types []string `json:"types"`
28-
Tags []string `json:"tags"`
29-
} `json:"query"`
23+
Query data.Query `json:"query"`
3024

3125
// Pagination fields.
3226
paginator.Set
@@ -44,31 +38,25 @@ type glossary struct {
4438

4539
// okResp represents the HTTP response wrapper.
4640
type okResp struct {
47-
Data interface{} `json:"data"`
48-
}
49-
50-
type httpResp struct {
51-
Status string `json:"status"`
52-
Message string `json:"message,omitempty"`
53-
Data interface{} `json:"data,omitempty"`
41+
Data any `json:"data"`
5442
}
5543

5644
// handleSearch performs a search and responds with JSON results.
5745
func handleSearch(c echo.Context) error {
58-
isAuthed := c.Get(isAuthed) != nil
46+
var (
47+
app = c.Get("app").(*App)
48+
isAuthed = c.Get(isAuthed) != nil
49+
)
5950

60-
_, out, err := doSearch(c, isAuthed)
51+
// Prepare the query.
52+
query, err := prepareQuery(c)
6153
if err != nil {
62-
var s int
63-
64-
// If out is nil, it's a non 500 "soft" error.
65-
if out != nil {
66-
s = http.StatusBadRequest
67-
} else {
68-
s = http.StatusInternalServerError
69-
}
54+
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
55+
}
7056

71-
return echo.NewHTTPError(s, err.Error())
57+
out, err := doSearch(query, isAuthed, app.pgAPI, app)
58+
if err != nil {
59+
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
7260
}
7361

7462
return c.JSON(http.StatusOK, okResp{out})
@@ -158,90 +146,107 @@ func handleServeBundle(c echo.Context, bundleType string, staticDir string) erro
158146
return c.Blob(http.StatusOK, contentType, buf.Bytes())
159147
}
160148

161-
// doSearch is a helper function that takes an HTTP query context,
162-
// gets search params from it, performs a search and returns results.
163-
func doSearch(c echo.Context, isAuthed bool) (data.Query, *results, error) {
149+
// prepareQuery extracts and validates search parameters from the HTTP context.
150+
// Returns a filled data.Query struct ready for searching.
151+
func prepareQuery(c echo.Context) (data.Query, error) {
164152
var (
165153
app = c.Get("app").(*App)
166154

167155
fromLang = c.Param("fromLang")
168156
toLang = c.Param("toLang")
169-
q = strings.TrimSpace(c.Param("q"))
170-
171-
qp = c.Request().URL.Query()
172-
pg = app.resultsPg.NewFromURL(qp)
173-
out = &results{}
157+
qStr = strings.TrimSpace(c.Param("q"))
174158
)
175159

176-
// Query from /path/:query
177-
q, err := url.QueryUnescape(q)
160+
// Scan query params.
161+
var q data.Query
162+
if err := c.Bind(&q); err != nil {
163+
return data.Query{}, fmt.Errorf("error parsing query: %v", err)
164+
}
165+
166+
// Query string from /path/:query
167+
qStr, err := url.QueryUnescape(qStr)
178168
if err != nil {
179-
return data.Query{}, nil, fmt.Errorf("error parsing query: %v", err)
169+
return data.Query{}, fmt.Errorf("error parsing query: %v", err)
180170
}
181-
q = strings.TrimSpace(q)
182-
if q == "" {
183-
v, err := url.QueryUnescape(qp.Get("q"))
171+
qStr = strings.TrimSpace(qStr)
172+
if qStr == "" {
173+
v, err := url.QueryUnescape(q.Query)
184174
if err != nil {
185-
return data.Query{}, nil, fmt.Errorf("error parsing query: %v", err)
175+
return data.Query{}, fmt.Errorf("error parsing query: %v", err)
186176
}
187-
q = strings.TrimSpace(v)
177+
qStr = strings.TrimSpace(v)
178+
}
179+
if qStr == "" {
180+
return data.Query{}, errors.New("no query given")
188181
}
189182

190-
if q == "" {
191-
return data.Query{}, nil, errors.New("no query given")
183+
// Languages not in path?
184+
if fromLang == "" {
185+
fromLang = q.FromLang
186+
}
187+
if toLang == "" {
188+
toLang = q.ToLang
192189
}
193190

191+
// Check languages.
194192
if _, ok := app.data.Langs[fromLang]; !ok {
195-
return data.Query{}, nil, errors.New("unknown `from` language")
193+
return data.Query{}, errors.New("unknown `from` language")
196194
}
197-
198195
if toLang == "*" {
199196
toLang = ""
200197
} else {
201198
if _, ok := app.data.Langs[toLang]; !ok {
202-
return data.Query{}, nil, errors.New("unknown `to` language")
199+
return data.Query{}, errors.New("unknown `to` language")
203200
}
204201
}
205202

206-
// Search query.
207-
query := data.Query{
208-
FromLang: fromLang,
209-
ToLang: toLang,
210-
Types: qp["type"],
211-
Tags: qp["tag"],
212-
Query: q,
213-
Status: data.StatusEnabled,
214-
Offset: pg.Offset,
215-
Limit: pg.Limit,
203+
// Check types.
204+
for _, t := range q.Types {
205+
if _, ok := app.data.Langs[fromLang].Types[t]; !ok {
206+
return data.Query{}, fmt.Errorf("unknown type %s", t)
207+
}
216208
}
217209

218-
if err = validateSearchQuery(query, app.data.Langs); err != nil {
219-
return query, out, err
210+
// Final query.
211+
q.Query = qStr
212+
q.FromLang = fromLang
213+
q.ToLang = toLang
214+
q.Status = data.StatusEnabled
215+
216+
if q.Types == nil {
217+
q.Types = []string{}
220218
}
219+
if q.Tags == nil {
220+
q.Tags = []string{}
221+
}
222+
223+
return q, nil
224+
}
225+
226+
// doSearch takes a prepared query and performs the search, returning results.
227+
func doSearch(q data.Query, isAuthed bool, pgn *paginator.Paginator, app *App) (*results, error) {
228+
// Pagination.
229+
pg := pgn.New(q.Page, q.PerPage)
230+
q.Offset = pg.Offset
231+
q.Limit = pg.Limit
221232

222233
// Search and compose results.
223-
out = &results{
224-
Entries: []data.Entry{},
225-
}
226-
res, total, err := app.data.Search(query)
234+
out := &results{Entries: []data.Entry{}}
235+
res, total, err := app.data.Search(q)
227236
if err != nil {
228237
app.lo.Printf("error querying db: %v", err)
229-
return query, nil, errors.New("error querying db")
238+
return nil, errors.New("error querying db")
230239
}
231240

232241
if len(res) == 0 {
233-
return query, out, nil
242+
out.Query = q
243+
244+
return out, nil
234245
}
235246

236247
// Load relations into the matches.
237-
if err := app.data.SearchAndLoadRelations(res, data.Query{
238-
ToLang: toLang,
239-
Offset: pg.Offset,
240-
Limit: pg.Limit,
241-
Status: data.StatusEnabled,
242-
}); err != nil {
243-
app.lo.Printf("error querying db for defs: %v", err)
244-
return query, nil, errors.New("error querying db for definitions")
248+
if err := app.data.SearchAndLoadRelations(res, q); err != nil {
249+
return nil, errors.New("error querying db for definitions")
245250
}
246251

247252
// If this is an un-authenticated query, hide the numerical IDs.
@@ -256,28 +261,17 @@ func doSearch(c echo.Context, isAuthed bool) (data.Query, *results, error) {
256261
}
257262
}
258263

264+
// Calculate pagination.
259265
pg.SetTotal(total)
260266

261-
out.Query.FromLang = fromLang
262-
out.Query.ToLang = toLang
263-
out.Query.Types = qp["type"]
264-
out.Query.Tags = qp["tag"]
265-
out.Query.Query = q
266-
267-
if out.Query.Types == nil {
268-
out.Query.Types = []string{}
269-
}
270-
if out.Query.Tags == nil {
271-
out.Query.Tags = []string{}
272-
}
273-
267+
out.Query = q
274268
out.Entries = res
275269
out.Page = pg.Page
276270
out.PerPage = pg.PerPage
277271
out.TotalPages = pg.TotalPages
278272
out.Total = total
279273

280-
return query, out, nil
274+
return out, nil
281275
}
282276

283277
// getGlossaryWords is a helper function that takes an HTTP query context,

cmd/dictpress/init.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
)
2121

2222
func initConstants(ko *koanf.Koanf) Consts {
23+
2324
c := Consts{
2425
Site: ko.String("site"),
2526
RootURL: ko.MustString("app.root_url"),
@@ -28,6 +29,9 @@ func initConstants(ko *koanf.Koanf) Consts {
2829
EnableSubmissions: ko.Bool("app.enable_submissions"),
2930
EnableGlossary: ko.Bool("glossary.enabled"),
3031
AdminAssets: ko.Strings("app.admin_assets"),
32+
33+
SiteMaxEntryRelationsPerType: ko.MustInt("site_results.max_entry_relations_per_type"),
34+
SiteMaxEntryContentItems: ko.MustInt("site_results.max_entry_content_items"),
3135
}
3236

3337
if len(c.AdminUsername) < 6 {

cmd/dictpress/main.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type Consts struct {
4545
EnableSubmissions bool
4646
EnableGlossary bool
4747
AdminUsername, AdminPassword []byte
48+
49+
SiteMaxEntryRelationsPerType int
50+
SiteMaxEntryContentItems int
4851
}
4952

5053
// App contains the "global" components that are
@@ -56,7 +59,8 @@ type App struct {
5659
data *data.Data
5760
i18n *i18n.I18n
5861
fs stuffbin.FileSystem
59-
resultsPg *paginator.Paginator
62+
pgSite *paginator.Paginator
63+
pgAPI *paginator.Paginator
6064
glossaryPg *paginator.Paginator
6165
lo *log.Logger
6266

@@ -202,12 +206,16 @@ func runServer(c *cli.Context) error {
202206
fs: fs,
203207
queries: queries,
204208
data: dt,
209+
lo: lo,
205210

206-
resultsPg: paginator.New(paginator.Opt{
207-
DefaultPerPage: ko.MustInt("results.default_per_page"),
208-
MaxPerPage: ko.MustInt("results.max_per_page"),
209-
NumPageNums: ko.MustInt("results.num_page_nums"),
210-
PageParam: "page", PerPageParam: "PerPageParam",
211+
pgSite: paginator.New(paginator.Opt{
212+
DefaultPerPage: ko.MustInt("site_results.per_page"),
213+
MaxPerPage: ko.MustInt("site_results.max_per_page"),
214+
NumPageNums: ko.MustInt("site_results.num_page_nums"),
215+
}),
216+
pgAPI: paginator.New(paginator.Opt{
217+
DefaultPerPage: ko.MustInt("api_results.per_page"),
218+
MaxPerPage: ko.MustInt("api_results.max_per_page"),
211219
}),
212220
}
213221

@@ -216,7 +224,6 @@ func runServer(c *cli.Context) error {
216224
DefaultPerPage: ko.MustInt("glossary.default_per_page"),
217225
MaxPerPage: ko.MustInt("glossary.max_per_page"),
218226
NumPageNums: ko.MustInt("glossary.num_page_nums"),
219-
PageParam: "page", PerPageParam: "PerPageParam",
220227
})
221228
}
222229

cmd/dictpress/site.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,32 @@ func handleIndexPage(c echo.Context) error {
8383

8484
// handleSearchPage renders the search results page.
8585
func handleSearchPage(c echo.Context) error {
86-
query, res, err := doSearch(c, false)
86+
var (
87+
app = c.Get("app").(*App)
88+
)
89+
90+
q, err := prepareQuery(c)
8791
if err != nil {
8892
return c.Render(http.StatusInternalServerError, "message", pageTpl{
89-
Title: "Error",
90-
Heading: "Error",
91-
Description: err.Error(),
93+
Title: "Error", Heading: "Error", Description: err.Error(),
94+
})
95+
}
96+
97+
// Apply search query limits on relations and content items.
98+
q.MaxRelations = app.consts.SiteMaxEntryRelationsPerType
99+
q.MaxContentItems = app.consts.SiteMaxEntryContentItems
100+
101+
res, err := doSearch(q, false, app.pgSite, app)
102+
if err != nil {
103+
return c.Render(http.StatusInternalServerError, "message", pageTpl{
104+
Title: "Error", Heading: "Error", Description: err.Error(),
92105
})
93106
}
94107

95108
return c.Render(http.StatusOK, "search", pageTpl{
96109
PageType: pageSearch,
97110
Results: res,
98-
Query: &query,
111+
Query: &q,
99112
})
100113
}
101114

0 commit comments

Comments
 (0)