From dea802bd91f3b3215eb6c440c8e2f21bfcf4688f Mon Sep 17 00:00:00 2001 From: al8763be Date: Thu, 21 Mar 2024 01:37:39 +0100 Subject: [PATCH 01/35] bummed handler --- backend/internal/database/db.go | 46 +++++++++++++++++++ backend/internal/database/db_test.go | 31 +++++++++++++ backend/internal/handlers/global_state.go | 1 + .../handlers/handlers_report_related.go | 36 +++++++++++++++ backend/main.go | 1 + testing.py | 43 +++++++++++++++++ 6 files changed, 158 insertions(+) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index fd0a083..7410b16 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -36,6 +36,7 @@ type Database interface { 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) + GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) @@ -353,6 +354,51 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } +func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) { + // Define the SQL query to fetch unsigned reports for a given user + query := ` + SELECT + report_id, + user_id, + project_id, + week, + development_time, + meeting_time, + admin_time, + own_work_time, + study_time, + testing_time, + signed_by + FROM + weekly_reports + WHERE + signed_by IS NULL + AND project_id = (SELECT id FROM projects WHERE name = ?) + ` + + // Execute the query + rows, err := d.Queryx(query, projectName) + if err != nil { + return nil, err + } + defer rows.Close() + + // Iterate over the rows and populate the result slice + var reports []types.WeeklyReport + for rows.Next() { + var report types.WeeklyReport + if err := rows.StructScan(&report); err != nil { + return nil, err + } + reports = append(reports, report) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return reports, nil +} + // IsSiteAdmin checks if a given username is a site admin func (d *Db) IsSiteAdmin(username string) (bool, error) { // Define the SQL query to check if the user is a site admin diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 139fba9..cff70a0 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -470,6 +470,37 @@ func TestGetWeeklyReport(t *testing.T) { // Check other fields similarly } +func TestGetUnsignedWeeklyReports(t *testing.T) { + db, err := setupAdvancedState() + if err != nil { + t.Error("setupState failed:", err) + } + + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.AddProject("testproject", "description", "testuser") + if err != nil { + t.Error("AddProject failed:", err) + } + + err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) + if err != nil { + t.Error("AddWeeklyReport failed:", err) + } + + reports, err := db.GetUnsignedWeeklyReports("testproject") + if err != nil { + t.Error("GetUnsignedWeeklyReports failed:", err) + } + + if reports == nil { + t.Error("Expected non-nil reports, got nil") + } +} + // TestSignWeeklyReport tests SignWeeklyReport function of the database func TestSignWeeklyReport(t *testing.T) { db, err := setupState() diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index b88bdcd..6b197bb 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -28,6 +28,7 @@ type GlobalState interface { ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project ChangeUserName(c *fiber.Ctx) error // WIP GetAllUsersProject(c *fiber.Ctx) error // WIP + GetUnsignedReports(c *fiber.Ctx) error // } // "Constructor" diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go index fcba523..534050a 100644 --- a/backend/internal/handlers/handlers_report_related.go +++ b/backend/internal/handlers/handlers_report_related.go @@ -115,6 +115,42 @@ func (gs *GState) SignReport(c *fiber.Ctx) error { return c.Status(200).SendString("Weekly report signed successfully") } +func (gs *GState) GetUnsignedReports(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 project name and week from query parameters + projectName := c.Query("projectName") + + log.Info("Getting unsigned reports for") + + if projectName == "" { + log.Info("Missing project name") + return c.Status(400).SendString("Missing project name") + } + + // Get the project manager's ID + isProjectManager, err := gs.Db.IsProjectManager(projectManagerUsername, projectName) + if err != nil { + log.Info("Failed to get project manager ID") + return c.Status(500).SendString("Failed to get project manager ID") + } + log.Info("User is Project Manager: ", isProjectManager) + + // Call the database function to get the unsigned weekly reports + reports, err := gs.Db.GetUnsignedWeeklyReports(projectName) + if err != nil { + log.Info("Error getting unsigned weekly reports:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning unsigned reports") + // Return the list of unsigned reports + return c.JSON(reports) +} + // GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error { // Extract the necessary parameters from the token diff --git a/backend/main.go b/backend/main.go index ff6b94e..835524c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -92,6 +92,7 @@ func main() { server.Get("/api/project/:projectId", gs.GetProject) server.Get("/api/project/getAllUsers", gs.GetAllUsersProject) server.Get("/api/getWeeklyReport", gs.GetWeeklyReport) + server.Get("/api/getUnsignedReports", gs.GetUnsignedReports) server.Post("/api/signReport", gs.SignReport) server.Put("/api/addUserToProject", gs.AddUserToProjectHandler) server.Put("/api/changeUserName", gs.ChangeUserName) diff --git a/testing.py b/testing.py index b8fbe43..3350497 100644 --- a/testing.py +++ b/testing.py @@ -41,6 +41,7 @@ getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser" checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager" ProjectRoleChangePath = base_url + "/api/ProjectRoleChange" getUsersProjectPath = base_url + "/api/getUsersProject" +getUsignedReportsPath = base_url + "/api/getUsignedReports" #ta bort auth i handlern för att få testet att gå igenom def test_ProjectRoleChange(): @@ -329,6 +330,47 @@ def test_get_weekly_reports_user(): assert response.status_code == 200, "Get weekly reports for user failed" gprint("test_get_weekly_reports_user successful") +def test_get_usigned_reports(): + # Log in as the user + + token = login(username, "always_same").json()["token"] + response = requests.post( + submitReportPath, + json={ + "projectName": projectName, + "week": 3, + "developmentTime": 10, + "meetingTime": 5, + "adminTime": 5, + "ownWorkTime": 10, + "studyTime": 10, + "testingTime": 10, + }, + headers={"Authorization": "Bearer " + token}, + ) + dprint(response.text) + assert response.status_code == 200, "Submit report failed" + + + # Get weekly reports for the user in the project + response = requests.get( + getWeeklyReportsUserPath + "/" + projectName, + headers={"Authorization": "Bearer " + token}, + ) + + dprint(response.text) + assert response.status_code == 200, "Get weekly reports for user failed" + + response = requests.get( + getUsignedReportsPath + "/" + projectName, + headers={"Authorization": "Bearer " + token}, + ) + dprint(response.text) + assert response.status_code == 200, "Get unsigned reports for user failed" + gprint("test_get_usigned_reports successful") + + + # Test function to check if a user is a project manager def test_check_if_project_manager(): # Log in as the user @@ -369,6 +411,7 @@ def test_ensure_manager_of_created_project(): if __name__ == "__main__": + test_get_usigned_reports() test_get_user_projects() test_create_user() test_login() From 9e2a3cca8114939f17e17e4594c8a25520a6f8a4 Mon Sep 17 00:00:00 2001 From: pavel Hamawand Date: Thu, 21 Mar 2024 01:56:27 +0100 Subject: [PATCH 02/35] Update the method signature in the API interface to use StrNameChange --- frontend/src/API/API.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 5c49a8d..a39ce9b 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -6,6 +6,7 @@ import { NewProject, UserProjectMember, WeeklyReport, + StrNameChange, } from "../Types/goTypes"; /** @@ -133,6 +134,16 @@ interface API { projectName: string, token: string, ): Promise>; + /** + * Changes the username of a user in the database. + * @param {StrNameChange} data The object containing the previous and new username. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response. + */ + changeUserName( + data: StrNameChange, + token: string, + ): Promise>; } /** An instance of the API */ @@ -342,7 +353,9 @@ export const api: API = { if (!response.ok) { return { success: false, message: "Failed to get weekly report" }; } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const data = (await response.json()) as WeeklyReport; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { success: true, data }; } } catch (e) { @@ -484,4 +497,12 @@ export const api: API = { }); } }, + changeUserName: function ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _data: StrNameChange, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + token: string, + ): Promise> { + throw new Error("Function not implemented."); + }, }; From 3e11b87eee3bf7c0ac8468e6a303790dc3956e64 Mon Sep 17 00:00:00 2001 From: pavel Hamawand Date: Thu, 21 Mar 2024 01:58:57 +0100 Subject: [PATCH 03/35] Modify the implementation of the changeUserName method in the api object --- frontend/src/API/API.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index a39ce9b..70e36c9 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -497,12 +497,28 @@ export const api: API = { }); } }, - changeUserName: function ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _data: StrNameChange, - // eslint-disable-next-line @typescript-eslint/no-unused-vars + + async changeUserName( + data: StrNameChange, token: string, ): Promise> { - throw new Error("Function not implemented."); + try { + const response = await fetch("/api/changeUserName", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + return { success: false, message: "Failed to change username" }; + } else { + return { success: true }; + } + } catch (e) { + return { success: false, message: "Failed to change username" }; + } }, }; From 19501122020f52baa1f607cf80b93c77e60f98e3 Mon Sep 17 00:00:00 2001 From: pavel Hamawand Date: Thu, 21 Mar 2024 02:22:23 +0100 Subject: [PATCH 04/35] implementing changeUsername component --- frontend/src/Components/ChangeUsername.tsx | 40 ++++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 3c35e94..04abc26 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -1,23 +1,41 @@ import React, { useState } from "react"; import InputField from "./InputField"; +import { api } from "../API/API"; function ChangeUsername(): JSX.Element { const [newUsername, setNewUsername] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); const handleChange = (e: React.ChangeEvent): void => { setNewUsername(e.target.value); }; - // const handleSubmit = async (): Promise => { - // try { - // // Call the API function to update the username - // await api.updateUsername(newUsername); - // // Optionally, add a success message or redirect the user - // } catch (error) { - // console.error("Error updating username:", error); - // // Optionally, handle the error - // } - // }; + const handleSubmit = async (): Promise => { + try { + // Call the API function to change the username + const response = await api.changeUserName( + { prevName: "currentName", newName: newUsername }, + "token", + ); + if (response.success) { + // Optionally, add a success message or redirect the user + console.log("Username changed successfully"); + } else { + // Handle the error message + console.error("Failed to change username:", response.message); + } + } catch (error) { + console.error("Error changing username:", error); + // Optionally, handle the error + setErrorMessage("Failed to change username"); + } + }; + + const handleButtonClick = (): void => { + handleSubmit().catch((error) => { + console.error("Error in handleSubmit:", error); + }); + }; return (
@@ -27,6 +45,8 @@ function ChangeUsername(): JSX.Element { value={newUsername} onChange={handleChange} /> + {errorMessage &&
{errorMessage}
} +
); } From baf11f19d6c5fab9a4856c22115845179b9d4b44 Mon Sep 17 00:00:00 2001 From: pavel Hamawand Date: Thu, 21 Mar 2024 02:47:51 +0100 Subject: [PATCH 05/35] added token --- frontend/src/Components/ChangeUsername.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 04abc26..247c2fb 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -13,9 +13,10 @@ function ChangeUsername(): JSX.Element { const handleSubmit = async (): Promise => { try { // Call the API function to change the username + const token = localStorage.getItem("accessToken") ?? ""; const response = await api.changeUserName( { prevName: "currentName", newName: newUsername }, - "token", + token, ); if (response.success) { // Optionally, add a success message or redirect the user From e9eb2e9ab60133840328485b2b70b53488678a8f Mon Sep 17 00:00:00 2001 From: pavel Hamawand Date: Thu, 21 Mar 2024 02:51:28 +0100 Subject: [PATCH 06/35] checks --- frontend/src/Components/ChangeUsername.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 247c2fb..e297a04 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -13,17 +13,23 @@ function ChangeUsername(): JSX.Element { const handleSubmit = async (): Promise => { try { // Call the API function to change the username - const token = localStorage.getItem("accessToken") ?? ""; + const token = localStorage.getItem("accessToken"); + if (!token) { + throw new Error("Access token not found"); + } + const response = await api.changeUserName( { prevName: "currentName", newName: newUsername }, token, ); + if (response.success) { // Optionally, add a success message or redirect the user console.log("Username changed successfully"); } else { // Handle the error message console.error("Failed to change username:", response.message); + setErrorMessage(response.message ?? "Failed to change username"); } } catch (error) { console.error("Error changing username:", error); From a73432669cc71db1c2a19a957278db705fda95fc Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 21 Mar 2024 03:36:30 +0100 Subject: [PATCH 07/35] New path --- frontend/src/main.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bac2292..5fd4d68 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -31,6 +31,7 @@ import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMembe import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx"; import NotFoundPage from "./Pages/NotFoundPage.tsx"; import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx"; +import AddUserToProject from "./Components/AddUserToProject.tsx"; // This is where the routes are mounted const router = createBrowserRouter([ @@ -147,6 +148,10 @@ const router = createBrowserRouter([ path: "/unauthorized", element: , }, + { + path: "/addUserToProject", + element: , + }, ]); // Semi-hacky way to get the root element From 2694beb0e8d80a95565b2f6a14c46e07e30f4464 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 21 Mar 2024 03:36:57 +0100 Subject: [PATCH 08/35] AddUserToProject API --- frontend/src/API/API.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 70e36c9..7920e0e 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -7,6 +7,7 @@ import { UserProjectMember, WeeklyReport, StrNameChange, + NewProjMember, } from "../Types/goTypes"; /** @@ -144,6 +145,10 @@ interface API { data: StrNameChange, token: string, ): Promise>; + addUserToProject( + user: NewProjMember, + token: string, + ): Promise>; } /** An instance of the API */ @@ -254,6 +259,30 @@ export const api: API = { } }, + async addUserToProject( + user: NewProjMember, + token: string, + ): Promise> { + try { + const response = await fetch("/api/addUserToProject", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + body: JSON.stringify(user), + }); + + if (!response.ok) { + return { success: false, message: "Failed to add member" }; + } else { + return { success: true, message: "Added member" }; + } + } catch (e) { + return { success: false, message: "Failed to add member" }; + } + }, + async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -353,9 +382,7 @@ export const api: API = { if (!response.ok) { return { success: false, message: "Failed to get weekly report" }; } else { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const data = (await response.json()) as WeeklyReport; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { success: true, data }; } } catch (e) { From c5bc6c1c585ae0efae9b818f70d2a4333ff42688 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 21 Mar 2024 03:37:37 +0100 Subject: [PATCH 09/35] Add user to project component --- frontend/src/Components/AddUserToProject.tsx | 132 +++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 frontend/src/Components/AddUserToProject.tsx diff --git a/frontend/src/Components/AddUserToProject.tsx b/frontend/src/Components/AddUserToProject.tsx new file mode 100644 index 0000000..debd0f9 --- /dev/null +++ b/frontend/src/Components/AddUserToProject.tsx @@ -0,0 +1,132 @@ +import { useState } from "react"; +import { APIResponse, api } from "../API/API"; +import { NewProjMember } from "../Types/goTypes"; +import Logo from "../assets/Logo.svg"; +import Button from "./Button"; +import GetAllUsers from "./GetAllUsers"; + +/** + * Tries to add a member to a project + * @param {Object} props - A NewProjMember + * @returns {boolean} True if added, false if not + */ +function MemberAdd(props: { memberToAdd: NewProjMember }): boolean { + let added = false; + + api + .addUserToProject( + props.memberToAdd, + localStorage.getItem("accessToken") ?? "", + ) + .then((response: APIResponse) => { + if (response.success) { + added = true; + } else { + console.error(response.message); + } + }) + .catch((error) => { + console.error("An error occurred during member add:", error); + }); + return added; +} + +/** + * Provides UI for adding a project to the system. + * @returns {JSX.Element} - Returns the component UI for adding a project + */ +function AddUserToProject(): JSX.Element { + const [name, setName] = useState(""); + const [users, setUsers] = useState([]); + const [role, setRole] = useState(""); + GetAllUsers({ setUsersProp: setUsers }); + + const handleClick = (): boolean => { + const newMember: NewProjMember = { + username: name, + projectname: localStorage.getItem("projectName") ?? "", + role: "user", + }; + return MemberAdd({ memberToAdd: newMember }); + }; + + return ( +
+
+
{ + e.preventDefault(); + MemberAdd({ + memberToAdd: { + username: "", + projectname: "", + role: "project_manager", + }, + }); + }} + > + TTIME Logo +

+ Add {name} to {localStorage.getItem("projectName") ?? ""} as {role} +

+

Role for user:

+
+
    +
  • { + setRole("user"); + }} + > + {"User"} +
  • +
  • { + setRole("project_manager"); + }} + > + {"Project manager"} +
  • +
+
+

User to add:

+
+
    +
    + {users.map((user) => ( +
  • { + setName(user); + }} + > + {user} +
  • + ))} +
+
+
+
+
+

+
+
+ ); +} + +export default AddUserToProject; From b2db9c54ca78a1fd0499e441630895f2a39ace91 Mon Sep 17 00:00:00 2001 From: Peter KW Date: Thu, 21 Mar 2024 03:39:18 +0100 Subject: [PATCH 10/35] Add member functionality added --- frontend/src/Components/ProjectInfoModal.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/Components/ProjectInfoModal.tsx b/frontend/src/Components/ProjectInfoModal.tsx index b153e9c..4d79cac 100644 --- a/frontend/src/Components/ProjectInfoModal.tsx +++ b/frontend/src/Components/ProjectInfoModal.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import Button from "./Button"; import { UserProjectMember } from "../Types/goTypes"; import GetUsersInProject from "./GetUsersInProject"; +import { Link } from "react-router-dom"; function ProjectInfoModal(props: { isVisible: boolean; @@ -50,6 +51,16 @@ function ProjectInfoModal(props: { }} type="button" /> + +