Compare commits

..

18 commits

Author SHA1 Message Date
Imbus
5696310a68 Merge branch 'dev' into frontend 2024-03-17 22:19:05 +01:00
dDogge
40caa2d158 Changed name to a test in db_test.go 2024-03-17 20:35:48 +01:00
dDogge
37bbbb6098 Added SignWeeklyReport function and 2 corresponding test, also small change to WeeklyReport.go 2024-03-17 20:33:53 +01:00
Imbus
e03727613d Extremely important formatting 2024-03-17 20:04:29 +01:00
dDogge
8a34fc07fa Weekly_report fixed for real this time 2024-03-17 19:58:44 +01:00
dDogge
b93df693d2 Fixed weekly_report 2024-03-17 19:56:16 +01:00
al8763be
447f2b73eb Merge branch 'gruppPP' into BumBranch 2024-03-17 19:42:01 +01:00
Imbus
3683552af8 Verbose debug printing in register endpoint 2024-03-17 19:33:13 +01:00
al8763be
c7fe5e8775 Merge branch 'dev' into frontend 2024-03-17 19:28:57 +01:00
al8763be
402b0ac08b Update API.test.ts 2024-03-17 19:28:03 +01:00
Imbus
9240d5e052 Verbose debug printing in login endpoint 2024-03-17 19:24:13 +01:00
Peter KW
1977125923 Now exporting APIResponse interface 2024-03-17 19:18:03 +01:00
Peter KW
b999e47f94 Changes to handlesubmit 2024-03-17 19:17:34 +01:00
Peter KW
e7e79ced13 Now checks if user is in database with api.login and sets proper authority level based on name for now 2024-03-17 19:16:57 +01:00
pavel Hamawand
141e5c8bb6 minor fix 2024-03-17 16:21:36 +01:00
Peter KW
b7fcafd75c Merge branch 'frontend' into gruppPP 2024-03-17 15:54:06 +01:00
Peter KW
060dc1ee3d Merge branch 'frontend' into gruppPP 2024-03-17 15:39:16 +01:00
pavel Hamawand
79eb59ad46 button fix 2024-03-17 14:13:35 +01:00
10 changed files with 228 additions and 39 deletions

View file

