From e67c54540c389cfca61b0778ea7d3eab219e9f3b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 18:08:02 +0200 Subject: [PATCH 01/18] 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", { From 9a0f855d2b3f78bf4d18fff7d8faff62d0739215 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 4 Apr 2024 11:26:39 +0200 Subject: [PATCH 02/18] Fixes to adding members --- frontend/src/API/API.ts | 24 ++-- frontend/src/Components/AddMember.tsx | 55 ++++----- frontend/src/Components/AddUserToProject.tsx | 118 +++++++++++-------- 3 files changed, 104 insertions(+), 93 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 16aebff..414d978 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,4 +1,4 @@ -import { NewProjMember } from "../Components/AddMember"; +import { AddMemberInfo } from "../Components/AddMember"; import { ProjectRoleChange } from "../Components/ChangeRole"; import { projectTimes } from "../Components/GetProjectTimes"; import { ProjectMember } from "../Components/GetUsersInProject"; @@ -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. @@ -196,7 +196,7 @@ interface API { ): Promise>; addUserToProject( - user: NewProjMember, + addMemberInfo: AddMemberInfo, token: string, ): Promise>; @@ -335,18 +335,20 @@ export const api: API = { }, async addUserToProject( - user: NewProjMember, + addMemberInfo: AddMemberInfo, token: string, ): Promise> { try { - const response = await fetch("/api/addUserToProject", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, + const response = await fetch( + `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, }, - body: JSON.stringify(user), - }); + ); if (!response.ok) { return { success: false, message: "Failed to add member" }; diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx index 194afe8..d8036b7 100644 --- a/frontend/src/Components/AddMember.tsx +++ b/frontend/src/Components/AddMember.tsx @@ -1,44 +1,35 @@ -import { APIResponse, api } from "../API/API"; +import { api } from "../API/API"; -export interface NewProjMember { - username: string; - role: string; - projectname: string; +export interface AddMemberInfo { + userName: string; + projectName: string; } /** * Tries to add a member to a project - * @param {Object} props - A NewProjMember - * @returns {boolean} True if added, false if not + * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name + * @returns {Promise} */ -function AddMember(props: { memberToAdd: NewProjMember }): boolean { - let added = false; - if ( - props.memberToAdd.username === "" || - props.memberToAdd.role === "" || - props.memberToAdd.projectname === "" - ) { - alert("All fields must be filled before adding"); - return added; +async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise { + if (props.memberToAdd.userName === "") { + alert("You must choose at least one user to add"); + return; } - api - .addUserToProject( + try { + const response = await api.addUserToProject( props.memberToAdd, localStorage.getItem("accessToken") ?? "", - ) - .then((response: APIResponse) => { - if (response.success) { - alert("Member added"); - added = true; - } else { - alert("Member not added"); - console.error(response.message); - } - }) - .catch((error) => { - console.error("An error occurred during member add:", error); - }); - return added; + ); + if (response.success) { + alert(`[${props.memberToAdd.userName}] added`); + } else { + alert(`[${props.memberToAdd.userName}] not added`); + console.error(response.message); + } + } catch (error) { + alert(`[${props.memberToAdd.userName}] not added`); + console.error("An error occurred during member add:", error); + } } export default AddMember; diff --git a/frontend/src/Components/AddUserToProject.tsx b/frontend/src/Components/AddUserToProject.tsx index 039d3c0..f567560 100644 --- a/frontend/src/Components/AddUserToProject.tsx +++ b/frontend/src/Components/AddUserToProject.tsx @@ -1,71 +1,86 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import Button from "./Button"; -import GetAllUsers from "./GetAllUsers"; -import AddMember, { NewProjMember } from "./AddMember"; +import AddMember, { AddMemberInfo } from "./AddMember"; import BackButton from "./BackButton"; +import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; +import GetAllUsers from "./GetAllUsers"; /** * Provides UI for adding a member to a project. * @returns {JSX.Element} - Returns the component UI for adding a member */ function AddUserToProject(props: { projectName: string }): JSX.Element { - const [name, setName] = useState(""); + const [names, setNames] = useState([]); const [users, setUsers] = useState([]); - const [role, setRole] = useState(""); - GetAllUsers({ setUsersProp: setUsers }); + const [usersProj, setUsersProj] = useState([]); - const handleClick = (): boolean => { - const newMember: NewProjMember = { - username: name, - projectname: props.projectName, - role: role, - }; - return AddMember({ memberToAdd: newMember }); + // Gets all users and project members for filtering + GetAllUsers({ setUsersProp: setUsers }); + GetUsersInProject({ + setUsersProp: setUsersProj, + projectName: props.projectName, + }); + /* + * Filters the members from users so that users who are already + * members are not shown + */ + useEffect(() => { + setUsers((prevUsers) => { + const filteredUsers = prevUsers.filter( + (user) => + !usersProj.some((projectUser) => projectUser.Username === user), + ); + return filteredUsers; + }); + }, [usersProj]); + + // Attempts to add all of the selected users to the project + const handleAddClick = async (): Promise => { + if (names.length === 0) + alert("You have to choose at least one user to add"); + for (const name of names) { + const newMember: AddMemberInfo = { + userName: name, + projectName: props.projectName, + }; + await AddMember({ memberToAdd: newMember }); + } + setNames([]); + location.reload(); + }; + + // Updates the names that have been selected + const handleUserClick = (user: string): void => { + setNames((prevNames): string[] => { + if (!prevNames.includes(user)) { + return [...prevNames, user]; + } + return prevNames.filter((name) => name !== user); + }); }; return ( -
-

