@@ -51,12 +51,6 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
51
51
52
52
query := s .Client .Collection ("questions" ).Query
53
53
54
- // Filtering by title
55
- titleParam := r .URL .Query ().Get ("title" )
56
- if titleParam != "" {
57
- query = query .Where ("title" , "==" , titleParam )
58
- }
59
-
60
54
// Filtering by complexity (multi-select)
61
55
complexityParam := r .URL .Query ().Get ("complexity" )
62
56
if complexityParam != "" {
@@ -65,23 +59,15 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
65
59
for _ , complexityStr := range complexityStrs {
66
60
complexityType , err := models .ParseComplexity (complexityStr )
67
61
if err != nil {
68
- http .Error (w , "Failed to filter by complexity: " + err .Error (), http .StatusInternalServerError )
62
+ http .Error (w , "Failed to filter by complexity: " + err .Error (), http .StatusBadRequest )
69
63
return
70
64
}
71
65
complexityInts = append (complexityInts , int (complexityType ))
72
66
}
73
67
query = query .Where ("complexity" , "in" , complexityInts )
74
68
}
75
69
76
- // Filtering by categories (multi-select): Partially using array-contains-any first
77
- categoriesParam := r .URL .Query ().Get ("categories" )
78
- var categories []string
79
- if categoriesParam != "" {
80
- categories = strings .Split (categoriesParam , "," )
81
- query = query .Where ("categories" , "array-contains-any" , categories )
82
- }
83
-
84
- // Sorting
70
+ // Sorting (Generalisable to multiple fields but limited by firebase composite indexes)
85
71
sortFieldsParam := r .URL .Query ()["sortField" ]
86
72
sortValuesParam := r .URL .Query ()["sortValue" ]
87
73
@@ -123,12 +109,27 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
123
109
http .Error (w , "Failed to to get fetch questions: " + err .Error (), http .StatusInternalServerError )
124
110
}
125
111
112
+ // Parse categories from query param (multi-select)
113
+ categoriesParam := r .URL .Query ().Get ("categories" )
114
+ var categories []string
115
+ if categoriesParam != "" {
116
+ categories = strings .Split (categoriesParam , "," )
117
+ }
118
+
119
+ // Parse title keywords from title query param
120
+ titleParam := r .URL .Query ().Get ("title" )
121
+ var keywordsQuery []string
122
+ if titleParam != "" {
123
+ keywordsQuery = getKeywordsFromTitle (titleParam )
124
+ }
125
+
126
126
// Filter the results to check if the document contains all categories (implementation of array-contains-all)
127
127
var filteredResults []* firestore.DocumentSnapshot
128
- if categories != nil {
129
- for _ , doc := range results {
130
- data := doc .Data ()
128
+ for _ , doc := range results {
129
+ data := doc .Data ()
131
130
131
+ var passedCategoryFilterDoc * firestore.DocumentSnapshot
132
+ if categories != nil {
132
133
// Retrieve the "categories" field from the document and convert to []string
133
134
if docCategories , ok := data ["categories" ].([]interface {}); ok {
134
135
stringCategories := make ([]string , len (docCategories ))
@@ -139,12 +140,33 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
139
140
}
140
141
141
142
if containsAllCategoriesSet (stringCategories , categories ) {
142
- filteredResults = append ( filteredResults , doc )
143
+ passedCategoryFilterDoc = doc
143
144
}
144
145
}
146
+ } else {
147
+ passedCategoryFilterDoc = doc
145
148
}
146
- } else {
147
- filteredResults = results
149
+
150
+ if passedCategoryFilterDoc == nil {
151
+ continue
152
+ }
153
+
154
+ var passedTitleKeywordsFilterDoc * firestore.DocumentSnapshot
155
+ if keywordsQuery != nil {
156
+ if docTitle , ok := data ["title" ].(string ); ok {
157
+ if titleContainsKeywords (docTitle , keywordsQuery ) {
158
+ passedTitleKeywordsFilterDoc = passedCategoryFilterDoc
159
+ }
160
+ }
161
+ } else {
162
+ passedTitleKeywordsFilterDoc = passedCategoryFilterDoc
163
+ }
164
+
165
+ if passedTitleKeywordsFilterDoc == nil {
166
+ continue
167
+ }
168
+
169
+ filteredResults = append (filteredResults , passedTitleKeywordsFilterDoc )
148
170
}
149
171
150
172
// Pagination
@@ -189,6 +211,9 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
189
211
totalCount := len (filteredResults )
190
212
totalPages := (totalCount + limit - 1 ) / limit
191
213
currentPage := (offset / limit ) + 1
214
+ if len (questions ) == 0 {
215
+ currentPage = 0
216
+ }
192
217
hasNextPage := totalPages > currentPage
193
218
194
219
// Construct response
@@ -235,6 +260,34 @@ func paginateResults(results []*firestore.DocumentSnapshot, offset, limit int) [
235
260
return results [start :end ]
236
261
}
237
262
263
+ // getKeywordsFromTitle Get keywords from question's title
264
+ func getKeywordsFromTitle (title string ) []string {
265
+ return strings .Split (strings .ToLower (strings .TrimSpace (title )), " " )
266
+ }
267
+
268
+ // titleContainsKeywords Check if title contains all keywords
269
+ func titleContainsKeywords (title string , queryKeywords []string ) bool {
270
+ // TODO: Implement using trie for better performance
271
+ titleWords := strings .Fields (strings .ToLower (strings .TrimSpace (title )))
272
+
273
+ // Iterate through each keyword.
274
+ for _ , queryKeyword := range queryKeywords {
275
+ matched := false
276
+ // Check if the queryKeyword is a prefix of any word in the title.
277
+ for _ , titleWord := range titleWords {
278
+ if strings .HasPrefix (titleWord , queryKeyword ) {
279
+ matched = true
280
+ break
281
+ }
282
+ }
283
+ // If the queryKeyword is not a prefix of any title word, return false.
284
+ if ! matched {
285
+ return false
286
+ }
287
+ }
288
+ return true
289
+ }
290
+
238
291
//Manual test cases
239
292
//
240
293
//curl -X GET "http://localhost:8080/questions"
0 commit comments