1
1
package handlers
2
2
3
3
import (
4
+ "cloud.google.com/go/firestore"
4
5
"encoding/json"
6
+ "google.golang.org/api/iterator"
5
7
"net/http"
6
8
"question-service/models"
7
-
8
- "google.golang.org/api/iterator "
9
+ "strconv"
10
+ "strings "
9
11
)
10
12
11
- // TODO: add filters/pagination/sorter
12
13
func (s * Service ) ListQuestions (w http.ResponseWriter , r * http.Request ) {
13
14
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 )
15
88
16
89
var questions []models.Question
17
90
for {
@@ -21,20 +94,60 @@ func (s *Service) ListQuestions(w http.ResponseWriter, r *http.Request) {
21
94
break
22
95
}
23
96
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 )
25
98
return
26
99
}
27
100
28
101
// Map data
29
102
var question models.Question
30
- question .ID = doc .Ref .ID
31
103
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 )
33
105
return
34
106
}
107
+ question .Ref = doc .Ref .ID
35
108
questions = append (questions , question )
36
109
}
37
110
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
+
38
125
w .Header ().Set ("Content-Type" , "application/json" )
39
- json .NewEncoder (w ).Encode (questions )
126
+ json .NewEncoder (w ).Encode (response )
40
127
}
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"
0 commit comments