From a39cfedad3518a858b760d1f9592c6b8be2dc873 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:31:39 +0200 Subject: [PATCH 1/8] Rename, fix and testing for getAllWeeklyReports path --- backend/internal/database/db.go | 6 +-- backend/internal/database/db_test.go | 5 +-- .../reports/GetWeeklyReportsUserHandler.go | 40 ++++++++++++++----- backend/main.go | 2 +- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index f4c0f6e..6bf6fba 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -35,7 +35,7 @@ type Database interface { GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) - GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) + GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) @@ -463,8 +463,8 @@ func (d *Db) Migrate() error { return nil } -// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. -func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { +// GetAllWeeklyReports retrieves weekly reports for a specific user and project. +func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) { query := ` SELECT wr.week, diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index fe3e6cd..a691a4d 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) { t.Error("AddWeeklyReport failed:", err) } - reports, err := db.GetWeeklyReportsUser("testuser", "testproject") + reports, err := db.GetAllWeeklyReports("testuser", "testproject") if err != nil { t.Error("GetWeeklyReportsUser failed:", err) } @@ -962,6 +962,5 @@ func TestRemoveProject(t *testing.T) { if len(projects) != 0 { t.Error("RemoveProject failed: expected 0, got", len(projects)) } - + } - \ No newline at end of file diff --git a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go b/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go index da8a90b..825c0cc 100644 --- a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go +++ b/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go @@ -8,29 +8,49 @@ import ( "github.com/golang-jwt/jwt/v5" ) -// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project -func GetWeeklyReportsUserHandler(c *fiber.Ctx) error { +// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project +func GetAllWeeklyReports(c *fiber.Ctx) error { // Extract the necessary parameters from the token user := c.Locals("user").(*jwt.Token) claims := user.Claims.(jwt.MapClaims) username := claims["name"].(string) - // Extract necessary (path) parameters from the request + // Extract project name and week from query parameters projectName := c.Params("projectName") + target_user := c.Query("targetUser") // The user whose reports are being requested - // TODO: Here we need to check whether the user is a member of the project - // If not, we should return an error. On the other hand, if the user not a member, - // the returned list of reports will (should) allways be empty. + // If the target user is not empty, use it as the username + if target_user == "" { + target_user = username + } + + log.Info(username, " trying to get all weekly reports for: ", target_user) + + if projectName == "" { + log.Info("Missing project name") + return c.Status(400).SendString("Missing project name") + } + + // If the token user is not an admin, check if the target user is the same as the token user + pm, err := db.GetDb(c).IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if pm == false && target_user != username { + log.Info("Unauthorized access") + return c.Status(403).SendString("Unauthorized access") + } // Retrieve weekly reports for the user in the project from the database - reports, err := db.GetDb(c).GetWeeklyReportsUser(username, projectName) + reports, err := db.GetDb(c).GetAllWeeklyReports(username, projectName) if err != nil { log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err) return c.Status(500).SendString(err.Error()) } - log.Info("Returning weekly reports for user:", username, "in project:", projectName) - - // Return the list of reports as JSON + log.Info("Returning weekly report") + // Return the retrieved weekly report return c.JSON(reports) } diff --git a/backend/main.go b/backend/main.go index f811a58..b5ecacf 100644 --- a/backend/main.go +++ b/backend/main.go @@ -128,7 +128,7 @@ func main() { // reportGroup := api.Group("/report") // Not currently in use api.Get("/getWeeklyReport", reports.GetWeeklyReport) api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) - api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler) + api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports) api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Put("/signReport/:reportId", reports.SignReport) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) From fcd035fe6e4cea059dfad4062d9c7528d569814d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:32:07 +0200 Subject: [PATCH 2/8] TS Api for getAllWeeklyReport --- frontend/src/API/API.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 16aebff..c4cf445 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -100,7 +100,7 @@ interface API { ): Promise>; /** Gets a weekly report for a specific user, project and week. - * Keep in mind that the user within the token needs to be PM + * Keep in mind that the user within the token needs to be PM * of the project to get the report, unless the user is the target user. * @param {string} projectName The name of the project. * @param {string} week The week number. @@ -122,9 +122,10 @@ interface API { * @param {string} token The token of the user * @returns {APIResponse} A list of weekly reports */ - getWeeklyReportsForUser( + getAllWeeklyReportsForUser( projectName: string, token: string, + targetUser?: string, ): Promise>; /** Gets all the projects of a user @@ -558,18 +559,22 @@ export const api: API = { } }, - async getWeeklyReportsForUser( + async getAllWeeklyReportsForUser( projectName: string, token: string, + targetUser?: string, ): Promise> { try { - const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, + const response = await fetch( + `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, }, - }); + ); if (!response.ok) { return { From 9c5aa1041443573be97c3cb3df4cab074dcc2086 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:32:50 +0200 Subject: [PATCH 3/8] Tests for getAllWeeklyReports --- testing/helpers.py | 2 +- testing/testing.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/testing/helpers.py b/testing/helpers.py index 20b00b1..5f2f367 100644 --- a/testing/helpers.py +++ b/testing/helpers.py @@ -25,7 +25,7 @@ signReportPath = base_url + "/api/signReport" addUserToProjectPath = base_url + "/api/addUserToProject" promoteToAdminPath = base_url + "/api/promoteToAdmin" getUserProjectsPath = base_url + "/api/getUserProjects" -getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser" +getAllWeeklyReportsPath = base_url + "/api/getAllWeeklyReports" checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager" ProjectRoleChangePath = base_url + "/api/ProjectRoleChange" getUsersProjectPath = base_url + "/api/getUsersProject" diff --git a/testing/testing.py b/testing/testing.py index a2dfb64..ba38ced 100644 --- a/testing/testing.py +++ b/testing/testing.py @@ -217,14 +217,15 @@ def test_sign_report(): # Test function to get weekly reports for a user in a project -def test_get_weekly_reports_user(): +def test_get_all_weekly_reports(): # Log in as the user token = login(username, "always_same").json()["token"] # Get weekly reports for the user in the project response = requests.get( - getWeeklyReportsUserPath + "/" + projectName, + getAllWeeklyReportsPath + "/" + projectName, headers={"Authorization": "Bearer " + token}, + params={"targetUser": username}, ) dprint(response.text) @@ -507,7 +508,7 @@ if __name__ == "__main__": test_get_project() test_sign_report() test_add_user_to_project() - test_get_weekly_reports_user() + test_get_all_weekly_reports() test_check_if_project_manager() test_ProjectRoleChange() test_ensure_manager_of_created_project() From 12a2691d557ac21149c872bf055f4054236e3b0d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:40:48 +0200 Subject: [PATCH 4/8] Rename --- .../{GetWeeklyReportsUserHandler.go => GetAllWeeklyReports.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/internal/handlers/reports/{GetWeeklyReportsUserHandler.go => GetAllWeeklyReports.go} (100%) diff --git a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go b/backend/internal/handlers/reports/GetAllWeeklyReports.go similarity index 100% rename from backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go rename to backend/internal/handlers/reports/GetAllWeeklyReports.go From 12810075f9d9ce1eeaf490f51c12e15024bb496b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:44:50 +0200 Subject: [PATCH 5/8] Logic error in getAllWeeklyReports fixed --- backend/internal/handlers/reports/GetAllWeeklyReports.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/internal/handlers/reports/GetAllWeeklyReports.go b/backend/internal/handlers/reports/GetAllWeeklyReports.go index 825c0cc..ee81c82 100644 --- a/backend/internal/handlers/reports/GetAllWeeklyReports.go +++ b/backend/internal/handlers/reports/GetAllWeeklyReports.go @@ -31,7 +31,7 @@ func GetAllWeeklyReports(c *fiber.Ctx) error { return c.Status(400).SendString("Missing project name") } - // If the token user is not an admin, check if the target user is the same as the token user + // If the user is not a project manager, they can only view their own reports pm, err := db.GetDb(c).IsProjectManager(username, projectName) if err != nil { log.Info("Error checking if user is project manager:", err) @@ -44,9 +44,9 @@ func GetAllWeeklyReports(c *fiber.Ctx) error { } // Retrieve weekly reports for the user in the project from the database - reports, err := db.GetDb(c).GetAllWeeklyReports(username, projectName) + reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName) if err != nil { - log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err) + log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err) return c.Status(500).SendString(err.Error()) } From f9c30c4951b8cbb91eb21895667fc85c284ecaf0 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:48:02 +0200 Subject: [PATCH 6/8] Docs --- backend/docs/docs.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 7a08b0e..c8b020d 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -108,6 +108,56 @@ const docTemplate = `{ } } }, + "/promote/{projectName}": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Promote a user to project manager", + "consumes": [ + "text/plain" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "Auth" + ], + "summary": "Promote to project manager", + "parameters": [ + { + "type": "string", + "description": "Project name", + "name": "projectName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "User name", + "name": "userName", + "in": "query", + "required": true + } + ], + "responses": { + "403": { + "description": "Forbidden", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/promoteToAdmin": { "post": { "security": [ From 76ae587116e080ba758400055548394b6eaf55b0 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 18:05:56 +0200 Subject: [PATCH 7/8] Fix for javascript optional parameter formatting error --- frontend/src/API/API.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index c4cf445..b3c6eae 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -538,7 +538,7 @@ export const api: API = { ): Promise> { try { const response = await fetch( - `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser}`, + `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, { method: "GET", headers: { @@ -566,7 +566,7 @@ export const api: API = { ): Promise> { try { const response = await fetch( - `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`, + `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, { method: "GET", headers: { From e67c54540c389cfca61b0778ea7d3eab219e9f3b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 18:08:02 +0200 Subject: [PATCH 8/8] RemoveUserFromProject handler implemented, corresponding TS api, untested --- backend/internal/database/db.go | 10 +++++ .../projects/RemoveUserFromProject.go | 40 +++++++++++++++++++ backend/main.go | 1 + frontend/src/API/API.ts | 31 ++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 backend/internal/handlers/projects/RemoveUserFromProject.go diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 6bf6fba..0bd67bc 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -17,6 +17,7 @@ type Database interface { AddUser(username string, password string) error CheckUser(username string, password string) bool RemoveUser(username string) error + RemoveUserFromProject(username string, projectname string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error @@ -86,6 +87,10 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles JOIN projects ON user_roles.project_id = projects.id WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` +const removeUserFromProjectQuery = `DELETE FROM user_roles + WHERE user_id = (SELECT id FROM users WHERE username = ?) + AND project_id = (SELECT id FROM projects WHERE name = ?)` + // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -147,6 +152,11 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err } +func (d *Db) RemoveUserFromProject(username string, projectname string) error { + _, err := d.Exec(removeUserFromProjectQuery, username, projectname) + return err +} + // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // Execute the SQL query to change the user's role diff --git a/backend/internal/handlers/projects/RemoveUserFromProject.go b/backend/internal/handlers/projects/RemoveUserFromProject.go new file mode 100644 index 0000000..7aefcf8 --- /dev/null +++ b/backend/internal/handlers/projects/RemoveUserFromProject.go @@ -0,0 +1,40 @@ +package projects + +import ( + db "ttime/internal/database" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +func RemoveUserFromProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + pm_name := claims["name"].(string) + + project := c.Params("projectName") + username := c.Query("userName") + + // Check if the user is a project manager + isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isPM { + log.Info("User: ", pm_name, " is not a project manager in project: ", project) + return c.Status(403).SendString("User is not a project manager") + } + + // Remove the user from the project + if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil { + log.Info("Error removing user from project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return success message + log.Info("User : ", username, " removed from project: ", project) + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/main.go b/backend/main.go index b5ecacf..42daa5c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -121,6 +121,7 @@ func main() { api.Post("/ProjectRoleChange", projects.ProjectRoleChange) api.Put("/promoteToPm/:projectName", projects.PromoteToPm) api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) + api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/project/:projectID", projects.DeleteProject) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index b3c6eae..3c0f0e9 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -201,6 +201,12 @@ interface API { token: string, ): Promise>; + removeUserFromProject( + user: string, + project: string, + token: string, + ): Promise>; + removeProject( projectName: string, token: string, @@ -359,6 +365,31 @@ export const api: API = { } }, + async removeUserFromProject( + user: string, + project: string, + token: string, + ): Promise> { + try { + const response = await fetch( + `/api/removeUserFromProject/${project}?userName=${user}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }, + ); + if (!response.ok) { + return { success: false, message: "Failed to remove member" }; + } + } catch (e) { + return { success: false, message: "Failed to remove member" }; + } + return { success: true, message: "Removed member" }; + }, + async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", {