From 48d5941cc7f0bfa2a057dccaf1cb04542b419a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= Date: Thu, 16 Nov 2023 07:35:06 -0500 Subject: [PATCH 1/4] feat(rubrics): Create endpoint to update an objective criteria --- src/rubrics/application/use_cases.go | 19 ++++++++ .../domain/definitions/rubrics_repository.go | 2 + .../domain/dtos/update_criteria_dto.go | 8 ++++ .../infrastructure/http/http_controller.go | 48 +++++++++++++++++++ .../infrastructure/http/http_routes.go | 7 +++ .../implementations/rubrics_repository.go | 19 ++++++++ .../requests/update_criteria_request.go | 6 +++ 7 files changed, 109 insertions(+) create mode 100644 src/rubrics/domain/dtos/update_criteria_dto.go create mode 100644 src/rubrics/infrastructure/requests/update_criteria_request.go diff --git a/src/rubrics/application/use_cases.go b/src/rubrics/application/use_cases.go index 06002c1..facba35 100644 --- a/src/rubrics/application/use_cases.go +++ b/src/rubrics/application/use_cases.go @@ -100,3 +100,22 @@ func (useCases *RubricsUseCases) AddCriteriaToObjective(dto *dtos.AddCriteriaToO return criteriaUUID, nil } + +func (useCases *RubricsUseCases) UpdateCriteria(dto *dtos.UpdateCriteriaDTO) (err error) { + // Check if the criteria belongs to a rubric that belongs to the teacher + teacherOwnsCriteria, err := useCases.RubricsRepository.DoesTeacherOwnCriteria(dto.TeacherUUID, dto.CriteriaUUID) + if err != nil { + return err + } + if !teacherOwnsCriteria { + return &errors.TeacherDoesNotOwnsRubric{} + } + + // Update the criteria + err = useCases.RubricsRepository.UpdateCriteria(dto) + if err != nil { + return err + } + + return nil +} diff --git a/src/rubrics/domain/definitions/rubrics_repository.go b/src/rubrics/domain/definitions/rubrics_repository.go index 0174382..4392037 100644 --- a/src/rubrics/domain/definitions/rubrics_repository.go +++ b/src/rubrics/domain/definitions/rubrics_repository.go @@ -16,5 +16,7 @@ type RubricsRepository interface { AddObjectiveToRubric(rubricUUID string, objectiveDescription string) (objectiveUUID string, err error) UpdateObjective(dto *dtos.UpdateObjectiveDTO) (err error) + AddCriteriaToObjective(dto *dtos.AddCriteriaToObjectiveDTO) (criteriaUUID string, err error) + UpdateCriteria(dto *dtos.UpdateCriteriaDTO) (err error) } diff --git a/src/rubrics/domain/dtos/update_criteria_dto.go b/src/rubrics/domain/dtos/update_criteria_dto.go new file mode 100644 index 0000000..917aa0e --- /dev/null +++ b/src/rubrics/domain/dtos/update_criteria_dto.go @@ -0,0 +1,8 @@ +package dtos + +type UpdateCriteriaDTO struct { + TeacherUUID string + CriteriaUUID string + CriteriaDescription string + CriteriaWeight float64 +} diff --git a/src/rubrics/infrastructure/http/http_controller.go b/src/rubrics/infrastructure/http/http_controller.go index 36b613c..2053eac 100644 --- a/src/rubrics/infrastructure/http/http_controller.go +++ b/src/rubrics/infrastructure/http/http_controller.go @@ -248,3 +248,51 @@ func (controller *RubricsController) HandleUpdateObjective(c *gin.Context) { c.Status(http.StatusNoContent) } + +func (controller *RubricsController) HandleUpdateCriteria(c *gin.Context) { + teacher_uuid := c.GetString("session_uuid") + + // Validate criteria UUID + criteria_uuid := c.Param("criteriaUUID") + if err := shared_infrastructure.GetValidator().Var(criteria_uuid, "uuid4"); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Invalid criteria uuid", + }) + return + } + + // Parse request body + var request requests.UpdateCriteriaRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Invalid request body", + }) + return + } + + // Validate request body + if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Validation error", + "errors": err.Error(), + }) + return + } + + // Create DTO + dto := dtos.UpdateCriteriaDTO{ + TeacherUUID: teacher_uuid, + CriteriaUUID: criteria_uuid, + CriteriaDescription: request.Description, + CriteriaWeight: request.Weight, + } + + // Update the criteria + err := controller.UseCases.UpdateCriteria(&dto) + if err != nil { + c.Error(err) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/src/rubrics/infrastructure/http/http_routes.go b/src/rubrics/infrastructure/http/http_routes.go index 0f00a87..5dbff84 100644 --- a/src/rubrics/infrastructure/http/http_routes.go +++ b/src/rubrics/infrastructure/http/http_routes.go @@ -59,4 +59,11 @@ func StartRubricsRoutes(g *gin.RouterGroup) { shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), controller.HandleUpdateObjective, ) + + rubricsGroup.PUT( + "/criteria/:criteriaUUID", + shared_infrastructure.WithAuthenticationMiddleware(), + shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleUpdateCriteria, + ) } diff --git a/src/rubrics/infrastructure/implementations/rubrics_repository.go b/src/rubrics/infrastructure/implementations/rubrics_repository.go index 718afe6..93a1495 100644 --- a/src/rubrics/infrastructure/implementations/rubrics_repository.go +++ b/src/rubrics/infrastructure/implementations/rubrics_repository.go @@ -330,3 +330,22 @@ func (repository *RubricsPostgresRepository) AddCriteriaToObjective(dto *dtos.Ad return criteriaUUID, nil } + +func (repository *RubricsPostgresRepository) UpdateCriteria(dto *dtos.UpdateCriteriaDTO) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + // Update the criteria + query := ` + UPDATE criteria + SET description = $1, weight = $2 + WHERE id = $3 + ` + + _, err = repository.Connection.ExecContext(ctx, query, dto.CriteriaDescription, dto.CriteriaWeight, dto.CriteriaUUID) + if err != nil { + return err + } + + return nil +} diff --git a/src/rubrics/infrastructure/requests/update_criteria_request.go b/src/rubrics/infrastructure/requests/update_criteria_request.go new file mode 100644 index 0000000..eabed7d --- /dev/null +++ b/src/rubrics/infrastructure/requests/update_criteria_request.go @@ -0,0 +1,6 @@ +package requests + +type UpdateCriteriaRequest struct { + Description string `json:"description" validate:"required,min=8,max=510"` + Weight float64 `json:"weight" default:"0" validate:"numeric,min=0,max=100"` +} From ca14030a43467d066adc8306bd7f26274eb7689e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= Date: Thu, 16 Nov 2023 07:35:30 -0500 Subject: [PATCH 2/4] docs(http): Update bruno collection --- .../add-criteria-to-rubric-objective.bru | 2 +- docs/bruno/rubrics/add-objective-to-rubric.bru | 2 +- docs/bruno/rubrics/create-rubric.bru | 2 +- docs/bruno/rubrics/get-rubric-by-uuid.bru | 2 +- .../rubrics/get-rubrics-created-by-teacher.bru | 2 +- docs/bruno/rubrics/update-criteria.bru | 18 ++++++++++++++++++ docs/bruno/rubrics/update-objective.bru | 2 +- 7 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 docs/bruno/rubrics/update-criteria.bru diff --git a/docs/bruno/rubrics/add-criteria-to-rubric-objective.bru b/docs/bruno/rubrics/add-criteria-to-rubric-objective.bru index 84a6397..4618fc7 100644 --- a/docs/bruno/rubrics/add-criteria-to-rubric-objective.bru +++ b/docs/bruno/rubrics/add-criteria-to-rubric-objective.bru @@ -1,7 +1,7 @@ meta { name: add-criteria-to-rubric-objective type: http - seq: 6 + seq: 1 } post { diff --git a/docs/bruno/rubrics/add-objective-to-rubric.bru b/docs/bruno/rubrics/add-objective-to-rubric.bru index 494505b..4b4367f 100644 --- a/docs/bruno/rubrics/add-objective-to-rubric.bru +++ b/docs/bruno/rubrics/add-objective-to-rubric.bru @@ -1,7 +1,7 @@ meta { name: add-objective-to-rubric type: http - seq: 7 + seq: 3 } post { diff --git a/docs/bruno/rubrics/create-rubric.bru b/docs/bruno/rubrics/create-rubric.bru index 692e0ae..886ec4c 100644 --- a/docs/bruno/rubrics/create-rubric.bru +++ b/docs/bruno/rubrics/create-rubric.bru @@ -1,7 +1,7 @@ meta { name: create-rubric type: http - seq: 10 + seq: 7 } post { diff --git a/docs/bruno/rubrics/get-rubric-by-uuid.bru b/docs/bruno/rubrics/get-rubric-by-uuid.bru index 2484ca1..a1d6391 100644 --- a/docs/bruno/rubrics/get-rubric-by-uuid.bru +++ b/docs/bruno/rubrics/get-rubric-by-uuid.bru @@ -1,7 +1,7 @@ meta { name: get-rubric-by-uuid type: http - seq: 8 + seq: 5 } get { diff --git a/docs/bruno/rubrics/get-rubrics-created-by-teacher.bru b/docs/bruno/rubrics/get-rubrics-created-by-teacher.bru index 74c7fc3..61e56ed 100644 --- a/docs/bruno/rubrics/get-rubrics-created-by-teacher.bru +++ b/docs/bruno/rubrics/get-rubrics-created-by-teacher.bru @@ -1,7 +1,7 @@ meta { name: get-rubrics-created-by-teacher type: http - seq: 9 + seq: 6 } get { diff --git a/docs/bruno/rubrics/update-criteria.bru b/docs/bruno/rubrics/update-criteria.bru new file mode 100644 index 0000000..19130cd --- /dev/null +++ b/docs/bruno/rubrics/update-criteria.bru @@ -0,0 +1,18 @@ +meta { + name: update-criteria + type: http + seq: 2 +} + +put { + url: {{BASE_URL}}/rubrics/criteria/d986f035-4d1b-4498-8ab3-1b9c923dbf9d + body: json + auth: none +} + +body:json { + { + "description": "Sin evidencia", + "weight": 0 + } +} diff --git a/docs/bruno/rubrics/update-objective.bru b/docs/bruno/rubrics/update-objective.bru index 35fd16a..f6e8263 100644 --- a/docs/bruno/rubrics/update-objective.bru +++ b/docs/bruno/rubrics/update-objective.bru @@ -1,7 +1,7 @@ meta { name: update-objective type: http - seq: 6 + seq: 4 } put { From f96bc6d51969b70b03e6e19f822c6e4bb5963a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= Date: Thu, 16 Nov 2023 07:36:17 -0500 Subject: [PATCH 3/4] docs(http): Update insomnia collection --- docs/insomnia/collection.json | 49 ++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/docs/insomnia/collection.json b/docs/insomnia/collection.json index be376cb..02e2639 100644 --- a/docs/insomnia/collection.json +++ b/docs/insomnia/collection.json @@ -1,13 +1,13 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2023-11-16T00:00:17.420Z", - "__export_source": "insomnia.desktop.app:v8.4.1", + "__export_date": "2023-11-16T12:35:55.652Z", + "__export_source": "insomnia.desktop.app:v8.4.2", "resources": [ { "_id": "req_c2f0c73479b541a49dbada7341b1c575", "parentId": "fld_471e0003712e4ff0a2978078fa774ef7", - "modified": 1699472729783, + "modified": 1700137908954, "created": 1699472694976, "url": "{{BASE_URL}}/accounts/admins", "name": "register-admin", @@ -660,7 +660,7 @@ { "_id": "req_00f7ec30e93c4a3bb7ec90e2b80407b6", "parentId": "fld_4915fda7f3a64b3ba16e401d430745a1", - "modified": 1700090526394, + "modified": 1700092857989, "created": 1700090427249, "url": "{{ _.BASE_URL }}/rubrics/objectives/1fc6b653-6977-4025-981e-6c937a10a251", "name": "update-objective", @@ -689,7 +689,7 @@ { "_id": "req_dc979436daa84e19bfc983515f7f0f9b", "parentId": "fld_4915fda7f3a64b3ba16e401d430745a1", - "modified": 1700060008053, + "modified": 1700137892992, "created": 1699473189242, "url": "{{ _.BASE_URL }}/rubrics/objectives/e81fecb0-620c-4af5-80d9-5e9a6af88b68/criteria", "name": "add-criteria-to-objective", @@ -697,7 +697,7 @@ "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"description\": \"It does not present a list of functional requirements\",\n\t\"weight\": -1\n}" + "text": "{\n\t\"description\": \"It does not present a list of functional requirements\",\n\t\"weight\": 0\n}" }, "parameters": [], "headers": [ @@ -715,6 +715,35 @@ "settingFollowRedirects": "global", "_type": "request" }, + { + "_id": "req_73cce43293304b7cb3b760903512af7a", + "parentId": "fld_4915fda7f3a64b3ba16e401d430745a1", + "modified": 1700137970119, + "created": 1700137853193, + "url": "{{ _.BASE_URL }}/rubrics/criteria/d986f035-4d1b-4498-8ab3-1b9c923dbf9d", + "name": "update-criteria", + "description": "", + "method": "PUT", + "body": { + "mimeType": "application/json", + "text": "{\n\t\"description\": \"Sin evidencia\",\n\t\"weight\": 0\n}" + }, + "parameters": [], + "headers": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "User-Agent", "value": "insomnia/8.4.2" } + ], + "authentication": {}, + "metaSortKey": -1699473189142, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, { "_id": "env_c0a0aa13c1388a57edad6b5876667ffb89c3f19d", "parentId": "wrk_d434a8527f444bf9a18c549e59efeee5", @@ -731,21 +760,21 @@ { "_id": "jar_c0a0aa13c1388a57edad6b5876667ffb89c3f19d", "parentId": "wrk_d434a8527f444bf9a18c549e59efeee5", - "modified": 1700090481591, + "modified": 1700137934108, "created": 1699472219279, "name": "Default Jar", "cookies": [ { "key": "session", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMmI3OTAxOTUtNzkxZC00ODFiLTk0NjMtZjg3ZTIxNjYzZDk2Iiwicm9sZSI6InRlYWNoZXIiLCJpc3MiOiJjb2RlbGFicyIsImV4cCI6MTcwMDExMjA4MSwibmJmIjoxNzAwMDkwNDgxLCJpYXQiOjE3MDAwOTA0ODF9.2Zo3CV7Qf1H7q9fbeqrhyrqTYn-UGiJnelDsAvMm92A", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMmI3OTAxOTUtNzkxZC00ODFiLTk0NjMtZjg3ZTIxNjYzZDk2Iiwicm9sZSI6InRlYWNoZXIiLCJpc3MiOiJjb2RlbGFicyIsImV4cCI6MTcwMDE1OTUzNCwibmJmIjoxNzAwMTM3OTM0LCJpYXQiOjE3MDAxMzc5MzR9.49rizwDc8xUg5k6-auur9tdCvokp-8FWSEW3l2aRqxc", "maxAge": 21600, "domain": "localhost", "path": "/", "httpOnly": true, "hostOnly": true, "creation": "2023-11-08T19:42:29.254Z", - "lastAccessed": "2023-11-15T23:21:21.590Z", - "id": "91936dac-fbdd-49bc-96e2-50326472280f" + "lastAccessed": "2023-11-16T12:32:14.108Z", + "id": "ac7429d7-8082-4484-a6b1-6b2bcb838153" } ], "_type": "cookie_jar" From f3c39076010c9e8ada5c99e2f52dec4af23ae627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= Date: Thu, 16 Nov 2023 07:43:05 -0500 Subject: [PATCH 4/4] test(rubrics): Add test to ensure teachers can update objectives criteria --- __tests__/integration/rubrics_test.go | 58 +++++++++++++++++++++ __tests__/integration/rubrics_utils_test.go | 8 +++ 2 files changed, 66 insertions(+) diff --git a/__tests__/integration/rubrics_test.go b/__tests__/integration/rubrics_test.go index c55c224..4b67e99 100644 --- a/__tests__/integration/rubrics_test.go +++ b/__tests__/integration/rubrics_test.go @@ -430,3 +430,61 @@ func TestAddCriteriaToObjective(t *testing.T) { c.NotEmpty(criteria["uuid"]) c.NotEmpty(criteria["weight"]) } + +func TestUpdateCriteria(t *testing.T) { + c := require.New(t) + + // Login as a teacher + w, r := PrepareRequest("POST", "/api/v1/session/login", map[string]interface{}{ + "email": registeredTeacherEmail, + "password": registeredTeacherPass, + }) + router.ServeHTTP(w, r) + firstTeacherCookie := w.Result().Cookies()[0] + + // Create a rubric + response, status := CreateRubric(firstTeacherCookie, map[string]interface{}{ + "name": "Rubric 1", + }) + c.Equal(http.StatusCreated, status) + firstTeacherRubricUUID := response["uuid"].(string) + + // Get the criteria UUID + response, status = GetRubricByUUID(firstTeacherCookie, firstTeacherRubricUUID) + c.Equal(http.StatusOK, status) + + rubric := response["rubric"].(map[string]interface{}) + c.Equal(1, len(rubric["objectives"].([]interface{}))) + + objective := rubric["objectives"].([]interface{})[0].(map[string]interface{}) + c.Equal(1, len(objective["criteria"].([]interface{}))) + + criteria := objective["criteria"].([]interface{})[0].(map[string]interface{}) + criteriaUUID := criteria["uuid"].(string) + + // Test cases + newDescription := "New description" + newWeight := 0.125 + + testCases := []GenericTestCase{ + GenericTestCase{ + Payload: map[string]interface{}{ + "description": "short", + "weight": 0.125, + }, + ExpectedStatusCode: http.StatusBadRequest, + }, + GenericTestCase{ + Payload: map[string]interface{}{ + "description": newDescription, + "weight": newWeight, + }, + ExpectedStatusCode: http.StatusNoContent, + }, + } + + for _, testCase := range testCases { + _, status := UpdateCriteria(firstTeacherCookie, criteriaUUID, testCase.Payload) + c.Equal(testCase.ExpectedStatusCode, status) + } +} diff --git a/__tests__/integration/rubrics_utils_test.go b/__tests__/integration/rubrics_utils_test.go index b9cbb73..8624152 100644 --- a/__tests__/integration/rubrics_utils_test.go +++ b/__tests__/integration/rubrics_utils_test.go @@ -49,3 +49,11 @@ func AddCriteriaToObjective(cookie *http.Cookie, objectiveUUID string, payload m return ParseJsonResponse(w.Body), w.Code } + +func UpdateCriteria(cookie *http.Cookie, criteriaUUID string, payload map[string]interface{}) (response map[string]interface{}, status int) { + w, r := PrepareRequest("PUT", "/api/v1/rubrics/criteria/"+criteriaUUID, payload) + r.AddCookie(cookie) + router.ServeHTTP(w, r) + + return ParseJsonResponse(w.Body), w.Code +}