From 2c22c80d665296eb85fe69ac7699acc1e4b7dcd0 Mon Sep 17 00:00:00 2001 From: ruba Date: Mon, 29 Sep 2025 21:07:25 -0500 Subject: [PATCH 1/5] Refactor professor course and section pipelines into a pipeline builder function --- api/controllers/professor.go | 158 ++++++++++++++++++----------------- api/controllers/section.go | 56 ++++++------- api/docs/docs.go | 52 ++++++------ api/docs/swagger.yaml | 51 ++++++----- api/routes/grades.go | 2 +- api/server.go | 24 +++--- 6 files changed, 170 insertions(+), 173 deletions(-) diff --git a/api/controllers/professor.go b/api/controllers/professor.go index 7e23154b..49b94edd 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -197,6 +197,84 @@ 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"}, + }}}, + } + + // endpoint-specific stages + if endpoint == "courses" { + return append(baseStages, + // 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"]}}, + ) + } + + if endpoint == "sections" { + return append(baseStages, + // 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"]}}, + ) + } + + return baseStages // 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) @@ -221,49 +299,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) @@ -351,41 +387,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 e2ca5f0b..38ddda7d 100644 --- a/api/controllers/section.go +++ b/api/controllers/section.go @@ -123,32 +123,32 @@ func SectionById(c *gin.Context) { // @Router /section/courses [get] // @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 professors (e.g. For starting at the 17th professor, former_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" -// @Param academic_session.end_date query string false "The date of classes ending for the section" -// @Param teaching_assistants.first_name query string false "The first name of one of the teaching assistants of the section" -// @Param teaching_assistants.last_name query string false "The last name of one of the teaching assistants of the section" -// @Param teaching_assistants.role query string false "The role of one of the teaching assistants of the section" -// @Param teaching_assistants.email query string false "The email of one of the teaching assistants of the section" -// @Param internal_class_number query string false "The internal (university) number used to reference this section" -// @Param instruction_mode query string false "The instruction modality for this section" -// @Param meetings.start_date query string false "The start date of one of the section's meetings" -// @Param meetings.end_date query string false "The end date of one of the section's meetings" -// @Param meetings.meeting_days query string false "One of the days that one of the section's meetings" -// @Param meetings.start_time query string false "The time one of the section's meetings starts" -// @Param meetings.end_time query string false "The time one of the section's meetings ends" -// @Param meetings.modality query string false "The modality of one of the section's meetings" -// @Param meetings.location.building query string false "The building of one of the section's meetings" -// @Param meetings.location.room query string false "The room of one of the section's meetings" -// @Param meetings.location.map_uri query string false "A hyperlink to the UTD room locator of one of the section's meetings" -// @Param core_flags query string false "One of core requirement codes this section fulfills" -// @Param syllabus_uri query string false "A link to the syllabus on the web" +// @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" +// @Param academic_session.end_date query string false "The date of classes ending for the section" +// @Param teaching_assistants.first_name query string false "The first name of one of the teaching assistants of the section" +// @Param teaching_assistants.last_name query string false "The last name of one of the teaching assistants of the section" +// @Param teaching_assistants.role query string false "The role of one of the teaching assistants of the section" +// @Param teaching_assistants.email query string false "The email of one of the teaching assistants of the section" +// @Param internal_class_number query string false "The internal (university) number used to reference this section" +// @Param instruction_mode query string false "The instruction modality for this section" +// @Param meetings.start_date query string false "The start date of one of the section's meetings" +// @Param meetings.end_date query string false "The end date of one of the section's meetings" +// @Param meetings.meeting_days query string false "One of the days that one of the section's meetings" +// @Param meetings.start_time query string false "The time one of the section's meetings starts" +// @Param meetings.end_time query string false "The time one of the section's meetings ends" +// @Param meetings.modality query string false "The modality of one of the section's meetings" +// @Param meetings.location.building query string false "The building of one of the section's meetings" +// @Param meetings.location.room query string false "The room of one of the section's meetings" +// @Param meetings.location.map_uri query string false "A hyperlink to the UTD room locator of one of the section's meetings" +// @Param core_flags query string false "One of core requirement codes this section fulfills" +// @Param syllabus_uri query string false "A link to the syllabus on the web" // @Success 200 {object} schema.APIResponse[[]schema.Course] "A list of courses" -// @Failure 500 {object} schema.APIResponse[string] "A string describing the error" -// @Failure 400 {object} schema.APIResponse[string] "A string describing the error" +// @Failure 500 {object} schema.APIResponse[string] "A string describing the error" +// @Failure 400 {object} schema.APIResponse[string] "A string describing the error" func SectionCourseSearch() gin.HandlerFunc { return func(c *gin.Context) { sectionCourse("Search", c) @@ -159,10 +159,10 @@ func SectionCourseSearch() gin.HandlerFunc { // @Router /section/{id}/course [get] // @Description "Returns the course of the section with given ID" // @Produce json -// @Param id path string true "ID of the section to get" +// @Param id path string true "ID of the section to get" // @Success 200 {object} schema.APIResponse[schema.Course] "A course" -// @Failure 500 {object} schema.APIResponse[string] "A string describing the error" -// @Failure 400 {object} schema.APIResponse[string] "A string describing the error" +// @Failure 500 {object} schema.APIResponse[string] "A string describing the error" +// @Failure 400 {object} schema.APIResponse[string] "A string describing the error" func SectionCourseById() gin.HandlerFunc { return func(c *gin.Context) { sectionCourse("ById", c) @@ -251,7 +251,7 @@ func sectionCourse(flag string, c *gin.Context) { // @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." // @Produce json // @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 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/docs/docs.go b/api/docs/docs.go index c5c7f26e..582f2db8 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -1631,13 +1631,13 @@ const docTemplate = `{ "parameters": [ { "type": "number", - "description": "The starting position of the current page of sections (e.g. For starting at the 17th section, former_offset=16).", + "description": "The starting position of the current page of professors (e.g. For starting at the 17th professor, former_offset=16).", "name": "former_offset", "in": "query" }, { "type": "number", - "description": "The starting position of the current page of courses from the predefined page of sections (e.g. For starting at the 18th course, latter_offset=17).", + "description": "The starting position of the current page of sections (e.g. For starting at the 17th professor, offset=16).", "name": "latter_offset", "in": "query" }, @@ -1800,13 +1800,13 @@ const docTemplate = `{ "parameters": [ { "type": "number", - "description": "The starting position of the current page of sections (e.g. For starting at the 17th section, former_offset=16).", + "description": "The starting position of the current page of professors (e.g. For starting at the 17th professor, former_offset=16).", "name": "former_offset", "in": "query" }, { "type": "number", - "description": "The starting position of the current page of professors from the predefined page of sections (e.g. For starting at the 18th professor, latter_offset=17).", + "description": "The starting position of the current page of sections (e.g. For starting at the 17th professor, offset=16).", "name": "latter_offset", "in": "query" }, @@ -2338,7 +2338,7 @@ const docTemplate = `{ "required": true }, { - "description": "params for Signed URL", + "description": "Request body", "name": "body", "in": "body", "required": true, @@ -2393,28 +2393,6 @@ const docTemplate = `{ } }, "definitions": { - "schema.ObjectSignedURLBody": { - "description": "request body", - "type": "object", - "properties": { - "expiration": { - "description": "timestamp for when the signed URL will expire", - "type": "string" - }, - "headers": { - "description": "headers for signed URL", - "type": "array", - "items": { - "type": "string" - } - }, - "method": { - "description": "method to be used with signed URL", - "type": "string", - "example": "PUT" - } - } - }, "schema.APIResponse-array_int": { "type": "object", "properties": { @@ -3103,6 +3081,26 @@ const docTemplate = `{ } } }, + "schema.ObjectSignedURLBody": { + "type": "object", + "properties": { + "expiration": { + "description": "timestamp for when the signed URL will expire", + "type": "string" + }, + "headers": { + "description": "headers for signed URL", + "type": "array", + "items": { + "type": "string" + } + }, + "method": { + "description": "method to be used with signed URL. For example, PUT", + "type": "string" + } + } + }, "schema.Professor": { "type": "object", "properties": { diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index 893001f5..7363aa54 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -1,20 +1,4 @@ definitions: - schema.ObjectSignedURLBody: - description: request body - properties: - expiration: - description: timestamp for when the signed URL will expire - type: string - headers: - description: headers for signed URL - items: - type: string - type: array - method: - description: method to be used with signed URL - example: PUT - type: string - type: object schema.APIResponse-array_int: properties: data: @@ -462,6 +446,20 @@ definitions: updated: type: string type: object + schema.ObjectSignedURLBody: + properties: + expiration: + description: timestamp for when the signed URL will expire + type: string + headers: + description: headers for signed URL + items: + type: string + type: array + method: + description: method to be used with signed URL. For example, PUT + type: string + type: object schema.Professor: properties: _id: @@ -1773,8 +1771,7 @@ paths: $ref: '#/definitions/schema.APIResponse-string' /section/{id}/course: get: - description: '"Returns the course of the section with given - ID"' + description: '"Returns the course of the section with given ID"' operationId: sectionCourseById parameters: - description: ID of the section to get @@ -1855,13 +1852,13 @@ paths: for pagination details."' operationId: sectionCourseSearch parameters: - - description: The starting position of the current page of sections (e.g. - For starting at the 17th section, former_offset=16). + - description: The starting position of the current page of professors (e.g. + For starting at the 17th professor, former_offset=16). in: query name: former_offset type: number - - description: The starting position of the current page of courses (e.g. For - starting at the 4th course, latter_offset=3). + - description: The starting position of the current page of sections (e.g. For + starting at the 17th professor, offset=16). in: query name: latter_offset type: number @@ -1971,13 +1968,13 @@ paths: for pagination details."' operationId: sectionProfessorSearch parameters: - - description: The starting position of the current page of sections (e.g. - For starting at the 17th section, former_offset=16). + - description: The starting position of the current page of professors (e.g. + For starting at the 17th professor, former_offset=16). in: query name: former_offset type: number - - description: The starting position of the current page of professors (e.g. For - starting at the 4th professor, latter_offset=3). + - description: The starting position of the current page of sections (e.g. For + starting at the 17th professor, offset=16). in: query name: latter_offset type: number @@ -2240,7 +2237,7 @@ paths: name: objectID required: true type: string - - description: Request body + - description: Request body in: body name: body required: true diff --git a/api/routes/grades.go b/api/routes/grades.go index ec503438..dff1f7ef 100644 --- a/api/routes/grades.go +++ b/api/routes/grades.go @@ -12,7 +12,7 @@ func GradesRoute(router *gin.Engine) { gradesGroup.OPTIONS("", controllers.Preflight) - // @TODO: Do we need this? + // @TODO: Do we need this? // ---- gradesGroup.OPTIONS("semester", controllers.Preflight) // ---- gradesGroup.OPTIONS("overall", controllers.Preflight) diff --git a/api/server.go b/api/server.go index a52acd79..7ff5142a 100644 --- a/api/server.go +++ b/api/server.go @@ -23,18 +23,18 @@ import ( // @Success 200 func swagger_controller_placeholder() {} -// @title dev-nebula-api -// @description The developer Nebula Labs API for access to pertinent UT Dallas data -// @version 1.1.0 -// @host api.utdnebula.com -// @schemes https http -// @x-google-backend {"address": "https://dev-nebula-api-1062216541483.us-south1.run.app"} -// @x-google-endpoints [{"name": "dev-nebula-api-2wy9quu2ri5uq.apigateway.nebula-api-368223.cloud.goog", "allowCors": true}] -// @x-google-management {"metrics": [{"name": "read-requests", "displayName": "Read Requests CUSTOM", "valueType": "INT64", "metricKind": "DELTA"}], "quota": {"limits": [{"name": "read-limit", "metric": "read-requests", "unit": "1/min/{project}", "values": {"STANDARD": 1000}}]}} -// @security api_key -// @securitydefinitions.apikey api_key -// @name x-api-key -// @in header +// @title dev-nebula-api +// @description The developer Nebula Labs API for access to pertinent UT Dallas data +// @version 1.1.0 +// @host api.utdnebula.com +// @schemes https http +// @x-google-backend {"address": "https://dev-nebula-api-1062216541483.us-south1.run.app"} +// @x-google-endpoints [{"name": "dev-nebula-api-2wy9quu2ri5uq.apigateway.nebula-api-368223.cloud.goog", "allowCors": true}] +// @x-google-management {"metrics": [{"name": "read-requests", "displayName": "Read Requests CUSTOM", "valueType": "INT64", "metricKind": "DELTA"}], "quota": {"limits": [{"name": "read-limit", "metric": "read-requests", "unit": "1/min/{project}", "values": {"STANDARD": 1000}}]}} +// @security api_key +// @securitydefinitions.apikey api_key +// @name x-api-key +// @in header func main() { From c22c940b942236d69319e172ad01dd5ebec03780 Mon Sep 17 00:00:00 2001 From: Ruba Osman <138681922+ruba0s@users.noreply.github.com> Date: Thu, 9 Oct 2025 21:49:47 -0500 Subject: [PATCH 2/5] Update professor.go pipeline builder function --- api/controllers/professor.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/api/controllers/professor.go b/api/controllers/professor.go index 1a3c29f6..aa6b27dc 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -244,13 +244,6 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s // 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"]}}, ) } @@ -267,16 +260,16 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s // 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"]}}, ) } + // 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"]}}, + return baseStages // fallback (shouldn't happen because we call with either courses or sections) } From 3388ccb26dd5b77609c33005c507e002aefc4cb6 Mon Sep 17 00:00:00 2001 From: Ruba Osman <138681922+ruba0s@users.noreply.github.com> Date: Thu, 9 Oct 2025 21:58:43 -0500 Subject: [PATCH 3/5] Update professor.go pipeline builder function --- api/controllers/professor.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api/controllers/professor.go b/api/controllers/professor.go index aa6b27dc..ca73ece7 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -220,6 +220,13 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s {Key: "foreignField", Value: "_id"}, {Key: "as", Value: "sections"}, }}}, + + // 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"]}}, } // endpoint-specific stages @@ -263,13 +270,6 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s ) } - // 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"]}}, - return baseStages // fallback (shouldn't happen because we call with either courses or sections) } From 9555b84d63220a8af32cb64ae05ff88fcbd642f0 Mon Sep 17 00:00:00 2001 From: ruba Date: Fri, 17 Oct 2025 18:54:25 -0500 Subject: [PATCH 4/5] Update professor.go pipeline course pagination stages --- api/controllers/professor.go | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/api/controllers/professor.go b/api/controllers/professor.go index 49b94edd..890958f5 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -215,11 +215,19 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s {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"]}}, } - // endpoint-specific stages if endpoint == "courses" { - return append(baseStages, + 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"}}}}, @@ -239,18 +247,13 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s // 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"]}}, - ) + return append(append(baseStages, courseStages...), paginationStages...) } if endpoint == "sections" { - return append(baseStages, + sectionStages := mongo.Pipeline{ // project the sections bson.D{{Key: "$project", Value: bson.D{{Key: "sections", Value: "$sections"}}}}, @@ -262,17 +265,12 @@ func professorPipeline(endpoint string, professorQuery bson.M, paginateMap map[s // 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"]}}, - ) + return append(append(baseStages, sectionStages...), paginationStages...) } - return baseStages // fallback (shouldn't happen because we call with either courses or sections) + 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 From a74fc7fb7350a9774a7bb4d611354cc961373955 Mon Sep 17 00:00:00 2001 From: ruba Date: Fri, 17 Oct 2025 19:06:43 -0500 Subject: [PATCH 5/5] Update professor.go pipeline course pagination stages --- api/go.mod | 2 +- api/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/go.mod b/api/go.mod index 181c7d54..f462ee0e 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 3c9eda23..2865906f 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=