Skip to content

Commit d34f75f

Browse files
Refactor section aggregation pipelines; unify courses & professors; handle single course for /section/:id/course
1 parent f3fe436 commit d34f75f

File tree

1 file changed

+92
-94
lines changed

1 file changed

+92
-94
lines changed

api/controllers/section.go

Lines changed: 92 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func SectionSearch(c *gin.Context) {
6666

6767
optionLimit, err := configs.GetOptionLimit(&query, c)
6868
if err != nil {
69-
respond(c, http.StatusBadRequest, "offset is not type integer", err.Error())
69+
respond[string](c, http.StatusBadRequest, "offset is not type integer", err.Error())
7070
return
7171
}
7272

@@ -84,7 +84,7 @@ func SectionSearch(c *gin.Context) {
8484
}
8585

8686
// return result
87-
respond(c, http.StatusOK, "success", sections)
87+
respond[[]schema.Section](c, http.StatusOK, "success", sections)
8888
}
8989

9090
// @Id sectionById
@@ -116,7 +116,7 @@ func SectionById(c *gin.Context) {
116116
}
117117

118118
// return result
119-
respond(c, http.StatusOK, "success", section)
119+
respond[schema.Section](c, http.StatusOK, "success", section)
120120
}
121121

122122
// @Id sectionCourseSearch
@@ -175,77 +175,52 @@ func sectionCourse(flag string, c *gin.Context) {
175175
defer cancel()
176176

177177
var sectionCourses []schema.Course
178-
var sectionQuery bson.M
179-
var err error
180-
if sectionQuery, err = getSectionQuery(flag, c); err != nil {
178+
sectionQuery, err := getSectionQuery(flag, c)
179+
180+
if err != nil {
181181
return
182182
}
183183

184-
paginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
184+
rawPaginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
185185
if err != nil {
186186
respond(c, http.StatusBadRequest, "Error offset is not type integer", err.Error())
187187
return
188188
}
189189

190-
// pipeline of query an array of courses from filtered sections
191-
sectionCoursePipeline := mongo.Pipeline{
192-
// filter the sections
193-
bson.D{{Key: "$match", Value: sectionQuery}},
194-
195-
// paginate the sections before pulling courses from those sections
196-
bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
197-
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
198-
199-
// lookup the course referenced by sections from the course collection
200-
bson.D{{Key: "$lookup", Value: bson.D{
201-
{Key: "from", Value: "courses"},
202-
{Key: "localField", Value: "course_reference"},
203-
{Key: "foreignField", Value: "_id"},
204-
{Key: "as", Value: "course_reference"},
205-
}}},
206-
207-
// project to remove every other fields except for courses
208-
bson.D{{Key: "$project", Value: bson.D{{Key: "courses", Value: "$course_reference"}}}},
209-
210-
// unwind the courses
211-
bson.D{{Key: "$unwind", Value: bson.D{
212-
{Key: "path", Value: "$courses"},
213-
{Key: "preserveNullAndEmptyArrays", Value: false},
214-
}}},
215190

216-
// replace the combinations of id and course with courses entirely
217-
bson.D{{Key: "$replaceWith", Value: "$courses"}},
218-
219-
// keep order deterministic between calls
220-
bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},
221-
222-
// paginate the courses
223-
bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
224-
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
191+
paginateMap := make(map[string]int)
192+
for k,v := range rawPaginateMap {
193+
paginateMap[k] = int(v)
225194
}
226195

227-
cursor, err := sectionCollection.Aggregate(ctx, sectionCoursePipeline)
196+
pipeline := buildSectionPipeline(sectionQuery, paginateMap, "courses", flag == "ById")
197+
cursor, err := sectionCollection.Aggregate(ctx, pipeline)
198+
228199
if err != nil {
229200
respondWithInternalError(c, err)
230201
return
231202
}
232-
233-
// Parse the array of courses
234-
if err = cursor.All(ctx, &sectionCourses); err != nil {
235-
respondWithInternalError(c, err)
236-
return
237-
}
238-
239-
switch flag {
240-
case "Search":
241-
respond(c, http.StatusOK, "success", sectionCourses)
242-
case "ById":
243-
// Each section is only referenced by only one course, so returning a single course is ideal
244-
// A better way of handling this might be needed in the future
245-
respond(c, http.StatusOK, "success", sectionCourses[0])
203+
if flag == "ById" {
204+
var course schema.Course
205+
if cursor.Next(ctx) {
206+
if err := cursor.Decode(&course); err != nil {
207+
respondWithInternalError(c,err)
208+
return
209+
}
210+
respond[*schema.Course](c, http.StatusOK, "success", &course)
211+
return
212+
}
213+
respond[interface{}](c, http.StatusOK, "success", nil)
214+
} else {
215+
if err := cursor.All(ctx, &sectionCourses); err != nil {
216+
respondWithInternalError(c, err)
217+
return
218+
}
219+
respond[[]schema.Course](c, http.StatusOK, "success", sectionCourses)
246220
}
247221
}
248222

223+
249224
// @Id sectionProfessorSearch
250225
// @Router /section/professors [get]
251226
// @Description "Returns paginated list of professors of all the sections matching the query's string-typed key-value pairs. See former_offset and latter_offset for pagination details."
@@ -290,72 +265,48 @@ func SectionProfessorSearch() gin.HandlerFunc {
290265
// @Success 200 {object} schema.APIResponse[[]schema.Professor] "A list of professors"
291266
// @Failure 500 {object} schema.APIResponse[string] "A string describing the error"
292267
// @Failure 400 {object} schema.APIResponse[string] "A string describing the error"
268+
293269
func SectionProfessorById() gin.HandlerFunc {
294270
return func(c *gin.Context) {
295271
sectionProfessor("ById", c)
296272
}
297273
}
298274

299-
// Get an array of professors from sections,
275+
// Get an array of professors sections,
300276
func sectionProfessor(flag string, c *gin.Context) {
301277
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
302278
defer cancel()
303279

304-
var sectionProfessors []schema.Professor
305-
var sectionQuery bson.M
306-
var err error
307-
if sectionQuery, err = getSectionQuery(flag, c); err != nil {
280+
sectionQuery, err := getSectionQuery(flag, c)
281+
282+
if err != nil {
308283
return
309284
}
310285

311-
paginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
286+
rawPaginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
312287
if err != nil {
313288
respond(c, http.StatusBadRequest, "Error offset is not type integer", err.Error())
314289
return
315290
}
316291

317-
// pipeline to query an array of professors from filtered sections
318-
sectionProfessorPipeline := mongo.Pipeline{
319-
bson.D{{Key: "$match", Value: sectionQuery}},
320-
321-
bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
322-
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
323-
324-
bson.D{{Key: "$lookup", Value: bson.D{
325-
{Key: "from", Value: "professors"},
326-
{Key: "localField", Value: "professors"},
327-
{Key: "foreignField", Value: "_id"},
328-
{Key: "as", Value: "professors"},
329-
}}},
330-
331-
bson.D{{Key: "$project", Value: bson.D{{Key: "professors", Value: "$professors"}}}},
332-
333-
bson.D{{Key: "$unwind", Value: bson.D{
334-
{Key: "path", Value: "$professors"},
335-
{Key: "preserveNullAndEmptyArrays", Value: false},
336-
}}},
337-
338-
bson.D{{Key: "$replaceWith", Value: "$professors"}},
339-
340-
bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},
341-
342-
bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
343-
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
292+
paginateMap := make(map[string]int)
293+
for k, v := range rawPaginateMap {
294+
paginateMap[k] = int(v)
344295
}
345296

346-
cursor, err := sectionCollection.Aggregate(ctx, sectionProfessorPipeline)
297+
pipeline := buildSectionPipeline(sectionQuery, paginateMap, "professors", flag == "ById")
298+
cursor, err := sectionCollection.Aggregate(ctx, pipeline)
299+
347300
if err != nil {
348301
respondWithInternalError(c, err)
349302
return
350303
}
351-
352-
// Parse the array of courses
304+
var sectionProfessors []schema.Professor
353305
if err = cursor.All(ctx, &sectionProfessors); err != nil {
354306
respondWithInternalError(c, err)
355307
return
356308
}
357-
358-
respond(c, http.StatusOK, "success", sectionProfessors)
309+
respond[[]schema.Professor](c, http.StatusOK, "success", sectionProfessors)
359310
}
360311

361312
// Determine the query of the section based on parameters passed from context.
@@ -385,3 +336,50 @@ func getSectionQuery(flag string, c *gin.Context) (bson.M, error) {
385336

386337
return sectionQuery, nil
387338
}
339+
340+
func buildSectionPipeline(
341+
sectionQuery bson.M,
342+
paginateMap map[string]int,
343+
lookupType string,
344+
single bool,
345+
) mongo.Pipeline {
346+
localField := "course_reference"
347+
field := lookupType
348+
349+
if lookupType == "professors" {
350+
localField = "professor_id"
351+
}
352+
pipeline := mongo.Pipeline{
353+
bson.D{{Key: "$match", Value: sectionQuery}},
354+
}
355+
if !single {
356+
pipeline = append(pipeline,
357+
bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
358+
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
359+
)
360+
}
361+
362+
pipeline = append(pipeline,
363+
bson.D{{Key: "$lookup", Value: bson.D{
364+
{Key: "from", Value: lookupType},
365+
{Key: "localField", Value: localField},
366+
{Key: "foreignField", Value: "_id"},
367+
{Key: "as", Value: field},
368+
}}},
369+
bson.D{{Key: "$project", Value: bson.D{{Key: field, Value: "$" + field}}}},
370+
)
371+
372+
if !single {
373+
pipeline = append(pipeline,
374+
bson.D{{Key: "$unwind", Value: bson.D{
375+
{Key: "path", Value: "$" + field},
376+
{Key: "preserveNullAndEmptyArrays", Value: false},
377+
}}},
378+
bson.D{{Key: "$replaceWith", Value: "$" + field}},
379+
bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},
380+
bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
381+
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
382+
)
383+
}
384+
return pipeline
385+
}

0 commit comments

Comments
 (0)