Skip to content

Commit f8c723c

Browse files
committed
Expand filtering to include complexity, categories and title
1 parent ef25197 commit f8c723c

File tree

7 files changed

+425
-12
lines changed

7 files changed

+425
-12
lines changed

services/question-service/handlers/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (s *Service) CreateQuestion(w http.ResponseWriter, r *http.Request) {
3131
}
3232

3333
// Map data
34-
question.ID = docRef.ID
34+
question.Ref = docRef.ID
3535

3636
w.Header().Set("Content-Type", "application/json")
3737
json.NewEncoder(w).Encode(question)
Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,90 @@
11
package handlers
22

33
import (
4+
"cloud.google.com/go/firestore"
45
"encoding/json"
6+
"google.golang.org/api/iterator"
57
"net/http"
68
"question-service/models"
7-
8-
"google.golang.org/api/iterator"
9+
"strconv"
10+
"strings"
911
)
1012

11-
// TODO: add filters/pagination/sorter
1213
func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
1314
ctx := r.Context()
14-
iter := s.Client.Collection("questions").Documents(ctx)
15+
16+
query := s.Client.Collection("questions").Query
17+
18+
// Filtering by title
19+
titleParam := r.URL.Query().Get("title")
20+
if titleParam != "" {
21+
query = query.Where("Title", "==", titleParam)
22+
}
23+
24+
// Filtering by complexity (multi-select)
25+
complexityParam := r.URL.Query().Get("complexity")
26+
if complexityParam != "" {
27+
complexities := strings.Split(complexityParam, ",")
28+
query = query.Where("Complexity", "in", complexities)
29+
}
30+
31+
// Filtering by categories (multi-select)
32+
categoriesParam := r.URL.Query().Get("categories")
33+
if categoriesParam != "" {
34+
categories := strings.Split(categoriesParam, ",")
35+
query = query.Where("Categories", "array-contains-any", categories)
36+
}
37+
38+
// Sorting
39+
sortField := r.URL.Query().Get("sortField")
40+
sortValue := r.URL.Query().Get("sortValue")
41+
if sortField != "" {
42+
order := firestore.Asc // Default ascending order
43+
if sortValue == "desc" {
44+
order = firestore.Desc
45+
}
46+
query = query.OrderBy(sortField, order)
47+
}
48+
49+
// Count total documents matching the filter
50+
totalIter, err := query.Documents(ctx).GetAll()
51+
totalCount := len(totalIter)
52+
if err != nil {
53+
http.Error(w, "Failed to count questions: "+err.Error(), http.StatusInternalServerError)
54+
return
55+
}
56+
57+
// Pagination
58+
currentPage := 1
59+
limitParam := r.URL.Query().Get("limit")
60+
limit, err := strconv.Atoi(limitParam) // convert limit to integer
61+
if err != nil || limit <= 0 {
62+
limit = 10 // Default to 10
63+
}
64+
startAfterParam := r.URL.Query().Get("startAfter")
65+
if startAfterParam != "" {
66+
// Calculate current page based on total documents before startAfter
67+
beforeIter := query.EndAt(startAfterParam).Documents(ctx)
68+
countBeforeStart := 0
69+
for {
70+
_, err := beforeIter.Next()
71+
if err == iterator.Done {
72+
break
73+
}
74+
if err != nil {
75+
http.Error(w, "Failed to retrieve documents: "+err.Error(), http.StatusInternalServerError)
76+
return
77+
}
78+
countBeforeStart++
79+
}
80+
currentPage = (countBeforeStart / limit) + 1
81+
82+
// start after previous page last question's document reference
83+
query = query.StartAfter(startAfterParam)
84+
}
85+
query = query.Limit(limit)
86+
87+
iter := query.Documents(ctx)
1588

1689
var questions []models.Question
1790
for {
@@ -21,20 +94,60 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
2194
break
2295
}
2396
if err != nil {
24-
http.Error(w, "Failed to retrieve questions", http.StatusInternalServerError)
97+
http.Error(w, "Failed to retrieve questions: "+err.Error(), http.StatusInternalServerError)
2598
return
2699
}
27100

