diff --git a/api/controllers/professor.go b/api/controllers/professor.go index 5b01e05..a1252ef 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -198,6 +198,82 @@ func ProfessorCourseById() gin.HandlerFunc { } } +// Pipeline builder for professor aggregate endpoints +func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[string]int64) mongo.Pipeline { + // common stages + baseStages := mongo.Pipeline{ + // filter the professors + bson.D{{Key: "$match", Value: professorQuery}}, + + // paginate the professors before pulling the courses from those professor + bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}}, // skip to the specified offset + bson.D{{Key: "$limit", Value: paginateMap["limit"]}}, // limit to the specified number of professors + + // lookup the array of sections from sections collection + bson.D{{Key: "$lookup", Value: bson.D{ + {Key: "from", Value: "sections"}, + {Key: "localField", Value: "sections"}, + {Key: "foreignField", Value: "_id"}, + {Key: "as", Value: "sections"}, + }}}, + + // keep order deterministic between calls + bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}}, + } + + // course pagination stages + paginationStages := mongo.Pipeline{ + bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}}, + bson.D{{Key: "$limit", Value: paginateMap["limit"]}}, + } + + if endpoint == "courses" { + courseStages := mongo.Pipeline{ + // project the courses referenced by each section in the array + bson.D{{Key: "$project", Value: bson.D{{Key: "courses", Value: "$sections.course_reference"}}}}, + + // lookup the array of courses from coures collection + bson.D{{Key: "$lookup", Value: bson.D{ + {Key: "from", Value: "courses"}, + {Key: "localField", Value: "courses"}, + {Key: "foreignField", Value: "_id"}, + {Key: "as", Value: "courses"}, + }}}, + + // unwind the courses + bson.D{{Key: "$unwind", Value: bson.D{ + {Key: "path", Value: "$courses"}, + {Key: "preserveNullAndEmptyArrays", Value: false}, // to avoid the professor documents that can't be replaced + }}}, + + // replace the combination of ids and courses with the courses entirely + bson.D{{Key: "$replaceWith", Value: "$courses"}}, + } + + return append(append(baseStages, courseStages...), paginationStages...) + } + + if endpoint == "sections" { + sectionStages := mongo.Pipeline{ + // project the sections + bson.D{{Key: "$project", Value: bson.D{{Key: "sections", Value: "$sections"}}}}, + + // unwind the sections + bson.D{{Key: "$unwind", Value: bson.D{ + {Key: "path", Value: "$sections"}, + {Key: "preserveNullAndEmptyArrays", Value: false}, // to avoid the professor documents that can't be replaced + }}}, + + // replace the combination of ids and sections with the sections entirely + bson.D{{Key: "$replaceWith", Value: "$sections"}}, + } + + return append(append(baseStages, sectionStages...), paginationStages...) + } + + return append(baseStages, paginationStages...) // fallback (shouldn't happen because we call with either courses or sections) +} + // Get all of the courses of the professors depending on the type of flag func professorCourse(flag string, c *gin.Context) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -222,49 +298,7 @@ func professorCourse(flag string, c *gin.Context) { } // Pipeline to query the courses from the filtered professors (or a single professor) - professorCoursePipeline := mongo.Pipeline{ - // filter the professors - bson.D{{Key: "$match", Value: professorQuery}}, - - // paginate the professors before pulling the courses from those professor - bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}}, // skip to the specified offset - bson.D{{Key: "$limit", Value: paginateMap["limit"]}}, // limit to the specified number of professors - - // lookup the array of sections from sections collection - bson.D{{Key: "$lookup", Value: bson.D{ - {Key: "from", Value: "sections"}, - {Key: "localField", Value: "sections"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "sections"}, - }}}, - - // project the courses referenced by each section in the array - bson.D{{Key: "$project", Value: bson.D{{Key: "courses", Value: "$sections.course_reference"}}}}, - - // lookup the array of courses from coures collection - bson.D{{Key: "$lookup", Value: bson.D{ - {Key: "from", Value: "courses"}, - {Key: "localField", Value: "courses"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "courses"}, - }}}, - - // unwind the courses - bson.D{{Key: "$unwind", Value: bson.D{ - {Key: "path", Value: "$courses"}, - {Key: "preserveNullAndEmptyArrays", Value: false}, // to avoid the professor documents that can't be replaced - }}}, - - // replace the combination of ids and courses with the 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"]}}, - } + professorCoursePipeline := professorPipeline("courses", professorQuery, paginateMap) // Perform aggreration on the pipeline cursor, err := professorCollection.Aggregate(ctx, professorCoursePipeline) @@ -354,41 +388,7 @@ func professorSection(flag string, c *gin.Context) { } // Pipeline to query the courses from the filtered professors (or a single professor) - professorSectionPipeline := mongo.Pipeline{ - // filter the professors - bson.D{{Key: "$match", Value: professorQuery}}, - - // paginate the professors before pulling the courses from those professor - bson.D{{Key: "$skip", Value: paginateMap["former_offset"]}}, // skip to the specified offset - bson.D{{Key: "$limit", Value: paginateMap["limit"]}}, // limit to the specified number of professors - - // lookup the array of sections from sections collection - bson.D{{Key: "$lookup", Value: bson.D{ - {Key: "from", Value: "sections"}, - {Key: "localField", Value: "sections"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "sections"}, - }}}, - - // project the sections - bson.D{{Key: "$project", Value: bson.D{{Key: "sections", Value: "$sections"}}}}, - - // unwind the sections - bson.D{{Key: "$unwind", Value: bson.D{ - {Key: "path", Value: "$sections"}, - {Key: "preserveNullAndEmptyArrays", Value: false}, // to avoid the professor documents that can't be replaced - }}}, - - // replace the combination of ids and sections with the sections entirely - bson.D{{Key: "$replaceWith", Value: "$sections"}}, - - // keep order deterministic between calls - bson.D{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}}, - - // paginate the sections - bson.D{{Key: "$skip", Value: paginateMap["latter_offset"]}}, - bson.D{{Key: "$limit", Value: paginateMap["limit"]}}, - } + professorSectionPipeline := professorPipeline("sections", professorQuery, paginateMap) // Perform aggreration on the pipeline cursor, err := professorCollection.Aggregate(ctx, professorSectionPipeline) diff --git a/api/controllers/section.go b/api/controllers/section.go index 8ff8241..f71745e 100644 --- a/api/controllers/section.go +++ b/api/controllers/section.go @@ -122,8 +122,8 @@ func SectionById(c *gin.Context) { // @Tags Sections // @Description "Returns paginated list of courses of all the sections matching the query's string-typed key-value pairs. See former_offset and latter_offset for pagination details." // @Produce json -// @Param former_offset query number false "The starting position of the current page of sections (e.g. For starting at the 16th section, former_offset=16)." -// @Param latter_offset query number false "The starting position of the current page of courses (e.g. For starting at the 16th course, latter_offset=16)." +// @Param former_offset query number false "The starting position of the current page of professors (e.g. For starting at the 17th professor, former_offset=16)." +// @Param latter_offset query number false "The starting position of the current page of sections (e.g. For starting at the 17th professor, offset=16)." // @Param section_number query string false "The section's official number" // @Param academic_session.name query string false "The name of the academic session of the section" // @Param academic_session.start_date query string false "The date of classes starting for the section" diff --git a/api/go.mod b/api/go.mod index 181c7d5..f462ee0 100644 --- a/api/go.mod +++ b/api/go.mod @@ -13,7 +13,7 @@ require ( github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 github.com/swaggo/swag v1.16.6 - go.mongodb.org/mongo-driver v1.17.3 + go.mongodb.org/mongo-driver v1.17.4 google.golang.org/api v0.224.0 ) diff --git a/api/go.sum b/api/go.sum index 3c9eda2..2865906 100644 --- a/api/go.sum +++ b/api/go.sum @@ -200,6 +200,8 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=