- User chosen: [{name}] +

+

+ {props.projectName} +

+

+ Choose users to add:

-

- Role chosen: [{role}] -

-

- Project chosen: [{props.projectName}] -

-

Choose role:

-
-
    -
  • { - setRole("member"); - }} - > - {"Member"} -
  • -
  • { - setRole("project_manager"); - }} - > - {"Project manager"} -
  • -
-
-

Choose user:

-
+
    {users.map((user) => (
  • { - setName(user); + handleUserClick(user); }} > {user} @@ -73,13 +88,16 @@ function AddUserToProject(props: { projectName: string }): JSX.Element { ))}
-
+

+ Number of users to be added: {names.length} +

+
From 13eb6597a7ad993dcd69c5b7887dda826052611f Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 4 Apr 2024 11:54:34 +0200 Subject: [PATCH 03/18] Some fixes to how they handle names and inputs --- frontend/src/Components/AddProject.tsx | 61 ++++++++++------------ frontend/src/Components/ChangeRoleView.tsx | 2 +- frontend/src/Components/ChangeUsername.tsx | 9 ++-- frontend/src/Components/Register.tsx | 10 ++-- frontend/src/Components/UserInfoModal.tsx | 2 +- 5 files changed, 44 insertions(+), 40 deletions(-) diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index e2ad8b9..c8a1c66 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,37 +1,10 @@ import { useState } from "react"; -import { APIResponse, api } from "../API/API"; +import { api } from "../API/API"; import { NewProject } from "../Types/goTypes"; import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; -/** - * Tries to add a project to the system - * @param {Object} props - Project name and description - * @returns {boolean} True if created, false if not - */ -function CreateProject(props: { name: string; description: string }): void { - const project: NewProject = { - name: props.name, - description: props.description, - }; - - api - .createProject(project, localStorage.getItem("accessToken") ?? "") - .then((response: APIResponse) => { - if (response.success) { - alert("Project added!"); - } else { - alert("Project NOT added!"); - console.error(response.message); - } - }) - .catch((error) => { - alert("Project NOT added!"); - console.error("An error occurred during creation:", error); - }); -} - /** * Provides UI for adding a project to the system. * @returns {JSX.Element} - Returns the component UI for adding a project @@ -40,6 +13,33 @@ function AddProject(): JSX.Element { const [name, setName] = useState(""); const [description, setDescription] = useState(""); + /** + * Tries to add a project to the system + */ + const handleCreateProject = async (): Promise => { + const project: NewProject = { + name: name.replace(/ /g, ""), + description: description.trim(), + }; + try { + const response = await api.createProject( + project, + localStorage.getItem("accessToken") ?? "", + ); + if (response.success) { + alert(`${project.name} added!`); + setDescription(""); + setName(""); + } else { + alert("Project not added, name could be taken"); + console.error(response.message); + } + } catch (error) { + alert("Project not added"); + console.error(error); + } + }; + return (
@@ -47,10 +47,7 @@ function AddProject(): JSX.Element { className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" onSubmit={(e) => { e.preventDefault(); - CreateProject({ - name: name, - description: description, - }); + void handleCreateProject(); }} > (); const handleRegister = async (): Promise => { + if (username === "" || password === "") { + alert("Must provide username and password"); + return; + } const newUser: NewUser = { - username: username ?? "", + username: username?.replace(/ /g, "") ?? "", password: password ?? "", }; const response = await api.registerUser(newUser); if (response.success) { - alert("User added!"); + alert(`${newUser.username} added!`); setPassword(""); setUsername(""); } else { - alert("User not added"); + alert("User not added, name could be taken"); setErrMessage(response.message ?? "Unknown error"); console.error(errMessage); } diff --git a/frontend/src/Components/UserInfoModal.tsx b/frontend/src/Components/UserInfoModal.tsx index 8cb4c9d..eae011c 100644 --- a/frontend/src/Components/UserInfoModal.tsx +++ b/frontend/src/Components/UserInfoModal.tsx @@ -28,7 +28,7 @@ function UserInfoModal(props: { const handleClickChangeName = (): void => { const nameChange: StrNameChange = { prevName: props.username, - newName: newUsername, + newName: newUsername.replace(/ /g, ""), }; ChangeUsername({ nameChange: nameChange }); }; From 0a951ecd2b691a52511b5c733598baa930d6a5bd Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:31:39 +0200 Subject: [PATCH 04/18] 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 2ca8c604186c1009fa66821b550966b8c4018f65 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:32:07 +0200 Subject: [PATCH 05/18] TS Api for getAllWeeklyReport --- frontend/src/API/API.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 414d978..fd6b4d8 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -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 @@ -560,18 +561,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 4319f307996d2ce2852556b99ae1d4cb0f5c05ba Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:32:50 +0200 Subject: [PATCH 06/18] 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 d7789ab844dd2b4e092f756fb3e670d5b50be683 Mon Sep 17 00:00:00 2001 From: Davenludd Date: Wed, 3 Apr 2024 17:44:26 +0200 Subject: [PATCH 07/18] Refactor API call in AllTimeReportsInProjectOtherUser component --- frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx index ef78642..570b94e 100644 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx @@ -17,10 +17,10 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getWeeklyReportsForDifferentUser( + const response = await api.getAllWeeklyReportsForUser( projectName ?? "", - username ?? "", token, + username ?? "", ); console.log(response); if (response.success) { From 7313a27b312043c4f0a553dfe01d4c47d559e032 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:40:48 +0200 Subject: [PATCH 08/18] 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 620073f688da805ff2960ac770fe1d63757f38ea Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:44:50 +0200 Subject: [PATCH 09/18] 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 82e72432f632af361173a6604598b1d2906e540c Mon Sep 17 00:00:00 2001 From: Davenludd Date: Wed, 3 Apr 2024 18:05:07 +0200 Subject: [PATCH 10/18] Update API method to get all weekly reports --- frontend/src/Components/AllTimeReportsInProject.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index 4fa9ad8..0d5916b 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getWeeklyReportsForUser( + const response = await api.getAllWeeklyReportsForUser( projectName ?? "", token, ); From 194e1d52a8e8693aaec02f67cafb5af0a67ade00 Mon Sep 17 00:00:00 2001 From: Davenludd Date: Wed, 3 Apr 2024 18:05:19 +0200 Subject: [PATCH 11/18] Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole --- frontend/src/Components/OtherUsersTR.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/Components/OtherUsersTR.tsx b/frontend/src/Components/OtherUsersTR.tsx index 2b00e16..ce7761c 100644 --- a/frontend/src/Components/OtherUsersTR.tsx +++ b/frontend/src/Components/OtherUsersTR.tsx @@ -29,6 +29,7 @@ export default function OtherUsersTR(): JSX.Element { projectName ?? "", fetchedWeek?.toString() ?? "0", token, + username ?? "", ); if (response.success) { @@ -86,6 +87,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={developmentTime === 0 ? "" : developmentTime} + readOnly /> @@ -97,6 +99,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={meetingTime === 0 ? "" : meetingTime} + readOnly /> @@ -108,6 +111,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={adminTime === 0 ? "" : adminTime} + readOnly /> @@ -119,6 +123,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={ownWorkTime === 0 ? "" : ownWorkTime} + readOnly /> @@ -130,6 +135,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={studyTime === 0 ? "" : studyTime} + readOnly /> @@ -141,6 +147,7 @@ export default function OtherUsersTR(): JSX.Element { min="0" className="border-2 border-black rounded-md text-center w-1/2" value={testingTime === 0 ? "" : testingTime} + readOnly /> From 266aaa482c2f1b26a27bff209dd428bb200636df Mon Sep 17 00:00:00 2001 From: Davenludd Date: Wed, 3 Apr 2024 18:05:30 +0200 Subject: [PATCH 12/18] Fix initial state values in TimePerRole component --- frontend/src/Components/TimePerActivity.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/Components/TimePerActivity.tsx b/frontend/src/Components/TimePerActivity.tsx index 6175845..c5e4a9f 100644 --- a/frontend/src/Components/TimePerActivity.tsx +++ b/frontend/src/Components/TimePerActivity.tsx @@ -8,12 +8,12 @@ import { projectTimes } from "./GetProjectTimes"; * @returns JSX.Element */ export default function TimePerRole(): JSX.Element { - const [development, setDevelopment] = useState(); - const [meeting, setMeeting] = useState(); - const [admin, setAdmin] = useState(); - const [own_work, setOwnWork] = useState(); - const [study, setStudy] = useState(); - const [testing, setTesting] = useState(); + const [development, setDevelopment] = useState(0); + const [meeting, setMeeting] = useState(0); + const [admin, setAdmin] = useState(0); + const [own_work, setOwnWork] = useState(0); + const [study, setStudy] = useState(0); + const [testing, setTesting] = useState(0); const token = localStorage.getItem("accessToken") ?? ""; const { projectName } = useParams(); From ee49bbde69f8d26d1fd98f5350d9a80967033fa1 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 17:48:02 +0200 Subject: [PATCH 13/18] 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 10d06767c4a35ee8e6c604f9c371ab6f8beef832 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 3 Apr 2024 18:05:56 +0200 Subject: [PATCH 14/18] 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 fd6b4d8..e9820a0 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -540,7 +540,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: { @@ -568,7 +568,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 f75e6e4a6e8941742e36033a7af8e5d1f1823112 Mon Sep 17 00:00:00 2001 From: Davenludd Date: Wed, 3 Apr 2024 18:36:17 +0200 Subject: [PATCH 15/18] Update useEffect dependencies in AllTimeReportsInProjectOtherUser component --- frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx index 570b94e..cde9fa7 100644 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx @@ -31,7 +31,7 @@ function AllTimeReportsInProject(): JSX.Element { }; void getWeeklyReports(); - }, []); + }, [projectName, username]); return ( <> From 2d8d200340da02676c575a071cde0cb61a229cb6 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 4 Apr 2024 12:22:28 +0200 Subject: [PATCH 16/18] Can now remove users from projects --- frontend/src/Components/MemberInfoModal.tsx | 13 +++--- .../src/Components/RemoveUserFromProj.tsx | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 frontend/src/Components/RemoveUserFromProj.tsx diff --git a/frontend/src/Components/MemberInfoModal.tsx b/frontend/src/Components/MemberInfoModal.tsx index 8b32367..adcb39f 100644 --- a/frontend/src/Components/MemberInfoModal.tsx +++ b/frontend/src/Components/MemberInfoModal.tsx @@ -1,8 +1,8 @@ import Button from "./Button"; -import DeleteUser from "./DeleteUser"; import UserProjectListAdmin from "./UserProjectListAdmin"; import { useState } from "react"; import ChangeRoleView from "./ChangeRoleView"; +import RemoveUserFromProj from "./RemoveUserFromProj"; function MemberInfoModal(props: { projectName: string; @@ -42,13 +42,16 @@ function MemberInfoModal(props: {