diff --git a/api/controllers/autocomplete.go b/api/controllers/autocomplete.go index a8ff3d2..2c75385 100644 --- a/api/controllers/autocomplete.go +++ b/api/controllers/autocomplete.go @@ -14,185 +14,183 @@ import ( "go.mongodb.org/mongo-driver/mongo" ) -func AutocompleteDAG() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - var autocompleteDAG []map[string]interface{} - - autocompletePipeline := mongo.Pipeline{ - bson.D{ - {Key: "$lookup", - Value: bson.D{ - {Key: "from", Value: "sections"}, - {Key: "localField", Value: "sections"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "section"}, - }, +func AutocompleteDAG(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + var autocompleteDAG []map[string]interface{} + + autocompletePipeline := mongo.Pipeline{ + bson.D{ + {Key: "$lookup", + Value: bson.D{ + {Key: "from", Value: "sections"}, + {Key: "localField", Value: "sections"}, + {Key: "foreignField", Value: "_id"}, + {Key: "as", Value: "section"}, }, }, + }, - bson.D{ - {Key: "$unwind", - Value: bson.D{ - {Key: "path", Value: "$section"}, - {Key: "preserveNullAndEmptyArrays", Value: true}, - }, + bson.D{ + {Key: "$unwind", + Value: bson.D{ + {Key: "path", Value: "$section"}, + {Key: "preserveNullAndEmptyArrays", Value: true}, }, }, - - bson.D{ - {Key: "$lookup", - Value: bson.D{ - {Key: "from", Value: "professors"}, - {Key: "localField", Value: "section.professors"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "professor"}, - }, + }, + + bson.D{ + {Key: "$lookup", + Value: bson.D{ + {Key: "from", Value: "professors"}, + {Key: "localField", Value: "section.professors"}, + {Key: "foreignField", Value: "_id"}, + {Key: "as", Value: "professor"}, }, }, + }, - bson.D{ - {Key: "$unwind", - Value: bson.D{ - {Key: "path", Value: "$professor"}, - {Key: "preserveNullAndEmptyArrays", Value: true}, - }, + bson.D{ + {Key: "$unwind", + Value: bson.D{ + {Key: "path", Value: "$professor"}, + {Key: "preserveNullAndEmptyArrays", Value: true}, }, }, - - bson.D{ - {Key: "$project", - Value: bson.D{ - {Key: "subject_prefix", Value: "$subject_prefix"}, - {Key: "course_number", Value: "$course_number"}, - {Key: "academic_session.name", Value: "$section.academic_session.name"}, - {Key: "section_number", Value: "$section.section_number"}, - {Key: "professor", - Value: bson.D{ - {Key: "first_name", Value: "$professor.first_name"}, - {Key: "last_name", Value: "$professor.last_name"}, - }, + }, + + bson.D{ + {Key: "$project", + Value: bson.D{ + {Key: "subject_prefix", Value: "$subject_prefix"}, + {Key: "course_number", Value: "$course_number"}, + {Key: "academic_session.name", Value: "$section.academic_session.name"}, + {Key: "section_number", Value: "$section.section_number"}, + {Key: "professor", + Value: bson.D{ + {Key: "first_name", Value: "$professor.first_name"}, + {Key: "last_name", Value: "$professor.last_name"}, }, }, }, }, - - bson.D{ - {Key: "$group", - Value: bson.D{ - {Key: "_id", - Value: bson.D{ - {Key: "subject_prefix", Value: "$subject_prefix"}, - {Key: "course_number", Value: "$course_number"}, - {Key: "academic_session", Value: "$academic_session"}, - {Key: "section_number", Value: "$section_number"}, - }, + }, + + bson.D{ + {Key: "$group", + Value: bson.D{ + {Key: "_id", + Value: bson.D{ + {Key: "subject_prefix", Value: "$subject_prefix"}, + {Key: "course_number", Value: "$course_number"}, + {Key: "academic_session", Value: "$academic_session"}, + {Key: "section_number", Value: "$section_number"}, }, - {Key: "professor", - Value: bson.D{ - {Key: "$push", Value: "$professor"}, - }, + }, + {Key: "professor", + Value: bson.D{ + {Key: "$push", Value: "$professor"}, }, }, }, }, - - bson.D{ - {Key: "$group", - Value: bson.D{ - {Key: "_id", - Value: bson.D{ - {Key: "subject_prefix", Value: "$_id.subject_prefix"}, - {Key: "course_number", Value: "$_id.course_number"}, - {Key: "academic_session", Value: "$_id.academic_session"}, - }, + }, + + bson.D{ + {Key: "$group", + Value: bson.D{ + {Key: "_id", + Value: bson.D{ + {Key: "subject_prefix", Value: "$_id.subject_prefix"}, + {Key: "course_number", Value: "$_id.course_number"}, + {Key: "academic_session", Value: "$_id.academic_session"}, }, - {Key: "sections", - Value: bson.D{ - {Key: "$push", - Value: bson.D{ - {Key: "section_number", Value: "$_id.section_number"}, - {Key: "professors", Value: "$professor"}, - }, + }, + {Key: "sections", + Value: bson.D{ + {Key: "$push", + Value: bson.D{ + {Key: "section_number", Value: "$_id.section_number"}, + {Key: "professors", Value: "$professor"}, }, }, }, }, }, }, - - bson.D{ - {Key: "$group", - Value: bson.D{ - {Key: "_id", - Value: bson.D{ - {Key: "subject_prefix", Value: "$_id.subject_prefix"}, - {Key: "course_number", Value: "$_id.course_number"}, - }, + }, + + bson.D{ + {Key: "$group", + Value: bson.D{ + {Key: "_id", + Value: bson.D{ + {Key: "subject_prefix", Value: "$_id.subject_prefix"}, + {Key: "course_number", Value: "$_id.course_number"}, }, - {Key: "academic_sessions", - Value: bson.D{ - {Key: "$push", - Value: bson.D{ - {Key: "academic_session", Value: "$_id.academic_session"}, - {Key: "sections", Value: "$sections"}, - }, + }, + {Key: "academic_sessions", + Value: bson.D{ + {Key: "$push", + Value: bson.D{ + {Key: "academic_session", Value: "$_id.academic_session"}, + {Key: "sections", Value: "$sections"}, }, }, }, }, }, }, - - bson.D{ - {Key: "$group", - Value: bson.D{ - {Key: "_id", - Value: bson.D{ - {Key: "subject_prefix", Value: "$_id.subject_prefix"}, - }, + }, + + bson.D{ + {Key: "$group", + Value: bson.D{ + {Key: "_id", + Value: bson.D{ + {Key: "subject_prefix", Value: "$_id.subject_prefix"}, }, - {Key: "course_numbers", - Value: bson.D{ - {Key: "$push", - Value: bson.D{ - {Key: "course_number", Value: "$_id.course_number"}, - {Key: "academic_sessions", Value: "$academic_sessions"}, - }, + }, + {Key: "course_numbers", + Value: bson.D{ + {Key: "$push", + Value: bson.D{ + {Key: "course_number", Value: "$_id.course_number"}, + {Key: "academic_sessions", Value: "$academic_sessions"}, }, }, }, }, }, }, - - bson.D{ - {Key: "$project", - Value: bson.D{ - primitive.E{Key: "_id", Value: 0}, - {Key: "subject_prefix", Value: "$_id.subject_prefix"}, - {Key: "course_numbers", Value: "$course_numbers"}, - }, + }, + + bson.D{ + {Key: "$project", + Value: bson.D{ + primitive.E{Key: "_id", Value: 0}, + {Key: "subject_prefix", Value: "$_id.subject_prefix"}, + {Key: "course_numbers", Value: "$course_numbers"}, }, }, - } - - // get cursor for aggregation pipeline query results - // pipeline aggregated against the courses collection - cursor, err := courseCollection.Aggregate(ctx, autocompletePipeline) - if err != nil { - c.JSON(http.StatusInternalServerError, responses.AutocompleteResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, &autocompleteDAG); err != nil { - panic(err) - } - - // return result - c.JSON(http.StatusOK, responses.AutocompleteResponse{Status: http.StatusOK, Message: "success", Data: autocompleteDAG}) + }, + } + + // get cursor for aggregation pipeline query results + // pipeline aggregated against the courses collection + cursor, err := courseCollection.Aggregate(ctx, autocompletePipeline) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.AutocompleteResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, &autocompleteDAG); err != nil { + panic(err) + } + + // return result + c.JSON(http.StatusOK, responses.AutocompleteResponse{Status: http.StatusOK, Message: "success", Data: autocompleteDAG}) } diff --git a/api/controllers/course.go b/api/controllers/course.go index c10a93f..c5c0350 100644 --- a/api/controllers/course.go +++ b/api/controllers/course.go @@ -36,47 +36,45 @@ var courseCollection *mongo.Collection = configs.GetCollection("courses") // @Param lecture_contact_hours query string false "The weekly contact hours in lecture for a course" // @Param offering_frequency query string false "The frequency of offering a course" // @Success 200 {array} schema.Course "A list of courses" -func CourseSearch() gin.HandlerFunc { - return func(c *gin.Context) { - //name := c.Query("name") // value of specific query parameter: string - //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - var courses []schema.Course - - // build query key value pairs (only one value per key) - query, err := schema.FilterQuery[schema.Course](c) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) - return - } - - optionLimit, err := configs.GetOptionLimit(&query, c) - if err != nil { - log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) - c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) - return - } - - // get cursor for query results - cursor, err := courseCollection.Find(ctx, query, optionLimit) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, &courses); err != nil { - log.WritePanic(err) - panic(err) - } - - // return result - c.JSON(http.StatusOK, responses.MultiCourseResponse{Status: http.StatusOK, Message: "success", Data: courses}) +func CourseSearch(c *gin.Context) { + //name := c.Query("name") // value of specific query parameter: string + //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + var courses []schema.Course + + // build query key value pairs (only one value per key) + query, err := schema.FilterQuery[schema.Course](c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) + return + } + + optionLimit, err := configs.GetOptionLimit(&query, c) + if err != nil { + log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) + c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + return + } + + // get cursor for query results + cursor, err := courseCollection.Find(ctx, query, optionLimit) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, &courses); err != nil { + log.WritePanic(err) + panic(err) } + + // return result + c.JSON(http.StatusOK, responses.MultiCourseResponse{Status: http.StatusOK, Message: "success", Data: courses}) } // @Id courseById @@ -85,58 +83,53 @@ func CourseSearch() gin.HandlerFunc { // @Produce json // @Param id path string true "ID of the course to get" // @Success 200 {object} schema.Course "A course" -func CourseById() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - courseId := c.Param("id") - - var course schema.Course - - // parse object id from id parameter - objId, err := primitive.ObjectIDFromHex(courseId) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } - - // find and parse matching course - err = courseCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&course) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // return result - c.JSON(http.StatusOK, responses.SingleCourseResponse{Status: http.StatusOK, Message: "success", Data: course}) +func CourseById(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + courseId := c.Param("id") + + var course schema.Course + + // parse object id from id parameter + objId, err := primitive.ObjectIDFromHex(courseId) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) + return } -} -func CourseAll() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // find and parse matching course + err = courseCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&course) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } - var courses []schema.Course + // return result + c.JSON(http.StatusOK, responses.SingleCourseResponse{Status: http.StatusOK, Message: "success", Data: course}) +} - defer cancel() +func CourseAll(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - cursor, err := courseCollection.Find(ctx, bson.M{}) + var courses []schema.Course - if err != nil { - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } + defer cancel() - // retrieve and parse all valid documents - if err = cursor.All(ctx, &courses); err != nil { - panic(err) - } + cursor, err := courseCollection.Find(ctx, bson.M{}) - // return result - c.JSON(http.StatusOK, responses.MultiCourseResponse{Status: http.StatusOK, Message: "success", Data: courses}) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + // retrieve and parse all valid documents + if err = cursor.All(ctx, &courses); err != nil { + panic(err) } + + // return result + c.JSON(http.StatusOK, responses.MultiCourseResponse{Status: http.StatusOK, Message: "success", Data: courses}) } diff --git a/api/controllers/degree.go b/api/controllers/degree.go index 49d06bf..0aaf907 100644 --- a/api/controllers/degree.go +++ b/api/controllers/degree.go @@ -19,77 +19,73 @@ import ( var degreeCollection *mongo.Collection = configs.GetCollection("degrees") -func DegreeSearch() gin.HandlerFunc { - return func(c *gin.Context) { - //name := c.Query("name") // value of specific query parameter: string - //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - - var degrees []schema.Degree - - defer cancel() - - // build query key value pairs (only one value per key) - query, err := schema.FilterQuery[schema.Degree](c) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) - return - } - - optionLimit, err := configs.GetOptionLimit(&query, c) - if err != nil { - log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) - c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) - return - } - - // get cursor for query results - cursor, err := degreeCollection.Find(ctx, query, optionLimit) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, °rees); err != nil { - log.WritePanic(err) - panic(err) - } - - // return result - c.JSON(http.StatusOK, responses.MultiDegreeResponse{Status: http.StatusOK, Message: "success", Data: degrees}) +func DegreeSearch(c *gin.Context) { + //name := c.Query("name") // value of specific query parameter: string + //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + var degrees []schema.Degree + + defer cancel() + + // build query key value pairs (only one value per key) + query, err := schema.FilterQuery[schema.Degree](c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) + return + } + + optionLimit, err := configs.GetOptionLimit(&query, c) + if err != nil { + log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) + c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + return } -} -func DegreeById() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // get cursor for query results + cursor, err := degreeCollection.Find(ctx, query, optionLimit) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, °rees); err != nil { + log.WritePanic(err) + panic(err) + } - degreeId := c.Param("id") + // return result + c.JSON(http.StatusOK, responses.MultiDegreeResponse{Status: http.StatusOK, Message: "success", Data: degrees}) +} - var degree schema.Degree +func DegreeById(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + degreeId := c.Param("id") - // parse object id from id parameter - objId, err := primitive.ObjectIDFromHex(degreeId) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } + var degree schema.Degree - // find and parse matching degree - err = degreeCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(°ree) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } + defer cancel() - // return result - c.JSON(http.StatusOK, responses.SingleDegreeResponse{Status: http.StatusOK, Message: "success", Data: degree}) + // parse object id from id parameter + objId, err := primitive.ObjectIDFromHex(degreeId) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) + return } + + // find and parse matching degree + err = degreeCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(°ree) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // return result + c.JSON(http.StatusOK, responses.SingleDegreeResponse{Status: http.StatusOK, Message: "success", Data: degree}) } diff --git a/api/controllers/exam.go b/api/controllers/exam.go index f23b275..ae58415 100644 --- a/api/controllers/exam.go +++ b/api/controllers/exam.go @@ -34,49 +34,47 @@ type examFilter struct { // @Param name query string false "The name of the exam" // @Param level query string false "The level of the IB exam (should it be an IB exam)" // @Success 200 {array} responses.MultiExamResponse "A list of exams" -func ExamSearch() gin.HandlerFunc { - return func(c *gin.Context) { - //name := c.Query("name") // value of specific query parameter: string - //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - - // var exams []models.Exam - var exams []map[string]interface{} - - defer cancel() - - // build query key value pairs (only one value per key) - query, err := schema.FilterQuery[examFilter](c) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) - return - } - - optionLimit, err := configs.GetOptionLimit(&query, c) - if err != nil { - log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) - c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) - return - } - - // get cursor for query results - cursor, err := examCollection.Find(ctx, query, optionLimit) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, &exams); err != nil { - log.WritePanic(err) - panic(err) - } - - // return result - c.JSON(http.StatusOK, responses.MultiExamResponse{Status: http.StatusOK, Message: "success", Data: exams}) +func ExamSearch(c *gin.Context) { + //name := c.Query("name") // value of specific query parameter: string + //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + // var exams []models.Exam + var exams []map[string]interface{} + + defer cancel() + + // build query key value pairs (only one value per key) + query, err := schema.FilterQuery[examFilter](c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) + return + } + + optionLimit, err := configs.GetOptionLimit(&query, c) + if err != nil { + log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) + c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + return } + + // get cursor for query results + cursor, err := examCollection.Find(ctx, query, optionLimit) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, &exams); err != nil { + log.WritePanic(err) + panic(err) + } + + // return result + c.JSON(http.StatusOK, responses.MultiExamResponse{Status: http.StatusOK, Message: "success", Data: exams}) } // @Id examById @@ -85,64 +83,60 @@ func ExamSearch() gin.HandlerFunc { // @Produce json // @Param id path string true "ID of the exam to get" // @Success 200 {object} responses.SingleExamResponse "An exam" -func ExamById() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - - examId := c.Param("id") - - // @TODO: Fix with model - There is NO typechecking! - // var exam models.Exam - var exam map[string]interface{} - - defer cancel() - - // parse object id from id parameter - objId, err := primitive.ObjectIDFromHex(examId) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } - - // find and parse matching exam - err = examCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&exam) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // return result - c.JSON(http.StatusOK, responses.SingleExamResponse{Status: http.StatusOK, Message: "success", Data: exam}) +func ExamById(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + examId := c.Param("id") + + // @TODO: Fix with model - There is NO typechecking! + // var exam models.Exam + var exam map[string]interface{} + + defer cancel() + + // parse object id from id parameter + objId, err := primitive.ObjectIDFromHex(examId) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) + return } -} -func ExamAll() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // find and parse matching exam + err = examCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&exam) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } - // @TODO: Fix with model - There is NO typechecking! - var exams []map[string]interface{} + // return result + c.JSON(http.StatusOK, responses.SingleExamResponse{Status: http.StatusOK, Message: "success", Data: exam}) +} - defer cancel() +func ExamAll(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - // get cursor for all exams in the collection - cursor, err := examCollection.Find(ctx, bson.M{}) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } + // @TODO: Fix with model - There is NO typechecking! + var exams []map[string]interface{} - // retrieve and parse all valid documents - err = cursor.All(ctx, &exams) - if err != nil { - log.WritePanic(err) - panic(err) - } + defer cancel() - // return result - c.JSON(http.StatusOK, responses.MultiExamResponse{Status: http.StatusOK, Message: "success", Data: exams}) + // get cursor for all exams in the collection + cursor, err := examCollection.Find(ctx, bson.M{}) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return } + + // retrieve and parse all valid documents + err = cursor.All(ctx, &exams) + if err != nil { + log.WritePanic(err) + panic(err) + } + + // return result + c.JSON(http.StatusOK, responses.MultiExamResponse{Status: http.StatusOK, Message: "success", Data: exams}) } diff --git a/api/controllers/grades.go b/api/controllers/grades.go index 475be55..f76ba33 100644 --- a/api/controllers/grades.go +++ b/api/controllers/grades.go @@ -60,8 +60,10 @@ import ( // @Param last_name query string false "The professors's last name" // @Param section_number query string false "The number of the section" // @Success 200 {array} responses.GradeResponse "An array of grade distributions for each semester included" -func GradeAggregationBySemester() gin.HandlerFunc { - return GradesAggregation("semester") +func GradeAggregationSemester() gin.HandlerFunc { + return func(c *gin.Context) { + gradesAggregation("semester", c) + } } // @Id gradeAggregationOverall @@ -75,242 +77,242 @@ func GradeAggregationBySemester() gin.HandlerFunc { // @Param section_number query string false "The number of the section" // @Success 200 {array} integer "A grade distribution array" func GradesAggregationOverall() gin.HandlerFunc { - return GradesAggregation("overall") + return func(c *gin.Context) { + gradesAggregation("overall", c) + } } -func GradesAggregation(flag string) gin.HandlerFunc { - return func(c *gin.Context) { - var grades []map[string]interface{} - var results []map[string]interface{} - - var cursor *mongo.Cursor - var collection *mongo.Collection - var pipeline mongo.Pipeline - - var sectionMatch bson.D - var courseMatch bson.D - var courseFind bson.D - var professorMatch bson.D - var professorFind bson.D - - var err error - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // @TODO: Recommend forcing using first_name and last_name to ensure single professors per query. - // All professors sharing the name will be aggregated together in the current implementation - prefix := c.Query("prefix") - number := c.Query("number") - section_number := c.Query("section_number") - first_name := c.Query("first_name") - last_name := c.Query("last_name") - - professor := (first_name != "" || last_name != "") - - lookupSectionsStage := bson.D{ - {Key: "$lookup", Value: bson.D{ - {Key: "from", Value: "sections"}, - {Key: "localField", Value: "sections"}, - {Key: "foreignField", Value: "_id"}, - {Key: "as", Value: "sections"}, - }}, - } +// base function, returns the grade distribution depending on type of flag +func gradesAggregation(flag string, c *gin.Context) { + var grades []map[string]interface{} + var results []map[string]interface{} + + var cursor *mongo.Cursor + var collection *mongo.Collection + var pipeline mongo.Pipeline + + var sectionMatch bson.D + var courseMatch bson.D + var courseFind bson.D + var professorMatch bson.D + var professorFind bson.D + + var err error + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // @TODO: Recommend forcing using first_name and last_name to ensure single professors per query. + // All professors sharing the name will be aggregated together in the current implementation + prefix := c.Query("prefix") + number := c.Query("number") + section_number := c.Query("section_number") + first_name := c.Query("first_name") + last_name := c.Query("last_name") + + professor := (first_name != "" || last_name != "") + + lookupSectionsStage := bson.D{ + {Key: "$lookup", Value: bson.D{ + {Key: "from", Value: "sections"}, + {Key: "localField", Value: "sections"}, + {Key: "foreignField", Value: "_id"}, + {Key: "as", Value: "sections"}, + }}, + } - unwindSectionsStage := bson.D{{Key: "$unwind", Value: bson.D{{Key: "path", Value: "$sections"}}}} + unwindSectionsStage := bson.D{{Key: "$unwind", Value: bson.D{{Key: "path", Value: "$sections"}}}} - projectGradeDistributionStage := bson.D{ - {Key: "$project", Value: bson.D{ - {Key: "_id", Value: "$sections.academic_session.name"}, - {Key: "grade_distribution", Value: "$sections.grade_distribution"}, - }}, - } + projectGradeDistributionStage := bson.D{ + {Key: "$project", Value: bson.D{ + {Key: "_id", Value: "$sections.academic_session.name"}, + {Key: "grade_distribution", Value: "$sections.grade_distribution"}, + }}, + } - projectGradeDistributionWithSectionsStage := bson.D{ - {Key: "$project", Value: bson.D{ - {Key: "_id", Value: "$academic_session.name"}, - {Key: "grade_distribution", Value: "$grade_distribution"}, - }}, - } + projectGradeDistributionWithSectionsStage := bson.D{ + {Key: "$project", Value: bson.D{ + {Key: "_id", Value: "$academic_session.name"}, + {Key: "grade_distribution", Value: "$grade_distribution"}, + }}, + } - unwindGradeDistributionStage := bson.D{ - {Key: "$unwind", Value: bson.D{ - {Key: "path", Value: "$grade_distribution"}, - {Key: "includeArrayIndex", Value: "ix"}, - }}, - } + unwindGradeDistributionStage := bson.D{ + {Key: "$unwind", Value: bson.D{ + {Key: "path", Value: "$grade_distribution"}, + {Key: "includeArrayIndex", Value: "ix"}, + }}, + } - groupGradesStage := bson.D{ - {Key: "$group", Value: bson.D{ - {Key: "_id", Value: bson.D{ - {Key: "academic_session", Value: "$_id"}, - {Key: "ix", Value: "$ix"}, - }}, - {Key: "grades", Value: bson.D{{Key: "$push", Value: "$grade_distribution"}}}, + groupGradesStage := bson.D{ + {Key: "$group", Value: bson.D{ + {Key: "_id", Value: bson.D{ + {Key: "academic_session", Value: "$_id"}, + {Key: "ix", Value: "$ix"}, }}, - } + {Key: "grades", Value: bson.D{{Key: "$push", Value: "$grade_distribution"}}}, + }}, + } - sortGradesStage := bson.D{ - {Key: "$sort", Value: bson.D{ - {Key: "_id.ix", Value: 1}, - {Key: "_id", Value: 1}, - }}, - } + sortGradesStage := bson.D{ + {Key: "$sort", Value: bson.D{ + {Key: "_id.ix", Value: 1}, + {Key: "_id", Value: 1}, + }}, + } - sumGradesStage := bson.D{{Key: "$addFields", Value: bson.D{{Key: "grades", Value: bson.D{{Key: "$sum", Value: "$grades"}}}}}} + sumGradesStage := bson.D{{Key: "$addFields", Value: bson.D{{Key: "grades", Value: bson.D{{Key: "$sum", Value: "$grades"}}}}}} - groupGradeDistributionStage := bson.D{ - {Key: "$group", Value: bson.D{ - {Key: "_id", Value: "$_id.academic_session"}, - {Key: "grade_distribution", Value: bson.D{{Key: "$push", Value: "$grades"}}}, - }}, + groupGradeDistributionStage := bson.D{ + {Key: "$group", Value: bson.D{ + {Key: "_id", Value: "$_id.academic_session"}, + {Key: "grade_distribution", Value: bson.D{{Key: "$push", Value: "$grades"}}}, + }}, + } + switch { + case prefix != "" && number == "" && section_number == "" && !professor: + // Filter on Course + collection = courseCollection + courseMatch = bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix}}} + pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} + + case prefix != "" && number != "" && section_number == "" && !professor: + // Filter on Course + collection = courseCollection + courseMatch := bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix, "course_number": number}}} + pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} + + case prefix != "" && number != "" && section_number != "" && !professor: + // Filter on Course then Section + collection = courseCollection + courseMatch := bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix, "course_number": number}}} + sectionMatch := bson.D{{Key: "$match", Value: bson.M{"sections.section_number": section_number}}} + pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, sectionMatch, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} + + case prefix == "" && number == "" && section_number == "" && professor: + // Filter on Professor + collection = professorCollection + + // Build professorMatch + if last_name == "" { + professorMatch = bson.D{{Key: "$match", Value: bson.M{"first_name": first_name}}} + } else if first_name == "" { + professorMatch = bson.D{{Key: "$match", Value: bson.M{"last_name": last_name}}} + } else { + professorMatch = bson.D{{Key: "$match", Value: bson.M{"first_name": first_name, "last_name": last_name}}} } - switch { - case prefix != "" && number == "" && section_number == "" && !professor: - // Filter on Course - collection = courseCollection - courseMatch = bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix}}} - pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} - - case prefix != "" && number != "" && section_number == "" && !professor: - // Filter on Course - collection = courseCollection - courseMatch := bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix, "course_number": number}}} - pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} - - case prefix != "" && number != "" && section_number != "" && !professor: - // Filter on Course then Section - collection = courseCollection - courseMatch := bson.D{{Key: "$match", Value: bson.M{"subject_prefix": prefix, "course_number": number}}} - sectionMatch := bson.D{{Key: "$match", Value: bson.M{"sections.section_number": section_number}}} - pipeline = mongo.Pipeline{courseMatch, lookupSectionsStage, unwindSectionsStage, sectionMatch, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} - - case prefix == "" && number == "" && section_number == "" && professor: - // Filter on Professor - collection = professorCollection - - // Build professorMatch - if last_name == "" { - professorMatch = bson.D{{Key: "$match", Value: bson.M{"first_name": first_name}}} - } else if first_name == "" { - professorMatch = bson.D{{Key: "$match", Value: bson.M{"last_name": last_name}}} - } else { - professorMatch = bson.D{{Key: "$match", Value: bson.M{"first_name": first_name, "last_name": last_name}}} - } - - // Build grades pipeline - pipeline = mongo.Pipeline{professorMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} - - case prefix != "" && professor: - // Filter on Section by Matching Course and Professor IDs - // Here we get the valid course ids and professor ids - // and then we perform the grades aggregation against the sections collection, - // matching on the course_reference and professor - - var profIDs []primitive.ObjectID - var courseIDs []primitive.ObjectID - - collection = sectionCollection - - // Find valid professor ids - if last_name == "" { - professorFind = bson.D{{Key: "first_name", Value: first_name}} - } else if first_name == "" { - professorFind = bson.D{{Key: "last_name", Value: last_name}} - } else { - professorFind = bson.D{{Key: "first_name", Value: first_name}, {Key: "last_name", Value: last_name}} - } + // Build grades pipeline + pipeline = mongo.Pipeline{professorMatch, lookupSectionsStage, unwindSectionsStage, projectGradeDistributionStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} - cursor, err = professorCollection.Find(ctx, professorFind) - if err != nil { - c.JSON(http.StatusInternalServerError, responses.GradeResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - if err = cursor.All(ctx, &results); err != nil { - panic(err) - } + case prefix != "" && professor: + // Filter on Section by Matching Course and Professor IDs - for _, prof := range results { - profID := prof["_id"].(primitive.ObjectID) - profIDs = append(profIDs, profID) - } + // Here we get the valid course ids and professor ids + // and then we perform the grades aggregation against the sections collection, + // matching on the course_reference and professor - // Get valid course ids - if number == "" { - courseFind = bson.D{{Key: "subject_prefix", Value: prefix}} - } else { - courseFind = bson.D{{Key: "subject_prefix", Value: prefix}, {Key: "course_number", Value: number}} - } + var profIDs []primitive.ObjectID + var courseIDs []primitive.ObjectID - cursor, err = courseCollection.Find(ctx, courseFind) - if err != nil { - c.JSON(http.StatusInternalServerError, responses.GradeResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - if err = cursor.All(ctx, &results); err != nil { - panic(err) - } + collection = sectionCollection - for _, course := range results { - courseID := course["_id"].(primitive.ObjectID) - courseIDs = append(courseIDs, courseID) - } + // Find valid professor ids + if last_name == "" { + professorFind = bson.D{{Key: "first_name", Value: first_name}} + } else if first_name == "" { + professorFind = bson.D{{Key: "last_name", Value: last_name}} + } else { + professorFind = bson.D{{Key: "first_name", Value: first_name}, {Key: "last_name", Value: last_name}} + } - // Build sectionMatch - if section_number == "" { - sectionMatch = - bson.D{{Key: "$match", Value: bson.D{ - {Key: "course_reference", Value: bson.D{{Key: "$in", Value: courseIDs}}}, - {Key: "professors", Value: bson.D{{Key: "$in", Value: profIDs}}}, - }}} - } else { - sectionMatch = - bson.D{{Key: "$match", Value: bson.D{ - {Key: "course_reference", Value: bson.D{{Key: "$in", Value: courseIDs}}}, - {Key: "professors", Value: bson.D{{Key: "$in", Value: profIDs}}}, - {Key: "section_number", Value: section_number}, - }}} - } + cursor, err = professorCollection.Find(ctx, professorFind) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.GradeResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + if err = cursor.All(ctx, &results); err != nil { + panic(err) + } - // Build grades pipeline - pipeline = mongo.Pipeline{sectionMatch, projectGradeDistributionWithSectionsStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} + for _, prof := range results { + profID := prof["_id"].(primitive.ObjectID) + profIDs = append(profIDs, profID) + } - default: - c.JSON(http.StatusBadRequest, responses.GradeResponse{Status: http.StatusBadRequest, Message: "error", Data: "Invalid query parameters."}) - return + // Get valid course ids + if number == "" { + courseFind = bson.D{{Key: "subject_prefix", Value: prefix}} + } else { + courseFind = bson.D{{Key: "subject_prefix", Value: prefix}, {Key: "course_number", Value: number}} } - // peform aggregation - cursor, err = collection.Aggregate(ctx, pipeline) + cursor, err = courseCollection.Find(ctx, courseFind) if err != nil { - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + c.JSON(http.StatusInternalServerError, responses.GradeResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) return } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, &grades); err != nil { + if err = cursor.All(ctx, &results); err != nil { panic(err) } - if flag == "overall" { - // combine all semester grade_distributions - overallResponse := make([]int32, 14) - for _, sem := range grades { - if len(sem["grade_distribution"].(primitive.A)) != 14 { - print("Length of Array: ") - println(len(sem["grade_distribution"].(primitive.A))) - } - for i, grade := range sem["grade_distribution"].(primitive.A) { - overallResponse[i] += grade.(int32) - } - } - c.JSON(http.StatusOK, responses.GradeResponse{Status: http.StatusOK, Message: "success", Data: overallResponse}) - } else if flag == "semester" { - c.JSON(http.StatusOK, responses.GradeResponse{Status: http.StatusOK, Message: "success", Data: grades}) + for _, course := range results { + courseID := course["_id"].(primitive.ObjectID) + courseIDs = append(courseIDs, courseID) + } + + // Build sectionMatch + if section_number == "" { + sectionMatch = + bson.D{{Key: "$match", Value: bson.D{ + {Key: "course_reference", Value: bson.D{{Key: "$in", Value: courseIDs}}}, + {Key: "professors", Value: bson.D{{Key: "$in", Value: profIDs}}}, + }}} } else { - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: "Endpoint broken"}) + sectionMatch = + bson.D{{Key: "$match", Value: bson.D{ + {Key: "course_reference", Value: bson.D{{Key: "$in", Value: courseIDs}}}, + {Key: "professors", Value: bson.D{{Key: "$in", Value: profIDs}}}, + {Key: "section_number", Value: section_number}, + }}} } + // Build grades pipeline + pipeline = mongo.Pipeline{sectionMatch, projectGradeDistributionWithSectionsStage, unwindGradeDistributionStage, groupGradesStage, sortGradesStage, sumGradesStage, groupGradeDistributionStage} + + default: + c.JSON(http.StatusBadRequest, responses.GradeResponse{Status: http.StatusBadRequest, Message: "error", Data: "Invalid query parameters."}) + return + } + + // peform aggregation + cursor, err = collection.Aggregate(ctx, pipeline) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, &grades); err != nil { + panic(err) + } + + if flag == "overall" { + // combine all semester grade_distributions + overallResponse := make([]int32, 14) + for _, sem := range grades { + if len(sem["grade_distribution"].(primitive.A)) != 14 { + print("Length of Array: ") + println(len(sem["grade_distribution"].(primitive.A))) + } + for i, grade := range sem["grade_distribution"].(primitive.A) { + overallResponse[i] += grade.(int32) + } + } + c.JSON(http.StatusOK, responses.GradeResponse{Status: http.StatusOK, Message: "success", Data: overallResponse}) + } else if flag == "semester" { + c.JSON(http.StatusOK, responses.GradeResponse{Status: http.StatusOK, Message: "success", Data: grades}) + } else { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: "Endpoint broken"}) } } diff --git a/api/controllers/professor.go b/api/controllers/professor.go index 9ed76f4..705a075 100644 --- a/api/controllers/professor.go +++ b/api/controllers/professor.go @@ -44,48 +44,46 @@ var professorCollection *mongo.Collection = configs.GetCollection("professors") // @Param office_hours.location.map_uri query string false "A hyperlink to the UTD room locator of one of the office hours meetings of the professor" // @Param sections query string false "The _id of one of the sections the professor teaches" // @Success 200 {array} schema.Professor "A list of professors" -func ProfessorSearch() gin.HandlerFunc { - return func(c *gin.Context) { - //name := c.Query("name") // value of specific query parameter: string - //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - - var professors []schema.Professor - - defer cancel() - - // build query key value pairs (only one value per key) - query, err := schema.FilterQuery[schema.Professor](c) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) - return - } - - optionLimit, err := configs.GetOptionLimit(&query, c) - if err != nil { - log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) - c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) - return - } - - // get cursor for query results - cursor, err := professorCollection.Find(ctx, query, optionLimit) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // retrieve and parse all valid documents - if err = cursor.All(ctx, &professors); err != nil { - log.WritePanic(err) - panic(err) - } - - // return result - c.JSON(http.StatusOK, responses.MultiProfessorResponse{Status: http.StatusOK, Message: "success", Data: professors}) +func ProfessorSearch(c *gin.Context) { + //name := c.Query("name") // value of specific query parameter: string + //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + var professors []schema.Professor + + defer cancel() + + // build query key value pairs (only one value per key) + query, err := schema.FilterQuery[schema.Professor](c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) + return + } + + optionLimit, err := configs.GetOptionLimit(&query, c) + if err != nil { + log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) + c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + return + } + + // get cursor for query results + cursor, err := professorCollection.Find(ctx, query, optionLimit) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, &professors); err != nil { + log.WritePanic(err) + panic(err) } + + // return result + c.JSON(http.StatusOK, responses.MultiProfessorResponse{Status: http.StatusOK, Message: "success", Data: professors}) } // @Id professorById @@ -94,59 +92,54 @@ func ProfessorSearch() gin.HandlerFunc { // @Produce json // @Param id path string true "ID of the professor to get" // @Success 200 {object} schema.Professor "A professor" -func ProfessorById() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - - professorId := c.Param("id") - - var professor schema.Professor - - defer cancel() - - // parse object id from id parameter - objId, err := primitive.ObjectIDFromHex(professorId) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } - - // find and parse matching professor - err = professorCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&professor) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } - - // return result - c.JSON(http.StatusOK, responses.SingleProfessorResponse{Status: http.StatusOK, Message: "success", Data: professor}) +func ProfessorById(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + professorId := c.Param("id") + + var professor schema.Professor + + defer cancel() + + // parse object id from id parameter + objId, err := primitive.ObjectIDFromHex(professorId) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) + return } -} -func ProfessorAll() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // find and parse matching professor + err = professorCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&professor) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } - var professors []schema.Professor + // return result + c.JSON(http.StatusOK, responses.SingleProfessorResponse{Status: http.StatusOK, Message: "success", Data: professor}) +} - defer cancel() +func ProfessorAll(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - cursor, err := professorCollection.Find(ctx, bson.M{}) + var professors []schema.Professor - if err != nil { - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } + defer cancel() - // retrieve and parse all valid documents - if err = cursor.All(ctx, &professors); err != nil { - panic(err) - } + cursor, err := professorCollection.Find(ctx, bson.M{}) - // return result - c.JSON(http.StatusOK, responses.MultiProfessorResponse{Status: http.StatusOK, Message: "success", Data: professors}) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return + } + // retrieve and parse all valid documents + if err = cursor.All(ctx, &professors); err != nil { + panic(err) } + + // return result + c.JSON(http.StatusOK, responses.MultiProfessorResponse{Status: http.StatusOK, Message: "success", Data: professors}) } diff --git a/api/controllers/section.go b/api/controllers/section.go index 95107d6..1df2929 100644 --- a/api/controllers/section.go +++ b/api/controllers/section.go @@ -47,68 +47,66 @@ var sectionCollection *mongo.Collection = configs.GetCollection("sections") // @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 {array} schema.Section "A list of sections" -func SectionSearch() gin.HandlerFunc { - return func(c *gin.Context) { - //name := c.Query("name") // value of specific query parameter: string - //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string +func SectionSearch(c *gin.Context) { + //name := c.Query("name") // value of specific query parameter: string + //queryParams := c.Request.URL.Query() // map of all query params: map[string][]string - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - var sections []schema.Section + var sections []schema.Section - defer cancel() + defer cancel() - // build query key value pairs (only one value per key) - query, err := schema.FilterQuery[schema.Section](c) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) - return - } - - if v, ok := query["course_reference"]; ok { - objId, err := primitive.ObjectIDFromHex(v.(string)) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } else { - query["course_reference"] = objId - } - } - - if v, ok := query["professor"]; ok { - objId, err := primitive.ObjectIDFromHex(v.(string)) - if err != nil { - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } else { - query["professor"] = objId - } - } + // build query key value pairs (only one value per key) + query, err := schema.FilterQuery[schema.Section](c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "schema validation error", Data: err.Error()}) + return + } - optionLimit, err := configs.GetOptionLimit(&query, c) + if v, ok := query["course_reference"]; ok { + objId, err := primitive.ObjectIDFromHex(v.(string)) if err != nil { - log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) - c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) return + } else { + query["course_reference"] = objId } + } - // get cursor for query results - cursor, err := sectionCollection.Find(ctx, query, optionLimit) + if v, ok := query["professor"]; ok { + objId, err := primitive.ObjectIDFromHex(v.(string)) if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) return + } else { + query["professor"] = objId } + } - // retrieve and parse all valid documents - if err = cursor.All(ctx, §ions); err != nil { - log.WritePanic(err) - panic(err) - } + optionLimit, err := configs.GetOptionLimit(&query, c) + if err != nil { + log.WriteErrorWithMsg(err, log.OffsetNotTypeInteger) + c.JSON(http.StatusConflict, responses.ErrorResponse{Status: http.StatusConflict, Message: "Error offset is not type integer", Data: err.Error()}) + return + } - // return result - c.JSON(http.StatusOK, responses.MultiSectionResponse{Status: http.StatusOK, Message: "success", Data: sections}) + // get cursor for query results + cursor, err := sectionCollection.Find(ctx, query, optionLimit) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return } + + // retrieve and parse all valid documents + if err = cursor.All(ctx, §ions); err != nil { + log.WritePanic(err) + panic(err) + } + + // return result + c.JSON(http.StatusOK, responses.MultiSectionResponse{Status: http.StatusOK, Message: "success", Data: sections}) } // @Id sectionById @@ -117,33 +115,31 @@ func SectionSearch() gin.HandlerFunc { // @Produce json // @Param id path string true "ID of the section to get" // @Success 200 {object} schema.Section "A section" -func SectionById() gin.HandlerFunc { - return func(c *gin.Context) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) +func SectionById(c *gin.Context) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - sectionId := c.Param("id") + sectionId := c.Param("id") - var section schema.Section + var section schema.Section - defer cancel() + defer cancel() - // parse object id from id parameter - objId, err := primitive.ObjectIDFromHex(sectionId) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) - return - } - - // find and parse matching section - err = sectionCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(§ion) - if err != nil { - log.WriteError(err) - c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) - return - } + // parse object id from id parameter + objId, err := primitive.ObjectIDFromHex(sectionId) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusBadRequest, responses.ErrorResponse{Status: http.StatusBadRequest, Message: "error", Data: err.Error()}) + return + } - // return result - c.JSON(http.StatusOK, responses.SingleSectionResponse{Status: http.StatusOK, Message: "success", Data: section}) + // find and parse matching section + err = sectionCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(§ion) + if err != nil { + log.WriteError(err) + c.JSON(http.StatusInternalServerError, responses.ErrorResponse{Status: http.StatusInternalServerError, Message: "error", Data: err.Error()}) + return } + + // return result + c.JSON(http.StatusOK, responses.SingleSectionResponse{Status: http.StatusOK, Message: "success", Data: section}) } diff --git a/api/routes/autocomplete.go b/api/routes/autocomplete.go index f9acda4..1b2f055 100644 --- a/api/routes/autocomplete.go +++ b/api/routes/autocomplete.go @@ -10,5 +10,5 @@ func AutocompleteRoute(router *gin.Engine) { // All routes related to autocomplete come here autocompleteGroup := router.Group("/autocomplete") - autocompleteGroup.GET("/dag", controllers.AutocompleteDAG()) + autocompleteGroup.GET("/dag", controllers.AutocompleteDAG) } diff --git a/api/routes/course.go b/api/routes/course.go index 346aac4..9feae03 100644 --- a/api/routes/course.go +++ b/api/routes/course.go @@ -11,7 +11,7 @@ func CourseRoute(router *gin.Engine) { courseGroup := router.Group("/course") courseGroup.OPTIONS("", controllers.Preflight) - courseGroup.GET("", controllers.CourseSearch()) - courseGroup.GET(":id", controllers.CourseById()) - courseGroup.GET("all", controllers.CourseAll()) + courseGroup.GET("", controllers.CourseSearch) + courseGroup.GET(":id", controllers.CourseById) + courseGroup.GET("all", controllers.CourseAll) } diff --git a/api/routes/degree.go b/api/routes/degree.go index 9dfc8b1..84c0e32 100644 --- a/api/routes/degree.go +++ b/api/routes/degree.go @@ -11,6 +11,6 @@ func DegreeRoute(router *gin.Engine) { degreeGroup := router.Group("/degree") degreeGroup.OPTIONS("", controllers.Preflight) - degreeGroup.GET("", controllers.DegreeSearch()) - degreeGroup.GET(":id", controllers.DegreeById()) + degreeGroup.GET("", controllers.DegreeSearch) + degreeGroup.GET(":id", controllers.DegreeById) } diff --git a/api/routes/exam.go b/api/routes/exam.go index 3a6e1aa..0566ea2 100644 --- a/api/routes/exam.go +++ b/api/routes/exam.go @@ -11,7 +11,7 @@ func ExamRoute(router *gin.Engine) { examGroup := router.Group("/exam") examGroup.OPTIONS("", controllers.Preflight) - examGroup.GET("", controllers.ExamSearch()) - examGroup.GET("all", controllers.ExamAll()) - examGroup.GET(":id", controllers.ExamById()) + examGroup.GET("", controllers.ExamSearch) + examGroup.GET("all", controllers.ExamAll) + examGroup.GET(":id", controllers.ExamById) } diff --git a/api/routes/grades.go b/api/routes/grades.go index d6fc3ab..734ee58 100644 --- a/api/routes/grades.go +++ b/api/routes/grades.go @@ -16,6 +16,6 @@ func GradesRoute(router *gin.Engine) { // ---- gradesGroup.OPTIONS("semester", controllers.Preflight) // ---- gradesGroup.OPTIONS("overall", controllers.Preflight) - gradesGroup.GET("semester", controllers.GradesAggregation("semester")) - gradesGroup.GET("overall", controllers.GradesAggregation("overall")) + gradesGroup.GET("semester", controllers.GradeAggregationSemester()) + gradesGroup.GET("overall", controllers.GradesAggregationOverall()) } diff --git a/api/routes/professor.go b/api/routes/professor.go index 7aa0708..d006e70 100644 --- a/api/routes/professor.go +++ b/api/routes/professor.go @@ -11,7 +11,7 @@ func ProfessorRoute(router *gin.Engine) { professorGroup := router.Group("/professor") professorGroup.OPTIONS("", controllers.Preflight) - professorGroup.GET("", controllers.ProfessorSearch()) - professorGroup.GET(":id", controllers.ProfessorById()) - professorGroup.GET("all", controllers.ProfessorAll()) + professorGroup.GET("", controllers.ProfessorSearch) + professorGroup.GET(":id", controllers.ProfessorById) + professorGroup.GET("all", controllers.ProfessorAll) } diff --git a/api/routes/section.go b/api/routes/section.go index 395218c..5b7dc10 100644 --- a/api/routes/section.go +++ b/api/routes/section.go @@ -11,7 +11,7 @@ func SectionRoute(router *gin.Engine) { sectionGroup := router.Group("/section") sectionGroup.OPTIONS("", controllers.Preflight) - sectionGroup.GET("", controllers.SectionSearch()) - sectionGroup.GET(":id", controllers.SectionById()) + sectionGroup.GET("", controllers.SectionSearch) + sectionGroup.GET(":id", controllers.SectionById) sectionGroup.GET(":id/evaluation", controllers.EvalBySectionID) }