28101
// Map data
29102
var question models.Question
30-
question.ID = doc.Ref.ID
31103
if err := doc.DataTo(&question); err != nil {
32-
http.Error(w, "Failed to parse question", http.StatusInternalServerError)
104+
http.Error(w, "Failed to parse question: "+err.Error(), http.StatusInternalServerError)
33105
return
34106
}
107+
question.Ref = doc.Ref.ID
35108
questions = append(questions, question)
36109
}
37110

111+
// Calculate pagination info
112+
totalPages := (totalCount + limit - 1) / limit
113+
hasNextPage := totalPages > currentPage
114+
115+
// Construct response
116+
response := models.QuestionResponse{
117+
TotalCount: totalCount,
118+
TotalPages: totalPages,
119+
CurrentPage: currentPage,
120+
Limit: limit,
121+
HasNextPage: hasNextPage,
122+
Questions: questions,
123+
}
124+
38125
w.Header().Set("Content-Type", "application/json")
39-
json.NewEncoder(w).Encode(questions)
126+
json.NewEncoder(w).Encode(response)
40127
}
128+
129+
//Manual test cases
130+
//
131+
//curl -X GET "http://localhost:8080/questions"
132+
//
133+
//curl -X GET "http://localhost:8080/questions?title=Reverse%20a%20String"
134+
//
135+
//curl -X GET "http://localhost:8080/questions?complexity=Easy,Medium"
136+
//
137+
//curl -X GET "http://localhost:8080/questions?categories=Algorithms,Data%20Structures"
138+
//
139+
//curl -X GET "http://localhost:8080/questions?sortField=title&sortValue=asc"
140+
//
141+
//curl -X GET "http://localhost:8080/questions?sortField=title&sortValue=desc"
142+
//
143+
//curl -X GET "http://localhost:8080/questions?sortField=complexity&sortValue=desc"
144+
//
145+
//curl -X GET "http://localhost:8080/questions?limit=5"
146+
//
147+
//curl -X GET "http://localhost:8080/questions?limit=5&startAfter=<last_question_document_ref>"
148+
//
149+
//curl -X GET "http://localhost:8080/questions?complexity=Easy,Medium&sortField=title&sortValue=asc&limit=5"
150+
//
151+
//curl -X GET "http://localhost:8080/questions?categories=Data%20Structures,Algorithms&complexity=Hard,Easy"
152+
//
153+
//curl -X GET "http://localhost:8080/questions?complexity=InvalidComplexity"

services/question-service/handlers/read.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (s *Service) ReadQuestion(w http.ResponseWriter, r *http.Request) {
2828

2929
// Map data
3030
var question models.Question
31-
question.ID = doc.Ref.ID
31+
question.Ref = doc.Ref.ID
3232
if err := doc.DataTo(&question); err != nil {
3333
http.Error(w, "Failed to map question data", http.StatusInternalServerError)
3434
return

services/question-service/handlers/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (s *Service) UpdateQuestion(w http.ResponseWriter, r *http.Request) {
5151
}
5252

5353
// Map data
54-
question.ID = id
54+
question.Ref = id
5555

5656
w.Header().Set("Content-Type", "application/json")
5757
json.NewEncoder(w).Encode(question)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package models
2+
3+
type QuestionResponse struct {
4+
TotalCount int `json:"totalCount"`
5+
TotalPages int `json:"totalPages"`
6+
CurrentPage int `json:"currentPage"`
7+
Limit int `json:"limit"`
8+
HasNextPage bool `json:"hasNextPage"`
9+
Questions []Question `json:"questions"`
10+
}

services/question-service/models/question.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package models
22

33
// TODO: currently the Question model is a simplified model
44
type Question struct {
5-
ID string `json:"id"`
5+
ID int `json:"id"`
6+
Ref string `json:"ref"`
67
Title string `json:"title"`
78
Description string `json:"description"`
89
Categories []string `json:"categories"`

0 commit comments

Comments
 (0)