From a5e3d4259d9a7f0e6aa4121fa0c85b9163ef0863 Mon Sep 17 00:00:00 2001 From: al8763be Date: Tue, 9 Apr 2024 17:39:10 +0200 Subject: [PATCH] unsignReport handler + API function --- backend/internal/database/db.go | 31 +++++++ backend/internal/database/db_test.go | 88 +++++++++++++++++++ .../internal/handlers/reports/UnsignReport.go | 41 +++++++++ backend/main.go | 2 + frontend/src/API/API.ts | 32 +++++++ testing/helpers.py | 7 ++ testing/testing.py | 63 +++++++++++++ 7 files changed, 264 insertions(+) create mode 100644 backend/internal/handlers/reports/UnsignReport.go diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 22e11e9..6ee275a 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -45,6 +45,7 @@ type Database interface { UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error RemoveProject(projectname string) error GetUserName(id int) (string, error) + UnsignWeeklyReport(reportId int, projectManagerId int) error } // This struct is a wrapper type that holds the database connection @@ -372,6 +373,36 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } +func (d *Db) UnsignWeeklyReport(reportId int, projectManagerId int) error { + // Retrieve the project ID associated with the report + var reportProjectID int + err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId) + if err != nil { + return err + } + + managerQuery := `SELECT project_id FROM user_roles + WHERE user_id = ? + AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?) + AND p_role = 'project_manager'` + + // Retrieve the project ID associated with the project manager + var managerProjectID int + err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId) + if err != nil { + return err + } + + // Check if the project manager is in the same project as the report + if reportProjectID != managerProjectID { + return errors.New("project manager doesn't have permission to unsign the report") + } + + // Update the signed_by field of the specified report + _, err = d.Exec("UPDATE weekly_reports SET signed_by = NULL WHERE report_id = ?;", projectManagerId, reportId) + return err +} + func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) { // Define the SQL query to fetch unsigned reports for a given user query := ` diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index a691a4d..1c93baf 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -583,6 +583,94 @@ func TestSignWeeklyReport(t *testing.T) { } } +func TestUnsignWeeklyReport(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + // Add project manager + err = db.AddUser("projectManager", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add a regular user + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add project + err = db.AddProject("testproject", "description", "projectManager") + if err != nil { + t.Error("AddProject failed:", err) + } + + // Add both regular users as members to the project + err = db.AddUserToProject("testuser", "testproject", "member") + if err != nil { + t.Error("AddUserToProject failed:", err) + } + + err = db.AddUserToProject("projectManager", "testproject", "project_manager") + if err != nil { + t.Error("AddUserToProject failed:", err) + } + + // Add a weekly report for one of the regular users + err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) + if err != nil { + t.Error("AddWeeklyReport failed:", err) + } + + // Retrieve the added report + report, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Print project manager's ID + projectManagerID, err := db.GetUserId("projectManager") + if err != nil { + t.Error("GetUserId failed:", err) + } + + // Sign the report with the project manager + err = db.SignWeeklyReport(report.ReportId, projectManagerID) + if err != nil { + t.Error("SignWeeklyReport failed:", err) + } + + // Retrieve the report again to check if it's signed + signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Ensure the report is signed by the project manager + if *signedReport.SignedBy != projectManagerID { + t.Errorf("Expected SignedBy to be %d, got %d", projectManagerID, *signedReport.SignedBy) + } + + // Unsign the report + err = db.UnsignWeeklyReport(report.ReportId, projectManagerID) + if err != nil { + t.Error("UnsignWeeklyReport failed:", err) + } + + // Retrieve the report again to check if it's unsigned + unsignedReport, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Ensure the report is unsigned + if unsignedReport.SignedBy != nil { + t.Error("Expected SignedBy to be nil, got", unsignedReport.SignedBy) + } +} + // TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { db, err := setupState() diff --git a/backend/internal/handlers/reports/UnsignReport.go b/backend/internal/handlers/reports/UnsignReport.go new file mode 100644 index 0000000..ea0f480 --- /dev/null +++ b/backend/internal/handlers/reports/UnsignReport.go @@ -0,0 +1,41 @@ +package reports + +import ( + "strconv" + db "ttime/internal/database" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +func UnsignReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + projectManagerUsername := claims["name"].(string) + + // Extract report ID from the path + reportId, err := strconv.Atoi(c.Params("reportId")) + if err != nil { + log.Info("Invalid report ID") + return c.Status(400).SendString("Invalid report ID") + } + + // Get the project manager's ID + projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername) + if err != nil { + log.Info("Failed to get project manager ID for user: ", projectManagerUsername) + return c.Status(500).SendString("Failed to get project manager ID") + } + + // Call the database function to sign the weekly report + err = db.GetDb(c).UnsignWeeklyReport(reportId, projectManagerID) + if err != nil { + log.Info("Error Unsigning weekly report:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Project manager ID: ", projectManagerID, " signed report ID: ", reportId) + return c.Status(200).SendString("Weekly report unsigned successfully") +} diff --git a/backend/main.go b/backend/main.go index 7b19dd9..8fe7d1d 100644 --- a/backend/main.go +++ b/backend/main.go @@ -126,6 +126,7 @@ func main() { api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/project/:projectID", projects.DeleteProject) + // All report related routes // reportGroup := api.Group("/report") // Not currently in use api.Get("/getWeeklyReport", reports.GetWeeklyReport) @@ -134,6 +135,7 @@ func main() { api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Put("/signReport/:reportId", reports.SignReport) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) + api.Put("/unsignReport/:reportId", reports.UnsignReport) // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 86ad6dc..64bfd2e 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -221,6 +221,15 @@ interface API { */ signReport(reportId: number, token: string): Promise>; + /** + * Unsigns a report. Keep in mind that the user which the token belongs to must be + * the project manager of the project the report belongs to. + * + * @param {number} reportId The id of the report to sign + * @param {string} token The authentication token + */ + unsignReport(reportId: number, token: string): Promise>; + /** * Promotes a user to project manager within a project. * @@ -846,6 +855,29 @@ export const api: API = { } }, + async unsignReport( + reportId: number, + token: string, + ): Promise> { + try { + const response = await fetch(`/api/unsignReport/${reportId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }); + + if (!response.ok) { + return { success: false, message: "Failed to unsign report" }; + } else { + return { success: true, message: "Report unsigned" }; + } + } catch (e) { + return { success: false, message: "Failed to unsign report" }; + } + }, + async promoteToPm( userName: string, projectName: string, diff --git a/testing/helpers.py b/testing/helpers.py index 0a75689..6436922 100644 --- a/testing/helpers.py +++ b/testing/helpers.py @@ -34,6 +34,7 @@ getChangeUserNamePath = base_url + "/api/changeUserName" getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport" removeProjectPath = base_url + "/api/removeProject" promoteToPmPath = base_url + "/api/promoteToPm" +unsignReportPath = base_url + "/api/unsignReport" debug_output = False @@ -149,3 +150,9 @@ def signReport(project_manager_token: string, report_id: int): signReportPath + "/" + str(report_id), headers={"Authorization": "Bearer " + project_manager_token}, ) + +def unsignReport(project_manager_token: string, report_id: int): + return requests.put( + unsignReportPath + "/" + str(report_id), + headers={"Authorization": "Bearer " + project_manager_token}, + ) diff --git a/testing/testing.py b/testing/testing.py index ba38ced..c3c987c 100644 --- a/testing/testing.py +++ b/testing/testing.py @@ -215,6 +215,68 @@ def test_sign_report(): assert report_id != None, "Get report failed" gprint("test_sign_report successful") +# Test function to unsign a report +def test_unsign_report(): + # Pm user + pm_username = "pm" + randomString() + pm_password = "admin_password2" + + # User to add + member_user = "member" + randomString() + member_passwd = "password" + + # Name of the project to be created + project_name = "project" + randomString() + + # Register and get the tokens for both users + pm_token = register_and_login(pm_username, pm_password) + member_token = register_and_login(member_user, member_passwd) + + # Create the project + response = create_project(pm_token, project_name) + assert response.status_code == 200, "Create project failed" + + # Add the user to the project + response = addToProject(pm_token, member_user, project_name) + + # Submit a report for the project + response = submitReport( + member_token, + { + "projectName": project_name, + "week": 1, + "developmentTime": 10, + "meetingTime": 5, + "adminTime": 5, + "ownWorkTime": 10, + "studyTime": 10, + "testingTime": 10, + }, + ) + assert response.status_code == 200, "Submit report failed" + + # Retrieve the report ID + report_id = getReport(member_token, member_user, project_name)["reportId"] + + # Sign the report as the project manager + response = signReport(pm_token, report_id) + assert response.status_code == 200, "Sign report failed" + dprint("Sign report successful") + + # Retrieve the report ID again for confirmation + report_id = getReport(member_token, member_user, project_name)["reportId"] + assert report_id != None, "Get report failed" + + # Unsign the report as the project manager + response = unsignReport(pm_token, report_id) + assert response.status_code == 200, "Unsign report failed" + dprint("Unsign report successful") + + # Retrieve the report ID again for confirmation + report_id = getReport(member_token, member_user, project_name)["reportId"] + assert report_id != None, "Get report failed" + gprint("test_unsign_report successful") + # Test function to get weekly reports for a user in a project def test_get_all_weekly_reports(): @@ -497,6 +559,7 @@ def test_promote_to_manager(): if __name__ == "__main__": + test_unsign_report() test_promote_to_manager() test_remove_project() test_get_user_projects()