Skip to content

Commit 5e3d606

Browse files
authored
feat: add search parameter for all resources with name and note (#568)
With the new 'search' query parameter for the list endpoints, all string fields of a resource can be searched at the same time.
1 parent 82cd269 commit 5e3d606

File tree

12 files changed

+118
-53
lines changed

12 files changed

+118
-53
lines changed

api/docs.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ const docTemplate = `{
138138
"description": "Is the account hidden?",
139139
"name": "hidden",
140140
"in": "query"
141+
},
142+
{
143+
"type": "string",
144+
"description": "Search for this text in name and note",
145+
"name": "search",
146+
"in": "query"
141147
}
142148
],
143149
"responses": {
@@ -688,6 +694,12 @@ const docTemplate = `{
688694
"description": "Filter by currency",
689695
"name": "currency",
690696
"in": "query"
697+
},
698+
{
699+
"type": "string",
700+
"description": "Search for this text in name and note",
701+
"name": "search",
702+
"in": "query"
691703
}
692704
],
693705
"responses": {
@@ -1205,6 +1217,12 @@ const docTemplate = `{
12051217
"description": "Is the category hidden?",
12061218
"name": "hidden",
12071219
"in": "query"
1220+
},
1221+
{
1222+
"type": "string",
1223+
"description": "Search for this text in name and note",
1224+
"name": "search",
1225+
"in": "query"
12081226
}
12091227
],
12101228
"responses": {
@@ -1495,6 +1513,12 @@ const docTemplate = `{
14951513
"description": "Is the envelope hidden?",
14961514
"name": "hidden",
14971515
"in": "query"
1516+
},
1517+
{
1518+
"type": "string",
1519+
"description": "Search for this text in name and note",
1520+
"name": "search",
1521+
"in": "query"
14981522
}
14991523
],
15001524
"responses": {

api/swagger.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@
126126
"description": "Is the account hidden?",
127127
"name": "hidden",
128128
"in": "query"
129+
},
130+
{
131+
"type": "string",
132+
"description": "Search for this text in name and note",
133+
"name": "search",
134+
"in": "query"
129135
}
130136
],
131137
"responses": {
@@ -676,6 +682,12 @@
676682
"description": "Filter by currency",
677683
"name": "currency",
678684
"in": "query"
685+
},
686+
{
687+
"type": "string",
688+
"description": "Search for this text in name and note",
689+
"name": "search",
690+
"in": "query"
679691
}
680692
],
681693
"responses": {
@@ -1193,6 +1205,12 @@
11931205
"description": "Is the category hidden?",
11941206
"name": "hidden",
11951207
"in": "query"
1208+
},
1209+
{
1210+
"type": "string",
1211+
"description": "Search for this text in name and note",
1212+
"name": "search",
1213+
"in": "query"
11961214
}
11971215
],
11981216
"responses": {
@@ -1483,6 +1501,12 @@
14831501
"description": "Is the envelope hidden?",
14841502
"name": "hidden",
14851503
"in": "query"
1504+
},
1505+
{
1506+
"type": "string",
1507+
"description": "Search for this text in name and note",
1508+
"name": "search",
1509+
"in": "query"
14861510
}
14871511
],
14881512
"responses": {

api/swagger.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,10 @@ paths:
10391039
in: query
10401040
name: hidden
10411041
type: boolean
1042+
- description: Search for this text in name and note
1043+
in: query
1044+
name: search
1045+
type: string
10421046
produces:
10431047
- application/json
10441048
responses:
@@ -1408,6 +1412,10 @@ paths:
14081412
in: query
14091413
name: currency
14101414
type: string
1415+
- description: Search for this text in name and note
1416+
in: query
1417+
name: search
1418+
type: string
14111419
produces:
14121420
- application/json
14131421
responses:
@@ -1765,6 +1773,10 @@ paths:
17651773
in: query
17661774
name: hidden
17671775
type: boolean
1776+
- description: Search for this text in name and note
1777+
in: query
1778+
name: search
1779+
type: string
17681780
produces:
17691781
- application/json
17701782
responses:
@@ -1960,6 +1972,10 @@ paths:
19601972
in: query
19611973
name: hidden
19621974
type: boolean
1975+
- description: Search for this text in name and note
1976+
in: query
1977+
name: search
1978+
type: string
19631979
produces:
19641980
- application/json
19651981
responses:

pkg/controllers/account.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package controllers
22

33
import (
4-
"fmt"
54
"net/http"
65

76
"github.com/envelope-zero/backend/v2/pkg/httperrors"
87
"github.com/envelope-zero/backend/v2/pkg/httputil"
98
"github.com/envelope-zero/backend/v2/pkg/models"
109
"github.com/gin-gonic/gin"
1110
"github.com/google/uuid"
12-
"golang.org/x/exp/slices"
1311
)
1412

1513
type AccountListResponse struct {
@@ -32,6 +30,7 @@ type AccountQueryFilter struct {
3230
OnBudget bool `form:"onBudget"` // Is the account on-budget?
3331
External bool `form:"external"` // Is the account external?
3432
Hidden bool `form:"hidden"` // Is the account hidden?
33+
Search string `form:"search" filterField:"false"`
3534
}
3635

3736
func (f AccountQueryFilter) ToCreate(c *gin.Context) (models.AccountCreate, bool) {
@@ -154,6 +153,7 @@ func (co Controller) CreateAccount(c *gin.Context) {
154153
// @Param onBudget query bool false "Is the account on-budget?"
155154
// @Param external query bool false "Is the account external?"
156155
// @Param hidden query bool false "Is the account hidden?"
156+
// @Param search query string false "Search for this text in name and note"
157157
func (co Controller) GetAccounts(c *gin.Context) {
158158
var filter AccountQueryFilter
159159
if err := c.Bind(&filter); err != nil {
@@ -174,17 +174,7 @@ func (co Controller) GetAccounts(c *gin.Context) {
174174
AccountCreate: create,
175175
}, queryFields...)
176176

177-
if filter.Name != "" {
178-
query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", filter.Name))
179-
} else if slices.Contains(setFields, "Name") {
180-
query = query.Where("name = ''")
181-
}
182-
183-
if filter.Note != "" {
184-
query = query.Where("note LIKE ?", fmt.Sprintf("%%%s%%", filter.Note))
185-
} else if slices.Contains(setFields, "Note") {
186-
query = query.Where("note = ''")
187-
}
177+
query = stringFilters(co.DB, query, setFields, filter.Name, filter.Note, filter.Search)
188178

189179
var accounts []models.Account
190180
if !queryWithRetry(c, query.Find(&accounts)) {

pkg/controllers/account_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ func (suite *TestSuiteStandard) TestGetAccountsFilter() {
134134
{"Internal", "external=false", 3},
135135
{"Not Hidden", "hidden=false", 3},
136136
{"Hidden", "hidden=true", 2},
137+
{"Search for 'na", "search=na", 3},
138+
{"Search for 'fi", "search=fi", 4},
137139
}
138140

139141
for _, tt := range tests {

pkg/controllers/budget.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package controllers
22

33
import (
4-
"fmt"
54
"net/http"
65

76
"github.com/envelope-zero/backend/v2/internal/types"
@@ -11,7 +10,6 @@ import (
1110
"github.com/gin-gonic/gin"
1211
"github.com/google/uuid"
1312
"github.com/shopspring/decimal"
14-
"golang.org/x/exp/slices"
1513
)
1614

1715
type BudgetListResponse struct {
@@ -30,6 +28,7 @@ type BudgetQueryFilter struct {
3028
Name string `form:"name" filterField:"false"`
3129
Note string `form:"note" filterField:"false"`
3230
Currency string `form:"currency"`
31+
Search string `form:"search" filterField:"false"`
3332
}
3433

3534
// swagger:enum AllocationMode
@@ -215,6 +214,7 @@ func (co Controller) CreateBudget(c *gin.Context) {
215214
// @Param name query string false "Filter by name"
216215
// @Param note query string false "Filter by note"
217216
// @Param currency query string false "Filter by currency"
217+
// @Param search query string false "Search for this text in name and note"
218218
func (co Controller) GetBudgets(c *gin.Context) {
219219
var filter BudgetQueryFilter
220220

@@ -234,17 +234,7 @@ func (co Controller) GetBudgets(c *gin.Context) {
234234
},
235235
}, queryFields...)
236236

237-
if filter.Name != "" {
238-
query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", filter.Name))
239-
} else if slices.Contains(setFields, "Name") {
240-
query = query.Where("name = ''")
241-
}
242-
243-
if filter.Note != "" {
244-
query = query.Where("note LIKE ?", fmt.Sprintf("%%%s%%", filter.Note))
245-
} else if slices.Contains(setFields, "Note") {
246-
query = query.Where("note = ''")
247-
}
237+
query = stringFilters(co.DB, query, setFields, filter.Name, filter.Note, filter.Search)
248238

249239
if !queryWithRetry(c, query.Find(&budgets)) {
250240
return

pkg/controllers/budget_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ func (suite *TestSuiteStandard) TestGetBudgetsFilter() {
131131
{"Empty Name with Note", "name=&note=This is a specific note", 1},
132132
{"No currency", "currency=", 1},
133133
{"No name", "name=", 1},
134+
{"Search for 'stRing'", "search=stRing", 2},
135+
{"Search for 'Note'", "search=Note", 3},
134136
}
135137

136138
var re controllers.BudgetListResponse

pkg/controllers/category.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package controllers
22

33
import (
4-
"fmt"
54
"net/http"
65

76
"github.com/envelope-zero/backend/v2/pkg/httperrors"
87
"github.com/envelope-zero/backend/v2/pkg/httputil"
98
"github.com/envelope-zero/backend/v2/pkg/models"
109
"github.com/gin-gonic/gin"
1110
"github.com/google/uuid"
12-
"golang.org/x/exp/slices"
1311
)
1412

1513
type CategoryListResponse struct {
@@ -30,6 +28,7 @@ type CategoryQueryFilter struct {
3028
BudgetID string `form:"budget"`
3129
Note string `form:"note" filterField:"false"`
3230
Hidden bool `form:"hidden"`
31+
Search string `form:"search" filterField:"false"`
3332
}
3433

3534
func (f CategoryQueryFilter) ToCreate(c *gin.Context) (models.CategoryCreate, bool) {
@@ -148,6 +147,7 @@ func (co Controller) CreateCategory(c *gin.Context) {
148147
// @Param note query string false "Filter by note"
149148
// @Param budget query string false "Filter by budget ID"
150149
// @Param hidden query bool false "Is the category hidden?"
150+
// @Param search query string false "Search for this text in name and note"
151151
func (co Controller) GetCategories(c *gin.Context) {
152152
var filter CategoryQueryFilter
153153

@@ -167,17 +167,7 @@ func (co Controller) GetCategories(c *gin.Context) {
167167
CategoryCreate: create,
168168
}, queryFields...)
169169

170-
if filter.Name != "" {
171-
query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", filter.Name))
172-
} else if slices.Contains(setFields, "Name") {
173-
query = query.Where("name = ''")
174-
}
175-
176-
if filter.Note != "" {
177-
query = query.Where("note LIKE ?", fmt.Sprintf("%%%s%%", filter.Note))
178-
} else if slices.Contains(setFields, "Note") {
179-
query = query.Where("note = ''")
180-
}
170+
query = stringFilters(co.DB, query, setFields, filter.Name, filter.Note, filter.Search)
181171

182172
var categories []models.Category
183173
if !queryWithRetry(c, query.Find(&categories)) {

pkg/controllers/category_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ func (suite *TestSuiteStandard) TestGetCategoriesFilter() {
156156
{"Fuzzy note", "note=Groceries", 2},
157157
{"Not hidden", "hidden=false", 2},
158158
{"Hidden", "hidden=true", 1},
159+
{"Search for 'groceries'", "search=groceries", 2},
160+
{"Search for 'FOR'", "search=FOR", 2},
159161
}
160162

161163
for _, tt := range tests {

pkg/controllers/envelope.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package controllers
22

33
import (
4-
"fmt"
54
"net/http"
65

76
"github.com/envelope-zero/backend/v2/internal/types"
@@ -10,7 +9,6 @@ import (
109
"github.com/envelope-zero/backend/v2/pkg/models"
1110
"github.com/gin-gonic/gin"
1211
"github.com/google/uuid"
13-
"golang.org/x/exp/slices"
1412
)
1513

1614
type EnvelopeListResponse struct {
@@ -30,6 +28,7 @@ type EnvelopeQueryFilter struct {
3028
CategoryID string `form:"category"`
3129
Note string `form:"note" filterField:"false"`
3230
Hidden bool `form:"hidden"`
31+
Search string `form:"search" filterField:"false"`
3332
}
3433

3534
func (f EnvelopeQueryFilter) ToCreate(c *gin.Context) (models.EnvelopeCreate, bool) {
@@ -147,6 +146,7 @@ func (co Controller) CreateEnvelope(c *gin.Context) {
147146
// @Param note query string false "Filter by note"
148147
// @Param category query string false "Filter by category ID"
149148
// @Param hidden query bool false "Is the envelope hidden?"
149+
// @Param search query string false "Search for this text in name and note"
150150
func (co Controller) GetEnvelopes(c *gin.Context) {
151151
var filter EnvelopeQueryFilter
152152

@@ -165,17 +165,7 @@ func (co Controller) GetEnvelopes(c *gin.Context) {
165165
EnvelopeCreate: create,
166166
}, queryFields...)
167167

168-
if filter.Name != "" {
169-
query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", filter.Name))
170-
} else if slices.Contains(setFields, "Name") {
171-
query = query.Where("name = ''")
172-
}
173-
174-
if filter.Note != "" {
175-
query = query.Where("note LIKE ?", fmt.Sprintf("%%%s%%", filter.Note))
176-
} else if slices.Contains(setFields, "Note") {
177-
query = query.Where("note = ''")
178-
}
168+
query = stringFilters(co.DB, query, setFields, filter.Name, filter.Note, filter.Search)
179169

180170
var envelopes []models.Envelope
181171
if !queryWithRetry(c, query.Find(&envelopes)) {

0 commit comments

Comments
 (0)