Skip to content

Commit b387e1d

Browse files
committed
Update discounts endpoints
1 parent 17e970e commit b387e1d

1 file changed

Lines changed: 114 additions & 26 deletions

File tree

api/controllers/discounts.go

Lines changed: 114 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"regexp"
8+
"strconv"
89
"strings"
910
"time"
1011

@@ -53,26 +54,59 @@ func DiscountSearch(c *gin.Context) {
5354
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
5455
defer cancel()
5556

56-
query, err := buildDiscountSearchQuery(c)
57-
if err != nil {
58-
respond(c, http.StatusBadRequest, "Invalid query parameters", err.Error())
59-
return
60-
}
57+
var cursor *mongo.Cursor
58+
var err error
6159

62-
optionLimit, err := configs.GetOptionLimit(&query, c)
63-
if err != nil {
64-
respond(c, http.StatusBadRequest, "offset is not type integer", err.Error())
65-
return
66-
}
60+
_, hasQ := c.GetQuery("q")
61+
_, hasBusiness := c.GetQuery("business")
62+
_, hasAddress := c.GetQuery("address")
63+
_, hasDiscount := c.GetQuery("discount")
64+
_, hasCategory := c.GetQuery("category")
65+
if hasQ {
66+
if hasBusiness || hasAddress || hasDiscount || hasCategory {
67+
// q may only be used alone
68+
respond(c, http.StatusBadRequest, "Invalid query parameters", "Parameter q may not be used with other parameters")
69+
return
70+
}
6771

68-
cursor, err := discountCollection.Find(ctx, query, optionLimit)
69-
if err != nil {
70-
respondWithInternalError(c, err)
71-
return
72+
pipeline, err := buildFuzzySearchPipeline(c)
73+
if err != nil {
74+
respond(c, http.StatusBadRequest, "Invalid query parameters", err.Error())
75+
return
76+
}
77+
cursor, err = discountCollection.Aggregate(ctx, pipeline)
78+
if err != nil {
79+
respondWithInternalError(c, err)
80+
return
81+
}
82+
83+
} else {
84+
if !hasBusiness && !hasAddress && !hasDiscount && !hasCategory {
85+
respond(c, http.StatusBadRequest, "Invalid query parameters", "Unknown query")
86+
return
87+
}
88+
89+
query, err := buildDiscountSearchQuery(c)
90+
if err != nil {
91+
respond(c, http.StatusBadRequest, "Invalid query parameters", err.Error())
92+
return
93+
}
94+
95+
optionLimit, err := configs.GetOptionLimit(&query, c)
96+
if err != nil {
97+
respond(c, http.StatusBadRequest, "offset is not type integer", err.Error())
98+
return
99+
}
100+
cursor, err = discountCollection.Find(ctx, query, optionLimit)
101+
if err != nil {
102+
respondWithInternalError(c, err)
103+
return
104+
}
72105
}
106+
73107
defer cursor.Close(ctx)
74108

75-
discounts := make([]schema.DiscountProgram, 0)
109+
var discounts []schema.DiscountProgram
76110
if err = cursor.All(ctx, &discounts); err != nil {
77111
respondWithInternalError(c, err)
78112
return
@@ -82,24 +116,15 @@ func DiscountSearch(c *gin.Context) {
82116

83117
}
84118

119+
// Build the query for field-based search
85120
func buildDiscountSearchQuery(c *gin.Context) (bson.M, error) {
86121
business, hasBusiness := c.GetQuery("business")
87122
address, hasAddress := c.GetQuery("address")
88123
discount, hasDiscount := c.GetQuery("discount")
89124
category, hasCategory := c.GetQuery("category")
90-
q, hasQ := c.GetQuery("q")
91125

92126
query := bson.M{}
93127

94-
if hasQ {
95-
// q may only be used alone
96-
if hasBusiness || hasAddress || hasDiscount || hasCategory {
97-
return nil, fmt.Errorf("parameter q may not be used with other parameters")
98-
}
99-
query["$text"] = bson.D{{Key: "$search", Value: q}}
100-
return query, nil
101-
}
102-
103128
// We use regexp.QuoteMeta and option i to essentially do string.toLower().contains(key) on fields
104129
if hasBusiness {
105130
cleanedBusiness := strings.TrimSpace(regexp.QuoteMeta(business))
@@ -125,9 +150,72 @@ func buildDiscountSearchQuery(c *gin.Context) (bson.M, error) {
125150
}
126151
}
127152
if !found {
128-
return nil, fmt.Errorf("unknown category %s. Valid categories are %s", category, strings.Join(discountCategories, ", "))
153+
return nil, fmt.Errorf("unknown category %s. Valid categories are %s", category, strings.Join(discountCategories, " | "))
129154
}
130155
}
131156

132157
return query, nil
133158
}
159+
160+
// Build the pipeline to perform fuzzy search on q
161+
func buildFuzzySearchPipeline(c *gin.Context) (mongo.Pipeline, error) {
162+
q, _ := c.GetQuery("q")
163+
if strings.TrimSpace(q) == "" {
164+
return mongo.Pipeline{}, fmt.Errorf("empty q")
165+
}
166+
167+
var offset int64
168+
var err error
169+
if c.Query("offset") == "" {
170+
offset = 0
171+
} else {
172+
offset, err = strconv.ParseInt(c.Query("offset"), 10, 64)
173+
if err != nil {
174+
return mongo.Pipeline{}, err
175+
}
176+
}
177+
178+
var fuzzySearchArr bson.A
179+
fields := [4]string{"category", "discount", "business", "address"}
180+
maxEditsList := [4]int{2, 2, 2, 1}
181+
boostScores := [4]int{5, 3, 2, 1}
182+
for i, field := range fields {
183+
fuzzySearchArr = append(fuzzySearchArr, bson.D{
184+
{Key: "text", Value: bson.D{
185+
{Key: "query", Value: q},
186+
{Key: "path", Value: field},
187+
{Key: "fuzzy", Value: bson.D{
188+
{Key: "maxEdits", Value: maxEditsList[i]},
189+
}},
190+
{Key: "score", Value: bson.D{
191+
{Key: "boost", Value: bson.D{
192+
{Key: "value", Value: boostScores[i]},
193+
}},
194+
}},
195+
}},
196+
})
197+
}
198+
199+
return mongo.Pipeline{
200+
// Fuzzy searches
201+
bson.D{
202+
{Key: "$search", Value: bson.D{
203+
{Key: "index", Value: "discount_searches"},
204+
{Key: "compound", Value: bson.D{
205+
{Key: "should", Value: fuzzySearchArr},
206+
}},
207+
}},
208+
},
209+
210+
// Sort and paginate
211+
bson.D{
212+
{Key: "$sort", Value: bson.D{
213+
{Key: "score", Value: bson.D{
214+
{Key: "$meta", Value: "searchScore"},
215+
}},
216+
}},
217+
},
218+
bson.D{{Key: "$skip", Value: offset}},
219+
bson.D{{Key: "$limit", Value: configs.GetEnvLimit()}},
220+
}, nil
221+
}

0 commit comments

Comments
 (0)