diff --git a/app/controller/problem_set.go b/app/controller/problem_set.go index ae276331..f6f293da 100644 --- a/app/controller/problem_set.go +++ b/app/controller/problem_set.go @@ -485,19 +485,19 @@ func RefreshGrades(c echo.Context) error { }) } -func GetGrades(c echo.Context) error { +func GetProblemSetGrades(c echo.Context) error { problemSet := models.ProblemSet{} if err := base.DB.Preload("Problems").Preload("Class.Students").Preload("Grades"). First(&problemSet, "id = ? and class_id = ?", c.Param("id"), c.Param("class_id")).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return c.JSON(http.StatusNotFound, response.ErrorResp("NOT_FOUND", nil)) } - panic(errors.Wrap(err, "could not get problem set for getting grades")) + panic(errors.Wrap(err, "could not get problem set for getting problem set grades")) } if err := utils.CreateEmptyGrades(&problemSet); err != nil { - panic(errors.Wrap(err, "could not get grades")) + panic(errors.Wrap(err, "could not create empty grades to get problem set grades")) } - return c.JSON(http.StatusOK, response.GetGradesResponse{ + return c.JSON(http.StatusOK, response.GetProblemSetGradesResponse{ Message: "SUCCESS", Error: nil, Data: struct { @@ -507,3 +507,31 @@ func GetGrades(c echo.Context) error { }, }) } + +func GetClassGrades(c echo.Context) error { + class := models.Class{} + if err := base.DB.Preload("Students").Preload("ProblemSets.Grades").Preload("ProblemSets.Problems"). + First(&class, "id = ?", c.Param("id")).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.JSON(http.StatusNotFound, response.ErrorResp("NOT_FOUND", nil)) + } + panic(errors.Wrap(err, "could not get class for getting class grades")) + } + ret := make([]*resource.ProblemSetWithGrades, 0, len(class.ProblemSets)) + for _, problemSet := range class.ProblemSets { + problemSet.Class = &class + if err := utils.CreateEmptyGrades(problemSet); err != nil { + panic(errors.Wrap(err, "could not create empty grades to get class grades")) + } + ret = append(ret, resource.GetProblemSetWithGrades(problemSet)) + } + return c.JSON(http.StatusOK, response.GetClassGradesResponse{ + Message: "SUCCESS", + Error: nil, + Data: struct { + ProblemSets []*resource.ProblemSetWithGrades `json:"problem_sets"` + }{ + ret, + }, + }) +} diff --git a/app/controller/problem_set_test.go b/app/controller/problem_set_test.go index 4adffcb2..b3d2e252 100644 --- a/app/controller/problem_set_test.go +++ b/app/controller/problem_set_test.go @@ -1420,18 +1420,37 @@ func TestRefreshGrades(t *testing.T) { }) } -func TestGetGrades(t *testing.T) { +func TestGetProblemSetGrades(t *testing.T) { t.Parallel() - user1 := createUserForTest(t, "get_grades", 1) - user2 := createUserForTest(t, "get_grades", 2) - class := createClassForTest(t, "get_grades", 0, nil, []*models.User{&user1, &user2}) - problemSet := createProblemSetForTest(t, "get_grades_fail", 0, &class, nil, inProgress) + + // Prepare fake data + user1 := createUserForTest(t, "get_problem_set_grades", 1) + user2 := createUserForTest(t, "get_problem_set_grades", 2) + class := createClassForTest(t, "get_problem_set_grades", 0, nil, []*models.User{&user1, &user2}) + problem1 := createProblemForTest(t, "get_problem_set_grades", 1, nil, user1) + problem2 := createProblemForTest(t, "get_problem_set_grades", 2, nil, user1) + problemSet := createProblemSetForTest(t, "get_problem_set_grades", 0, &class, []models.Problem{problem1, problem2}, inProgress) + jsonExisting, err := json.Marshal(map[uint]uint{ + problem1.ID: 40, + problem2.ID: 0, + }) + assert.NoError(t, err) + gradeExisting := models.Grade{ + UserID: user1.ID, + ProblemSetID: problemSet.ID, + ClassID: class.ID, + Detail: jsonExisting, + Total: 40, + } + assert.NoError(t, err) + assert.NoError(t, base.DB.Create(&gradeExisting).Error) + failTests := []failTest{ { name: "NonExistingClass", method: "GET", - path: base.Echo.Reverse("problemSet.GetGrades", -1, problemSet.ID), - req: request.GetGradesRequest{}, + path: base.Echo.Reverse("problemSet.GetProblemSetGrades", -1, problemSet.ID), + req: request.GetProblemSetGradesRequest{}, reqOptions: []reqOption{ applyAdminUser, }, @@ -1441,8 +1460,8 @@ func TestGetGrades(t *testing.T) { { name: "NonExistingProblemSet", method: "GET", - path: base.Echo.Reverse("problemSet.GetGrades", class.ID, -1), - req: request.GetGradesRequest{}, + path: base.Echo.Reverse("problemSet.GetProblemSetGrades", class.ID, -1), + req: request.GetProblemSetGradesRequest{}, reqOptions: []reqOption{ applyAdminUser, }, @@ -1452,8 +1471,8 @@ func TestGetGrades(t *testing.T) { { name: "PermissionDenied", method: "GET", - path: base.Echo.Reverse("problemSet.GetGrades", class.ID, -1), - req: request.GetGradesRequest{}, + path: base.Echo.Reverse("problemSet.GetProblemSetGrades", class.ID, problemSet.ID), + req: request.GetProblemSetGradesRequest{}, reqOptions: []reqOption{ applyNormalUser, }, @@ -1462,46 +1481,39 @@ func TestGetGrades(t *testing.T) { }, } - runFailTests(t, failTests, "GetGrades") + runFailTests(t, failTests, "GetProblemSetGrades") - t.Run("Empty", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { t.Parallel() - user1 := createUserForTest(t, "get_grades", 3) - user2 := createUserForTest(t, "get_grades", 4) - class := createClassForTest(t, "get_grades", 4, nil, []*models.User{&user1, &user2}) - problem1 := createProblemForTest(t, "get_grades", 1, nil, user1) - problem2 := createProblemForTest(t, "get_grades", 2, nil, user1) - ps := createProblemSetForTest(t, "get_grades_empty", 0, &class, []models.Problem{problem1, problem2}, inProgress) + httpResp := makeResp(makeReq(t, "GET", - base.Echo.Reverse("problemSet.GetGrades", class.ID, ps.ID), nil, applyAdminUser)) + base.Echo.Reverse("problemSet.GetProblemSetGrades", class.ID, problemSet.ID), nil, applyAdminUser)) databaseProblemSet := models.ProblemSet{} - assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet, ps.ID).Error) - j, err := json.Marshal(map[uint]uint{ + assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet, problemSet.ID).Error) + + jsonEmpty, err := json.Marshal(map[uint]uint{ problem1.ID: 0, problem2.ID: 0, }) assert.NoError(t, err) - for i := range ps.Problems { - ps.Problems[i].TestCases = nil - } expectedProblemSet := models.ProblemSet{ - ID: ps.ID, + ID: problemSet.ID, ClassID: class.ID, Class: nil, - Name: ps.Name, - Description: ps.Description, - Problems: ps.Problems, + Name: problemSet.Name, + Description: problemSet.Description, + Problems: problemSet.Problems, Grades: []*models.Grade{ { ID: databaseProblemSet.Grades[0].ID, UserID: user1.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j, - Total: 0, + Detail: jsonExisting, + Total: 40, CreatedAt: databaseProblemSet.Grades[0].CreatedAt, UpdatedAt: databaseProblemSet.Grades[0].UpdatedAt, }, @@ -1509,27 +1521,27 @@ func TestGetGrades(t *testing.T) { ID: databaseProblemSet.Grades[1].ID, UserID: user2.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j, + Detail: jsonEmpty, Total: 0, CreatedAt: databaseProblemSet.Grades[1].CreatedAt, UpdatedAt: databaseProblemSet.Grades[1].UpdatedAt, }, }, - StartTime: ps.StartTime, - EndTime: ps.EndTime, - CreatedAt: ps.CreatedAt, + StartTime: problemSet.StartTime, + EndTime: problemSet.EndTime, + CreatedAt: problemSet.CreatedAt, UpdatedAt: databaseProblemSet.UpdatedAt, DeletedAt: gorm.DeletedAt{}, } assert.Equal(t, expectedProblemSet, databaseProblemSet) - resp := response.GetGradesResponse{} + resp := response.GetProblemSetGradesResponse{} assert.Equal(t, http.StatusOK, httpResp.StatusCode) mustJsonDecode(httpResp, &resp) - assert.Equal(t, response.GetGradesResponse{ + assert.Equal(t, response.GetProblemSetGradesResponse{ Message: "SUCCESS", Error: nil, Data: struct { @@ -1539,188 +1551,188 @@ func TestGetGrades(t *testing.T) { }, }, resp) }) - t.Run("Partially", func(t *testing.T) { +} + +func TestGetClassGrades(t *testing.T) { + t.Parallel() + user1 := createUserForTest(t, "get_class_grades", 1) + user2 := createUserForTest(t, "get_class_grades", 2) + class := createClassForTest(t, "get_class_grades", 0, nil, []*models.User{&user1, &user2}) + problem1 := createProblemForTest(t, "get_class_grades", 1, nil, user1) + problem2 := createProblemForTest(t, "get_class_grades", 2, nil, user1) + problemSet1 := createProblemSetForTest(t, "get_class_grades", 1, &class, []models.Problem{problem1, problem2}, inProgress) + problemSet2 := createProblemSetForTest(t, "get_class_grades", 2, &class, []models.Problem{problem1}, inProgress) + jsonExisting1, err := json.Marshal(map[uint]uint{ + problem1.ID: 0, + problem2.ID: 60, + }) + assert.NoError(t, err) + jsonExisting2, err := json.Marshal(map[uint]uint{ + problem1.ID: 70, + }) + assert.NoError(t, err) + gradeExisting1 := models.Grade{ + UserID: user1.ID, + ProblemSetID: problemSet1.ID, + ClassID: class.ID, + Detail: jsonExisting1, + Total: 60, + } + assert.NoError(t, err) + gradeExisting2 := models.Grade{ + UserID: user2.ID, + ProblemSetID: problemSet2.ID, + ClassID: class.ID, + Detail: jsonExisting2, + Total: 70, + } + assert.NoError(t, err) + assert.NoError(t, base.DB.Create(&gradeExisting1).Error) + assert.NoError(t, base.DB.Create(&gradeExisting2).Error) + + failTests := []failTest{ + { + name: "NonExistingClass", + method: "GET", + path: base.Echo.Reverse("class.getClassGrades", -1), + req: request.GetClassGradesRequest{}, + reqOptions: []reqOption{ + applyAdminUser, + }, + statusCode: http.StatusNotFound, + resp: response.ErrorResp("NOT_FOUND", nil), + }, + { + name: "PermissionDenied", + method: "GET", + path: base.Echo.Reverse("class.getClassGrades", class.ID), + req: request.GetClassGradesRequest{}, + reqOptions: []reqOption{ + applyNormalUser, + }, + statusCode: http.StatusForbidden, + resp: response.ErrorResp("PERMISSION_DENIED", nil), + }, + } + + runFailTests(t, failTests, "GetClassGrades") + + t.Run("Success", func(t *testing.T) { t.Parallel() - user1 := createUserForTest(t, "get_grades", 5) - user2 := createUserForTest(t, "get_grades", 6) - class := createClassForTest(t, "get_grades", 7, nil, []*models.User{&user1, &user2}) - problem1 := createProblemForTest(t, "get_grades", 3, nil, user1) - problem2 := createProblemForTest(t, "get_grades", 4, nil, user1) - ps := createProblemSetForTest(t, "get_grades_partially", 0, &class, []models.Problem{problem1, problem2}, inProgress) - j1, err := json.Marshal(map[uint]uint{ - problem1.ID: 40, + + httpResp := makeResp(makeReq(t, "GET", + base.Echo.Reverse("class.getClassGrades", class.ID), nil, applyAdminUser)) + databaseProblemSet1 := models.ProblemSet{} + databaseProblemSet2 := models.ProblemSet{} + assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet1, problemSet1.ID).Error) + assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet2, problemSet2.ID).Error) + + jsonEmpty1, err := json.Marshal(map[uint]uint{ + problem1.ID: 0, problem2.ID: 0, }) - assert.NoError(t, err) - j2, err := json.Marshal(map[uint]uint{ + jsonEmpty2, err := json.Marshal(map[uint]uint{ problem1.ID: 0, - problem2.ID: 0, }) assert.NoError(t, err) - grade1 := models.Grade{ - UserID: user1.ID, - ProblemSetID: ps.ID, - ClassID: class.ID, - Detail: j1, - Total: 40, - } - assert.NoError(t, err) - assert.NoError(t, base.DB.Create(&grade1).Error) - httpResp := makeResp(makeReq(t, "GET", - base.Echo.Reverse("problemSet.GetGrades", class.ID, ps.ID), nil, applyAdminUser)) - databaseProblemSet := models.ProblemSet{} - assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet, ps.ID).Error) - - assert.NoError(t, err) - expectedProblemSet := models.ProblemSet{ - ID: ps.ID, + expectedProblemSet1 := models.ProblemSet{ + ID: problemSet1.ID, ClassID: class.ID, Class: nil, - Name: ps.Name, - Description: ps.Description, - Problems: ps.Problems, + Name: problemSet1.Name, + Description: problemSet1.Description, + Problems: problemSet1.Problems, Grades: []*models.Grade{ { - ID: databaseProblemSet.Grades[0].ID, + ID: databaseProblemSet1.Grades[0].ID, UserID: user1.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet1.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j1, - Total: 40, - CreatedAt: databaseProblemSet.Grades[0].CreatedAt, - UpdatedAt: databaseProblemSet.Grades[0].UpdatedAt, + Detail: jsonExisting1, + Total: 60, + CreatedAt: databaseProblemSet1.Grades[0].CreatedAt, + UpdatedAt: databaseProblemSet1.Grades[0].UpdatedAt, }, { - ID: databaseProblemSet.Grades[1].ID, + ID: databaseProblemSet1.Grades[1].ID, UserID: user2.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet1.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j2, + Detail: jsonEmpty1, Total: 0, - CreatedAt: databaseProblemSet.Grades[1].CreatedAt, - UpdatedAt: databaseProblemSet.Grades[1].UpdatedAt, + CreatedAt: databaseProblemSet1.Grades[1].CreatedAt, + UpdatedAt: databaseProblemSet1.Grades[1].UpdatedAt, }, }, - StartTime: ps.StartTime, - EndTime: ps.EndTime, - CreatedAt: ps.CreatedAt, - UpdatedAt: databaseProblemSet.UpdatedAt, + StartTime: problemSet1.StartTime, + EndTime: problemSet1.EndTime, + CreatedAt: problemSet1.CreatedAt, + UpdatedAt: databaseProblemSet1.UpdatedAt, DeletedAt: gorm.DeletedAt{}, } - assert.Equal(t, expectedProblemSet, databaseProblemSet) - resp := response.GetGradesResponse{} - assert.Equal(t, http.StatusOK, httpResp.StatusCode) - mustJsonDecode(httpResp, &resp) - assert.Equal(t, response.GetGradesResponse{ - Message: "SUCCESS", - Error: nil, - Data: struct { - *resource.ProblemSetWithGrades `json:"problem_set"` - }{ - resource.GetProblemSetWithGrades(&expectedProblemSet), - }, - }, resp) - }) - t.Run("Full", func(t *testing.T) { - t.Parallel() - user1 := createUserForTest(t, "get_grades", 8) - user2 := createUserForTest(t, "get_grades", 9) - class := createClassForTest(t, "get_grades", 10, nil, []*models.User{&user1, &user2}) - problem1 := createProblemForTest(t, "get_grades", 5, nil, user1) - problem2 := createProblemForTest(t, "get_grades", 6, nil, user1) - ps := createProblemSetForTest(t, "get_grades_full", 0, &class, []models.Problem{problem1, problem2}, inProgress) - j1, err := json.Marshal(map[uint]uint{ - problem1.ID: 40, - problem2.ID: 0, - }) - assert.NoError(t, err) - j2, err := json.Marshal(map[uint]uint{ - problem1.ID: 100, - problem2.ID: 30, - }) - assert.NoError(t, err) - grade1 := models.Grade{ - UserID: user1.ID, - ProblemSetID: ps.ID, - ClassID: class.ID, - Detail: j1, - Total: 40, - } - assert.NoError(t, err) - assert.NoError(t, base.DB.Create(&grade1).Error) - grade2 := models.Grade{ - UserID: user2.ID, - ProblemSetID: ps.ID, - ClassID: class.ID, - Detail: j2, - Total: 130, - } - assert.NoError(t, err) - assert.NoError(t, base.DB.Create(&grade2).Error) - httpResp := makeResp(makeReq(t, "GET", - base.Echo.Reverse("problemSet.GetGrades", class.ID, ps.ID), nil, applyAdminUser)) - databaseProblemSet := models.ProblemSet{} - assert.NoError(t, base.DB.Preload("Grades").Preload("Problems").First(&databaseProblemSet, ps.ID).Error) - - assert.NoError(t, err) - expectedProblemSet := models.ProblemSet{ - ID: ps.ID, + expectedProblemSet2 := models.ProblemSet{ + ID: problemSet2.ID, ClassID: class.ID, Class: nil, - Name: ps.Name, - Description: ps.Description, - Problems: ps.Problems, + Name: problemSet2.Name, + Description: problemSet2.Description, + Problems: problemSet2.Problems, Grades: []*models.Grade{ { - ID: databaseProblemSet.Grades[0].ID, - UserID: user1.ID, + ID: databaseProblemSet2.Grades[0].ID, + UserID: user2.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet2.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j1, - Total: 40, - CreatedAt: databaseProblemSet.Grades[0].CreatedAt, - UpdatedAt: databaseProblemSet.Grades[0].UpdatedAt, + Detail: jsonExisting2, + Total: 70, + CreatedAt: databaseProblemSet2.Grades[0].CreatedAt, + UpdatedAt: databaseProblemSet2.Grades[0].UpdatedAt, }, { - ID: databaseProblemSet.Grades[1].ID, - UserID: user2.ID, + ID: databaseProblemSet2.Grades[1].ID, + UserID: user1.ID, User: nil, - ProblemSetID: ps.ID, + ProblemSetID: problemSet2.ID, ProblemSet: nil, ClassID: class.ID, Class: nil, - Detail: j2, - Total: 130, - CreatedAt: databaseProblemSet.Grades[1].CreatedAt, - UpdatedAt: databaseProblemSet.Grades[1].UpdatedAt, + Detail: jsonEmpty2, + Total: 0, + CreatedAt: databaseProblemSet2.Grades[1].CreatedAt, + UpdatedAt: databaseProblemSet2.Grades[1].UpdatedAt, }, }, - StartTime: ps.StartTime, - EndTime: ps.EndTime, - CreatedAt: ps.CreatedAt, - UpdatedAt: databaseProblemSet.UpdatedAt, + StartTime: problemSet2.StartTime, + EndTime: problemSet2.EndTime, + CreatedAt: problemSet2.CreatedAt, + UpdatedAt: databaseProblemSet2.UpdatedAt, DeletedAt: gorm.DeletedAt{}, } - assert.Equal(t, expectedProblemSet, databaseProblemSet) - resp := response.GetGradesResponse{} + assert.Equal(t, expectedProblemSet1, databaseProblemSet1) + assert.Equal(t, expectedProblemSet2.Grades, databaseProblemSet2.Grades) + assert.Equal(t, expectedProblemSet2, databaseProblemSet2) + resp := response.GetClassGradesResponse{} assert.Equal(t, http.StatusOK, httpResp.StatusCode) mustJsonDecode(httpResp, &resp) - assert.Equal(t, response.GetGradesResponse{ + assert.Equal(t, response.GetClassGradesResponse{ Message: "SUCCESS", Error: nil, Data: struct { - *resource.ProblemSetWithGrades `json:"problem_set"` + ProblemSets []*resource.ProblemSetWithGrades `json:"problem_sets"` }{ - resource.GetProblemSetWithGrades(&expectedProblemSet), + ProblemSets: []*resource.ProblemSetWithGrades{ + resource.GetProblemSetWithGrades(&expectedProblemSet1), + resource.GetProblemSetWithGrades(&expectedProblemSet2), + }, }, }, resp) }) diff --git a/app/request/problem_set.go b/app/request/problem_set.go index f5a231b4..ab38705f 100644 --- a/app/request/problem_set.go +++ b/app/request/problem_set.go @@ -46,7 +46,10 @@ type GetProblemSetProblemInputFileRequest struct { type GetProblemSetProblemOutputFileRequest struct { } -type GetGradesRequest struct { +type GetClassGradesRequest struct { +} + +type GetProblemSetGradesRequest struct { } type RefreshGradesRequest struct { diff --git a/app/response/problem_set.go b/app/response/problem_set.go index 5cad1b62..449f6d0c 100644 --- a/app/response/problem_set.go +++ b/app/response/problem_set.go @@ -82,7 +82,15 @@ type GetProblemSetProblemResponse struct { } `json:"data"` } -type GetGradesResponse struct { +type GetClassGradesResponse struct { + Message string `json:"message"` + Error interface{} `json:"error"` + Data struct { + ProblemSets []*resource.ProblemSetWithGrades `json:"problem_sets"` + } `json:"data"` +} + +type GetProblemSetGradesResponse struct { Message string `json:"message"` Error interface{} `json:"error"` Data struct { diff --git a/app/routes.go b/app/routes.go index 65d6ecd5..329c6ace 100644 --- a/app/routes.go +++ b/app/routes.go @@ -1,7 +1,6 @@ package app import ( - "encoding/json" "net/http" "net/http/pprof" @@ -190,6 +189,16 @@ func Register(e *echo.Echo) { B: middleware.UnscopedPermission{P: "manage_class"}, }), ) + manageClassGrades := api.Group("", + middleware.ValidateParams(map[string]string{ + "id": "NOT_FOUND", + }), + middleware.Logged, middleware.EmailVerified, + middleware.HasPermission(middleware.OrPermission{ + A: middleware.ScopedPermission{P: "manage_grades", T: "class"}, + B: middleware.UnscopedPermission{P: "manage_grades"}, + }), + ) api.POST("/class", controller.CreateClass, middleware.Logged, middleware.EmailVerified, middleware.HasPermission(middleware.UnscopedPermission{P: "manage_class"}), @@ -201,6 +210,7 @@ func Register(e *echo.Echo) { manageClass.POST("/class/:id/students", controller.AddStudents).Name = "class.addStudents" manageClass.DELETE("/class/:id/students", controller.DeleteStudents).Name = "class.deleteStudents" manageClass.DELETE("/class/:id", controller.DeleteClass).Name = "class.deleteClass" + manageClassGrades.GET("/class/:id/grades", controller.GetClassGrades).Name = "class.getClassGrades" // problem set APIs createProblemSet := api.Group("", @@ -239,15 +249,15 @@ func Register(e *echo.Echo) { B: middleware.CustomPermission{F: middleware.ProblemSetStarted}, }), ) - problemSetGrades := api.Group("", + manageProblemSetGrades := api.Group("", middleware.ValidateParams(map[string]string{ "id": "NOT_FOUND", "class_id": "CLASS_NOT_FOUND", }), middleware.Logged, middleware.EmailVerified, middleware.HasPermission(middleware.OrPermission{ - A: middleware.ScopedPermission{P: "read_answers", T: "class", IdFieldName: "class_id"}, - B: middleware.UnscopedPermission{P: "read_answers"}, + A: middleware.ScopedPermission{P: "manage_grades", T: "class", IdFieldName: "class_id"}, + B: middleware.UnscopedPermission{P: "manage_grades"}, }), ) api.GET("/class/:class_id/problem_set/:problem_set_id", controller.GetProblemSet, @@ -291,8 +301,8 @@ func Register(e *echo.Echo) { B: middleware.UnscopedPermission{P: "read_problem_secrets"}, }, })).Name = "problemSet.getProblemSetProblemOutputFile" - problemSetGrades.GET("/class/:class_id/problem_set/:id/grades", controller.GetGrades).Name = "problemSet.GetGrades" - problemSetGrades.POST("/class/:class_id/problem_set/:id/grades/refresh", controller.RefreshGrades).Name = "problemSet.RefreshGrades" + manageProblemSetGrades.GET("/class/:class_id/problem_set/:id/grades", controller.GetProblemSetGrades).Name = "problemSet.GetProblemSetGrades" + manageProblemSetGrades.POST("/class/:class_id/problem_set/:id/grades/refresh", controller.RefreshGrades).Name = "problemSet.RefreshGrades" // problem set submission APIs problemSetSubmission := api.Group("", @@ -356,9 +366,9 @@ func Register(e *echo.Echo) { return nil }) } - data, err := json.MarshalIndent(e.Routes(), "", " ") - if err != nil { - panic(err) - } - log.Debug(string(data)) + //data, err := json.MarshalIndent(e.Routes(), "", " ") + //if err != nil { + // panic(err) + //} + //log.Debug(string(data)) } diff --git a/base/utils/problem_set.go b/base/utils/problem_set.go index 66e18d35..e89076e3 100644 --- a/base/utils/problem_set.go +++ b/base/utils/problem_set.go @@ -116,24 +116,30 @@ func RefreshGrades(problemSet *models.ProblemSet) error { return nil } +// CreateEmptyGrades Creates empty grades(score 0 for all the problems) +// for users who don't have a grade for this problem set. func CreateEmptyGrades(problemSet *models.ProblemSet) error { gradeLock.Lock() defer gradeLock.Unlock() - gradeSet := make(map[uint]bool) - for _, g := range problemSet.Grades { - //fmt.Println(g) - gradeSet[g.UserID] = true - } - grades := make([]*models.Grade, 0, len(problemSet.Class.Students)-len(problemSet.Grades)) - copy(grades, problemSet.Grades) + + // Create empty grade JSON object detail := make(map[uint]uint) for _, p := range problemSet.Problems { detail[p.ID] = 0 } emptyDetail, err := json.Marshal(detail) if err != nil { - return errors.Wrap(err, "could not marshal grade detail when getting grades") + return errors.Wrap(err, "could not marshal grade detail for empty grade") } + + // Record students who have a grade + gradeSet := make(map[uint]bool) + for _, g := range problemSet.Grades { + gradeSet[g.UserID] = true + } + + // Generate empty grade slice + grades := make([]*models.Grade, 0, len(problemSet.Class.Students)-len(problemSet.Grades)) for _, u := range problemSet.Class.Students { if gradeSet[u.ID] { continue @@ -147,11 +153,15 @@ func CreateEmptyGrades(problemSet *models.ProblemSet) error { } grades = append(grades, &newGrade) } + + // Store empty grades into DB if len(grades) > 0 { if err = base.DB.Create(&grades).Error; err != nil { - return errors.Wrap(err, "could not create grades when getting grades") + return errors.Wrap(err, "could not create empty grades") } } + + // Update problem set problemSet.Grades = append(problemSet.Grades, grades...) return nil }