Compare commits

..

No commits in common. "5696310a687e7d59a5469fe870074034164b42fe" and "1a412dc8a06b335c1d7d50b5311d465f0bffa3be" have entirely different histories.

10 changed files with 40 additions and 229 deletions

View file

@ -2,7 +2,6 @@ package database
import ( import (
"embed" "embed"
"errors"
"path/filepath" "path/filepath"
"ttime/internal/types" "ttime/internal/types"
@ -31,7 +30,6 @@ 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
@ -272,8 +270,7 @@ 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
@ -285,34 +282,6 @@ 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,7 +1,6 @@
package database package database
import ( import (
"fmt"
"testing" "testing"
) )
@ -411,128 +410,3 @@ 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 IF NOT EXISTS weekly_reports ( CREATE TABLE 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,16 +22,13 @@ 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")
} }
@ -60,11 +57,9 @@ 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)
@ -79,16 +74,13 @@ 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,6 +41,4 @@ 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,8 +8,9 @@ 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");
}); });
@ -23,7 +24,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");
}); });
@ -33,7 +34,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");
@ -44,7 +45,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");
}); });
@ -64,7 +65,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",
@ -79,7 +80,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
export interface APIResponse<T> { interface APIResponse<T> {
success: boolean; success: boolean;
message?: string; message?: string;
data?: T; data?: T;

View file

@ -1,54 +1,34 @@
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 {
* Checks if user is in database with api.login and then sets proper authority level //Example users for testing without backend, remove when using backend
* TODO: change so that it checks for user type (admin, user, pm) somehow instead const admin: NewUser = {
**/ username: "admin",
function LoginCheck(props: { password: "123",
username: string; };
password: string; const pmanager: NewUser = {
setAuthority: Dispatch<SetStateAction<number>>; username: "pmanager",
}): number { password: "123",
const user: NewUser = { };
username: props.username, const user: NewUser = {
password: props.password, username: "user",
password: "123",
}; };
api
.login(user)
.then((response: APIResponse<string>) => {
if (response.success) {
if (response.data !== undefined) {
const token = response.data;
//TODO: change so that it checks for user type (admin, user, pm) instead
if (token !== "" && props.username === "admin") {
props.setAuthority((prevAuth) => {
prevAuth = 1;
return prevAuth;
});
} else if (token !== "" && props.username === "pm") {
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);
});
//TODO: Compare with db instead when finished
if (props.username === admin.username && props.password === admin.password) {
return 1;
} else if (
props.username === pmanager.username &&
props.password === pmanager.password
) {
return 2;
} else if (
props.username === user.username &&
props.password === user.password
) {
return 3;
}
return 0; return 0;
} }

View file

@ -15,10 +15,9 @@ 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();
LoginCheck({ props.setAuthority((prevAuth) => {
username: username, prevAuth = LoginCheck({ username: username, password: password });
password: password, return prevAuth;
setAuthority: props.setAuthority,
}); });
} }

View file

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