Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 92 additions & 94 deletions api/controllers/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func SectionSearch(c *gin.Context) {

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

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

// return result
respond(c, http.StatusOK, "success", sections)
respond[[]schema.Section](c, http.StatusOK, "success", sections)
}

// @Id sectionById
Expand Down Expand Up @@ -116,7 +116,7 @@ func SectionById(c *gin.Context) {
}

// return result
respond(c, http.StatusOK, "success", section)
respond[schema.Section](c, http.StatusOK, "success", section)
}

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

var sectionCourses []schema.Course
var sectionQuery bson.M
var err error
if sectionQuery, err = getSectionQuery(flag, c); err != nil {
sectionQuery, err := getSectionQuery(flag, c)

if err != nil {
return
}

paginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
rawPaginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
if err != nil {
respond(c, http.StatusBadRequest, "Error offset is not type integer", err.Error())
return
}

// pipeline of query an array of courses from filtered sections
sectionCoursePipeline := mongo.Pipeline{
// filter the sections
bson.D{{Key: "$match", Value: sectionQuery}},

// paginate the sections before pulling courses from those sections
bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},

// lookup the course referenced by sections from the course collection
bson.D{{Key: "$lookup", Value: bson.D{
{Key: "from", Value: "courses"},
{Key: "localField", Value: "course_reference"},
{Key: "foreignField", Value: "_id"},
{Key: "as", Value: "course_reference"},
}}},

// project to remove every other fields except for courses
bson.D{{Key: "$project", Value: bson.D{{Key: "courses", Value: "$course_reference"}}}},

// unwind the courses
bson.D{{Key: "$unwind", Value: bson.D{
{Key: "path", Value: "$courses"},
{Key: "preserveNullAndEmptyArrays", Value: false},
}}},

// replace the combinations of id and course with courses entirely
bson.D{{Key: "$replaceWith", Value: "$courses"}},

// keep order deterministic between calls
bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},

// paginate the courses
bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
paginateMap := make(map[string]int)
for k,v := range rawPaginateMap {
paginateMap[k] = int(v)
}

cursor, err := sectionCollection.Aggregate(ctx, sectionCoursePipeline)
pipeline := buildSectionPipeline(sectionQuery, paginateMap, "courses", flag == "ById")
cursor, err := sectionCollection.Aggregate(ctx, pipeline)

if err != nil {
respondWithInternalError(c, err)
return
}

// Parse the array of courses
if err = cursor.All(ctx, &sectionCourses); err != nil {
respondWithInternalError(c, err)
return
}

switch flag {
case "Search":
respond(c, http.StatusOK, "success", sectionCourses)
case "ById":
// Each section is only referenced by only one course, so returning a single course is ideal
// A better way of handling this might be needed in the future
respond(c, http.StatusOK, "success", sectionCourses[0])
if flag == "ById" {
var course schema.Course
if cursor.Next(ctx) {
if err := cursor.Decode(&course); err != nil {
respondWithInternalError(c,err)
return
}
respond[*schema.Course](c, http.StatusOK, "success", &course)
return
}
respond[interface{}](c, http.StatusOK, "success", nil)
} else {
if err := cursor.All(ctx, &sectionCourses); err != nil {
respondWithInternalError(c, err)
return
}
respond[[]schema.Course](c, http.StatusOK, "success", sectionCourses)
}
}


// @Id sectionProfessorSearch
// @Router /section/professors [get]
// @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."
Expand Down Expand Up @@ -290,72 +265,48 @@ func SectionProfessorSearch() gin.HandlerFunc {
// @Success 200 {object} schema.APIResponse[[]schema.Professor] "A list of professors"
// @Failure 500 {object} schema.APIResponse[string] "A string describing the error"
// @Failure 400 {object} schema.APIResponse[string] "A string describing the error"

func SectionProfessorById() gin.HandlerFunc {
return func(c *gin.Context) {
sectionProfessor("ById", c)
}
}

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

var sectionProfessors []schema.Professor
var sectionQuery bson.M
var err error
if sectionQuery, err = getSectionQuery(flag, c); err != nil {
sectionQuery, err := getSectionQuery(flag, c)

if err != nil {
return
}

paginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
rawPaginateMap, err := configs.GetAggregateLimit(&sectionQuery, c)
if err != nil {
respond(c, http.StatusBadRequest, "Error offset is not type integer", err.Error())
return
}

// pipeline to query an array of professors from filtered sections
sectionProfessorPipeline := mongo.Pipeline{
bson.D{{Key: "$match", Value: sectionQuery}},

bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},

bson.D{{Key: "$lookup", Value: bson.D{
{Key: "from", Value: "professors"},
{Key: "localField", Value: "professors"},
{Key: "foreignField", Value: "_id"},
{Key: "as", Value: "professors"},
}}},

bson.D{{Key: "$project", Value: bson.D{{Key: "professors", Value: "$professors"}}}},

bson.D{{Key: "$unwind", Value: bson.D{
{Key: "path", Value: "$professors"},
{Key: "preserveNullAndEmptyArrays", Value: false},
}}},

bson.D{{Key: "$replaceWith", Value: "$professors"}},

bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},

bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
paginateMap := make(map[string]int)
for k, v := range rawPaginateMap {
paginateMap[k] = int(v)
}

cursor, err := sectionCollection.Aggregate(ctx, sectionProfessorPipeline)
pipeline := buildSectionPipeline(sectionQuery, paginateMap, "professors", flag == "ById")
cursor, err := sectionCollection.Aggregate(ctx, pipeline)

if err != nil {
respondWithInternalError(c, err)
return
}

// Parse the array of courses
var sectionProfessors []schema.Professor
if err = cursor.All(ctx, &sectionProfessors); err != nil {
respondWithInternalError(c, err)
return
}

respond(c, http.StatusOK, "success", sectionProfessors)
respond[[]schema.Professor](c, http.StatusOK, "success", sectionProfessors)
}

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

return sectionQuery, nil
}

func buildSectionPipeline(
sectionQuery bson.M,
paginateMap map[string]int,
lookupType string,
single bool,
) mongo.Pipeline {
localField := "course_reference"
field := lookupType

if lookupType == "professors" {
localField = "professor_id"
}
pipeline := mongo.Pipeline{
bson.D{{Key: "$match", Value: sectionQuery}},
}
if !single {
pipeline = append(pipeline,
bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
)
}

pipeline = append(pipeline,
bson.D{{Key: "$lookup", Value: bson.D{
{Key: "from", Value: lookupType},
{Key: "localField", Value: localField},
{Key: "foreignField", Value: "_id"},
{Key: "as", Value: field},
}}},
bson.D{{Key: "$project", Value: bson.D{{Key: field, Value: "$" + field}}}},
)

if !single {
pipeline = append(pipeline,
bson.D{{Key: "$unwind", Value: bson.D{
{Key: "path", Value: "$" + field},
{Key: "preserveNullAndEmptyArrays", Value: false},
}}},
bson.D{{Key: "$replaceWith", Value: "$" + field}},
bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},
bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}},
bson.D{{Key: "$limit", Value: paginateMap["limit"]}},
)
}
return pipeline
}