Skip to content

Commit b18ccd4

Browse files
authored
Merge branch 'staging' into filter-category
2 parents e5ebfa9 + 60301d6 commit b18ccd4

File tree

1 file changed

+75
-22
lines changed
  • services/question-service/handlers

1 file changed

+75
-22
lines changed

services/question-service/handlers/list.go

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
5151

5252
query := s.Client.Collection("questions").Query
5353

54-
// Filtering by title
55-
titleParam := r.URL.Query().Get("title")
56-
if titleParam != "" {
57-
query = query.Where("title", "==", titleParam)
58-
}
59-
6054
// Filtering by complexity (multi-select)
6155
complexityParam := r.URL.Query().Get("complexity")
6256
if complexityParam != "" {
@@ -65,23 +59,15 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
6559
for _, complexityStr := range complexityStrs {
6660
complexityType, err := models.ParseComplexity(complexityStr)
6761
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)
6963
return
7064
}
7165
complexityInts = append(complexityInts, int(complexityType))
7266
}
7367
query = query.Where("complexity", "in", complexityInts)
7468
}
7569

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)
8571
sortFieldsParam := r.URL.Query()["sortField"]
8672
sortValuesParam := r.URL.Query()["sortValue"]
8773

@@ -123,12 +109,27 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
123109
http.Error(w, "Failed to to get fetch questions: "+err.Error(), http.StatusInternalServerError)
124110
}
125111

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+
126126
// Filter the results to check if the document contains all categories (implementation of array-contains-all)
127127
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()
131130

131+
var passedCategoryFilterDoc *firestore.DocumentSnapshot
132+
if categories != nil {
132133
// Retrieve the "categories" field from the document and convert to []string
133134
if docCategories, ok := data["categories"].([]interface{}); ok {
134135
stringCategories := make([]string, len(docCategories))
@@ -139,12 +140,33 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
139140
}
140141

141142
if containsAllCategoriesSet(stringCategories, categories) {
142-
filteredResults = append(filteredResults, doc)
143+
passedCategoryFilterDoc = doc
143144
}
144145
}
146+
} else {
147+
passedCategoryFilterDoc = doc
145148
}
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)
148170
}
149171

150172
// Pagination
@@ -189,6 +211,9 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
189211
totalCount := len(filteredResults)
190212
totalPages := (totalCount + limit - 1) / limit
191213
currentPage := (offset / limit) + 1
214+
if len(questions) == 0 {
215+
currentPage = 0
216+
}
192217
hasNextPage := totalPages > currentPage
193218

194219
// Construct response
@@ -235,6 +260,34 @@ func paginateResults(results []*firestore.DocumentSnapshot, offset, limit int) [
235260
return results[start:end]
236261
}
237262

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+
238291
//Manual test cases
239292
//
240293
//curl -X GET "http://localhost:8080/questions"

0 commit comments

Comments
 (0)