From 7b78767f5016251fe1d47feecc4c56385526fec5 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 01:14:30 +0100 Subject: [PATCH 01/24] restore --- frontend/src/API/API.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index fc2367b..6a52fb8 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -4,6 +4,7 @@ import { User, Project, NewProject, + WeeklyReport, } from "../Types/goTypes"; // This type of pattern should be hard to misuse @@ -49,9 +50,10 @@ interface API { token: string, ): Promise>; getWeeklyReportsForProject( + username: string, projectName: string, token: string, - ): Promise>; + ): Promise>; /** Gets all the projects of a user*/ getUserProjects(token: string): Promise>; /** Gets a project from id*/ @@ -272,15 +274,19 @@ export const api: API = { } }, - async getWeeklyReportsForProject(projectName: string, token: string) { + async getWeeklyReportsForProject( + username: string, + projectName: string, + token: string, + ) { try { - const response = await fetch("/api/getWeeklyReportsForProject", { + const response = await fetch("/api/getWeeklyReportsUser", { method: "GET", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, - body: JSON.stringify({ projectName }), + body: JSON.stringify({ username, projectName }), }); if (!response.ok) { @@ -289,7 +295,7 @@ export const api: API = { message: "Failed to get weekly reports for project", }; } else { - const data = (await response.json()) as NewWeeklyReport[]; + const data = (await response.json()) as WeeklyReport[]; return { success: true, data }; } } catch (e) { From 45761dadcb0df76979885c3f99c37b56edd93911 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 17:50:51 +0100 Subject: [PATCH 02/24] Fix for checkProjectManager endpoint and test_check_if_project_manager --- backend/internal/handlers/handlers_project_related.go | 6 +++++- backend/main.go | 2 +- testing.py | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go index 7b95c26..99696e7 100644 --- a/backend/internal/handlers/handlers_project_related.go +++ b/backend/internal/handlers/handlers_project_related.go @@ -212,8 +212,12 @@ func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error { // IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { + // Get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + // Extract necessary parameters from the request query string - username := c.Query("username") projectName := c.Query("projectName") // Check if the user is a project manager for the specified project diff --git a/backend/main.go b/backend/main.go index dc4bf0a..ff6b94e 100644 --- a/backend/main.go +++ b/backend/main.go @@ -98,7 +98,7 @@ func main() { server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) server.Get("/api/users/all", gs.ListAllUsers) server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler) - server.Get("api/checkIfProjectManager", gs.IsProjectManagerHandler) + server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler) server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) diff --git a/testing.py b/testing.py index 9181d39..568cb87 100644 --- a/testing.py +++ b/testing.py @@ -329,9 +329,8 @@ def test_check_if_project_manager(): # Check if the user is a project manager for the project response = requests.get( - checkIfProjectManagerPath, + checkIfProjectManagerPath + "/" + projectName, headers={"Authorization": "Bearer " + token}, - params={"username": username, "projectName": projectName}, ) dprint(response.text) From bef8a6af8526609809b23c9174b39054456b49f4 Mon Sep 17 00:00:00 2001 From: Melker Date: Wed, 20 Mar 2024 17:40:10 +0100 Subject: [PATCH 03/24] =?UTF-8?q?GetTotalTimePerActivity=20och=20ett=20hal?= =?UTF-8?q?vt=20test=20f=C3=B6r=20den?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/database/db.go | 41 ++++++++++++++++++++++++++++ backend/internal/database/db_test.go | 33 ++++++---------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 59d3277..3f2d2f7 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -39,6 +39,9 @@ type Database interface { SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) + GetTotalTimePerActivity(projectName string) (map[string]int, error) + + } // This struct is a wrapper type that holds the database connection @@ -519,3 +522,41 @@ func (d *Db) MigrateSampleData() error { return nil } + +func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) { + + query := ` + SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time + FROM weekly_reports + JOIN projects ON weekly_reports.project_id = projects.id + WHERE projects.name = ? + ` + + rows, err := d.DB.Query(query, projectName) + if err != nil { + return nil, err + } + defer rows.Close() + + totalTime := make(map[string]int) + + for rows.Next() { + var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int + if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil { + return nil, err + } + + totalTime["development"] += developmentTime + totalTime["meeting"] += meetingTime + totalTime["admin"] += adminTime + totalTime["own_work"] += ownWorkTime + totalTime["study"] += studyTime + totalTime["testing"] += testingTime + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return totalTime, nil +} diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 6757522..043b6d0 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -676,38 +676,23 @@ func TestIsProjectManager(t *testing.T) { } } -func TestChangeUserName(t *testing.T) { +func TestGetTotalTimePerActivity(t *testing.T) { + // Initialize your test database connection db, err := setupState() if err != nil { t.Error("setupState failed:", err) } - // Add a user - err = db.AddUser("testuser", "password") + // Run the query to get total time per activity + totalTime, err := db.GetTotalTimePerActivity("projecttest") if err != nil { - t.Error("AddUser failed:", err) + t.Error("GetTotalTimePerActivity failed:", err) } - // Change the user's name - err = db.ChangeUserName("testuser", "newname") - if err != nil { - t.Error("ChangeUserName failed:", err) + // Check if the totalTime map is not nil + if totalTime == nil { + t.Error("Expected non-nil totalTime map, got nil") } - // Retrieve the user's ID - userID, err := db.GetUserId("newname") - if err != nil { - t.Error("GetUserId failed:", err) - } - - // Ensure the user's ID matches the expected value - if userID != 1 { - t.Errorf("Expected user ID to be 1, got %d", userID) - } - - // Attempt to retrieve the user by the old name - _, err = db.GetUserId("testuser") - if err == nil { - t.Error("Expected GetUserId to fail for the old name, but it didn't") - } + // ska lägga till fler assertions } From 3525598df25cacb0d65e04a2b366d7dd1442039f Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 18:47:37 +0100 Subject: [PATCH 04/24] Tests for AddProject to ensure creator is Manager --- backend/internal/database/db_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 6757522..e834135 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -711,3 +711,31 @@ func TestChangeUserName(t *testing.T) { t.Error("Expected GetUserId to fail for the old name, but it didn't") } } + +func TestEnsureManagerOfCreatedProject(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + // Add a user + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add a project + err = db.AddProject("testproject", "description", "testuser") + if err != nil { + t.Error("AddProject failed:", err) + } + + managerState, err := db.IsProjectManager("testuser", "testproject") + if err != nil { + t.Error("IsProjectManager failed:", err) + } + + if !managerState { + t.Error("Expected testuser to be a project manager, but it's not.") + } +} From 8fbb4be0ad05d5814feb2ee8483f8c8155b5a155 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 19:38:52 +0100 Subject: [PATCH 05/24] merge again --- frontend/src/API/API.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 636d8e3..6a508b5 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -274,15 +274,6 @@ export const api: API = { } }, -<<<<<<< HEAD - async getWeeklyReportsForProject( - username: string, - projectName: string, - token: string, - ) { - try { - const response = await fetch("/api/getWeeklyReportsUser", { -======= async getWeeklyReportsForUser( username: string, projectName: string, @@ -290,16 +281,11 @@ export const api: API = { ): Promise> { try { const response = await fetch(`/api/getWeeklyReportsUser?username=${username}&projectName=${projectName}`, { ->>>>>>> af5813681d11500c0cfe723af1b8d2522bf277d2 method: "GET", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, -<<<<<<< HEAD - body: JSON.stringify({ username, projectName }), -======= ->>>>>>> af5813681d11500c0cfe723af1b8d2522bf277d2 }); if (!response.ok) { From e8c660705fb4819b97c5decf6564e0f2021a83d8 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 20:05:04 +0100 Subject: [PATCH 06/24] sampledata now has weeklyReports for user & user2 --- .../database/sample_data/0010_sample_data.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index 4dac91b..53cf1bf 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -33,3 +33,15 @@ VALUES (3,3,"member"); INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) VALUES (2,1,"project_manager"); + +INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL); + +INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL); + +INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL); + +INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL); From 91c9f5049126b8c48c0cbcefe77a08aa5dddfb63 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 20:08:00 +0100 Subject: [PATCH 07/24] sampledata now has weeklyReports for user & user2 --- backend/internal/database/sample_data/0010_sample_data.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index 53cf1bf..800bd11 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -40,6 +40,9 @@ VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL); INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL); +INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL); + INSERT INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL); From a9e515925ebaeebb0aea8a5ee3e39a85b40591b3 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 20:24:56 +0100 Subject: [PATCH 08/24] getWeeklyReportsForUser fixed --- 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 1a34e41..d05f92c 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -16,8 +16,8 @@ function AllTimeReportsInProject(): JSX.Element { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; const response = await api.getWeeklyReportsForUser( - token, projectName ?? "", + token, ); console.log(response); if (response.success) { From 9f61a36a68833757f3c32aa123f6637180d0e4a5 Mon Sep 17 00:00:00 2001 From: Johanna Date: Wed, 20 Mar 2024 20:36:34 +0100 Subject: [PATCH 09/24] Added comments to TypeScript API, merge --- frontend/src/API/API.ts | 143 ++++++++++++++++-- frontend/src/Components/AddProject.tsx | 6 +- .../Components/AllTimeReportsInProject.tsx | 72 +++++++++ 3 files changed, 202 insertions(+), 19 deletions(-) create mode 100644 frontend/src/Components/AllTimeReportsInProject.tsx diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 8fd66d3..8778129 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -4,50 +4,135 @@ import { User, Project, NewProject, + WeeklyReport, } from "../Types/goTypes"; -// This type of pattern should be hard to misuse +/** + * Response object returned by API methods. + */ export interface APIResponse { + /** Indicates whether the API call was successful */ success: boolean; + /** Optional message providing additional information or error description */ message?: string; + /** Optional data returned by the API method */ data?: T; } -// Note that all protected routes also require a token -// Defines all the methods that an instance of the API must implement +/** + * Interface defining methods that an instance of the API must implement. + */ interface API { - /** Register a new user */ + /** + * Register a new user + * @param {NewUser} user The user object to be registered + * @returns {Promise>} A promise containing the API response with the user data. + */ registerUser(user: NewUser): Promise>; - /** Remove a user */ + + /** + * Removes a user. + * @param {string} username The username of the user to be removed. + * @param {string} token The authentication token. + * @returns {Promise>} A promise containing the API response with the removed user data. + */ removeUser(username: string, token: string): Promise>; - /** Login */ + + /** + * Check if user is project manager. + * @param {string} username The username of the user. + * @param {string} projectName The name of the project. + * @param {string} token The authentication token. + * @returns {Promise>} A promise containing the API response indicating if the user is a project manager. + */ + checkIfProjectManager( + username: string, + projectName: string, + token: string, + ): Promise>; + + /** Logs in a user with the provided credentials. + * @param {NewUser} NewUser The user object containing username and password. + * @returns {Promise>} A promise resolving to an API response with a token. + */ login(NewUser: NewUser): Promise>; - /** Renew the token */ + + /** + * Renew the token + * @param {string} token The current authentication token. + * @returns {Promise>} A promise resolving to an API response with a renewed token. + */ renewToken(token: string): Promise>; - /** Create a project */ + + /** Promote user to admin */ + + /** Creates a new project. + * @param {NewProject} project The project object containing name and description. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the created project. + */ createProject( project: NewProject, token: string, ): Promise>; - /** Submit a weekly report */ + + /** Submits a weekly report + * @param {NewWeeklyReport} weeklyReport The weekly report object. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the submitted report. + */ submitWeeklyReport( - project: NewWeeklyReport, + weeklyReport: NewWeeklyReport, token: string, ): Promise>; - /**Gets a weekly report*/ + + /** Gets a weekly report for a specific user, project and week + * @param {string} username The username of the user. + * @param {string} projectName The name of the project. + * @param {string} week The week number. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the retrieved report. + */ getWeeklyReport( username: string, projectName: string, week: string, token: string, - ): Promise>; - /** Gets all the projects of a user*/ + ): Promise>; + + /** + * Returns all the weekly reports for a user in a particular project + * The username is derived from the token + * @param {string} projectName The name of the project + * @param {string} token The token of the user + * @returns {APIResponse} A list of weekly reports + */ + getWeeklyReportsForUser( + username: string, + projectName: string, + token: string, + ): Promise>; + + /** Gets all the projects of a user + * @param {string} token - The authentication token. + * @returns {Promise>} A promise containing the API response with the user's projects. + */ getUserProjects(token: string): Promise>; - /** Gets a project from id*/ + + /** Gets a project by its id. + * @param {number} id The id of the project to retrieve. + * @returns {Promise>} A promise resolving to an API response containing the project data. + */ getProject(id: number): Promise>; + + /** Gets a list of all users. + * @param {string} token The authentication token of the requesting user. + * @returns {Promise>} A promise resolving to an API response containing the list of users. + */ + getAllUsers(token: string): Promise>; } -// Export an instance of the API +/** An instance of the API */ export const api: API = { async registerUser(user: NewUser): Promise> { try { @@ -253,7 +338,6 @@ export const api: API = { } }, - // Gets a projet by id, currently untested since we have no javascript-based tests async getProject(id: number): Promise> { try { const response = await fetch(`/api/project/${id}`, { @@ -278,4 +362,31 @@ export const api: API = { }; } }, + + async getAllUsers(token: string): Promise> { + try { + const response = await fetch("/api/users/all", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }); + + if (!response.ok) { + return Promise.resolve({ + success: false, + message: "Failed to get users", + }); + } else { + const data = (await response.json()) as string[]; + return Promise.resolve({ success: true, data }); + } + } catch (e) { + return Promise.resolve({ + success: false, + message: "API is not ok", + }); + } + }, }; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index 45814e3..f5f4a08 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -7,7 +7,7 @@ import Button from "./Button"; /** * Tries to add a project to the system - * @param props - Project name and description + * @param {Object} props - Project name and description * @returns {boolean} True if created, false if not */ function CreateProject(props: { name: string; description: string }): boolean { @@ -34,8 +34,8 @@ function CreateProject(props: { name: string; description: string }): boolean { } /** - * Tries to add a project to the system - * @returns {JSX.Element} UI for project adding + * Provides UI for adding a project to the system. + * @returns {JSX.Element} - Returns the component UI for adding a project */ function AddProject(): JSX.Element { const [name, setName] = useState(""); diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx new file mode 100644 index 0000000..ab99e04 --- /dev/null +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -0,0 +1,72 @@ +//Info: This component is used to display all the time reports for a project. It will display the week number, +//total time spent, and if the report has been signed or not. The user can click on a report to edit it. +import { useEffect, useState } from "react"; +import { WeeklyReport } from "../Types/goTypes"; +import { Link, useParams } from "react-router-dom"; +import { api } from "../API/API"; + +/** + * Renders a component that displays all the time reports for a specific project. + * @returns {JSX.Element} representing the component. + */ +function AllTimeReportsInProject(): JSX.Element { + const { projectName } = useParams(); + const [weeklyReports, setWeeklyReports] = useState([]); + + const getWeeklyReports = async (): Promise => { + const token = localStorage.getItem("accessToken") ?? ""; + const response = await api.getWeeklyReportsForProject( + localStorage.getItem("username") ?? "", + projectName ?? "", + token, + ); + console.log(response); + if (response.success) { + setWeeklyReports(response.data ?? []); + } else { + console.error(response.message); + } + }; + + // Call getProjects when the component mounts + useEffect(() => { + void getWeeklyReports(); + }); + + return ( + <> +
+ {weeklyReports.map((newWeeklyReport, index) => ( + +
+

+ {"Week: "} + {newWeeklyReport.week} +

+

+ {"Total Time: "} + {newWeeklyReport.developmentTime + + newWeeklyReport.meetingTime + + newWeeklyReport.adminTime + + newWeeklyReport.ownWorkTime + + newWeeklyReport.studyTime + + newWeeklyReport.testingTime}{" "} + min +

+

+ {"Signed: "} + {newWeeklyReport.signedBy ? "YES" : "NO"} +

+
+ + ))} +
+ + ); +} + +export default AllTimeReportsInProject; From ae77bcf0bbf88aa4ef238f18b1e828065c8a962a Mon Sep 17 00:00:00 2001 From: Johanna Date: Wed, 20 Mar 2024 20:36:34 +0100 Subject: [PATCH 10/24] Added comments to TypeScript API, merge --- frontend/src/API/API.ts | 98 +++++++++++++++---- frontend/src/Components/AddProject.tsx | 6 +- .../Components/AllTimeReportsInProject.tsx | 2 +- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index cfb53b0..47299f3 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -7,52 +7,102 @@ import { WeeklyReport, } from "../Types/goTypes"; -// This type of pattern should be hard to misuse +/** + * Response object returned by API methods. + */ export interface APIResponse { + /** Indicates whether the API call was successful */ success: boolean; + /** Optional message providing additional information or error description */ message?: string; + /** Optional data returned by the API method */ data?: T; } -// Note that all protected routes also require a token -// Defines all the methods that an instance of the API must implement +/** + * Interface defining methods that an instance of the API must implement. + */ interface API { - /** Register a new user */ + /** + * Register a new user + * @param {NewUser} user The user object to be registered + * @returns {Promise>} A promise containing the API response with the user data. + */ registerUser(user: NewUser): Promise>; - /** Remove a user */ + + /** + * Removes a user. + * @param {string} username The username of the user to be removed. + * @param {string} token The authentication token. + * @returns {Promise>} A promise containing the API response with the removed user data. + */ removeUser(username: string, token: string): Promise>; - /** Check if user is project manager */ + + /** + * Check if user is project manager. + * @param {string} username The username of the user. + * @param {string} projectName The name of the project. + * @param {string} token The authentication token. + * @returns {Promise>} A promise containing the API response indicating if the user is a project manager. + */ checkIfProjectManager( username: string, projectName: string, token: string, ): Promise>; - /** Login */ + + /** Logs in a user with the provided credentials. + * @param {NewUser} NewUser The user object containing username and password. + * @returns {Promise>} A promise resolving to an API response with a token. + */ login(NewUser: NewUser): Promise>; - /** Renew the token */ + + /** + * Renew the token + * @param {string} token The current authentication token. + * @returns {Promise>} A promise resolving to an API response with a renewed token. + */ renewToken(token: string): Promise>; + /** Promote user to admin */ - /** Create a project */ + + /** Creates a new project. + * @param {NewProject} project The project object containing name and description. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the created project. + */ createProject( project: NewProject, token: string, ): Promise>; - /** Submit a weekly report */ + + /** Submits a weekly report + * @param {NewWeeklyReport} weeklyReport The weekly report object. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the submitted report. + */ submitWeeklyReport( - project: NewWeeklyReport, + weeklyReport: NewWeeklyReport, token: string, ): Promise>; - /**Gets a weekly report*/ + + /** Gets a weekly report for a specific user, project and week + * @param {string} username The username of the user. + * @param {string} projectName The name of the project. + * @param {string} week The week number. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response with the retrieved report. + */ getWeeklyReport( username: string, projectName: string, week: string, token: string, ): Promise>; + /** * Returns all the weekly reports for a user in a particular project * The username is derived from the token - * * @param {string} projectName The name of the project * @param {string} token The token of the user * @returns {APIResponse} A list of weekly reports @@ -61,15 +111,27 @@ interface API { projectName: string, token: string, ): Promise>; - /** Gets all the projects of a user*/ + + /** Gets all the projects of a user + * @param {string} token - The authentication token. + * @returns {Promise>} A promise containing the API response with the user's projects. + */ getUserProjects(token: string): Promise>; - /** Gets a project from id*/ + + /** Gets a project by its id. + * @param {number} id The id of the project to retrieve. + * @returns {Promise>} A promise resolving to an API response containing the project data. + */ getProject(id: number): Promise>; - /** Gets a project from id*/ + + /** Gets a list of all users. + * @param {string} token The authentication token of the requesting user. + * @returns {Promise>} A promise resolving to an API response containing the list of users. + */ getAllUsers(token: string): Promise>; } -// Export an instance of the API +/** An instance of the API */ export const api: API = { async registerUser(user: NewUser): Promise> { try { @@ -336,7 +398,6 @@ export const api: API = { } }, - // Gets a projet by id, currently untested since we have no javascript-based tests async getProject(id: number): Promise> { try { const response = await fetch(`/api/project/${id}`, { @@ -362,7 +423,6 @@ export const api: API = { } }, - // Gets all users async getAllUsers(token: string): Promise> { try { const response = await fetch("/api/users/all", { diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index 45814e3..f5f4a08 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -7,7 +7,7 @@ import Button from "./Button"; /** * Tries to add a project to the system - * @param props - Project name and description + * @param {Object} props - Project name and description * @returns {boolean} True if created, false if not */ function CreateProject(props: { name: string; description: string }): boolean { @@ -34,8 +34,8 @@ function CreateProject(props: { name: string; description: string }): boolean { } /** - * Tries to add a project to the system - * @returns {JSX.Element} UI for project adding + * Provides UI for adding a project to the system. + * @returns {JSX.Element} - Returns the component UI for adding a project */ function AddProject(): JSX.Element { const [name, setName] = useState(""); diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index 1a34e41..614019c 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -7,7 +7,7 @@ import { api } from "../API/API"; /** * Renders a component that displays all the time reports for a specific project. - * @returns JSX.Element representing the component. + * @returns {JSX.Element} representing the component. */ function AllTimeReportsInProject(): JSX.Element { const { projectName } = useParams(); From 49ca0431573f76cfc7b3b7731969266801779c8b Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 21:51:02 +0100 Subject: [PATCH 11/24] =?UTF-8?q?ingen=20d=C3=A5lig=20st=C3=A4mmning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/database/migrations/0035_weekly_report.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 8f76b80..be2a2d3 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS weekly_reports ( - report_id INTEGER PRIMARY KEY AUTOINCREMENT, + report_id INTEGER AUTO_INCREMENT UNIQUE, user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, week INTEGER NOT NULL, @@ -12,5 +12,6 @@ CREATE TABLE IF NOT EXISTS weekly_reports ( signed_by INTEGER, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (project_id) REFERENCES projects(id), - FOREIGN KEY (signed_by) REFERENCES users(id) + FOREIGN KEY (signed_by) REFERENCES users(id), + PRIMARY KEY (user_id, project_id, week) ); \ No newline at end of file From cdea2dce1cf59019e188ef4210df90de88ffa443 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 21:51:36 +0100 Subject: [PATCH 12/24] Very large changes related to database and its interface --- backend/internal/database/db.go | 126 +++++++----------- backend/internal/database/db_test.go | 97 +++++++++++--- .../handlers/handlers_project_related.go | 26 ++-- backend/internal/types/project.go | 5 +- testing.py | 73 +++++----- 5 files changed, 179 insertions(+), 148 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 3f2d2f7..fd0a083 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -40,8 +40,6 @@ type Database interface { IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) GetTotalTimePerActivity(projectName string) (map[string]int, error) - - } // This struct is a wrapper type that holds the database connection @@ -63,14 +61,16 @@ var sampleData embed.FS // TODO: Possibly break these out into separate files bundled with the embed package? const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" -const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" +const projectInsert = "INSERT INTO projects (name, description, owner_user_id) VALUES (?, ?, (SELECT id FROM users WHERE username = ?))" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), ProjectLookup AS (SELECT id FROM projects WHERE name = ?) INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` -const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" -const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" +const addUserToProject = `INSERT OR IGNORE INTO user_roles (user_id, project_id, p_role) + VALUES ((SELECT id FROM users WHERE username = ?), + (SELECT id FROM projects WHERE name = ?), ?)` +const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)" const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p JOIN user_roles ur ON p.id = ur.project_id JOIN users u ON ur.user_id = u.id @@ -78,6 +78,11 @@ const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p const deleteProject = `DELETE FROM projects WHERE id = ? AND owner_username = ?` +const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles + JOIN users ON user_roles.user_id = users.id + JOIN projects ON user_roles.project_id = projects.id + WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` + // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -135,41 +140,15 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve // AddUserToProject adds a user to a project with a specified role. func (d *Db) AddUserToProject(username string, projectname string, role string) error { - var userid int - userid, err := d.GetUserId(username) - if err != nil { - panic(err) - } - - var projectid int - projectid, err2 := d.GetProjectId(projectname) - if err2 != nil { - panic(err2) - } - - _, err3 := d.Exec(addUserToProject, userid, projectid, role) - return err3 + _, err := d.Exec(addUserToProject, username, projectname, role) + return err } // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - // Get the user ID - var userid int - userid, err := d.GetUserId(username) - if err != nil { - panic(err) - } - - // Get the project ID - var projectid int - projectid, err2 := d.GetProjectId(projectname) - if err2 != nil { - panic(err2) - } - // Execute the SQL query to change the user's role - _, err3 := d.Exec(changeUserRole, role, userid, projectid) - return err3 + _, err := d.Exec(changeUserRole, role, username, projectname) + return err } // ChangeUserName changes the username of a user. @@ -218,6 +197,7 @@ func (d *Db) GetProjectId(projectname string) (int, error) { // Creates a new project in the database, associated with a user func (d *Db) AddProject(name string, description string, username string) error { tx := d.MustBegin() + // Insert the project into the database _, err := tx.Exec(projectInsert, name, description, username) if err != nil { if err := tx.Rollback(); err != nil { @@ -225,7 +205,9 @@ func (d *Db) AddProject(name string, description string, username string) error } return err } - _, err = tx.Exec(changeUserRole, "project_manager", username, name) + + // Add creator to project as project manager + _, err = tx.Exec(addUserToProject, username, name, "project_manager") if err != nil { if err := tx.Rollback(); err != nil { return err @@ -465,23 +447,9 @@ func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types. // IsProjectManager checks if a given username is a project manager for the specified project func (d *Db) IsProjectManager(username string, projectname string) (bool, error) { - // Define the SQL query to check if the user is a project manager for the project - query := ` - SELECT COUNT(*) FROM user_roles - JOIN users ON user_roles.user_id = users.id - JOIN projects ON user_roles.project_id = projects.id - WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager' - ` - - // Execute the query - var count int - err := d.Get(&count, query, username, projectname) - if err != nil { - return false, err - } - - // If count is greater than 0, the user is a project manager for the project - return count > 0, nil + var manager bool + err := d.Get(&manager, isProjectManagerQuery, username, projectname) + return manager, err } // MigrateSampleData applies sample data to the database. @@ -524,39 +492,39 @@ func (d *Db) MigrateSampleData() error { } func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) { - - query := ` + + query := ` SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time FROM weekly_reports JOIN projects ON weekly_reports.project_id = projects.id WHERE projects.name = ? ` - - rows, err := d.DB.Query(query, projectName) - if err != nil { - return nil, err - } - defer rows.Close() - totalTime := make(map[string]int) + rows, err := d.DB.Query(query, projectName) + if err != nil { + return nil, err + } + defer rows.Close() - for rows.Next() { - var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int - if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil { - return nil, err - } - - totalTime["development"] += developmentTime - totalTime["meeting"] += meetingTime - totalTime["admin"] += adminTime - totalTime["own_work"] += ownWorkTime - totalTime["study"] += studyTime - totalTime["testing"] += testingTime - } + totalTime := make(map[string]int) - if err := rows.Err(); err != nil { - return nil, err - } + for rows.Next() { + var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int + if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil { + return nil, err + } - return totalTime, nil + totalTime["development"] += developmentTime + totalTime["meeting"] += meetingTime + totalTime["admin"] += adminTime + totalTime["own_work"] += ownWorkTime + totalTime["study"] += studyTime + totalTime["testing"] += testingTime + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return totalTime, nil } diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index df10abc..139fba9 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -1,7 +1,6 @@ package database import ( - "fmt" "testing" ) @@ -17,12 +16,61 @@ func setupState() (Database, error) { return db, nil } +// This is a more advanced setup that includes more data in the database. +// This is useful for more complex testing scenarios. +func setupAdvancedState() (Database, error) { + db, err := setupState() + if err != nil { + return nil, err + } + + // Add a user + if err = db.AddUser("demouser", "password"); err != nil { + return nil, err + } + + // Add a project + if err = db.AddProject("projecttest", "description", "demouser"); err != nil { + return nil, err + } + + // Add a weekly report + if err = db.AddWeeklyReport("projecttest", "demouser", 1, 1, 1, 1, 1, 1, 1); err != nil { + return nil, err + } + + return db, nil +} + // TestDbConnect tests the connection to the database func TestDbConnect(t *testing.T) { db := DbConnect(":memory:") _ = db } +func TestSetupAdvancedState(t *testing.T) { + db, err := setupAdvancedState() + if err != nil { + t.Error("setupAdvancedState failed:", err) + } + + // Check if the user was added + if _, err = db.GetUserId("demouser"); err != nil { + t.Error("GetUserId failed:", err) + } + + // Check if the project was added + projects, err := db.GetAllProjects() + if err != nil { + t.Error("GetAllProjects failed:", err) + } + if len(projects) != 1 { + t.Error("GetAllProjects failed: expected 1, got", len(projects)) + } + + // To be continued... +} + // TestDbAddUser tests the AddUser function of the database func TestDbAddUser(t *testing.T) { db, err := setupState() @@ -58,12 +106,12 @@ func TestDbGetUserId(t *testing.T) { // TestDbAddProject tests the AddProject function of the database func TestDbAddProject(t *testing.T) { - db, err := setupState() + db, err := setupAdvancedState() if err != nil { t.Error("setupState failed:", err) } - err = db.AddProject("test", "description", "test") + err = db.AddProject("test", "description", "demouser") if err != nil { t.Error("AddProject failed:", err) } @@ -168,20 +216,15 @@ func TestChangeUserRole(t *testing.T) { t.Error("AddProject failed:", err) } - err = db.AddUserToProject("testuser", "testproject", "user") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - role, err := db.GetUserRole("testuser", "testproject") if err != nil { t.Error("GetUserRole failed:", err) } - if role != "user" { - t.Error("GetUserRole failed: expected user, got", role) + if role != "project_manager" { + t.Error("GetUserRole failed: expected project_manager, got", role) } - err = db.ChangeUserRole("testuser", "testproject", "admin") + err = db.ChangeUserRole("testuser", "testproject", "member") if err != nil { t.Error("ChangeUserRole failed:", err) } @@ -190,8 +233,8 @@ func TestChangeUserRole(t *testing.T) { if err != nil { t.Error("GetUserRole failed:", err) } - if role != "admin" { - t.Error("GetUserRole failed: expected admin, got", role) + if role != "member" { + t.Error("GetUserRole failed: expected member, got", role) } } @@ -480,7 +523,6 @@ func TestSignWeeklyReport(t *testing.T) { if err != nil { t.Error("GetUserId failed:", err) } - fmt.Println("Project Manager's ID:", projectManagerID) // Sign the report with the project manager err = db.SignWeeklyReport(report.ReportId, projectManagerID) @@ -519,7 +561,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { t.Error("AddUser failed:", err) } - // Add project + // Add project, projectManager is the owner err = db.AddProject("testproject", "description", "projectManager") if err != nil { t.Error("AddProject failed:", err) @@ -543,14 +585,25 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { t.Error("GetWeeklyReport failed:", err) } - anotherManagerID, err := db.GetUserId("projectManager") + managerID, err := db.GetUserId("projectManager") if err != nil { t.Error("GetUserId failed:", err) } - err = db.SignWeeklyReport(report.ReportId, anotherManagerID) - if err == nil { - t.Error("Expected SignWeeklyReport to fail with a project manager who is not in the project, but it didn't") + err = db.SignWeeklyReport(report.ReportId, managerID) + 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 != managerID { + t.Errorf("Expected SignedBy to be %d, got %d", managerID, *signedReport.SignedBy) } } @@ -715,6 +768,12 @@ func TestEnsureManagerOfCreatedProject(t *testing.T) { t.Error("AddProject failed:", err) } + // Set user to a project manager + // err = db.AddUserToProject("testuser", "testproject", "project_manager") + // if err != nil { + // t.Error("AddUserToProject failed:", err) + // } + managerState, err := db.IsProjectManager("testuser", "testproject") if err != nil { t.Error("IsProjectManager failed:", err) diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go index 99696e7..603f4cd 100644 --- a/backend/internal/handlers/handlers_project_related.go +++ b/backend/internal/handlers/handlers_project_related.go @@ -65,8 +65,8 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { //check token and get username of current user user := c.Locals("user").(*jwt.Token) claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - log.Info(projectManagerUsername) + username := claims["name"].(string) + // Extract the necessary parameters from the request data := new(types.RoleChange) if err := c.BodyParser(data); err != nil { @@ -74,18 +74,19 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { return c.Status(400).SendString(err.Error()) } - // dubble diping and checcking if current user is + log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) - if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, data.Projectname); err != nil { + // Dubble diping and checcking if current user is + if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil { log.Warn("Error checking if projectmanager:", err) return c.Status(500).SendString(err.Error()) } else if !ismanager { - log.Warn("tried chaning role when not projectmanager:", err) - return c.Status(401).SendString("you can not change role when not projectManager") + log.Warn("User is not projectmanager") + return c.Status(401).SendString("User is not projectmanager") } // Change the user's role within the project in the database - if err := gs.Db.ChangeUserRole(data.Username, data.Projectname, data.Role); err != nil { + if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil { return c.Status(500).SendString(err.Error()) } @@ -218,7 +219,9 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { username := claims["name"].(string) // Extract necessary parameters from the request query string - projectName := c.Query("projectName") + projectName := c.Params("projectName") + + log.Info("Checking if user ", username, " is a project manager for project ", projectName) // Check if the user is a project manager for the specified project isManager, err := gs.Db.IsProjectManager(username, projectName) @@ -228,10 +231,5 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { } // Return the result as JSON - return c.JSON(map[string]bool{"isProjectManager": isManager}) -} - -func (gs *GState) CreateTask(c *fiber.Ctx) error { - - return nil + return c.JSON(fiber.Map{"isProjectManager": isManager}) } diff --git a/backend/internal/types/project.go b/backend/internal/types/project.go index 6a7c91a..2e26eb9 100644 --- a/backend/internal/types/project.go +++ b/backend/internal/types/project.go @@ -14,9 +14,12 @@ type NewProject struct { Description string `json:"description"` } +// Used to change the role of a user in a project. +// If name is identical to the name contained in the token, the role can be changed. +// If the name is different, only a project manager can change the role. type RoleChange struct { + UserName string `json:"username"` Role string `json:"role" tstype:"'project_manager' | 'user'"` - Username string `json:"username"` Projectname string `json:"projectname"` } diff --git a/testing.py b/testing.py index 568cb87..384d7ce 100644 --- a/testing.py +++ b/testing.py @@ -20,8 +20,8 @@ def randomString(len=10): # Defined once per test run -username = randomString() -projectName = randomString() +username = "user_" + randomString() +projectName = "project_" + randomString() # The base URL of the API base_url = "http://localhost:8080" @@ -45,30 +45,37 @@ getUsersProjectPath = base_url + "/api/getUsersProject" #ta bort auth i handlern för att få testet att gå igenom def test_ProjectRoleChange(): dprint("Testing ProjectRoleChange") - project_manager = randomString() - register(project_manager, "project_manager_password") + localUsername = randomString() + localProjectName = randomString() + register(localUsername, "username_password") - token = login(project_manager, "project_manager_password").json()[ + token = login(localUsername, "username_password").json()[ "token" ] + + # Just checking since this test is built somewhat differently than the others + assert token != None, "Login failed" + response = requests.post( addProjectPath, - json={"name": projectName, "description": "This is a project"}, + json={"name": localProjectName, "description": "This is a project"}, headers={"Authorization": "Bearer " + token}, ) + + if response.status_code != 200: + print("Add project failed") + response = requests.post( ProjectRoleChangePath, headers={"Authorization": "Bearer " + token}, json={ - "username": username, - "projectName": projectName, - "week": 1 + "projectName": localProjectName, + "role": "project_manager", }, ) - if response.status_code != 200: - print("auth not working, för att man inte kan få tag på pm token atm, för att få igenom det så ta bort auth i handler") - - assert response.status_code == 200, "change role successfully" + + assert response.status_code == 200, "ProjectRoleChange failed" + gprint("test_ProjectRoleChange successful") def test_get_user_projects(): @@ -337,33 +344,28 @@ def test_check_if_project_manager(): assert response.status_code == 200, "Check if project manager failed" gprint("test_check_if_project_manager successful") -def test_list_all_users_project(): - # Log in as a user who is a member of the project - admin_username = randomString() - admin_password = "admin_password2" - dprint( - "Registering with username: ", admin_username, " and password: ", admin_password - ) - response = requests.post( - registerPath, json={"username": admin_username, "password": admin_password} - ) - dprint(response.text) +def test_ensure_manager_of_created_project(): + # Create a new user to add to the project + newUser = "karen_" + randomString() + newProject = "HR_" + randomString() + register(newUser, "new_user_password") + token = login(newUser, "new_user_password").json()["token"] - # Log in as the admin - admin_token = login(admin_username, admin_password).json()["token"] + # Create a new project response = requests.post( - promoteToAdminPath, - json={"username": admin_username}, - headers={"Authorization": "Bearer " + admin_token}, + addProjectPath, + json={"name": newProject, "description": "This is a project"}, + headers={"Authorization": "Bearer " + token}, ) + assert response.status_code == 200, "Add project failed" - # Make a request to list all users associated with the project response = requests.get( - getUsersProjectPath + "/" + projectName, - headers={"Authorization": "Bearer " + admin_token}, + checkIfProjectManagerPath + "/" + newProject, + headers={"Authorization": "Bearer " + token}, ) - assert response.status_code == 200, "List all users project failed" - gprint("test_list_all_users_project sucessful") + assert response.status_code == 200, "Check if project manager failed" + assert response.json()["isProjectManager"] == True, "User is not project manager" + gprint("test_ensure_admin_of_created_project successful") if __name__ == "__main__": @@ -379,4 +381,5 @@ if __name__ == "__main__": test_get_weekly_reports_user() test_check_if_project_manager() test_ProjectRoleChange() - test_list_all_users_project() + #test_list_all_users_project() + test_ensure_manager_of_created_project() From 4288b064f3886351c5178ccd6c14bedef9ae8da5 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 21:53:08 +0100 Subject: [PATCH 13/24] =?UTF-8?q?fixade=20d=C3=A5lig=20st=C3=A4mmning=20ig?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/db.sqlite3-journal | Bin 0 -> 4616 bytes .../database/sample_data/0010_sample_data.sql | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 backend/db.sqlite3-journal diff --git a/backend/db.sqlite3-journal b/backend/db.sqlite3-journal new file mode 100644 index 0000000000000000000000000000000000000000..fd14fa4b519545ad5a261fe3da992d38a41ff1a3 GIT binary patch literal 4616 zcmeI$Ee?P%41m#Y1wjG~hr)6(GJ}C^aAi0Fo)N^(0QoBVld3*hksM`vjA`mb>|64R z?!N*>lWw!_6nCXWAb Date: Wed, 20 Mar 2024 21:54:13 +0100 Subject: [PATCH 14/24] Typescript type regeneration --- frontend/src/Types/goTypes.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/Types/goTypes.ts b/frontend/src/Types/goTypes.ts index fabc0c9..f43ede7 100644 --- a/frontend/src/Types/goTypes.ts +++ b/frontend/src/Types/goTypes.ts @@ -144,9 +144,14 @@ export interface NewProject { name: string; description: string; } +/** + * Used to change the role of a user in a project. + * If name is identical to the name contained in the token, the role can be changed. + * If the name is different, only a project manager can change the role. + */ export interface RoleChange { - role: 'project_manager' | 'user'; username: string; + role: 'project_manager' | 'user'; projectname: string; } export interface NameChange { From 0dc7e3bf995685d8fdebc4952a86b5c12a6dae45 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 21:56:03 +0100 Subject: [PATCH 15/24] Deleted sqlite3-journal --- .gitignore | 1 + backend/db.sqlite3-journal | Bin 4616 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 backend/db.sqlite3-journal diff --git a/.gitignore b/.gitignore index 313b735..05f913b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ bin database.txt plantuml.jar db.sqlite3 +db.sqlite3-journal diagram.puml backend/*.png backend/*.jpg diff --git a/backend/db.sqlite3-journal b/backend/db.sqlite3-journal deleted file mode 100644 index fd14fa4b519545ad5a261fe3da992d38a41ff1a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4616 zcmeI$Ee?P%41m#Y1wjG~hr)6(GJ}C^aAi0Fo)N^(0QoBVld3*hksM`vjA`mb>|64R z?!N*>lWw!_6nCXWAb Date: Wed, 20 Mar 2024 22:02:34 +0100 Subject: [PATCH 16/24] fixed getWeeklyReport --- frontend/src/API/API.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 47299f3..0859748 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -87,14 +87,12 @@ interface API { ): Promise>; /** Gets a weekly report for a specific user, project and week - * @param {string} username The username of the user. * @param {string} projectName The name of the project. * @param {string} week The week number. * @param {string} token The authentication token. * @returns {Promise>} A promise resolving to an API response with the retrieved report. */ getWeeklyReport( - username: string, projectName: string, week: string, token: string, @@ -319,20 +317,21 @@ export const api: API = { }, async getWeeklyReport( - username: string, projectName: string, week: string, token: string, ): Promise> { try { - const response = await fetch("/api/getWeeklyReport", { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, + const response = await fetch( + `/api/getWeeklyReport?projectName=${projectName}&week=${week}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, }, - body: JSON.stringify({ username, projectName, week }), - }); + ); if (!response.ok) { return { success: false, message: "Failed to get weekly report" }; From ec138163c6f3b6d8d3731abc8a07ef0ac22a2326 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 21:51:36 +0100 Subject: [PATCH 17/24] Very large changes related to database and its interface --- backend/internal/database/db.go | 126 +++++++----------- backend/internal/database/db_test.go | 97 +++++++++++--- .../handlers/handlers_project_related.go | 26 ++-- backend/internal/types/project.go | 5 +- testing.py | 73 +++++----- 5 files changed, 179 insertions(+), 148 deletions(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 3f2d2f7..fd0a083 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -40,8 +40,6 @@ type Database interface { IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) GetTotalTimePerActivity(projectName string) (map[string]int, error) - - } // This struct is a wrapper type that holds the database connection @@ -63,14 +61,16 @@ var sampleData embed.FS // TODO: Possibly break these out into separate files bundled with the embed package? const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" -const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" +const projectInsert = "INSERT INTO projects (name, description, owner_user_id) VALUES (?, ?, (SELECT id FROM users WHERE username = ?))" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), ProjectLookup AS (SELECT id FROM projects WHERE name = ?) INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` -const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" -const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" +const addUserToProject = `INSERT OR IGNORE INTO user_roles (user_id, project_id, p_role) + VALUES ((SELECT id FROM users WHERE username = ?), + (SELECT id FROM projects WHERE name = ?), ?)` +const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)" const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p JOIN user_roles ur ON p.id = ur.project_id JOIN users u ON ur.user_id = u.id @@ -78,6 +78,11 @@ const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p const deleteProject = `DELETE FROM projects WHERE id = ? AND owner_username = ?` +const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles + JOIN users ON user_roles.user_id = users.id + JOIN projects ON user_roles.project_id = projects.id + WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` + // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -135,41 +140,15 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve // AddUserToProject adds a user to a project with a specified role. func (d *Db) AddUserToProject(username string, projectname string, role string) error { - var userid int - userid, err := d.GetUserId(username) - if err != nil { - panic(err) - } - - var projectid int - projectid, err2 := d.GetProjectId(projectname) - if err2 != nil { - panic(err2) - } - - _, err3 := d.Exec(addUserToProject, userid, projectid, role) - return err3 + _, err := d.Exec(addUserToProject, username, projectname, role) + return err } // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { - // Get the user ID - var userid int - userid, err := d.GetUserId(username) - if err != nil { - panic(err) - } - - // Get the project ID - var projectid int - projectid, err2 := d.GetProjectId(projectname) - if err2 != nil { - panic(err2) - } - // Execute the SQL query to change the user's role - _, err3 := d.Exec(changeUserRole, role, userid, projectid) - return err3 + _, err := d.Exec(changeUserRole, role, username, projectname) + return err } // ChangeUserName changes the username of a user. @@ -218,6 +197,7 @@ func (d *Db) GetProjectId(projectname string) (int, error) { // Creates a new project in the database, associated with a user func (d *Db) AddProject(name string, description string, username string) error { tx := d.MustBegin() + // Insert the project into the database _, err := tx.Exec(projectInsert, name, description, username) if err != nil { if err := tx.Rollback(); err != nil { @@ -225,7 +205,9 @@ func (d *Db) AddProject(name string, description string, username string) error } return err } - _, err = tx.Exec(changeUserRole, "project_manager", username, name) + + // Add creator to project as project manager + _, err = tx.Exec(addUserToProject, username, name, "project_manager") if err != nil { if err := tx.Rollback(); err != nil { return err @@ -465,23 +447,9 @@ func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types. // IsProjectManager checks if a given username is a project manager for the specified project func (d *Db) IsProjectManager(username string, projectname string) (bool, error) { - // Define the SQL query to check if the user is a project manager for the project - query := ` - SELECT COUNT(*) FROM user_roles - JOIN users ON user_roles.user_id = users.id - JOIN projects ON user_roles.project_id = projects.id - WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager' - ` - - // Execute the query - var count int - err := d.Get(&count, query, username, projectname) - if err != nil { - return false, err - } - - // If count is greater than 0, the user is a project manager for the project - return count > 0, nil + var manager bool + err := d.Get(&manager, isProjectManagerQuery, username, projectname) + return manager, err } // MigrateSampleData applies sample data to the database. @@ -524,39 +492,39 @@ func (d *Db) MigrateSampleData() error { } func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) { - - query := ` + + query := ` SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time FROM weekly_reports JOIN projects ON weekly_reports.project_id = projects.id WHERE projects.name = ? ` - - rows, err := d.DB.Query(query, projectName) - if err != nil { - return nil, err - } - defer rows.Close() - totalTime := make(map[string]int) + rows, err := d.DB.Query(query, projectName) + if err != nil { + return nil, err + } + defer rows.Close() - for rows.Next() { - var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int - if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil { - return nil, err - } - - totalTime["development"] += developmentTime - totalTime["meeting"] += meetingTime - totalTime["admin"] += adminTime - totalTime["own_work"] += ownWorkTime - totalTime["study"] += studyTime - totalTime["testing"] += testingTime - } + totalTime := make(map[string]int) - if err := rows.Err(); err != nil { - return nil, err - } + for rows.Next() { + var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int + if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil { + return nil, err + } - return totalTime, nil + totalTime["development"] += developmentTime + totalTime["meeting"] += meetingTime + totalTime["admin"] += adminTime + totalTime["own_work"] += ownWorkTime + totalTime["study"] += studyTime + totalTime["testing"] += testingTime + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return totalTime, nil } diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index df10abc..139fba9 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -1,7 +1,6 @@ package database import ( - "fmt" "testing" ) @@ -17,12 +16,61 @@ func setupState() (Database, error) { return db, nil } +// This is a more advanced setup that includes more data in the database. +// This is useful for more complex testing scenarios. +func setupAdvancedState() (Database, error) { + db, err := setupState() + if err != nil { + return nil, err + } + + // Add a user + if err = db.AddUser("demouser", "password"); err != nil { + return nil, err + } + + // Add a project + if err = db.AddProject("projecttest", "description", "demouser"); err != nil { + return nil, err + } + + // Add a weekly report + if err = db.AddWeeklyReport("projecttest", "demouser", 1, 1, 1, 1, 1, 1, 1); err != nil { + return nil, err + } + + return db, nil +} + // TestDbConnect tests the connection to the database func TestDbConnect(t *testing.T) { db := DbConnect(":memory:") _ = db } +func TestSetupAdvancedState(t *testing.T) { + db, err := setupAdvancedState() + if err != nil { + t.Error("setupAdvancedState failed:", err) + } + + // Check if the user was added + if _, err = db.GetUserId("demouser"); err != nil { + t.Error("GetUserId failed:", err) + } + + // Check if the project was added + projects, err := db.GetAllProjects() + if err != nil { + t.Error("GetAllProjects failed:", err) + } + if len(projects) != 1 { + t.Error("GetAllProjects failed: expected 1, got", len(projects)) + } + + // To be continued... +} + // TestDbAddUser tests the AddUser function of the database func TestDbAddUser(t *testing.T) { db, err := setupState() @@ -58,12 +106,12 @@ func TestDbGetUserId(t *testing.T) { // TestDbAddProject tests the AddProject function of the database func TestDbAddProject(t *testing.T) { - db, err := setupState() + db, err := setupAdvancedState() if err != nil { t.Error("setupState failed:", err) } - err = db.AddProject("test", "description", "test") + err = db.AddProject("test", "description", "demouser") if err != nil { t.Error("AddProject failed:", err) } @@ -168,20 +216,15 @@ func TestChangeUserRole(t *testing.T) { t.Error("AddProject failed:", err) } - err = db.AddUserToProject("testuser", "testproject", "user") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - role, err := db.GetUserRole("testuser", "testproject") if err != nil { t.Error("GetUserRole failed:", err) } - if role != "user" { - t.Error("GetUserRole failed: expected user, got", role) + if role != "project_manager" { + t.Error("GetUserRole failed: expected project_manager, got", role) } - err = db.ChangeUserRole("testuser", "testproject", "admin") + err = db.ChangeUserRole("testuser", "testproject", "member") if err != nil { t.Error("ChangeUserRole failed:", err) } @@ -190,8 +233,8 @@ func TestChangeUserRole(t *testing.T) { if err != nil { t.Error("GetUserRole failed:", err) } - if role != "admin" { - t.Error("GetUserRole failed: expected admin, got", role) + if role != "member" { + t.Error("GetUserRole failed: expected member, got", role) } } @@ -480,7 +523,6 @@ func TestSignWeeklyReport(t *testing.T) { if err != nil { t.Error("GetUserId failed:", err) } - fmt.Println("Project Manager's ID:", projectManagerID) // Sign the report with the project manager err = db.SignWeeklyReport(report.ReportId, projectManagerID) @@ -519,7 +561,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { t.Error("AddUser failed:", err) } - // Add project + // Add project, projectManager is the owner err = db.AddProject("testproject", "description", "projectManager") if err != nil { t.Error("AddProject failed:", err) @@ -543,14 +585,25 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { t.Error("GetWeeklyReport failed:", err) } - anotherManagerID, err := db.GetUserId("projectManager") + managerID, err := db.GetUserId("projectManager") if err != nil { t.Error("GetUserId failed:", err) } - err = db.SignWeeklyReport(report.ReportId, anotherManagerID) - if err == nil { - t.Error("Expected SignWeeklyReport to fail with a project manager who is not in the project, but it didn't") + err = db.SignWeeklyReport(report.ReportId, managerID) + 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 != managerID { + t.Errorf("Expected SignedBy to be %d, got %d", managerID, *signedReport.SignedBy) } } @@ -715,6 +768,12 @@ func TestEnsureManagerOfCreatedProject(t *testing.T) { t.Error("AddProject failed:", err) } + // Set user to a project manager + // err = db.AddUserToProject("testuser", "testproject", "project_manager") + // if err != nil { + // t.Error("AddUserToProject failed:", err) + // } + managerState, err := db.IsProjectManager("testuser", "testproject") if err != nil { t.Error("IsProjectManager failed:", err) diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go index 99696e7..603f4cd 100644 --- a/backend/internal/handlers/handlers_project_related.go +++ b/backend/internal/handlers/handlers_project_related.go @@ -65,8 +65,8 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { //check token and get username of current user user := c.Locals("user").(*jwt.Token) claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - log.Info(projectManagerUsername) + username := claims["name"].(string) + // Extract the necessary parameters from the request data := new(types.RoleChange) if err := c.BodyParser(data); err != nil { @@ -74,18 +74,19 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { return c.Status(400).SendString(err.Error()) } - // dubble diping and checcking if current user is + log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) - if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, data.Projectname); err != nil { + // Dubble diping and checcking if current user is + if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil { log.Warn("Error checking if projectmanager:", err) return c.Status(500).SendString(err.Error()) } else if !ismanager { - log.Warn("tried chaning role when not projectmanager:", err) - return c.Status(401).SendString("you can not change role when not projectManager") + log.Warn("User is not projectmanager") + return c.Status(401).SendString("User is not projectmanager") } // Change the user's role within the project in the database - if err := gs.Db.ChangeUserRole(data.Username, data.Projectname, data.Role); err != nil { + if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil { return c.Status(500).SendString(err.Error()) } @@ -218,7 +219,9 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { username := claims["name"].(string) // Extract necessary parameters from the request query string - projectName := c.Query("projectName") + projectName := c.Params("projectName") + + log.Info("Checking if user ", username, " is a project manager for project ", projectName) // Check if the user is a project manager for the specified project isManager, err := gs.Db.IsProjectManager(username, projectName) @@ -228,10 +231,5 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { } // Return the result as JSON - return c.JSON(map[string]bool{"isProjectManager": isManager}) -} - -func (gs *GState) CreateTask(c *fiber.Ctx) error { - - return nil + return c.JSON(fiber.Map{"isProjectManager": isManager}) } diff --git a/backend/internal/types/project.go b/backend/internal/types/project.go index 6a7c91a..2e26eb9 100644 --- a/backend/internal/types/project.go +++ b/backend/internal/types/project.go @@ -14,9 +14,12 @@ type NewProject struct { Description string `json:"description"` } +// Used to change the role of a user in a project. +// If name is identical to the name contained in the token, the role can be changed. +// If the name is different, only a project manager can change the role. type RoleChange struct { + UserName string `json:"username"` Role string `json:"role" tstype:"'project_manager' | 'user'"` - Username string `json:"username"` Projectname string `json:"projectname"` } diff --git a/testing.py b/testing.py index 568cb87..384d7ce 100644 --- a/testing.py +++ b/testing.py @@ -20,8 +20,8 @@ def randomString(len=10): # Defined once per test run -username = randomString() -projectName = randomString() +username = "user_" + randomString() +projectName = "project_" + randomString() # The base URL of the API base_url = "http://localhost:8080" @@ -45,30 +45,37 @@ getUsersProjectPath = base_url + "/api/getUsersProject" #ta bort auth i handlern för att få testet att gå igenom def test_ProjectRoleChange(): dprint("Testing ProjectRoleChange") - project_manager = randomString() - register(project_manager, "project_manager_password") + localUsername = randomString() + localProjectName = randomString() + register(localUsername, "username_password") - token = login(project_manager, "project_manager_password").json()[ + token = login(localUsername, "username_password").json()[ "token" ] + + # Just checking since this test is built somewhat differently than the others + assert token != None, "Login failed" + response = requests.post( addProjectPath, - json={"name": projectName, "description": "This is a project"}, + json={"name": localProjectName, "description": "This is a project"}, headers={"Authorization": "Bearer " + token}, ) + + if response.status_code != 200: + print("Add project failed") + response = requests.post( ProjectRoleChangePath, headers={"Authorization": "Bearer " + token}, json={ - "username": username, - "projectName": projectName, - "week": 1 + "projectName": localProjectName, + "role": "project_manager", }, ) - if response.status_code != 200: - print("auth not working, för att man inte kan få tag på pm token atm, för att få igenom det så ta bort auth i handler") - - assert response.status_code == 200, "change role successfully" + + assert response.status_code == 200, "ProjectRoleChange failed" + gprint("test_ProjectRoleChange successful") def test_get_user_projects(): @@ -337,33 +344,28 @@ def test_check_if_project_manager(): assert response.status_code == 200, "Check if project manager failed" gprint("test_check_if_project_manager successful") -def test_list_all_users_project(): - # Log in as a user who is a member of the project - admin_username = randomString() - admin_password = "admin_password2" - dprint( - "Registering with username: ", admin_username, " and password: ", admin_password - ) - response = requests.post( - registerPath, json={"username": admin_username, "password": admin_password} - ) - dprint(response.text) +def test_ensure_manager_of_created_project(): + # Create a new user to add to the project + newUser = "karen_" + randomString() + newProject = "HR_" + randomString() + register(newUser, "new_user_password") + token = login(newUser, "new_user_password").json()["token"] - # Log in as the admin - admin_token = login(admin_username, admin_password).json()["token"] + # Create a new project response = requests.post( - promoteToAdminPath, - json={"username": admin_username}, - headers={"Authorization": "Bearer " + admin_token}, + addProjectPath, + json={"name": newProject, "description": "This is a project"}, + headers={"Authorization": "Bearer " + token}, ) + assert response.status_code == 200, "Add project failed" - # Make a request to list all users associated with the project response = requests.get( - getUsersProjectPath + "/" + projectName, - headers={"Authorization": "Bearer " + admin_token}, + checkIfProjectManagerPath + "/" + newProject, + headers={"Authorization": "Bearer " + token}, ) - assert response.status_code == 200, "List all users project failed" - gprint("test_list_all_users_project sucessful") + assert response.status_code == 200, "Check if project manager failed" + assert response.json()["isProjectManager"] == True, "User is not project manager" + gprint("test_ensure_admin_of_created_project successful") if __name__ == "__main__": @@ -379,4 +381,5 @@ if __name__ == "__main__": test_get_weekly_reports_user() test_check_if_project_manager() test_ProjectRoleChange() - test_list_all_users_project() + #test_list_all_users_project() + test_ensure_manager_of_created_project() From b00ce7a0ed6cf90af99b4ab0308633366ff4d1cd Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 21:54:13 +0100 Subject: [PATCH 18/24] Typescript type regeneration --- frontend/src/Types/goTypes.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/Types/goTypes.ts b/frontend/src/Types/goTypes.ts index fabc0c9..f43ede7 100644 --- a/frontend/src/Types/goTypes.ts +++ b/frontend/src/Types/goTypes.ts @@ -144,9 +144,14 @@ export interface NewProject { name: string; description: string; } +/** + * Used to change the role of a user in a project. + * If name is identical to the name contained in the token, the role can be changed. + * If the name is different, only a project manager can change the role. + */ export interface RoleChange { - role: 'project_manager' | 'user'; username: string; + role: 'project_manager' | 'user'; projectname: string; } export interface NameChange { From c4b8bef7f8e9fa58716d21a47d2ef73c565a50d9 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 22:37:15 +0100 Subject: [PATCH 19/24] fix database --- backend/internal/database/migrations/0035_weekly_report.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index be2a2d3..b0cbe82 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS weekly_reports ( - report_id INTEGER AUTO_INCREMENT UNIQUE, + report_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, week INTEGER NOT NULL, @@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS weekly_reports ( study_time INTEGER, testing_time INTEGER, signed_by INTEGER, + UNIQUE(user_id, project_id, week), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (project_id) REFERENCES projects(id), - FOREIGN KEY (signed_by) REFERENCES users(id), - PRIMARY KEY (user_id, project_id, week) + FOREIGN KEY (signed_by) REFERENCES users(id) ); \ No newline at end of file From fdaba36d5747bf62d31653e113812f5f9583ee8d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Wed, 20 Mar 2024 22:43:03 +0100 Subject: [PATCH 20/24] Log database error in SubmitWeeklyReport --- backend/internal/handlers/handlers_report_related.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go index 47d076d..fcba523 100644 --- a/backend/internal/handlers/handlers_report_related.go +++ b/backend/internal/handlers/handlers_report_related.go @@ -32,7 +32,7 @@ func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { } if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { - log.Info("Error adding weekly report") + log.Info("Error adding weekly report to db:", err) return c.Status(500).SendString(err.Error()) } From ddea76e24a781b4695cd06f09869dd6954a597a3 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 22:47:02 +0100 Subject: [PATCH 21/24] fixed AllTimeReportsInProject --- 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 fcd0f8f..14d3af9 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -30,7 +30,7 @@ function AllTimeReportsInProject(): JSX.Element { // Call getProjects when the component mounts useEffect(() => { void getWeeklyReports(); - }); + }, []); return ( <> From a2a8ccd185425b9448381e864d7c3f02f53dd4eb Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 23:18:51 +0100 Subject: [PATCH 22/24] =?UTF-8?q?b=C3=A4ttre=20st=C3=A4mmning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing.py b/testing.py index 384d7ce..b8fbe43 100644 --- a/testing.py +++ b/testing.py @@ -274,7 +274,7 @@ def test_sign_report(): submitReportPath, json={ "projectName": projectName, - "week": 1, + "week": 2, "developmentTime": 10, "meetingTime": 5, "adminTime": 5, From 01b934969a361e9084a0b839dcad20d5adb2aa05 Mon Sep 17 00:00:00 2001 From: al8763be Date: Wed, 20 Mar 2024 23:24:28 +0100 Subject: [PATCH 23/24] fixed dep array --- .../Components/AllTimeReportsInProject.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index 14d3af9..4fa9ad8 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -13,24 +13,24 @@ function AllTimeReportsInProject(): JSX.Element { const { projectName } = useParams(); const [weeklyReports, setWeeklyReports] = useState([]); - const getWeeklyReports = async (): Promise => { - const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getWeeklyReportsForUser( - projectName ?? "", - token, - ); - console.log(response); - if (response.success) { - setWeeklyReports(response.data ?? []); - } else { - console.error(response.message); - } - }; - // Call getProjects when the component mounts useEffect(() => { + const getWeeklyReports = async (): Promise => { + const token = localStorage.getItem("accessToken") ?? ""; + const response = await api.getWeeklyReportsForUser( + projectName ?? "", + token, + ); + console.log(response); + if (response.success) { + setWeeklyReports(response.data ?? []); + } else { + console.error(response.message); + } + }; + void getWeeklyReports(); - }, []); + }, [projectName]); return ( <> From 28935f285b5d7f01b41b765386649d352c6b1729 Mon Sep 17 00:00:00 2001 From: borean Date: Wed, 20 Mar 2024 23:24:15 +0100 Subject: [PATCH 24/24] fixed typo --- backend/internal/handlers/handlers_user_related.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go index 116ce90..4e54e38 100644 --- a/backend/internal/handlers/handlers_user_related.go +++ b/backend/internal/handlers/handlers_user_related.go @@ -207,8 +207,8 @@ func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error { // @Accept json // @Produce plain // @Param NewUser body types.NewUser true "user info" -// @Success 200 {json} json "Successfully prometed user" -// @Failure 400 {string} string "bad request" +// @Success 200 {json} json "Successfully promoted user" +// @Failure 400 {string} string "Bad request" // @Failure 401 {string} string "Unauthorized" // @Failure 500 {string} string "Internal server error" // @Router /promoteToAdmin [post]