diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c01fdc..a51280c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,45 +1,45 @@ -# [0.37.0](https://github.com/upb-code-labs/main-api/compare/v0.36.0...v0.37.0) (2023-12-30) +# [0.40.0](https://github.com/upb-code-labs/main-api/compare/v0.39.0...v0.40.0) (2023-12-30) ### Features -* Update test block ([#127](https://github.com/upb-code-labs/main-api/issues/127)) ([fa8c816](https://github.com/upb-code-labs/main-api/commit/fa8c816083650039ae831daeff69c8e65689796a)) +* Delete test block ([#132](https://github.com/upb-code-labs/main-api/issues/132)) ([3ddaa80](https://github.com/upb-code-labs/main-api/commit/3ddaa80089b3e88404bfa0f92070d5f1e21722de)) -# [0.36.0](https://github.com/upb-code-labs/main-api/compare/v0.35.0...v0.36.0) (2023-12-29) +# [0.39.0](https://github.com/upb-code-labs/main-api/compare/v0.38.0...v0.39.0) (2023-12-30) ### Features -* Create new test block ([#126](https://github.com/upb-code-labs/main-api/issues/126)) ([ae8c2ba](https://github.com/upb-code-labs/main-api/commit/ae8c2ba50914ec235cecb0a41784736f2a4692ac)) +* Delete markdown block ([#131](https://github.com/upb-code-labs/main-api/issues/131)) ([1aee6d6](https://github.com/upb-code-labs/main-api/commit/1aee6d6c4509953aa03d1d449cb173a916eedc27)) -# [0.35.0](https://github.com/upb-code-labs/main-api/compare/v0.34.0...v0.35.0) (2023-12-28) +# [0.38.0](https://github.com/upb-code-labs/main-api/compare/v0.37.0...v0.38.0) (2023-12-30) ### Features -* Download languages template ([#123](https://github.com/upb-code-labs/main-api/issues/123)) ([4f460f4](https://github.com/upb-code-labs/main-api/commit/4f460f41a90f38516b6dad38f40a97f5245efb99)) +* Update status of enrolled students ([#129](https://github.com/upb-code-labs/main-api/issues/129)) ([0797e01](https://github.com/upb-code-labs/main-api/commit/0797e01e7795c2c46065c33b3c6549f3136dd95e)) -# [0.34.0](https://github.com/upb-code-labs/main-api/compare/v0.33.0...v0.34.0) (2023-12-28) +# [0.37.0](https://github.com/upb-code-labs/main-api/compare/v0.36.0...v0.37.0) (2023-12-30) ### Features -* List supported languages ([#122](https://github.com/upb-code-labs/main-api/issues/122)) ([e7d7539](https://github.com/upb-code-labs/main-api/commit/e7d75396b242cdebc046d62ca184522bcfbb2ae2)) +* Update test block ([#127](https://github.com/upb-code-labs/main-api/issues/127)) ([fa8c816](https://github.com/upb-code-labs/main-api/commit/fa8c816083650039ae831daeff69c8e65689796a)) -# [0.33.0](https://github.com/upb-code-labs/main-api/compare/v0.32.0...v0.33.0) (2023-11-29) +# [0.36.0](https://github.com/upb-code-labs/main-api/compare/v0.35.0...v0.36.0) (2023-12-29) ### Features -* Update markdown block content ([#113](https://github.com/upb-code-labs/main-api/issues/113)) ([3a6f762](https://github.com/upb-code-labs/main-api/commit/3a6f762a1c5e1e3915bee380438803b0ce981aac)) +* Create new test block ([#126](https://github.com/upb-code-labs/main-api/issues/126)) ([ae8c2ba](https://github.com/upb-code-labs/main-api/commit/ae8c2ba50914ec235cecb0a41784736f2a4692ac)) diff --git a/__tests__/integration/blocks_test.go b/__tests__/integration/blocks_test.go index 4049025..c0a9644 100644 --- a/__tests__/integration/blocks_test.go +++ b/__tests__/integration/blocks_test.go @@ -27,7 +27,7 @@ func TestUpdateMarkdownBlockContent(t *testing.T) { "name": "Update markdown block content test - laboratory", "course_uuid": courseUUID, "opening_date": "2023-12-01T08:00", - "due_date": "2023-12-01T12:00", + "due_date": "3023-12-01T12:00", }) c.Equal(http.StatusCreated, status) laboratoryUUID := laboratoryCreationResponse["uuid"].(string) @@ -68,6 +68,51 @@ func TestUpdateMarkdownBlockContent(t *testing.T) { c.Equal("# Updated main title", block["content"].(string)) } +func TestDeleteMarkdownBlock(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) + cookie := w.Result().Cookies()[0] + + // Create a course + courseUUID, status := CreateCourse("Delete markdown block test - course") + c.Equal(http.StatusCreated, status) + + // Create a laboratory + laboratoryCreationResponse, status := CreateLaboratory(cookie, map[string]interface{}{ + "name": "Delete markdown block test - laboratory", + "course_uuid": courseUUID, + "opening_date": "2023-12-01T08:00", + "due_date": "3023-12-01T12:00", + }) + c.Equal(http.StatusCreated, status) + laboratoryUUID := laboratoryCreationResponse["uuid"].(string) + + // Create a markdown block + blockCreationResponse, status := CreateMarkdownBlock(cookie, laboratoryUUID) + c.Equal(http.StatusCreated, status) + markdownBlockUUID := blockCreationResponse["uuid"].(string) + + // Get the laboratory + response, status := GetLaboratoryByUUID(cookie, laboratoryUUID) + c.Equal(http.StatusOK, status) + c.Equal(1, len(response["markdown_blocks"].([]interface{}))) + + // Delete the markdown block + _, status = DeleteMarkdownBlock(cookie, markdownBlockUUID) + c.Equal(http.StatusNoContent, status) + + // Verify that the block was deleted + response, status = GetLaboratoryByUUID(cookie, laboratoryUUID) + c.Equal(http.StatusOK, status) + c.Equal(0, len(response["markdown_blocks"].([]interface{}))) +} + func TestUpdateTestBlock(t *testing.T) { c := require.New(t) @@ -142,3 +187,71 @@ func TestUpdateTestBlock(t *testing.T) { c.Equal(newName, block["name"].(string)) c.Equal(firstLanguageUUID, block["language_uuid"].(string)) } + +func TestDeleteTestBlock(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) + cookie := w.Result().Cookies()[0] + + // Create a course + courseUUID, status := CreateCourse("Delete test block test - course") + c.Equal(http.StatusCreated, status) + + // Create a laboratory + laboratoryCreationResponse, status := CreateLaboratory(cookie, map[string]interface{}{ + "name": "Delete test block test - laboratory", + "course_uuid": courseUUID, + "opening_date": "2023-12-01T08:00", + "due_date": "3023-12-01T00:00", + }) + c.Equal(http.StatusCreated, status) + laboratoryUUID := laboratoryCreationResponse["uuid"].(string) + + // Get the supported languages + languagesResponse, status := GetSupportedLanguages(cookie) + c.Equal(http.StatusOK, status) + + languages := languagesResponse["languages"].([]interface{}) + c.Greater(len(languages), 0) + + firstLanguage := languages[0].(map[string]interface{}) + firstLanguageUUID := firstLanguage["uuid"].(string) + + // Create a test block + zipFile, err := GetSampleTestsArchive() + c.Nil(err) + + blockCreationResponse, status := CreateTestBlock(&CreateTestBlockUtilsDTO{ + laboratoryUUID: laboratoryUUID, + languageUUID: firstLanguageUUID, + blockName: "Delete test block test - block", + cookie: cookie, + testFile: zipFile, + }) + c.Equal(http.StatusCreated, status) + testBlockUUID := blockCreationResponse["uuid"].(string) + + // Check that the test block was created + laboratoryResponse, status := GetLaboratoryByUUID(cookie, laboratoryUUID) + c.Equal(http.StatusOK, status) + + blocks := laboratoryResponse["test_blocks"].([]interface{}) + c.Equal(1, len(blocks)) + + // Delete the test block + _, status = DeleteTestBlock(cookie, testBlockUUID) + c.Equal(http.StatusNoContent, status) + + // Check that the test block was deleted + laboratoryResponse, status = GetLaboratoryByUUID(cookie, laboratoryUUID) + c.Equal(http.StatusOK, status) + + blocks = laboratoryResponse["test_blocks"].([]interface{}) + c.Equal(0, len(blocks)) +} diff --git a/__tests__/integration/blocks_utils_test.go b/__tests__/integration/blocks_utils_test.go index 22a040d..c2ab113 100644 --- a/__tests__/integration/blocks_utils_test.go +++ b/__tests__/integration/blocks_utils_test.go @@ -20,6 +20,16 @@ func UpdateMarkdownBlockContent(cookie *http.Cookie, blockUUID string, payload m return jsonResponse, w.Code } +func DeleteMarkdownBlock(cookie *http.Cookie, blockUUID string) (response map[string]interface{}, statusCode int) { + endpoint := fmt.Sprintf("/api/v1/blocks/markdown_blocks/%s", blockUUID) + w, r := PrepareRequest("DELETE", endpoint, nil) + r.AddCookie(cookie) + router.ServeHTTP(w, r) + + jsonResponse := ParseJsonResponse(w.Body) + return jsonResponse, w.Code +} + type UpdateTestBlockUtilsDTO struct { blockUUID string languageUUID string @@ -74,3 +84,13 @@ func UpdateTestBlock(dto *UpdateTestBlockUtilsDTO) (response map[string]interfac jsonResponse := ParseJsonResponse(w.Body) return jsonResponse, w.Code } + +func DeleteTestBlock(cookie *http.Cookie, blockUUID string) (response map[string]interface{}, statusCode int) { + endpoint := fmt.Sprintf("/api/v1/blocks/test_blocks/%s", blockUUID) + w, r := PrepareRequest("DELETE", endpoint, nil) + r.AddCookie(cookie) + router.ServeHTTP(w, r) + + jsonResponse := ParseJsonResponse(w.Body) + return jsonResponse, w.Code +} diff --git a/__tests__/integration/courses_test.go b/__tests__/integration/courses_test.go index d388c5a..e706834 100644 --- a/__tests__/integration/courses_test.go +++ b/__tests__/integration/courses_test.go @@ -679,3 +679,49 @@ func TestGetCourseLaboratories(t *testing.T) { } } } + +func TestSetStudentStatus(t *testing.T) { + c := require.New(t) + + // 1. Create a course + courseUUID, code := CreateCourse("Set student status test - course") + c.Equal(http.StatusCreated, code) + + // 2. Get the course invitation code + invitationCode, code := GetInvitationCode(courseUUID) + c.Equal(http.StatusOK, code) + + // 3. Add a student to the course + _, code = AddStudentToCourse(invitationCode) + c.Equal(http.StatusOK, code) + + // 4. Login as a teacher + w, r := PrepareRequest("POST", "/api/v1/session/login", map[string]interface{}{ + "email": registeredTeacherEmail, + "password": registeredTeacherPass, + }) + router.ServeHTTP(w, r) + cookie := w.Result().Cookies()[0] + + // 5. Get the student uuid + students, code := GetStudentsEnrolledInCourse(cookie, courseUUID) + c.Equal(http.StatusOK, code) + c.Equal(1, len(students["students"].([]interface{}))) + studentUUID := students["students"].([]interface{})[0].(map[string]interface{})["uuid"].(string) + + // 6. Set the student status + code = SetStudentStatus(&SetStudentStatusUtilsDTO{ + CourseUUID: courseUUID, + StudentUUID: studentUUID, + IsActive: false, + Cookie: cookie, + }) + c.Equal(http.StatusNoContent, code) + + // 7. Get the student status + students, code = GetStudentsEnrolledInCourse(cookie, courseUUID) + c.Equal(http.StatusOK, code) + c.Equal(1, len(students["students"].([]interface{}))) + student := students["students"].([]interface{})[0].(map[string]interface{}) + c.Equal(false, student["is_active"]) +} diff --git a/__tests__/integration/courses_utils_test.go b/__tests__/integration/courses_utils_test.go index 1d42e6a..ef828d7 100644 --- a/__tests__/integration/courses_utils_test.go +++ b/__tests__/integration/courses_utils_test.go @@ -133,3 +133,21 @@ func GetCourseLaboratories(cookie *http.Cookie, courseUUID string) (response map jsonResponse := ParseJsonResponse(w.Body) return jsonResponse, w.Code } + +type SetStudentStatusUtilsDTO struct { + CourseUUID string + StudentUUID string + IsActive bool + Cookie *http.Cookie +} + +func SetStudentStatus(dto *SetStudentStatusUtilsDTO) (statusCode int) { + endpoint := fmt.Sprintf("/api/v1/courses/%s/students/%s/status", dto.CourseUUID, dto.StudentUUID) + w, r := PrepareRequest("PATCH", endpoint, map[string]interface{}{ + "is_active": dto.IsActive, + }) + r.AddCookie(dto.Cookie) + router.ServeHTTP(w, r) + + return w.Code +} diff --git a/__tests__/integration/laboratorires_test.go b/__tests__/integration/laboratorires_test.go index e5656f4..a46c2fb 100644 --- a/__tests__/integration/laboratorires_test.go +++ b/__tests__/integration/laboratorires_test.go @@ -38,7 +38,7 @@ func TestCreateLaboratory(t *testing.T) { "name": "Create laboratory test - laboratory", "course_uuid": courseUUID, "opening_date": "2023-12-01T08:00", - "due_date": "2023-12-01T12:00", + "due_date": "3023-12-01T12:00", }, ExpectedStatusCode: http.StatusCreated, }, @@ -231,7 +231,7 @@ func TestCreateMarkdownBlock(t *testing.T) { "name": "Create markdown block test - laboratory", "course_uuid": courseUUID, "opening_date": "2023-12-01T08:00", - "due_date": "2023-12-01T12:00", + "due_date": "3023-12-01T12:00", }) laboratoryUUID := laboratoryCreationResponse["uuid"].(string) c.Equal(http.StatusCreated, status) diff --git a/__tests__/integration/main_test.go b/__tests__/integration/main_test.go index 7e909c7..eeb288e 100644 --- a/__tests__/integration/main_test.go +++ b/__tests__/integration/main_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/UPB-Code-Labs/main-api/src/accounts/infrastructure/requests" - config_infrastructure "github.com/UPB-Code-Labs/main-api/src/config/infrastructure" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + configInfrastructure "github.com/UPB-Code-Labs/main-api/src/config/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/gin-gonic/gin" ) @@ -40,7 +40,7 @@ type GenericTestCase struct { func TestMain(m *testing.M) { // Setup database setupDatabase() - defer shared_infrastructure.ClosePostgresConnection() + defer sharedInfrastructure.ClosePostgresConnection() // Setup http router setupRouter() @@ -52,12 +52,12 @@ func TestMain(m *testing.M) { } func setupDatabase() { - shared_infrastructure.GetPostgresConnection() - config_infrastructure.RunMigrations() + sharedInfrastructure.GetPostgresConnection() + configInfrastructure.RunMigrations() } func setupRouter() { - router = config_infrastructure.InstanceHttpServer() + router = configInfrastructure.InstanceHttpServer() } func registerBaseAccounts() { diff --git a/docs/bruno/blocks/delete-markdown-block.bru b/docs/bruno/blocks/delete-markdown-block.bru new file mode 100644 index 0000000..db6de30 --- /dev/null +++ b/docs/bruno/blocks/delete-markdown-block.bru @@ -0,0 +1,11 @@ +meta { + name: delete-markdown-block + type: http + seq: 2 +} + +delete { + url: {{BASE_URL}}/blocks/markdown_blocks/eb4ef0ee-3865-44c3-8d52-2620fb2653e5 + body: none + auth: none +} diff --git a/docs/bruno/blocks/delete-test-block.bru b/docs/bruno/blocks/delete-test-block.bru new file mode 100644 index 0000000..2f29d79 --- /dev/null +++ b/docs/bruno/blocks/delete-test-block.bru @@ -0,0 +1,11 @@ +meta { + name: delete-test-block + type: http + seq: 3 +} + +delete { + url: {{BASE_URL}}/blocks/test_blocks/eb4ef0ee-3865-44c3-8d52-2620fb2653e5 + body: none + auth: none +} diff --git a/docs/bruno/courses/update-enrolled-student-status.bru b/docs/bruno/courses/update-enrolled-student-status.bru new file mode 100644 index 0000000..8970095 --- /dev/null +++ b/docs/bruno/courses/update-enrolled-student-status.bru @@ -0,0 +1,11 @@ +meta { + name: update-enrolled-student-status + type: http + seq: 11 +} + +patch { + url: {{BASE_URL}}/courses/b6268472-d569-4f60-9d64-58441433e0b3/students + body: none + auth: none +} diff --git a/docs/openapi/spec.openapi.yaml b/docs/openapi/spec.openapi.yaml index 8abc92a..4318fae 100644 --- a/docs/openapi/spec.openapi.yaml +++ b/docs/openapi/spec.openapi.yaml @@ -669,7 +669,7 @@ paths: schema: $ref: "#/components/schemas/default_error_response" - /courses/{course_uuid}/students/{student_uuid}/deactivate: + /courses/{course_uuid}/students/{student_uuid}/status: patch: tags: - Courses @@ -689,9 +689,18 @@ paths: type: string example: "b0c553b3-ddb2-4392-9d94-b31d8c9c4a84" required: true + requestBody: + content: + application/json: + schema: + type: object + properties: + to_active: + type: boolean + example: False responses: "204": - description: The student was unrolled from the course. + description: The student status was updated to active / inactive. "400": description: Required fields were missed or doesn't fulfill the required format. content: diff --git a/sql/migrations/20230920232901_init.down.sql b/sql/migrations/20230920232901_init.down.sql index dea25a7..2e29c2a 100644 --- a/sql/migrations/20230920232901_init.down.sql +++ b/sql/migrations/20230920232901_init.down.sql @@ -29,6 +29,8 @@ DROP VIEW IF EXISTS objectives_owners; DROP VIEW IF EXISTS criteria_owners; -- ## Tables +DROP TABLE IF EXISTS files_deletion_error_logs; + DROP TABLE IF EXISTS archives; DROP TABLE IF EXISTS grade_has_criteria; diff --git a/sql/migrations/20230920232901_init.up.sql b/sql/migrations/20230920232901_init.up.sql index ba5941c..0b10315 100644 --- a/sql/migrations/20230920232901_init.up.sql +++ b/sql/migrations/20230920232901_init.up.sql @@ -95,6 +95,14 @@ CREATE TABLE IF NOT EXISTS archives ( "file_id" UUID NOT NULL UNIQUE ); +CREATE TABLE IF NOT EXISTS files_deletion_error_logs ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "file_id" UUID NOT NULL REFERENCES archives(id), + "file_type" VARCHAR(16) NOT NULL, + "requested_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "error_message" TEXT NOT NULL +); + CREATE TABLE IF NOT EXISTS languages ( "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), "template_archive_id" UUID NOT NULL UNIQUE REFERENCES archives(id), @@ -112,7 +120,7 @@ CREATE TABLE IF NOT EXISTS test_blocks ( CREATE TABLE IF NOT EXISTS submissions ( "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - "test_id" UUID NOT NULL REFERENCES test_blocks(id), + "test_block_id" UUID NOT NULL REFERENCES test_blocks(id) ON DELETE CASCADE, "student_id" UUID NOT NULL REFERENCES users(id), "archive_id" UUID NOT NULL UNIQUE REFERENCES archives(id), "passing" BOOLEAN NOT NULL DEFAULT FALSE, @@ -137,7 +145,7 @@ CREATE TABLE IF NOT EXISTS grade_has_criteria ( -- ### Unique indexes CREATE UNIQUE INDEX IF NOT EXISTS idx_class_users ON courses_has_users(course_id, user_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_submissions ON submissions(test_id, student_id); +CREATE UNIQUE INDEX IF NOT EXISTS idx_submissions ON submissions(test_block_id, student_id); CREATE UNIQUE INDEX IF NOT EXISTS idx_grades ON grades(laboratory_id, student_id); diff --git a/src/accounts/domain/errors/accounts_errors.go b/src/accounts/domain/errors/accounts_errors.go new file mode 100644 index 0000000..df95dd9 --- /dev/null +++ b/src/accounts/domain/errors/accounts_errors.go @@ -0,0 +1,42 @@ +package errors + +import ( + "fmt" + "net/http" +) + +type EmailAlreadyInUseError struct { + Email string +} + +func (err EmailAlreadyInUseError) Error() string { + return fmt.Sprintf("Email %s is already in use", err.Email) +} + +func (err EmailAlreadyInUseError) StatusCode() int { + return http.StatusConflict +} + +type InstitutionalIdAlreadyInUseError struct { + InstitutionalId string +} + +func (err InstitutionalIdAlreadyInUseError) Error() string { + return fmt.Sprintf("Institutional ID %s is already in use", err.InstitutionalId) +} + +func (err InstitutionalIdAlreadyInUseError) StatusCode() int { + return http.StatusConflict +} + +type UserNotFoundError struct { + Uuuid string +} + +func (err UserNotFoundError) Error() string { + return fmt.Sprintf("User with UUID %s not found", err.Uuuid) +} + +func (err UserNotFoundError) StatusCode() int { + return http.StatusNotFound +} diff --git a/src/accounts/domain/errors/email_already_in_use.go b/src/accounts/domain/errors/email_already_in_use.go deleted file mode 100644 index a0bff2d..0000000 --- a/src/accounts/domain/errors/email_already_in_use.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type EmailAlreadyInUseError struct { - Email string -} - -func (err EmailAlreadyInUseError) Error() string { - return fmt.Sprintf("Email %s is already in use", err.Email) -} - -func (err EmailAlreadyInUseError) StatusCode() int { - return http.StatusConflict -} diff --git a/src/accounts/domain/errors/institutional_id_in_use.go b/src/accounts/domain/errors/institutional_id_in_use.go deleted file mode 100644 index 09721e6..0000000 --- a/src/accounts/domain/errors/institutional_id_in_use.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type InstitutionalIdAlreadyInUseError struct { - InstitutionalId string -} - -func (err InstitutionalIdAlreadyInUseError) Error() string { - return fmt.Sprintf("Institutional ID %s is already in use", err.InstitutionalId) -} - -func (err InstitutionalIdAlreadyInUseError) StatusCode() int { - return http.StatusConflict -} diff --git a/src/accounts/domain/errors/user_not_found.go b/src/accounts/domain/errors/user_not_found.go deleted file mode 100644 index 10d5bff..0000000 --- a/src/accounts/domain/errors/user_not_found.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type UserNotFoundError struct { - Uuuid string -} - -func (err UserNotFoundError) Error() string { - return fmt.Sprintf("User with UUID %s not found", err.Uuuid) -} - -func (err UserNotFoundError) StatusCode() int { - return http.StatusNotFound -} diff --git a/src/accounts/infrastructure/http/http_controllers.go b/src/accounts/infrastructure/http/controllers.go similarity index 100% rename from src/accounts/infrastructure/http/http_controllers.go rename to src/accounts/infrastructure/http/controllers.go diff --git a/src/accounts/infrastructure/http/http_routes.go b/src/accounts/infrastructure/http/routes.go similarity index 59% rename from src/accounts/infrastructure/http/http_routes.go rename to src/accounts/infrastructure/http/routes.go index c613d62..b0b618a 100644 --- a/src/accounts/infrastructure/http/http_routes.go +++ b/src/accounts/infrastructure/http/routes.go @@ -3,7 +3,7 @@ package infrastructure import ( "github.com/UPB-Code-Labs/main-api/src/accounts/application" "github.com/UPB-Code-Labs/main-api/src/accounts/infrastructure/implementations" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/gin-gonic/gin" ) @@ -22,25 +22,25 @@ func StartAccountsRoutes(g *gin.RouterGroup) { accountsGroup.POST("/students", controller.HandleRegisterStudent) accountsGroup.GET( "/students", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), controller.HandleSearchStudents, ) accountsGroup.POST( "/admins", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"admin"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"admin"}), controller.HandleRegisterAdmin, ) accountsGroup.GET( "/admins", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"admin"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"admin"}), controller.HandleGetAdmins, ) accountsGroup.POST("/teachers", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"admin"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"admin"}), controller.HandleRegisterTeacher, ) } diff --git a/src/accounts/infrastructure/requests/accounts_request.go b/src/accounts/infrastructure/requests/accounts_request.go new file mode 100644 index 0000000..5b76ad1 --- /dev/null +++ b/src/accounts/infrastructure/requests/accounts_request.go @@ -0,0 +1,47 @@ +package requests + +import "github.com/UPB-Code-Labs/main-api/src/accounts/domain/dtos" + +type RegisterAdminRequest struct { + FullName string `json:"full_name" validate:"required,min=4,max=255"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=8,max=255,secure_password"` +} + +func (request *RegisterAdminRequest) ToDTO() *dtos.RegisterUserDTO { + return &dtos.RegisterUserDTO{ + FullName: request.FullName, + Email: request.Email, + Password: request.Password, + } +} + +type RegisterTeacherRequest struct { + FullName string `json:"full_name" validate:"required,min=4,max=255"` + Email string `json:"email" validate:"required,email,institutional_email"` + Password string `json:"password" validate:"required,min=8,max=255,secure_password"` +} + +func (request *RegisterTeacherRequest) ToDTO() *dtos.RegisterUserDTO { + return &dtos.RegisterUserDTO{ + FullName: request.FullName, + Email: request.Email, + Password: request.Password, + } +} + +type RegisterUserRequest struct { + FullName string `json:"full_name" validate:"required,min=4,max=255"` + Email string `json:"email" validate:"required,email,institutional_email"` + InstitutionalId string `json:"institutional_id" validate:"required,numeric,min=6,max=9"` + Password string `json:"password" validate:"required,min=8,max=255,secure_password"` +} + +func (request *RegisterUserRequest) ToDTO() *dtos.RegisterUserDTO { + return &dtos.RegisterUserDTO{ + FullName: request.FullName, + Email: request.Email, + InstitutionalId: request.InstitutionalId, + Password: request.Password, + } +} diff --git a/src/accounts/infrastructure/requests/register_admin_request.go b/src/accounts/infrastructure/requests/register_admin_request.go deleted file mode 100644 index 2ca6c5e..0000000 --- a/src/accounts/infrastructure/requests/register_admin_request.go +++ /dev/null @@ -1,17 +0,0 @@ -package requests - -import "github.com/UPB-Code-Labs/main-api/src/accounts/domain/dtos" - -type RegisterAdminRequest struct { - FullName string `json:"full_name" validate:"required,min=4,max=255"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=255,secure_password"` -} - -func (request *RegisterAdminRequest) ToDTO() *dtos.RegisterUserDTO { - return &dtos.RegisterUserDTO{ - FullName: request.FullName, - Email: request.Email, - Password: request.Password, - } -} diff --git a/src/accounts/infrastructure/requests/register_teacher_request.go b/src/accounts/infrastructure/requests/register_teacher_request.go deleted file mode 100644 index bd40c74..0000000 --- a/src/accounts/infrastructure/requests/register_teacher_request.go +++ /dev/null @@ -1,17 +0,0 @@ -package requests - -import "github.com/UPB-Code-Labs/main-api/src/accounts/domain/dtos" - -type RegisterTeacherRequest struct { - FullName string `json:"full_name" validate:"required,min=4,max=255"` - Email string `json:"email" validate:"required,email,institutional_email"` - Password string `json:"password" validate:"required,min=8,max=255,secure_password"` -} - -func (request *RegisterTeacherRequest) ToDTO() *dtos.RegisterUserDTO { - return &dtos.RegisterUserDTO{ - FullName: request.FullName, - Email: request.Email, - Password: request.Password, - } -} diff --git a/src/accounts/infrastructure/requests/register_user_request.go b/src/accounts/infrastructure/requests/register_user_request.go deleted file mode 100644 index 89b367a..0000000 --- a/src/accounts/infrastructure/requests/register_user_request.go +++ /dev/null @@ -1,19 +0,0 @@ -package requests - -import "github.com/UPB-Code-Labs/main-api/src/accounts/domain/dtos" - -type RegisterUserRequest struct { - FullName string `json:"full_name" validate:"required,min=4,max=255"` - Email string `json:"email" validate:"required,email,institutional_email"` - InstitutionalId string `json:"institutional_id" validate:"required,numeric,min=6,max=9"` - Password string `json:"password" validate:"required,min=8,max=255,secure_password"` -} - -func (request *RegisterUserRequest) ToDTO() *dtos.RegisterUserDTO { - return &dtos.RegisterUserDTO{ - FullName: request.FullName, - Email: request.Email, - InstitutionalId: request.InstitutionalId, - Password: request.Password, - } -} diff --git a/src/blocks/application/use_cases.go b/src/blocks/application/use_cases.go index bce52e1..d957de5 100644 --- a/src/blocks/application/use_cases.go +++ b/src/blocks/application/use_cases.go @@ -62,3 +62,33 @@ func (useCases *BlocksUseCases) UpdateTestBlock(dto dtos.UpdateTestBlockDTO) (er // Update the block return useCases.BlocksRepository.UpdateTestBlock(&dto) } + +func (useCases *BlocksUseCases) DeleteMarkdownBlock(dto dtos.DeleteBlockDTO) (err error) { + // Validate the teacher is the owner of the block + ownsBlock, err := useCases.BlocksRepository.DoesTeacherOwnsMarkdownBlock(dto.TeacherUUID, dto.BlockUUID) + if err != nil { + return err + } + + if !ownsBlock { + return errors.TeacherDoesNotOwnBlock{} + } + + // Delete the block + return useCases.BlocksRepository.DeleteMarkdownBlock(dto.BlockUUID) +} + +func (useCases *BlocksUseCases) DeleteTestBlock(dto dtos.DeleteBlockDTO) (err error) { + // Validate the teacher is the owner of the block + ownsBlock, err := useCases.BlocksRepository.DoesTeacherOwnsTestBlock(dto.TeacherUUID, dto.BlockUUID) + if err != nil { + return err + } + + if !ownsBlock { + return errors.TeacherDoesNotOwnBlock{} + } + + // Delete the block + return useCases.BlocksRepository.DeleteTestBlock(dto.BlockUUID) +} diff --git a/src/blocks/domain/definitions/blocks_repository.go b/src/blocks/domain/definitions/blocks_repository.go index a00da17..f974081 100644 --- a/src/blocks/domain/definitions/blocks_repository.go +++ b/src/blocks/domain/definitions/blocks_repository.go @@ -10,7 +10,7 @@ type BlockRepository interface { // Update the markdown text of a markdown block UpdateMarkdownBlockContent(blockUUID string, content string) (err error) - // Functions to check blocks ownership + // Check blocks ownership DoesTeacherOwnsMarkdownBlock(teacherUUID string, blockUUID string) (bool, error) DoesTeacherOwnsTestBlock(teacherUUID string, blockUUID string) (bool, error) @@ -25,4 +25,8 @@ type BlockRepository interface { // Update the test block information in the database UpdateTestBlock(*dtos.UpdateTestBlockDTO) (err error) + + // Delete blocks + DeleteMarkdownBlock(blockUUID string) (err error) + DeleteTestBlock(blockUUID string) (err error) } diff --git a/src/blocks/domain/dtos/blocks_dtos.go b/src/blocks/domain/dtos/blocks_dtos.go index 60fe4c2..296ff23 100644 --- a/src/blocks/domain/dtos/blocks_dtos.go +++ b/src/blocks/domain/dtos/blocks_dtos.go @@ -15,3 +15,8 @@ type UpdateTestBlockDTO struct { Name string NewTestArchive *multipart.File } + +type DeleteBlockDTO struct { + TeacherUUID string + BlockUUID string +} diff --git a/src/blocks/infrastructure/http/controllers.go b/src/blocks/infrastructure/http/controllers.go index a7a5bcc..7777dd1 100644 --- a/src/blocks/infrastructure/http/controllers.go +++ b/src/blocks/infrastructure/http/controllers.go @@ -125,3 +125,53 @@ func (controller *BlocksController) HandleUpdateTestBlock(c *gin.Context) { c.Status(http.StatusNoContent) } + +func (controller *BlocksController) HandleDeleteMarkdownBlock(c *gin.Context) { + teacherUUID := c.GetString("session_uuid") + blockUUID := c.Param("block_uuid") + + // Validate the block UUID + if err := sharedInfrastructure.GetValidator().Var(blockUUID, "uuid4"); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Block UUID is not valid", + }) + return + } + + // Delete the block + err := controller.UseCases.DeleteMarkdownBlock(dtos.DeleteBlockDTO{ + TeacherUUID: teacherUUID, + BlockUUID: blockUUID, + }) + if err != nil { + c.Error(err) + return + } + + c.Status(http.StatusNoContent) +} + +func (controller *BlocksController) HandleDeleteTestBlock(c *gin.Context) { + teacherUUID := c.GetString("session_uuid") + blockUUID := c.Param("block_uuid") + + // Validate the block UUID + if err := sharedInfrastructure.GetValidator().Var(blockUUID, "uuid4"); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Block UUID is not valid", + }) + return + } + + // Delete the block + err := controller.UseCases.DeleteTestBlock(dtos.DeleteBlockDTO{ + TeacherUUID: teacherUUID, + BlockUUID: blockUUID, + }) + if err != nil { + c.Error(err) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/src/blocks/infrastructure/http/routes.go b/src/blocks/infrastructure/http/routes.go index 3987040..93e047e 100644 --- a/src/blocks/infrastructure/http/routes.go +++ b/src/blocks/infrastructure/http/routes.go @@ -33,4 +33,18 @@ func StartBlocksRoutes(g *gin.RouterGroup) { sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), controller.HandleUpdateTestBlock, ) + + blocksGroup.DELETE( + "/markdown_blocks/:block_uuid", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleDeleteMarkdownBlock, + ) + + blocksGroup.DELETE( + "/test_blocks/:block_uuid", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleDeleteTestBlock, + ) } diff --git a/src/blocks/infrastructure/implementations/blocks_repository.go b/src/blocks/infrastructure/implementations/blocks_repository.go index 20aafe7..f83d750 100644 --- a/src/blocks/infrastructure/implementations/blocks_repository.go +++ b/src/blocks/infrastructure/implementations/blocks_repository.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "log" "mime/multipart" "net/http" "net/textproto" @@ -14,6 +15,8 @@ import ( "github.com/UPB-Code-Labs/main-api/src/blocks/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/blocks/domain/errors" + laboratoriesDomainErrors "github.com/UPB-Code-Labs/main-api/src/laboratories/domain/errors" + sharedEntities "github.com/UPB-Code-Labs/main-api/src/shared/domain/entities" sharedDomainErrors "github.com/UPB-Code-Labs/main-api/src/shared/domain/errors" sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" ) @@ -64,7 +67,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsMarkdownBlock(teacher var laboratoryUUID string if err := row.Scan(&laboratoryUUID); err != nil { if err == sql.ErrNoRows { - return false, nil + return false, errors.BlockNotFound{} } } @@ -83,7 +86,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsMarkdownBlock(teacher var laboratoryTeacherUUID string if err := row.Scan(&laboratoryTeacherUUID); err != nil { if err == sql.ErrNoRows { - return false, nil + return false, laboratoriesDomainErrors.LaboratoryNotFoundError{} } } @@ -105,7 +108,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsTestBlock(teacherUUID var laboratoryUUID string if err := row.Scan(&laboratoryUUID); err != nil { if err == sql.ErrNoRows { - return false, nil + return false, &errors.BlockNotFound{} } } @@ -124,7 +127,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsTestBlock(teacherUUID var laboratoryTeacherUUID string if err := row.Scan(&laboratoryTeacherUUID); err != nil { if err == sql.ErrNoRows { - return false, nil + return false, laboratoriesDomainErrors.LaboratoryNotFoundError{} } } @@ -334,3 +337,202 @@ func (repository *BlocksPostgresRepository) UpdateTestBlock(dto *dtos.UpdateTest return nil } + +func (repository *BlocksPostgresRepository) DeleteMarkdownBlock(blockUUID string) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + // Get the UUID of the block index + query := ` + SELECT block_index_id + FROM markdown_blocks + WHERE id = $1 + ` + + row := repository.Connection.QueryRowContext(ctx, query, blockUUID) + var blockIndexUUID string + if err := row.Scan(&blockIndexUUID); err != nil { + if err == sql.ErrNoRows { + return &errors.BlockNotFound{} + } + + return err + } + + // After deleting the block index, the block will be deleted automatically due to the `ON DELETE CASCADE` constraint + err = repository.deleteBlockIndex(blockIndexUUID) + if err != nil { + return err + } + + return nil +} + +func (repository *BlocksPostgresRepository) DeleteTestBlock(blockUUID string) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + // Get the UUIDs of the dependent archives before deleting the block + dependentArchivesUUIDs, err := repository.getDependentArchivesByTestBlockUUID(blockUUID) + if err != nil { + return err + } + + // Delete the dependent archives in a separate goroutine + go repository.deleteDependentArchives(dependentArchivesUUIDs) + + // Get the UUID of the block index + query := ` + SELECT block_index_id + FROM test_blocks + WHERE id = $1 + ` + + row := repository.Connection.QueryRowContext(ctx, query, blockUUID) + var blockIndexUUID string + if err := row.Scan(&blockIndexUUID); err != nil { + if err == sql.ErrNoRows { + return &errors.BlockNotFound{} + } + + return err + } + + // After deleting the block index, the block will be deleted automatically due to the `ON DELETE CASCADE` constraint + err = repository.deleteBlockIndex(blockIndexUUID) + if err != nil { + return err + } + + return nil +} + +func (repository *BlocksPostgresRepository) deleteBlockIndex(blockIndexUUID string) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + query := ` + DELETE FROM blocks_index + WHERE id = $1 + ` + + _, err = repository.Connection.ExecContext(ctx, query, blockIndexUUID) + if err != nil { + return err + } + + return nil +} + +func (repository *BlocksPostgresRepository) getDependentArchivesByTestBlockUUID(blockUUID string) (archives []*sharedEntities.StaticFileArchive, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + // Get the UUID of the test block's tests archive + query := ` + SELECT file_id + FROM archives + WHERE id = ( + SELECT test_archive_id + FROM test_blocks + WHERE id = $1 + ) + ` + + row := repository.Connection.QueryRowContext(ctx, query, blockUUID) + var testsArchiveUUID string + if err := row.Scan(&testsArchiveUUID); err != nil { + if err == sql.ErrNoRows { + return nil, &errors.BlockNotFound{} + } + + return nil, err + } + + archives = append(archives, &sharedEntities.StaticFileArchive{ + ArchiveUUID: testsArchiveUUID, + ArchiveType: "test", + }) + + // Get the UUID of the test block's submissions archives + query = ` + SELECT file_id + FROM archives + WHERE id IN ( + SELECT archive_id + FROM submissions + WHERE test_block_id = $1 + ) + ` + + rows, err := repository.Connection.QueryContext(ctx, query, blockUUID) + if err != nil { + return nil, err + } + + for rows.Next() { + var submissionArchiveUUID string + if err := rows.Scan(&submissionArchiveUUID); err != nil { + return nil, err + } + + archives = append(archives, &sharedEntities.StaticFileArchive{ + ArchiveUUID: submissionArchiveUUID, + ArchiveType: "submission", + }) + } + + return archives, nil +} + +func (repository *BlocksPostgresRepository) deleteDependentArchives(archives []*sharedEntities.StaticFileArchive) { + log.Printf("[INFO] - [BlocksPostgresRepository] - [deleteDependentArchives]: Deleting %d archives \n", len(archives)) + staticFilesEndpoint := fmt.Sprintf("%s/archives/delete", sharedInfrastructure.GetEnvironment().StaticFilesMicroserviceAddress) + + for _, archive := range archives { + // Create the request body + var body bytes.Buffer + err := json.NewEncoder(&body).Encode(archive) + if err != nil { + errMessage := fmt.Sprintf("[ERR] - [BlocksPostgresRepository] - [deleteDependentArchives]: Unable to encode the request: %s", err.Error()) + repository.saveDeletionErrorLog(archive, errMessage) + } + + // Create the request + req, err := http.NewRequest("POST", staticFilesEndpoint, &body) + if err != nil { + errMessage := fmt.Sprintf("[ERR] - [BlocksPostgresRepository] - [deleteDependentArchives]: Unable to create the request: %s", err.Error()) + repository.saveDeletionErrorLog(archive, errMessage) + } + + // Send the request + client := &http.Client{} + res, err := client.Do(req) + + // Forward error message if any + microserviceError := sharedInfrastructure.ParseMicroserviceError(res, err) + if microserviceError != nil { + errMessage := fmt.Sprintf("[ERR] - [BlocksPostgresRepository] - [deleteDependentArchives]: Microservice returned an error: %s", microserviceError.Error()) + repository.saveDeletionErrorLog(archive, errMessage) + } + } +} + +func (repository *BlocksPostgresRepository) saveDeletionErrorLog(archive *sharedEntities.StaticFileArchive, errorMessage string) { + // Log the error to the console + log.Println(errorMessage) + + // Save the error log to the database + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + query := ` + INSERT INTO files_deletion_error_logs (file_id, file_type, error_message) + VALUES ($1, $2, $3) + ` + + _, err := repository.Connection.ExecContext(ctx, query, archive.ArchiveUUID, archive.ArchiveType, errorMessage) + if err != nil { + log.Println("[ERR] - [BlocksPostgresRepository] - [saveDeletionErrorLog]: Unable to save the error log", err) + } +} diff --git a/src/config/infrastructure/http_server.go b/src/config/infrastructure/http_server.go index 0aced4e..e529b5c 100644 --- a/src/config/infrastructure/http_server.go +++ b/src/config/infrastructure/http_server.go @@ -1,33 +1,33 @@ package infrastructure import ( - accounts_http "github.com/UPB-Code-Labs/main-api/src/accounts/infrastructure/http" - blocks_http "github.com/UPB-Code-Labs/main-api/src/blocks/infrastructure/http" - courses_http "github.com/UPB-Code-Labs/main-api/src/courses/infrastructure/http" - laboratories_http "github.com/UPB-Code-Labs/main-api/src/laboratories/infrastructure/http" - languages_http "github.com/UPB-Code-Labs/main-api/src/languages/infrastructure/http" - rubrics_http "github.com/UPB-Code-Labs/main-api/src/rubrics/infrastructure/http" - session_http "github.com/UPB-Code-Labs/main-api/src/session/infrastructure/http" - shared_infra "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + accountsHttp "github.com/UPB-Code-Labs/main-api/src/accounts/infrastructure/http" + blocksHttp "github.com/UPB-Code-Labs/main-api/src/blocks/infrastructure/http" + coursesHttp "github.com/UPB-Code-Labs/main-api/src/courses/infrastructure/http" + laboratoriesHttp "github.com/UPB-Code-Labs/main-api/src/laboratories/infrastructure/http" + languagesHttp "github.com/UPB-Code-Labs/main-api/src/languages/infrastructure/http" + rubricsHttp "github.com/UPB-Code-Labs/main-api/src/rubrics/infrastructure/http" + sessionHttp "github.com/UPB-Code-Labs/main-api/src/session/infrastructure/http" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) var routesGroups = []func(*gin.RouterGroup){ - accounts_http.StartAccountsRoutes, - blocks_http.StartBlocksRoutes, - session_http.StartSessionRoutes, - courses_http.StartCoursesRoutes, - rubrics_http.StartRubricsRoutes, - laboratories_http.StartLaboratoriesRoutes, - languages_http.StartLanguagesRoutes, + accountsHttp.StartAccountsRoutes, + blocksHttp.StartBlocksRoutes, + sessionHttp.StartSessionRoutes, + coursesHttp.StartCoursesRoutes, + rubricsHttp.StartRubricsRoutes, + laboratoriesHttp.StartLaboratoriesRoutes, + languagesHttp.StartLanguagesRoutes, } func InstanceHttpServer() (r *gin.Engine) { engine := gin.Default() - engine.Use(shared_infra.ErrorHandlerMiddleware()) + engine.Use(sharedInfrastructure.ErrorHandlerMiddleware()) - isInProductionEnvironment := shared_infra.GetEnvironment().Environment == "production" + isInProductionEnvironment := sharedInfrastructure.GetEnvironment().Environment == "production" if isInProductionEnvironment { gin.SetMode(gin.ReleaseMode) } @@ -35,7 +35,7 @@ func InstanceHttpServer() (r *gin.Engine) { // Configure CORS rules corsConfig := cors.DefaultConfig() corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} - corsConfig.AllowOrigins = []string{shared_infra.GetEnvironment().WebClientUrl} + corsConfig.AllowOrigins = []string{sharedInfrastructure.GetEnvironment().WebClientUrl} corsConfig.AllowCredentials = true engine.Use(cors.New(corsConfig)) diff --git a/src/courses/application/use_cases.go b/src/courses/application/use_cases.go index d7f0d3d..c8f56b1 100644 --- a/src/courses/application/use_cases.go +++ b/src/courses/application/use_cases.go @@ -211,6 +211,28 @@ func (useCases *CoursesUseCases) GetEnrolledStudents(teacherUUID, courseUUID str return useCases.Repository.GetEnrolledStudents(courseUUID) } +func (useCases *CoursesUseCases) SetStudentStatus(dto *dtos.SetUserStatusDTO) error { + // Get the course + course, err := useCases.Repository.GetCourseByUUID(dto.CourseUUID) + if err != nil { + return err + } + + // Check the user is the teacher of the course + wantsToUpdateCourseTeacher := dto.UserUUID == course.TeacherUUID + if wantsToUpdateCourseTeacher { + return errors.CannotUpdateCourseTeacherStatus{} + } + + teacherOwnsCourse := course.TeacherUUID == dto.TeacherUUID + if !teacherOwnsCourse { + return errors.TeacherDoesNotOwnsCourseError{} + } + + // Update the student status + return useCases.Repository.SetStudentStatus(dto) +} + func (useCases *CoursesUseCases) GetCourseLaboratories(dto dtos.GetCourseLaboratoriesDTO) ([]*dtos.BaseLaboratoryDTO, error) { // Check the user is enrolled in the course isUserInCourse, err := useCases.Repository.IsUserInCourse(dto.UserUUID, dto.CourseUUID) diff --git a/src/courses/domain/definitions/courses_repository.go b/src/courses/domain/definitions/courses_repository.go index 5196c3b..866d273 100644 --- a/src/courses/domain/definitions/courses_repository.go +++ b/src/courses/domain/definitions/courses_repository.go @@ -17,6 +17,7 @@ type CoursesRepository interface { IsUserInCourse(userUUID, courseUUID string) (bool, error) GetEnrolledCourses(studentUUID string) (*dtos.EnrolledCoursesDto, error) GetEnrolledStudents(courseUUID string) ([]*dtos.EnrolledStudentDTO, error) + SetStudentStatus(dto *dtos.SetUserStatusDTO) error GetRandomColor() (*entities.Color, error) ToggleCourseVisibility(courseUUID, studentUUID string) (isHiddenAfterUpdate bool, err error) diff --git a/src/courses/domain/dtos/courses_dtos.go b/src/courses/domain/dtos/courses_dtos.go index 4152484..69a8516 100644 --- a/src/courses/domain/dtos/courses_dtos.go +++ b/src/courses/domain/dtos/courses_dtos.go @@ -27,6 +27,13 @@ type EnrolledStudentDTO struct { IsActive bool } +type SetUserStatusDTO struct { + TeacherUUID string + UserUUID string + CourseUUID string + ToActive bool +} + type GetInvitationCodeDTO struct { CourseUUID string TeacherUUID string diff --git a/src/courses/domain/errors/courses_errors.go b/src/courses/domain/errors/courses_errors.go new file mode 100644 index 0000000..e087125 --- /dev/null +++ b/src/courses/domain/errors/courses_errors.go @@ -0,0 +1,84 @@ +package errors + +import ( + "fmt" + "net/http" +) + +type NoCourseWithInvitationCodeError struct { + Code string +} + +func (err NoCourseWithInvitationCodeError) Error() string { + return fmt.Sprintf("Course with invitation code %s not found", err.Code) +} + +func (err NoCourseWithInvitationCodeError) StatusCode() int { + return http.StatusNotFound +} + +type NoCourseWithUUIDFound struct { + UUID string +} + +func (err NoCourseWithUUIDFound) Error() string { + return fmt.Sprintf("Course with UUID %s not found", err.UUID) +} + +func (err NoCourseWithUUIDFound) StatusCode() int { + return http.StatusNotFound +} + +type StudentAlreadyInCourse struct { + CourseName string +} + +func (err StudentAlreadyInCourse) Error() string { + return fmt.Sprintf("Student is already in the course %s", err.CourseName) +} + +func (err StudentAlreadyInCourse) StatusCode() int { + return http.StatusConflict +} + +type TeacherDoesNotOwnsCourseError struct { +} + +func (err TeacherDoesNotOwnsCourseError) Error() string { + return "You do not own the course" +} + +func (err TeacherDoesNotOwnsCourseError) StatusCode() int { + return http.StatusForbidden +} + +type UnchangedCourseNameError struct { +} + +func (err UnchangedCourseNameError) Error() string { + return "The course has the same name" +} + +func (err UnchangedCourseNameError) StatusCode() int { + return http.StatusBadRequest +} + +type UserNotInCourseError struct{} + +func (err UserNotInCourseError) Error() string { + return "You are not enrolled in the course" +} + +func (err UserNotInCourseError) StatusCode() int { + return http.StatusForbidden +} + +type CannotUpdateCourseTeacherStatus struct{} + +func (err CannotUpdateCourseTeacherStatus) Error() string { + return "You cannot update the teacher status" +} + +func (err CannotUpdateCourseTeacherStatus) StatusCode() int { + return http.StatusConflict +} diff --git a/src/courses/domain/errors/no_course_with_invitation_code_error.go b/src/courses/domain/errors/no_course_with_invitation_code_error.go deleted file mode 100644 index 7690fa8..0000000 --- a/src/courses/domain/errors/no_course_with_invitation_code_error.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type NoCourseWithInvitationCodeError struct { - Code string -} - -func (err NoCourseWithInvitationCodeError) Error() string { - return fmt.Sprintf("Course with invitation code %s not found", err.Code) -} - -func (err NoCourseWithInvitationCodeError) StatusCode() int { - return http.StatusNotFound -} diff --git a/src/courses/domain/errors/no_course_with_uuid_error.go b/src/courses/domain/errors/no_course_with_uuid_error.go deleted file mode 100644 index dcd95c1..0000000 --- a/src/courses/domain/errors/no_course_with_uuid_error.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type NoCourseWithUUIDFound struct { - UUID string -} - -func (err NoCourseWithUUIDFound) Error() string { - return fmt.Sprintf("Course with UUID %s not found", err.UUID) -} - -func (err NoCourseWithUUIDFound) StatusCode() int { - return http.StatusNotFound -} diff --git a/src/courses/domain/errors/student_already_in_course_error.go b/src/courses/domain/errors/student_already_in_course_error.go deleted file mode 100644 index 58edcec..0000000 --- a/src/courses/domain/errors/student_already_in_course_error.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" -) - -type StudentAlreadyInCourse struct { - CourseName string -} - -func (err StudentAlreadyInCourse) Error() string { - return fmt.Sprintf("Student is already in the course %s", err.CourseName) -} - -func (err StudentAlreadyInCourse) StatusCode() int { - return http.StatusConflict -} diff --git a/src/courses/domain/errors/teacher_does_not_owns_course_error.go b/src/courses/domain/errors/teacher_does_not_owns_course_error.go deleted file mode 100644 index bf72497..0000000 --- a/src/courses/domain/errors/teacher_does_not_owns_course_error.go +++ /dev/null @@ -1,16 +0,0 @@ -package errors - -import ( - "net/http" -) - -type TeacherDoesNotOwnsCourseError struct { -} - -func (err TeacherDoesNotOwnsCourseError) Error() string { - return "You do not own the course" -} - -func (err TeacherDoesNotOwnsCourseError) StatusCode() int { - return http.StatusForbidden -} diff --git a/src/courses/domain/errors/unchanged_course_name_error.go b/src/courses/domain/errors/unchanged_course_name_error.go deleted file mode 100644 index 575380b..0000000 --- a/src/courses/domain/errors/unchanged_course_name_error.go +++ /dev/null @@ -1,16 +0,0 @@ -package errors - -import ( - "net/http" -) - -type UnchangedCourseNameError struct { -} - -func (err UnchangedCourseNameError) Error() string { - return "The course has the same name" -} - -func (err UnchangedCourseNameError) StatusCode() int { - return http.StatusBadRequest -} diff --git a/src/courses/domain/errors/user_not_in_course_error.go b/src/courses/domain/errors/user_not_in_course_error.go deleted file mode 100644 index f5d7e5c..0000000 --- a/src/courses/domain/errors/user_not_in_course_error.go +++ /dev/null @@ -1,15 +0,0 @@ -package errors - -import ( - "net/http" -) - -type UserNotInCourseError struct{} - -func (err UserNotInCourseError) Error() string { - return "You are not enrolled in the course" -} - -func (err UserNotInCourseError) StatusCode() int { - return http.StatusForbidden -} diff --git a/src/courses/infrastructure/http/http_controllers.go b/src/courses/infrastructure/http/controllers.go similarity index 89% rename from src/courses/infrastructure/http/http_controllers.go rename to src/courses/infrastructure/http/controllers.go index ed57549..2a56c8a 100644 --- a/src/courses/infrastructure/http/http_controllers.go +++ b/src/courses/infrastructure/http/controllers.go @@ -336,3 +336,43 @@ func (controller *CoursesController) HandleGetCourseLaboratories(c *gin.Context) "laboratories": laboratories, }) } + +func (controller *CoursesController) HandleSetStudentStatus(c *gin.Context) { + teacherUUID := c.GetString("session_uuid") + courseUUID := c.Param("course_uuid") + studentUUID := c.Param("student_uuid") + + // Parse request body + var request requests.SetUserStatusRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Invalid request body", + }) + return + } + + // Validate request body + if err := infrastructure.GetValidator().Struct(request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "Validation error", + "errors": err.Error(), + }) + return + } + + // Set student status + dto := &dtos.SetUserStatusDTO{ + TeacherUUID: teacherUUID, + UserUUID: studentUUID, + CourseUUID: courseUUID, + ToActive: request.ToActive, + } + + err := controller.UseCases.SetStudentStatus(dto) + if err != nil { + c.Error(err) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/src/courses/infrastructure/http/http_routes.go b/src/courses/infrastructure/http/routes.go similarity index 92% rename from src/courses/infrastructure/http/http_routes.go rename to src/courses/infrastructure/http/routes.go index e8d1f3c..8b025d7 100644 --- a/src/courses/infrastructure/http/http_routes.go +++ b/src/courses/infrastructure/http/routes.go @@ -88,4 +88,11 @@ func StartCoursesRoutes(g *gin.RouterGroup) { infrastructure.WithAuthorizationMiddleware([]string{"teacher", "student"}), controller.HandleGetCourseLaboratories, ) + + coursesGroup.PATCH( + ":course_uuid/students/:student_uuid/status", + infrastructure.WithAuthenticationMiddleware(), + infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleSetStudentStatus, + ) } diff --git a/src/courses/infrastructure/implementations/courses_repository.go b/src/courses/infrastructure/implementations/courses_repository.go index ce90edb..ba681c7 100644 --- a/src/courses/infrastructure/implementations/courses_repository.go +++ b/src/courses/infrastructure/implementations/courses_repository.go @@ -9,7 +9,7 @@ import ( "github.com/UPB-Code-Labs/main-api/src/courses/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/courses/domain/entities" - courses_errors "github.com/UPB-Code-Labs/main-api/src/courses/domain/errors" + coursesErrors "github.com/UPB-Code-Labs/main-api/src/courses/domain/errors" "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" ) @@ -207,7 +207,7 @@ func (repository *CoursesPostgresRepository) GetCourseByUUID(uuid string) (*enti if err != nil { // Throw a domain error if the course was not found if err == sql.ErrNoRows { - return nil, courses_errors.NoCourseWithUUIDFound{ + return nil, coursesErrors.NoCourseWithUUIDFound{ UUID: uuid, } } @@ -385,6 +385,7 @@ func (repository *CoursesPostgresRepository) GetEnrolledStudents(courseUUID stri SELECT user_id, user_full_name, user_email, user_institutional_id, is_user_active FROM courses_has_users_view WHERE course_id = $1 AND user_role = 'student' + ORDER BY is_user_active DESC, user_full_name ASC ` rows, err := repository.Connection.QueryContext(ctx, query, courseUUID) @@ -414,6 +415,30 @@ func (repository *CoursesPostgresRepository) GetEnrolledStudents(courseUUID stri return enrolledStudents, nil } +func (repository *CoursesPostgresRepository) SetStudentStatus(dto *dtos.SetUserStatusDTO) error { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + query := ` + UPDATE courses_has_users + SET is_user_active = $1 + WHERE course_id = $2 AND user_id = $3 + ` + + _, err := repository.Connection.ExecContext( + ctx, + query, + dto.ToActive, + dto.CourseUUID, + dto.UserUUID, + ) + if err != nil { + return err + } + + return nil +} + func (repository *CoursesPostgresRepository) GetCourseLaboratories(courseUUID string) ([]*dtos.BaseLaboratoryDTO, error) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() diff --git a/src/courses/infrastructure/requests/courses_requests.go b/src/courses/infrastructure/requests/courses_requests.go index 901f154..c7cde2e 100644 --- a/src/courses/infrastructure/requests/courses_requests.go +++ b/src/courses/infrastructure/requests/courses_requests.go @@ -7,3 +7,7 @@ type CreateCourseRequest struct { type EnrollStudentRequest struct { StudentUUID string `json:"student_uuid" validate:"required,uuid4"` } + +type SetUserStatusRequest struct { + ToActive bool `json:"to_active"` +} diff --git a/src/courses/infrastructure/responses/enrolled_courses_response.go b/src/courses/infrastructure/responses/courses_responses.go similarity index 100% rename from src/courses/infrastructure/responses/enrolled_courses_response.go rename to src/courses/infrastructure/responses/courses_responses.go diff --git a/src/laboratories/application/use_cases.go b/src/laboratories/application/use_cases.go index 05743b3..64d88fa 100644 --- a/src/laboratories/application/use_cases.go +++ b/src/laboratories/application/use_cases.go @@ -2,18 +2,18 @@ package application import ( blocksDefinitions "github.com/UPB-Code-Labs/main-api/src/blocks/domain/definitions" - courses_definitions "github.com/UPB-Code-Labs/main-api/src/courses/domain/definitions" - courses_errors "github.com/UPB-Code-Labs/main-api/src/courses/domain/errors" + coursesDefinitions "github.com/UPB-Code-Labs/main-api/src/courses/domain/definitions" + coursesErrors "github.com/UPB-Code-Labs/main-api/src/courses/domain/errors" "github.com/UPB-Code-Labs/main-api/src/laboratories/domain/definitions" "github.com/UPB-Code-Labs/main-api/src/laboratories/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/laboratories/domain/entities" languagesDefinitions "github.com/UPB-Code-Labs/main-api/src/languages/domain/definitions" rubricsDefinitions "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/definitions" - rubrics_errors "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/errors" + rubricsErrors "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/errors" ) type LaboratoriesUseCases struct { - CoursesRepository courses_definitions.CoursesRepository + CoursesRepository coursesDefinitions.CoursesRepository LaboratoriesRepository definitions.LaboratoriesRepository RubricsRepository rubricsDefinitions.RubricsRepository LanguagesRepository languagesDefinitions.LanguagesRepository @@ -28,7 +28,7 @@ func (useCases *LaboratoriesUseCases) CreateLaboratory(dto *dtos.CreateLaborator } if !ownsCourse { - return nil, courses_errors.TeacherDoesNotOwnsCourseError{} + return nil, coursesErrors.TeacherDoesNotOwnsCourseError{} } // Create the laboratory @@ -49,7 +49,7 @@ func (useCases *LaboratoriesUseCases) GetLaboratory(dto *dtos.GetLaboratoryDTO) } if !isEnrolled { - return nil, courses_errors.UserNotInCourseError{} + return nil, coursesErrors.UserNotInCourseError{} } return laboratory, nil @@ -63,7 +63,7 @@ func (useCases *LaboratoriesUseCases) UpdateLaboratory(dto *dtos.UpdateLaborator } if !teacherOwnsLaboratory { - return &courses_errors.TeacherDoesNotOwnsCourseError{} + return &coursesErrors.TeacherDoesNotOwnsCourseError{} } // Check that the teacher owns the rubric @@ -73,7 +73,7 @@ func (useCases *LaboratoriesUseCases) UpdateLaboratory(dto *dtos.UpdateLaborator return err } if !teacherOwnsRubric { - return &rubrics_errors.TeacherDoesNotOwnsRubric{} + return &rubricsErrors.TeacherDoesNotOwnsRubric{} } } @@ -89,7 +89,7 @@ func (useCases *LaboratoriesUseCases) CreateMarkdownBlock(dto *dtos.CreateMarkdo } if !teacherOwnsLaboratory { - return "", &courses_errors.TeacherDoesNotOwnsCourseError{} + return "", &coursesErrors.TeacherDoesNotOwnsCourseError{} } // Create the block @@ -113,7 +113,7 @@ func (useCases *LaboratoriesUseCases) CreateTestBlock(dto *dtos.CreateTestBlockD } if !teacherOwnsLaboratory { - return "", &courses_errors.TeacherDoesNotOwnsCourseError{} + return "", &coursesErrors.TeacherDoesNotOwnsCourseError{} } // Check that the language exists diff --git a/src/laboratories/infrastructure/responses/.gitkeep b/src/laboratories/infrastructure/responses/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/languages/infrastructure/http/http_controllers.go b/src/languages/infrastructure/http/controllers.go similarity index 100% rename from src/languages/infrastructure/http/http_controllers.go rename to src/languages/infrastructure/http/controllers.go diff --git a/src/languages/infrastructure/http/http_routes.go b/src/languages/infrastructure/http/routes.go similarity index 62% rename from src/languages/infrastructure/http/http_routes.go rename to src/languages/infrastructure/http/routes.go index 49136c0..7f605b0 100644 --- a/src/languages/infrastructure/http/http_routes.go +++ b/src/languages/infrastructure/http/routes.go @@ -3,7 +3,7 @@ package http import ( "github.com/UPB-Code-Labs/main-api/src/languages/application" "github.com/UPB-Code-Labs/main-api/src/languages/infrastructure/implementations" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/gin-gonic/gin" ) @@ -20,14 +20,14 @@ func StartLanguagesRoutes(g *gin.RouterGroup) { langGroup.GET( "", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher", "student"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher", "student"}), controllers.HandleGetLanguages, ) langGroup.GET( "/:language_uuid/template", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher", "student"}), + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher", "student"}), controllers.HandleDownloadLanguageTemplate, ) } diff --git a/src/languages/infrastructure/implementations/languages_repository.go b/src/languages/infrastructure/implementations/languages_repository.go index 031381b..6904114 100644 --- a/src/languages/infrastructure/implementations/languages_repository.go +++ b/src/languages/infrastructure/implementations/languages_repository.go @@ -10,7 +10,7 @@ import ( "github.com/UPB-Code-Labs/main-api/src/languages/domain/entities" "github.com/UPB-Code-Labs/main-api/src/languages/domain/errors" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" ) type LanguagesRepository struct { @@ -23,7 +23,7 @@ var langRepositoryInstance *LanguagesRepository func GetLanguagesRepositoryInstance() *LanguagesRepository { if langRepositoryInstance == nil { langRepositoryInstance = &LanguagesRepository{ - Connection: shared_infrastructure.GetPostgresConnection(), + Connection: sharedInfrastructure.GetPostgresConnection(), } } @@ -120,11 +120,11 @@ func (repository *LanguagesRepository) GetTemplateArchiveUUIDByLanguageUUID(uuid func (repository *LanguagesRepository) GetTemplateBytes(uuid string) (template []byte, err error) { // Send a request to the static files microservice - staticFilesMsEndpoint := fmt.Sprintf("%s/templates/%s", shared_infrastructure.GetEnvironment().StaticFilesMicroserviceAddress, uuid) + staticFilesMsEndpoint := fmt.Sprintf("%s/templates/%s", sharedInfrastructure.GetEnvironment().StaticFilesMicroserviceAddress, uuid) resp, err := http.Get(staticFilesMsEndpoint) // If there is an error try to forward the error message - microserviceError := shared_infrastructure.ParseMicroserviceError(resp, err) + microserviceError := sharedInfrastructure.ParseMicroserviceError(resp, err) if microserviceError != nil { return nil, microserviceError } diff --git a/src/languages/infrastructure/responses/.gitkeep b/src/languages/infrastructure/responses/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/rubrics/domain/errors/criteria_not_found_error.go b/src/rubrics/domain/errors/criteria_errors.go similarity index 100% rename from src/rubrics/domain/errors/criteria_not_found_error.go rename to src/rubrics/domain/errors/criteria_errors.go diff --git a/src/rubrics/domain/errors/objective_not_found_error.go b/src/rubrics/domain/errors/objectives_errors.go similarity index 100% rename from src/rubrics/domain/errors/objective_not_found_error.go rename to src/rubrics/domain/errors/objectives_errors.go diff --git a/src/rubrics/domain/errors/rubric_not_found_error.go b/src/rubrics/domain/errors/rubric_not_found_error.go deleted file mode 100644 index c0d79f6..0000000 --- a/src/rubrics/domain/errors/rubric_not_found_error.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import "net/http" - -type RubricNotFoundError struct{} - -func (err *RubricNotFoundError) Error() string { - return "Rubric not found" -} - -func (err *RubricNotFoundError) StatusCode() int { - return http.StatusNotFound -} diff --git a/src/rubrics/domain/errors/teacher_does_not_owns_rubric_error.go b/src/rubrics/domain/errors/rubrics_errors.go similarity index 56% rename from src/rubrics/domain/errors/teacher_does_not_owns_rubric_error.go rename to src/rubrics/domain/errors/rubrics_errors.go index 4eb6dce..75832c1 100644 --- a/src/rubrics/domain/errors/teacher_does_not_owns_rubric_error.go +++ b/src/rubrics/domain/errors/rubrics_errors.go @@ -11,3 +11,13 @@ func (err *TeacherDoesNotOwnsRubric) Error() string { func (err *TeacherDoesNotOwnsRubric) StatusCode() int { return http.StatusForbidden } + +type RubricNotFoundError struct{} + +func (err *RubricNotFoundError) Error() string { + return "Rubric not found" +} + +func (err *RubricNotFoundError) StatusCode() int { + return http.StatusNotFound +} diff --git a/src/rubrics/infrastructure/http/http_controller.go b/src/rubrics/infrastructure/http/controller.go similarity index 87% rename from src/rubrics/infrastructure/http/http_controller.go rename to src/rubrics/infrastructure/http/controller.go index b88bbd9..30a94ec 100644 --- a/src/rubrics/infrastructure/http/http_controller.go +++ b/src/rubrics/infrastructure/http/controller.go @@ -6,7 +6,7 @@ import ( "github.com/UPB-Code-Labs/main-api/src/rubrics/application" "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/rubrics/infrastructure/requests" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/gin-gonic/gin" ) @@ -27,7 +27,7 @@ func (controller *RubricsController) HandleCreateRubric(c *gin.Context) { } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -75,7 +75,7 @@ func (controller *RubricsController) HandleGetRubricByUUID(c *gin.Context) { // Validate rubric UUID rubric_uuid := c.Param("rubricUUID") - if err := shared_infrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid rubric uuid", }) @@ -106,7 +106,7 @@ func (controller *RubricsController) HandleUpdateRubricName(c *gin.Context) { // Validate rubric UUID rubric_uuid := c.Param("rubricUUID") - if err := shared_infrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid rubric uuid", }) @@ -123,7 +123,7 @@ func (controller *RubricsController) HandleUpdateRubricName(c *gin.Context) { } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -153,7 +153,7 @@ func (controller *RubricsController) HandleAddObjectiveToRubric(c *gin.Context) // Validate rubric UUID rubric_uuid := c.Param("rubricUUID") - if err := shared_infrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(rubric_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid rubric uuid", }) @@ -170,7 +170,7 @@ func (controller *RubricsController) HandleAddObjectiveToRubric(c *gin.Context) } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -203,7 +203,7 @@ func (controller *RubricsController) HandleAddCriteriaToObjective(c *gin.Context // Validate objective UUID objective_uuid := c.Param("objectiveUUID") - if err := shared_infrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid objective uuid", }) @@ -220,7 +220,7 @@ func (controller *RubricsController) HandleAddCriteriaToObjective(c *gin.Context } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -254,7 +254,7 @@ func (controller *RubricsController) HandleUpdateObjective(c *gin.Context) { // Validate objective UUID objective_uuid := c.Param("objectiveUUID") - if err := shared_infrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid objective uuid", }) @@ -271,7 +271,7 @@ func (controller *RubricsController) HandleUpdateObjective(c *gin.Context) { } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -301,7 +301,7 @@ func (controller *RubricsController) HandleDeleteObjective(c *gin.Context) { // Validate objective UUID objective_uuid := c.Param("objectiveUUID") - if err := shared_infrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(objective_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid objective uuid", }) @@ -329,7 +329,7 @@ func (controller *RubricsController) HandleUpdateCriteria(c *gin.Context) { // Validate criteria UUID criteria_uuid := c.Param("criteriaUUID") - if err := shared_infrastructure.GetValidator().Var(criteria_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(criteria_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid criteria uuid", }) @@ -346,7 +346,7 @@ func (controller *RubricsController) HandleUpdateCriteria(c *gin.Context) { } // Validate request body - if err := shared_infrastructure.GetValidator().Struct(request); err != nil { + if err := sharedInfrastructure.GetValidator().Struct(request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Validation error", "errors": err.Error(), @@ -377,7 +377,7 @@ func (controller *RubricsController) HandleDeleteCriteria(c *gin.Context) { // Validate criteria UUID criteria_uuid := c.Param("criteriaUUID") - if err := shared_infrastructure.GetValidator().Var(criteria_uuid, "uuid4"); err != nil { + if err := sharedInfrastructure.GetValidator().Var(criteria_uuid, "uuid4"); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": "Invalid criteria uuid", }) diff --git a/src/rubrics/infrastructure/http/http_routes.go b/src/rubrics/infrastructure/http/http_routes.go deleted file mode 100644 index 91cc48f..0000000 --- a/src/rubrics/infrastructure/http/http_routes.go +++ /dev/null @@ -1,90 +0,0 @@ -package http - -import ( - "github.com/UPB-Code-Labs/main-api/src/rubrics/application" - "github.com/UPB-Code-Labs/main-api/src/rubrics/infrastructure/implementations" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" - "github.com/gin-gonic/gin" -) - -func StartRubricsRoutes(g *gin.RouterGroup) { - rubricsGroup := g.Group("/rubrics") - - useCases := application.RubricsUseCases{ - RubricsRepository: implementations.GetRubricsPgRepository(), - } - - controller := RubricsController{ - UseCases: &useCases, - } - - rubricsGroup.POST( - "", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleCreateRubric, - ) - - rubricsGroup.GET( - "", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleGetRubricsCreatedByTeacher, - ) - - rubricsGroup.GET( - "/:rubricUUID", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleGetRubricByUUID, - ) - - rubricsGroup.PATCH( - "/:rubricUUID/name", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleUpdateRubricName, - ) - - rubricsGroup.POST( - "/:rubricUUID/objectives", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleAddObjectiveToRubric, - ) - - rubricsGroup.POST( - "/objectives/:objectiveUUID/criteria", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleAddCriteriaToObjective, - ) - - rubricsGroup.PUT( - "/objectives/:objectiveUUID", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleUpdateObjective, - ) - - rubricsGroup.DELETE( - "/objectives/:objectiveUUID", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleDeleteObjective, - ) - - rubricsGroup.PUT( - "/criteria/:criteriaUUID", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleUpdateCriteria, - ) - - rubricsGroup.DELETE( - "/criteria/:criteriaUUID", - shared_infrastructure.WithAuthenticationMiddleware(), - shared_infrastructure.WithAuthorizationMiddleware([]string{"teacher"}), - controller.HandleDeleteCriteria, - ) -} diff --git a/src/rubrics/infrastructure/http/routes.go b/src/rubrics/infrastructure/http/routes.go new file mode 100644 index 0000000..367af9c --- /dev/null +++ b/src/rubrics/infrastructure/http/routes.go @@ -0,0 +1,90 @@ +package http + +import ( + "github.com/UPB-Code-Labs/main-api/src/rubrics/application" + "github.com/UPB-Code-Labs/main-api/src/rubrics/infrastructure/implementations" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + "github.com/gin-gonic/gin" +) + +func StartRubricsRoutes(g *gin.RouterGroup) { + rubricsGroup := g.Group("/rubrics") + + useCases := application.RubricsUseCases{ + RubricsRepository: implementations.GetRubricsPgRepository(), + } + + controller := RubricsController{ + UseCases: &useCases, + } + + rubricsGroup.POST( + "", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleCreateRubric, + ) + + rubricsGroup.GET( + "", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleGetRubricsCreatedByTeacher, + ) + + rubricsGroup.GET( + "/:rubricUUID", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleGetRubricByUUID, + ) + + rubricsGroup.PATCH( + "/:rubricUUID/name", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleUpdateRubricName, + ) + + rubricsGroup.POST( + "/:rubricUUID/objectives", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleAddObjectiveToRubric, + ) + + rubricsGroup.POST( + "/objectives/:objectiveUUID/criteria", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleAddCriteriaToObjective, + ) + + rubricsGroup.PUT( + "/objectives/:objectiveUUID", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleUpdateObjective, + ) + + rubricsGroup.DELETE( + "/objectives/:objectiveUUID", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleDeleteObjective, + ) + + rubricsGroup.PUT( + "/criteria/:criteriaUUID", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleUpdateCriteria, + ) + + rubricsGroup.DELETE( + "/criteria/:criteriaUUID", + sharedInfrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}), + controller.HandleDeleteCriteria, + ) +} diff --git a/src/rubrics/infrastructure/implementations/rubrics_repository.go b/src/rubrics/infrastructure/implementations/rubrics_repository.go index a39d7f3..6953aaa 100644 --- a/src/rubrics/infrastructure/implementations/rubrics_repository.go +++ b/src/rubrics/infrastructure/implementations/rubrics_repository.go @@ -8,7 +8,7 @@ import ( "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/entities" "github.com/UPB-Code-Labs/main-api/src/rubrics/domain/errors" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/lib/pq" ) @@ -22,7 +22,7 @@ var rubricsPgRepositoryInstance *RubricsPostgresRepository func GetRubricsPgRepository() *RubricsPostgresRepository { if rubricsPgRepositoryInstance == nil { rubricsPgRepositoryInstance = &RubricsPostgresRepository{ - Connection: shared_infrastructure.GetPostgresConnection(), + Connection: sharedInfrastructure.GetPostgresConnection(), } } diff --git a/src/session/application/use_cases.go b/src/session/application/use_cases.go index 357baea..70e95bd 100644 --- a/src/session/application/use_cases.go +++ b/src/session/application/use_cases.go @@ -3,17 +3,17 @@ package application import ( "database/sql" - accounts_definitions "github.com/UPB-Code-Labs/main-api/src/accounts/domain/definitions" + accountsDefinitions "github.com/UPB-Code-Labs/main-api/src/accounts/domain/definitions" "github.com/UPB-Code-Labs/main-api/src/accounts/domain/entities" - accounts_errors "github.com/UPB-Code-Labs/main-api/src/accounts/domain/errors" + accountsErrors "github.com/UPB-Code-Labs/main-api/src/accounts/domain/errors" "github.com/UPB-Code-Labs/main-api/src/session/domain/definitions" "github.com/UPB-Code-Labs/main-api/src/session/domain/dtos" "github.com/UPB-Code-Labs/main-api/src/session/domain/errors" ) type SessionUseCases struct { - AccountsRepository accounts_definitions.AccountsRepository - PasswordHasher accounts_definitions.PasswordsHasher + AccountsRepository accountsDefinitions.AccountsRepository + PasswordHasher accountsDefinitions.PasswordsHasher TokenHandler definitions.TokenHandler } @@ -55,7 +55,7 @@ func (useCases *SessionUseCases) WhoAmI(uuid string) (entities.User, error) { user, err := useCases.AccountsRepository.GetUserByUUID(uuid) if err != nil { if err == sql.ErrNoRows { - return entities.User{}, accounts_errors.UserNotFoundError{ + return entities.User{}, accountsErrors.UserNotFoundError{ Uuuid: uuid, } } diff --git a/src/session/domain/dtos/login_dto.go b/src/session/domain/dtos/login_dto.go deleted file mode 100644 index 8f4870e..0000000 --- a/src/session/domain/dtos/login_dto.go +++ /dev/null @@ -1,6 +0,0 @@ -package dtos - -type LoginDTO struct { - Email string - Password string -} diff --git a/src/session/domain/dtos/session_dto.go b/src/session/domain/dtos/session_dtos.go similarity index 77% rename from src/session/domain/dtos/session_dto.go rename to src/session/domain/dtos/session_dtos.go index 4652c8b..b0ad00d 100644 --- a/src/session/domain/dtos/session_dto.go +++ b/src/session/domain/dtos/session_dtos.go @@ -11,3 +11,8 @@ type SessionDTO struct { User entities.User Token string } + +type LoginDTO struct { + Email string + Password string +} diff --git a/src/session/domain/errors/invalid_credentials.go b/src/session/domain/errors/session_errors.go similarity index 100% rename from src/session/domain/errors/invalid_credentials.go rename to src/session/domain/errors/session_errors.go diff --git a/src/session/infrastructure/http/http_controllers.go b/src/session/infrastructure/http/controllers.go similarity index 100% rename from src/session/infrastructure/http/http_controllers.go rename to src/session/infrastructure/http/controllers.go diff --git a/src/session/infrastructure/http/http_routes.go b/src/session/infrastructure/http/routes.go similarity index 73% rename from src/session/infrastructure/http/http_routes.go rename to src/session/infrastructure/http/routes.go index 0146f06..8210c05 100644 --- a/src/session/infrastructure/http/http_routes.go +++ b/src/session/infrastructure/http/routes.go @@ -2,7 +2,7 @@ package http import ( accounts_impl "github.com/UPB-Code-Labs/main-api/src/accounts/infrastructure/implementations" - shared_infrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" + sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure" "github.com/UPB-Code-Labs/main-api/src/session/application" "github.com/gin-gonic/gin" @@ -14,7 +14,7 @@ func StartSessionRoutes(g *gin.RouterGroup) { useCases := application.SessionUseCases{ AccountsRepository: accounts_impl.GetAccountsPgRepository(), PasswordHasher: accounts_impl.GetArgon2PasswordsHasher(), - TokenHandler: shared_infrastructure.GetJwtTokenHandler(), + TokenHandler: sharedInfrastructure.GetJwtTokenHandler(), } controllers := &SessionControllers{ @@ -25,13 +25,13 @@ func StartSessionRoutes(g *gin.RouterGroup) { sessionGroup.DELETE( "/logout", - shared_infrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthenticationMiddleware(), controllers.HandleLogout, ) sessionGroup.GET( "/whoami", - shared_infrastructure.WithAuthenticationMiddleware(), + sharedInfrastructure.WithAuthenticationMiddleware(), controllers.HandleWhoAmI, ) } diff --git a/src/session/infrastructure/requests/login_request.go b/src/session/infrastructure/requests/session_requests.go similarity index 100% rename from src/session/infrastructure/requests/login_request.go rename to src/session/infrastructure/requests/session_requests.go diff --git a/src/session/infrastructure/responses/.gitkeep b/src/session/infrastructure/responses/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/domain/entities/StaticFileArchive.go b/src/shared/domain/entities/StaticFileArchive.go new file mode 100644 index 0000000..945ca84 --- /dev/null +++ b/src/shared/domain/entities/StaticFileArchive.go @@ -0,0 +1,6 @@ +package entities + +type StaticFileArchive struct { + ArchiveUUID string `json:"archive_uuid"` + ArchiveType string `json:"archive_type"` +} diff --git a/src/shared/infrastructure/middlewares.go b/src/shared/infrastructure/middlewares.go index 1cb1f79..2ab3468 100644 --- a/src/shared/infrastructure/middlewares.go +++ b/src/shared/infrastructure/middlewares.go @@ -1,7 +1,7 @@ package infrastructure import ( - shared_errors "github.com/UPB-Code-Labs/main-api/src/shared/domain/errors" + sharedErrors "github.com/UPB-Code-Labs/main-api/src/shared/domain/errors" "github.com/gin-gonic/gin" ) @@ -13,7 +13,7 @@ func ErrorHandlerMiddleware() gin.HandlerFunc { err := c.Errors[0] switch e := err.Err.(type) { - case shared_errors.DomainError: + case sharedErrors.DomainError: c.JSON(e.StatusCode(), gin.H{ "message": e.Error(), }) @@ -30,7 +30,7 @@ func WithAuthenticationMiddleware() gin.HandlerFunc { return func(c *gin.Context) { cookie, err := c.Cookie("session") if err != nil { - c.Error(shared_errors.UnauthorizedError{ + c.Error(sharedErrors.UnauthorizedError{ Message: "You must be logged in", }) c.Abort() @@ -39,7 +39,7 @@ func WithAuthenticationMiddleware() gin.HandlerFunc { claims, err := GetJwtTokenHandler().ValidateToken(cookie) if err != nil { - c.Error(shared_errors.UnauthorizedError{ + c.Error(sharedErrors.UnauthorizedError{ Message: "Your session has expired or is not valid", }) c.Abort() @@ -66,7 +66,7 @@ func WithAuthorizationMiddleware(role []string) gin.HandlerFunc { } if !isRoleAuthorized { - c.Error(shared_errors.NotEnoughPermissionsError{}) + c.Error(sharedErrors.NotEnoughPermissionsError{}) c.Abort() } diff --git a/version.json b/version.json index de1aa91..72a5e8d 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.37.0" + "version": "0.40.0" }