From 3683552af8cb1e766056dabc55c24ec5f40a7acb Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 17 Mar 2024 19:33:13 +0100 Subject: [PATCH 01/14] Verbose debug printing in register endpoint --- backend/internal/handlers/handlers_user_related.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go index c1c8abe..0619ea5 100644 --- a/backend/internal/handlers/handlers_user_related.go +++ b/backend/internal/handlers/handlers_user_related.go @@ -22,13 +22,16 @@ import ( func (gs *GState) Register(c *fiber.Ctx) error { u := new(types.NewUser) if err := c.BodyParser(u); err != nil { + println("Error parsing body") return c.Status(400).SendString(err.Error()) } + println("Adding user:", u.Username) if err := gs.Db.AddUser(u.Username, u.Password); err != nil { return c.Status(500).SendString(err.Error()) } + println("User added:", u.Username) return c.Status(200).SendString("User added") } From b93df693d253998b2cd16010d1a51cda013955af Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Sun, 17 Mar 2024 19:56:16 +0100 Subject: [PATCH 02/14] Fixed weekly_report --- backend/internal/database/migrations/0035_weekly_report.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 366d932..81f2eee 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,4 +1,4 @@ -CREATE TABLE weekly_reports ( +CREATE TABLE weekly_reports IF NOT EXISTS( report_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, From 8a34fc07fae73d457b213a7f05cf756e27895870 Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Sun, 17 Mar 2024 19:58:44 +0100 Subject: [PATCH 03/14] Weekly_report fixed for real this time --- backend/internal/database/migrations/0035_weekly_report.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 81f2eee..30876bd 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,4 +1,4 @@ -CREATE TABLE weekly_reports IF NOT EXISTS( +CREATE TABLE IF NOT EXISTS weekly_reports( report_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, From e03727613d25761f526d5a501c8af3bed408e7cb Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 17 Mar 2024 20:04:29 +0100 Subject: [PATCH 04/14] Extremely important formatting --- backend/internal/database/migrations/0035_weekly_report.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 30876bd..8f76b80 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS weekly_reports( +CREATE TABLE IF NOT EXISTS weekly_reports ( report_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, project_id INTEGER NOT NULL, From 37bbbb6098a9f9e2dcd79fdb0d71f71e3899a769 Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Sun, 17 Mar 2024 20:30:55 +0100 Subject: [PATCH 05/14] Added SignWeeklyReport function and 2 corresponding test, also small change to WeeklyReport.go --- backend/internal/database/db.go | 33 ++++++- backend/internal/database/db_test.go | 126 +++++++++++++++++++++++++ backend/internal/types/WeeklyReport.go | 2 + 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 7f4a89c..5cbb13f 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -2,6 +2,7 @@ package database import ( "embed" + "errors" "path/filepath" "ttime/internal/types" @@ -30,6 +31,7 @@ type Database interface { GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) + SignWeeklyReport(reportId int, projectManagerId int) error } // This struct is a wrapper type that holds the database connection @@ -270,7 +272,8 @@ func (d *Db) GetWeeklyReport(username string, projectName string, week int) (typ admin_time, own_work_time, study_time, - testing_time + testing_time, + signed_by FROM weekly_reports WHERE @@ -282,6 +285,34 @@ func (d *Db) GetWeeklyReport(username string, projectName string, week int) (typ return report, err } +// SignWeeklyReport signs a weekly report by updating the signed_by field +// with the provided project manager's ID, but only if the project manager +// is in the same project as the report +func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { + // Retrieve the project ID associated with the report + var reportProjectID int + err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId) + if err != nil { + return err + } + + // Retrieve the project ID associated with the project manager + var managerProjectID int + err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId) + if err != nil { + return err + } + + // Check if the project manager is in the same project as the report + if reportProjectID != managerProjectID { + return errors.New("project manager doesn't have permission to sign the report") + } + + // Update the signed_by field of the specified report + _, err = d.Exec("UPDATE weekly_reports SET signed_by = ? WHERE report_id = ?", projectManagerId, reportId) + return err +} + // Reads a directory of migration files and applies them to the database. // This will eventually be used on an embedded directory func (d *Db) Migrate() error { diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index f791066..1e8741d 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -1,6 +1,7 @@ package database import ( + "fmt" "testing" ) @@ -410,3 +411,128 @@ func TestGetWeeklyReport(t *testing.T) { } // Check other fields similarly } + +func TestSignWeeklyReport(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + // Add project manager + err = db.AddUser("projectManager", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add a regular user + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add project + err = db.AddProject("testproject", "description", "projectManager") + if err != nil { + t.Error("AddProject failed:", err) + } + + // Add both regular users as members to the project + err = db.AddUserToProject("testuser", "testproject", "member") + if err != nil { + t.Error("AddUserToProject failed:", err) + } + + err = db.AddUserToProject("projectManager", "testproject", "project_manager") + if err != nil { + t.Error("AddUserToProject failed:", err) + } + + // Add a weekly report for one of the regular users + err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) + if err != nil { + t.Error("AddWeeklyReport failed:", err) + } + + // Retrieve the added report + report, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Print project manager's ID + projectManagerID, err := db.GetUserId("projectManager") + if err != nil { + t.Error("GetUserId failed:", err) + } + fmt.Println("Project Manager's ID:", projectManagerID) + + // Sign the report with the project manager + err = db.SignWeeklyReport(report.ReportId, projectManagerID) + if err != nil { + t.Error("SignWeeklyReport failed:", err) + } + + // Retrieve the report again to check if it's signed + signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Ensure the report is signed by the project manager + if *signedReport.SignedBy != projectManagerID { + t.Errorf("Expected SignedBy to be %d, got %d", projectManagerID, *signedReport.SignedBy) + } +} + +func TestSignWeeklyReportByNonProjectManager(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + // Add project manager + err = db.AddUser("projectManager", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add a regular user + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + // Add project + err = db.AddProject("testproject", "description", "projectManager") + if err != nil { + t.Error("AddProject failed:", err) + } + + // Add the regular user as a member to the project + err = db.AddUserToProject("testuser", "testproject", "member") + if err != nil { + t.Error("AddUserToProject failed:", err) + } + + // Add a weekly report for the regular user + err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) + if err != nil { + t.Error("AddWeeklyReport failed:", err) + } + + // Retrieve the added report + report, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + anotherManagerID, 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") + } +} diff --git a/backend/internal/types/WeeklyReport.go b/backend/internal/types/WeeklyReport.go index b704cc8..299395a 100644 --- a/backend/internal/types/WeeklyReport.go +++ b/backend/internal/types/WeeklyReport.go @@ -41,4 +41,6 @@ type WeeklyReport struct { StudyTime int `json:"studyTime" db:"study_time"` // Total time spent on testing TestingTime int `json:"testingTime" db:"testing_time"` + // The project manager who signed it + SignedBy *int `json:"signedBy" db:"signed_by"` } From 40caa2d158add3a64058d7567d7e9d75d88be143 Mon Sep 17 00:00:00 2001 From: dDogge <> Date: Sun, 17 Mar 2024 20:35:48 +0100 Subject: [PATCH 06/14] Changed name to a test in db_test.go --- backend/internal/database/db_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 1e8741d..09de45b 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -484,7 +484,7 @@ func TestSignWeeklyReport(t *testing.T) { } } -func TestSignWeeklyReportByNonProjectManager(t *testing.T) { +func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { db, err := setupState() if err != nil { t.Error("setupState failed:", err) From 7339a69bceae1501e1cf2ea5c361cd10b498227c Mon Sep 17 00:00:00 2001 From: al8763be Date: Sun, 17 Mar 2024 22:20:34 +0100 Subject: [PATCH 07/14] getWeeklyReport --- frontend/src/API/API.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index cfd5b61..95a7e02 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -20,6 +20,10 @@ interface API { registerUser(user: NewUser): Promise>; /** Remove a user */ removeUser(username: string, token: string): Promise>; + /** Login */ + login(NewUser: NewUser): Promise>; + /** Renew the token */ + renewToken(token: string): Promise>; /** Create a project */ createProject( project: NewProject, @@ -30,15 +34,16 @@ interface API { project: NewWeeklyReport, token: string, ): Promise>; - /** Renew the token */ - renewToken(token: string): Promise>; + /**Gets a weekly report*/ + getWeeklyReport( + projectName: string, + token: string, + ): Promise>; /** Gets all the projects of a user*/ getUserProjects( username: string, token: string, ): Promise>; - /** Login */ - login(NewUser: NewUser): Promise>; } // Export an instance of the API From 6823102b4421c4af3029aef161b92a5f3a89f46d Mon Sep 17 00:00:00 2001 From: al8763be Date: Sun, 17 Mar 2024 22:27:21 +0100 Subject: [PATCH 08/14] Deleted API test --- frontend/src/API/API.test.ts | 87 ------------------------------------ frontend/src/API/API.ts | 3 +- 2 files changed, 2 insertions(+), 88 deletions(-) delete mode 100644 frontend/src/API/API.test.ts diff --git a/frontend/src/API/API.test.ts b/frontend/src/API/API.test.ts deleted file mode 100644 index dbae706..0000000 --- a/frontend/src/API/API.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, test } from "@jest/globals"; -import { api } from "../API/API"; -import { NewUser, NewWeeklyReport } from "../Types/goTypes"; - -describe("API", () => { - test("registerUser", async () => { - const user: NewUser = { - username: "lol", // Add the username property - password: "lol", - }; - const response = await api.registerUser(user); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("userId"); - }); - - test("createProject", async () => { - const project = { - name: "Project X", - description: "This is a test project", - }; - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.createProject(project, token); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("projectId"); - }); - - test("renewToken", async () => { - const refreshToken = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.renewToken(refreshToken); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("accessToken"); - expect(response.data).toHaveProperty("refreshToken"); - }); - - test("getUserProjects", async () => { - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - const username = "rrgumdzpmc"; - const response = await api.getUserProjects(username, token); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("projects"); - }); - - test("submitWeeklyReport", async () => { - const report: NewWeeklyReport = { - projectName: "vtmosxssst", - week: 2, - developmentTime: 40, - meetingTime: 5, - adminTime: 2, - ownWorkTime: 10, - studyTime: 12, - testingTime: 41, - }; - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.submitWeeklyReport(report, token); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty( - "message", - "Report submitted successfully", - ); - }); - - test("login", async () => { - const user: NewUser = { - username: "rrgumdzpmc", // Add an empty string value for the username property - password: "always_same", - }; - - const response = await api.login(user); - console.log(response.message); - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("accessToken"); - expect(response.data).toHaveProperty("refreshToken"); - }); -}); diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 95a7e02..f6e5979 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -36,8 +36,9 @@ interface API { ): Promise>; /**Gets a weekly report*/ getWeeklyReport( + username: string, projectName: string, - token: string, + week: string, ): Promise>; /** Gets all the projects of a user*/ getUserProjects( From c3ce25236fa15083362b1eb5297a82516c4b9145 Mon Sep 17 00:00:00 2001 From: al8763be Date: Sun, 17 Mar 2024 22:35:51 +0100 Subject: [PATCH 09/14] Full implementation of getWeeklyProject --- frontend/src/API/API.ts | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index f6e5979..32c5eb2 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -33,13 +33,14 @@ interface API { submitWeeklyReport( project: NewWeeklyReport, token: string, - ): Promise>; + ): Promise>; /**Gets a weekly report*/ getWeeklyReport( username: string, projectName: string, week: string, - ): Promise>; + token: string, + ): Promise>; /** Gets all the projects of a user*/ getUserProjects( username: string, @@ -169,9 +170,9 @@ export const api: API = { }, async submitWeeklyReport( - project: NewWeeklyReport, + weeklyReport: NewWeeklyReport, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch("/api/submitWeeklyReport", { method: "POST", @@ -179,7 +180,7 @@ export const api: API = { "Content-Type": "application/json", Authorization: "Bearer " + token, }, - body: JSON.stringify(project), + body: JSON.stringify(weeklyReport), }); if (!response.ok) { @@ -189,7 +190,7 @@ export const api: API = { }; } - const data = (await response.json()) as Project; + const data = (await response.json()) as NewWeeklyReport; return { success: true, data }; } catch (e) { return { @@ -199,6 +200,33 @@ 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, + }, + body: JSON.stringify({ username, projectName, week }), + }); + + if (!response.ok) { + return { success: false, message: "Failed to get weekly report" }; + } else { + const data = (await response.json()) as NewWeeklyReport; + return { success: true, data }; + } + } catch (e) { + return { success: false, message: "Failed to get weekly report" }; + } + }, + async login(NewUser: NewUser): Promise> { try { const response = await fetch("/api/login", { From e012b6ff1207b311948d45bf5e1589627f9830db Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 17 Mar 2024 22:48:38 +0100 Subject: [PATCH 10/14] Error checking in register component, redirect to login if success --- frontend/src/API/API.ts | 14 ++++++++++---- frontend/src/Components/Register.tsx | 21 +++++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 248ad37..9b11dba 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -37,13 +37,19 @@ export const api: API = { }); if (!response.ok) { - return { success: false, message: "Failed to register user" }; + return { + success: false, + message: "Failed to register user: " + response.status, + }; } else { - const data = (await response.json()) as User; - return { success: true, data }; + // const data = (await response.json()) as User; // The API does not currently return the user + return { success: true }; } } catch (e) { - return { success: false, message: "Failed to register user" }; + return { + success: false, + message: "Unknown error while registering user", + }; } }, diff --git a/frontend/src/Components/Register.tsx b/frontend/src/Components/Register.tsx index e4a3ba0..9e767ca 100644 --- a/frontend/src/Components/Register.tsx +++ b/frontend/src/Components/Register.tsx @@ -3,14 +3,26 @@ import { NewUser } from "../Types/Users"; import { api } from "../API/API"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; +import { useNavigate } from "react-router-dom"; export default function Register(): JSX.Element { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); + const [username, setUsername] = useState(); + const [password, setPassword] = useState(); + const [errMessage, setErrMessage] = useState(); + + const nav = useNavigate(); const handleRegister = async (): Promise => { - const newUser: NewUser = { userName: username, password }; - await api.registerUser(newUser); // TODO: Handle errors + const newUser: NewUser = { + userName: username ?? "", + password: password ?? "", + }; + const response = await api.registerUser(newUser); + if (response.success) { + nav("/"); // Instantly navigate to the login page + } else { + setErrMessage(response.message ?? "Unknown error"); + } }; return ( @@ -67,6 +79,7 @@ export default function Register(): JSX.Element { }} /> + {errMessage &&

{errMessage}

}