@ -2,6 +2,7 @@ package database
import ( import (
"embed" "embed"
"errors"
"path/filepath" "path/filepath"
"ttime/internal/types" "ttime/internal/types"
@ -30,6 +31,7 @@ type Database interface {
GetProject(projectId int) (types.Project, error) GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, 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 // 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, admin_time,
own_work_time, own_work_time,
study_time, study_time,
testing_time testing_time,
signed_by
FROM FROM
weekly_reports weekly_reports
WHERE WHERE
@ -282,6 +285,34 @@ func (d *Db) GetWeeklyReport(username string, projectName string, week int) (typ
return report, err 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. // Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory // This will eventually be used on an embedded directory
func (d *Db) Migrate() error { func (d *Db) Migrate() error {

View file

@ -1,6 +1,7 @@
package database package database
import ( import (
"fmt"
"testing" "testing"
) )
@ -410,3 +411,128 @@ func TestGetWeeklyReport(t *testing.T) {
} }
// Check other fields similarly // 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 TestSignWeeklyReportByAnotherProjectManager(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")
}
}

View file

@ -1,4 +1,4 @@
CREATE TABLE weekly_reports ( CREATE TABLE IF NOT EXISTS weekly_reports (
report_id INTEGER PRIMARY KEY AUTOINCREMENT, report_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
project_id INTEGER NOT NULL, project_id INTEGER NOT NULL,

View file

@ -22,13 +22,16 @@ import (
func (gs *GState) Register(c *fiber.Ctx) error { func (gs *GState) Register(c *fiber.Ctx) error {
u := new(types.NewUser) u := new(types.NewUser)
if err := c.BodyParser(u); err != nil { if err := c.BodyParser(u); err != nil {
println("Error parsing body")
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
println("Adding user:", u.Username)
if err := gs.Db.AddUser(u.Username, u.Password); err != nil { if err := gs.Db.AddUser(u.Username, u.Password); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
println("User added:", u.Username)
return c.Status(200).SendString("User added") return c.Status(200).SendString("User added")
} }
@ -57,9 +60,11 @@ func (gs *GState) Login(c *fiber.Ctx) error {
// The body type is identical to a NewUser // The body type is identical to a NewUser
u := new(types.NewUser) u := new(types.NewUser)
if err := c.BodyParser(u); err != nil { if err := c.BodyParser(u); err != nil {
println("Error parsing body")
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
println("Username:", u.Username)
if !gs.Db.CheckUser(u.Username, u.Password) { if !gs.Db.CheckUser(u.Username, u.Password) {
println("User not found") println("User not found")
return c.SendStatus(fiber.StatusUnauthorized) return c.SendStatus(fiber.StatusUnauthorized)
@ -74,13 +79,16 @@ func (gs *GState) Login(c *fiber.Ctx) error {
// Create token // Create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
println("Token created for user:", u.Username)
// Generate encoded token and send it as response. // Generate encoded token and send it as response.
t, err := token.SignedString([]byte("secret")) t, err := token.SignedString([]byte("secret"))
if err != nil { if err != nil {
println("Error signing token")
return c.SendStatus(fiber.StatusInternalServerError) return c.SendStatus(fiber.StatusInternalServerError)
} }
println("Successfully signed token for user:", u.Username)
return c.JSON(fiber.Map{"token": t}) return c.JSON(fiber.Map{"token": t})
} }

View file

@ -41,4 +41,6 @@ type WeeklyReport struct {
StudyTime int `json:"studyTime" db:"study_time"` StudyTime int `json:"studyTime" db:"study_time"`
// Total time spent on testing // Total time spent on testing
TestingTime int `json:"testingTime" db:"testing_time"` TestingTime int `json:"testingTime" db:"testing_time"`
// The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"`
} }

View file

@ -8,9 +8,8 @@ describe("API", () => {
username: "lol", // Add the username property username: "lol", // Add the username property
password: "lol", password: "lol",
}; };
const response = await api.registerUser(user); const response = await api.registerUser(user);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty("userId"); expect(response.data).toHaveProperty("userId");
}); });
@ -24,7 +23,7 @@ describe("API", () => {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.createProject(project, token); const response = await api.createProject(project, token);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty("projectId"); expect(response.data).toHaveProperty("projectId");
}); });
@ -34,7 +33,7 @@ describe("API", () => {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.renewToken(refreshToken); const response = await api.renewToken(refreshToken);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty("accessToken"); expect(response.data).toHaveProperty("accessToken");
expect(response.data).toHaveProperty("refreshToken"); expect(response.data).toHaveProperty("refreshToken");
@ -45,7 +44,7 @@ describe("API", () => {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const username = "rrgumdzpmc"; const username = "rrgumdzpmc";
const response = await api.getUserProjects(username, token); const response = await api.getUserProjects(username, token);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty("projects"); expect(response.data).toHaveProperty("projects");
}); });
@ -65,7 +64,7 @@ describe("API", () => {
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.submitWeeklyReport(report, token); const response = await api.submitWeeklyReport(report, token);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty( expect(response.data).toHaveProperty(
"message", "message",
@ -80,7 +79,7 @@ describe("API", () => {
}; };
const response = await api.login(user); const response = await api.login(user);
console.log(response.message);
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toHaveProperty("accessToken"); expect(response.data).toHaveProperty("accessToken");
expect(response.data).toHaveProperty("refreshToken"); expect(response.data).toHaveProperty("refreshToken");

View file

@ -7,7 +7,7 @@ import {
} from "../Types/goTypes"; } from "../Types/goTypes";
// This type of pattern should be hard to misuse // This type of pattern should be hard to misuse
interface APIResponse<T> { export interface APIResponse<T> {
success: boolean; success: boolean;
message?: string; message?: string;
data?: T; data?: T;

View file

@ -1,34 +1,54 @@
import { NewUser } from "../Types/goTypes"; import { NewUser } from "../Types/goTypes";
import { api, APIResponse } from "../API/API";
import { Dispatch, SetStateAction } from "react";
function LoginCheck(props: { username: string; password: string }): number { /*
//Example users for testing without backend, remove when using backend * Checks if user is in database with api.login and then sets proper authority level
const admin: NewUser = { * TODO: change so that it checks for user type (admin, user, pm) somehow instead
username: "admin", **/
password: "123", function LoginCheck(props: {
}; username: string;
const pmanager: NewUser = { password: string;
username: "pmanager", setAuthority: Dispatch<SetStateAction<number>>;
password: "123", }): number {
};
const user: NewUser = { const user: NewUser = {
username: "user", username: props.username,
password: "123", password: props.password,
}; };
api
//TODO: Compare with db instead when finished .login(user)
if (props.username === admin.username && props.password === admin.password) { .then((response: APIResponse<string>) => {
return 1; if (response.success) {
} else if ( if (response.data !== undefined) {
props.username === pmanager.username && const token = response.data;
props.password === pmanager.password //TODO: change so that it checks for user type (admin, user, pm) instead
) { if (token !== "" && props.username === "admin") {
return 2; props.setAuthority((prevAuth) => {
} else if ( prevAuth = 1;
props.username === user.username && return prevAuth;
props.password === user.password });
) { } else if (token !== "" && props.username === "pm") {
return 3; props.setAuthority((prevAuth) => {
prevAuth = 2;
return prevAuth;
});
} else if (token !== "" && props.username === "user") {
props.setAuthority((prevAuth) => {
prevAuth = 3;
return prevAuth;
});
} }
} else {
console.error("Token was undefined");
}
} else {
console.error("Token could not be fetched");
}
})
.catch((error) => {
console.error("An error occurred during login:", error);
});
return 0; return 0;
} }

View file

@ -15,9 +15,10 @@ function LoginPage(props: {
and if so, redirect to correct page */ and if so, redirect to correct page */
function handleSubmit(event: FormEvent<HTMLFormElement>): void { function handleSubmit(event: FormEvent<HTMLFormElement>): void {
event.preventDefault(); event.preventDefault();
props.setAuthority((prevAuth) => { LoginCheck({
prevAuth = LoginCheck({ username: username, password: password }); username: username,
return prevAuth; password: password,
setAuthority: props.setAuthority,
}); });
} }

View file

@ -14,6 +14,7 @@ function PMProjectMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type={"button"}
/> />
</Link> </Link>
<Link to="/PM-time-role"> <Link to="/PM-time-role">
@ -22,6 +23,7 @@ function PMProjectMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type={"button"}
/> />
</Link> </Link>
<BackButton /> <BackButton />