diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 320327a..7f4a89c 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -29,6 +29,7 @@ type Database interface { GetAllProjects() ([]types.Project, error) GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) + GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) } // This struct is a wrapper type that holds the database connection @@ -256,6 +257,31 @@ func (d *Db) GetAllUsersApplication() ([]string, error) { return usernames, nil } +func (d *Db) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) { + var report types.WeeklyReport + query := ` + SELECT + report_id, + user_id, + project_id, + week, + development_time, + meeting_time, + admin_time, + own_work_time, + study_time, + testing_time + FROM + weekly_reports + WHERE + user_id = (SELECT id FROM users WHERE username = ?) + AND project_id = (SELECT id FROM projects WHERE name = ?) + AND week = ? + ` + err := d.Get(&report, query, username, projectName, week) + return report, 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 9124c45..f791066 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -371,3 +371,42 @@ func TestAddProject(t *testing.T) { t.Error("Added project not found") } } + +func TestGetWeeklyReport(t *testing.T) { + db, err := setupState() + if err != nil { + t.Error("setupState failed:", err) + } + + err = db.AddUser("testuser", "password") + if err != nil { + t.Error("AddUser failed:", err) + } + + err = db.AddProject("testproject", "description", "testuser") + if err != nil { + t.Error("AddProject failed:", err) + } + + err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) + if err != nil { + t.Error("AddWeeklyReport failed:", err) + } + + report, err := db.GetWeeklyReport("testuser", "testproject", 1) + if err != nil { + t.Error("GetWeeklyReport failed:", err) + } + + // Check if the retrieved report matches the expected values + if report.UserId != 1 { + t.Errorf("Expected UserId to be 1, got %d", report.UserId) + } + if report.ProjectId != 1 { + t.Errorf("Expected ProjectId to be 1, got %d", report.ProjectId) + } + if report.Week != 1 { + t.Errorf("Expected Week to be 1, got %d", report.Week) + } + // Check other fields similarly +} diff --git a/backend/internal/database/migrations/0010_users.sql b/backend/internal/database/migrations/0010_users.sql index 5c9d329..d2e2dd1 100644 --- a/backend/internal/database/migrations/0010_users.sql +++ b/backend/internal/database/migrations/0010_users.sql @@ -3,7 +3,7 @@ -- username is what is used for login -- password is the hashed password CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, username VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index 58d8e97..99bce86 100644 --- a/backend/internal/database/migrations/0020_projects.sql +++ b/backend/internal/database/migrations/0020_projects.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS projects ( - id INTEGER PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, owner_user_id INTEGER NOT NULL, diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 0e29b97..366d932 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,14 +1,16 @@ CREATE TABLE weekly_reports ( - user_id INTEGER, - project_id INTEGER, - week INTEGER, + report_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + week INTEGER NOT NULL, development_time INTEGER, meeting_time INTEGER, admin_time INTEGER, own_work_time INTEGER, study_time INTEGER, testing_time INTEGER, + signed_by INTEGER, FOREIGN KEY (user_id) REFERENCES users(id), - FOREIGN KEY (project_id) REFERENCES projects(id) - PRIMARY KEY (user_id, project_id, week) -) \ No newline at end of file + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (signed_by) REFERENCES users(id) +); \ No newline at end of file diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 2378f7b..57a1969 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,13 +1,9 @@ package handlers import ( - "strconv" - "time" "ttime/internal/database" - "ttime/internal/types" "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" ) // The actual interface that we will use @@ -19,6 +15,7 @@ type GlobalState interface { CreateProject(c *fiber.Ctx) error // To create a new project GetUserProjects(c *fiber.Ctx) error // To get all projects SubmitWeeklyReport(c *fiber.Ctx) error + GetWeeklyReport(c *fiber.Ctx) error // GetProject(c *fiber.Ctx) error // To get a specific project // UpdateProject(c *fiber.Ctx) error // To update a project // DeleteProject(c *fiber.Ctx) error // To delete a project @@ -51,50 +48,6 @@ type GState struct { ButtonCount int } -// Register is a simple handler that registers a new user -// -// @Summary Register a new user -// @Description Register a new user -// @Tags User -// @Accept json -// @Produce json -// @Success 200 {string} string "User added" -// @Failure 400 {string} string "Bad request" -// @Failure 500 {string} string "Internal server error" -// @Router /api/register [post] -func (gs *GState) Register(c *fiber.Ctx) error { - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - return c.Status(400).SendString(err.Error()) - } - - if err := gs.Db.AddUser(u.Username, u.Password); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("User added") -} - -// This path should obviously be protected in the future -// UserDelete deletes a user from the database -func (gs *GState) UserDelete(c *fiber.Ctx) error { - // Read from path parameters - username := c.Params("username") - - // Read username from Locals - auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) - - if username != auth_username { - return c.Status(403).SendString("You can only delete yourself") - } - - if err := gs.Db.RemoveUser(username); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("User deleted") -} - func (gs *GState) GetButtonCount(c *fiber.Ctx) error { return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) } @@ -103,181 +56,3 @@ func (gs *GState) IncrementButtonCount(c *fiber.Ctx) error { gs.ButtonCount++ return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) } - -// Login is a simple login handler that returns a JWT token -func (gs *GState) Login(c *fiber.Ctx) error { - // The body type is identical to a NewUser - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - return c.Status(400).SendString(err.Error()) - } - - if !gs.Db.CheckUser(u.Username, u.Password) { - println("User not found") - return c.SendStatus(fiber.StatusUnauthorized) - } - - // Create the Claims - claims := jwt.MapClaims{ - "name": u.Username, - "admin": false, - "exp": time.Now().Add(time.Hour * 72).Unix(), - } - - // Create token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - - // Generate encoded token and send it as response. - t, err := token.SignedString([]byte("secret")) - if err != nil { - return c.SendStatus(fiber.StatusInternalServerError) - } - - return c.JSON(fiber.Map{"token": t}) -} - -// LoginRenew is a simple handler that renews the token -func (gs *GState) LoginRenew(c *fiber.Ctx) error { - // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - claims["exp"] = time.Now().Add(time.Hour * 72).Unix() - renewed := jwt.MapClaims{ - "name": claims["name"], - "admin": claims["admin"], - "exp": claims["exp"], - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) - t, err := token.SignedString([]byte("secret")) - if err != nil { - return c.SendStatus(fiber.StatusInternalServerError) - } - return c.JSON(fiber.Map{"token": t}) -} - -// CreateProject is a simple handler that creates a new project -func (gs *GState) CreateProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - - p := new(types.NewProject) - if err := c.BodyParser(p); err != nil { - return c.Status(400).SendString(err.Error()) - } - - // Get the username from the token and set it as the owner of the project - // This is ugly but - claims := user.Claims.(jwt.MapClaims) - owner := claims["name"].(string) - - if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("Project added") -} - -// GetUserProjects returns all projects that the user is a member of -func (gs *GState) GetUserProjects(c *fiber.Ctx) error { - // First we get the username from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Then dip into the database to get the projects - projects, err := gs.Db.GetProjectsForUser(username) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a json serialized list of projects - return c.JSON(projects) -} - -// ListAllUsers is a handler that returns a list of all users in the application database -func (gs *GState) ListAllUsers(c *fiber.Ctx) error { - // Get all users from the database - users, err := gs.Db.GetAllUsersApplication() - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the list of users as JSON - return c.JSON(users) -} - -func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error { - // Extract the project name from the request parameters or body - projectName := c.Params("projectName") - - // Get all users associated with the project from the database - users, err := gs.Db.GetAllUsersProject(projectName) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the list of users as JSON - return c.JSON(users) -} - -// ProjectRoleChange is a handler that changes a user's role within a project -func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { - // Extract the necessary parameters from the request - username := c.Params("username") - projectName := c.Params("projectName") - role := c.Params("role") - - // Change the user's role within the project in the database - if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} - -// GetProject retrieves a specific project by its ID -func (gs *GState) GetProject(c *fiber.Ctx) error { - // Extract the project ID from the request parameters or body - projectID := c.Params("projectID") - - // Parse the project ID into an integer - projectIDInt, err := strconv.Atoi(projectID) - if err != nil { - return c.Status(400).SendString("Invalid project ID") - } - - // Get the project from the database by its ID - project, err := gs.Db.GetProject(projectIDInt) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the project as JSON - return c.JSON(project) -} - -func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - report := new(types.NewWeeklyReport) - if err := c.BodyParser(report); err != nil { - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if report.Week < 1 || report.Week > 52 { - return c.Status(400).SendString("Invalid week number") - } - if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 { - return c.Status(400).SendString("Invalid time report") - } - - if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("Time report added") -} diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go new file mode 100644 index 0000000..6a430e9 --- /dev/null +++ b/backend/internal/handlers/handlers_project_related.go @@ -0,0 +1,98 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" +) + +// CreateProject is a simple handler that creates a new project +func (gs *GState) CreateProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + p := new(types.NewProject) + if err := c.BodyParser(p); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Get the username from the token and set it as the owner of the project + // This is ugly but + claims := user.Claims.(jwt.MapClaims) + owner := claims["name"].(string) + + if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Project added") +} + +// GetUserProjects returns all projects that the user is a member of +func (gs *GState) GetUserProjects(c *fiber.Ctx) error { + // First we get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Then dip into the database to get the projects + projects, err := gs.Db.GetProjectsForUser(username) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a json serialized list of projects + return c.JSON(projects) +} + +// ProjectRoleChange is a handler that changes a user's role within a project +func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { + // Extract the necessary parameters from the request + username := c.Params("username") + projectName := c.Params("projectName") + role := c.Params("role") + + // Change the user's role within the project in the database + if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} + +// GetProject retrieves a specific project by its ID +func (gs *GState) GetProject(c *fiber.Ctx) error { + // Extract the project ID from the request parameters or body + projectID := c.Params("projectID") + + // Parse the project ID into an integer + projectIDInt, err := strconv.Atoi(projectID) + if err != nil { + return c.Status(400).SendString("Invalid project ID") + } + + // Get the project from the database by its ID + project, err := gs.Db.GetProject(projectIDInt) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the project as JSON + return c.JSON(project) +} + +func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error { + // Extract the project name from the request parameters or body + projectName := c.Params("projectName") + + // Get all users associated with the project from the database + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the list of users as JSON + return c.JSON(users) +} diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go new file mode 100644 index 0000000..8754afd --- /dev/null +++ b/backend/internal/handlers/handlers_report_related.go @@ -0,0 +1,62 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" +) + +func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + report := new(types.NewWeeklyReport) + if err := c.BodyParser(report); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if report.Week < 1 || report.Week > 52 { + return c.Status(400).SendString("Invalid week number") + } + if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 { + return c.Status(400).SendString("Invalid time report") + } + + if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Time report added") +} + +// Handler for retrieving weekly report +func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the request + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Extract project name and week from query parameters + projectName := c.Query("projectName") + week := c.Query("week") + + // Convert week to integer + weekInt, err := strconv.Atoi(week) + if err != nil { + return c.Status(400).SendString("Invalid week number") + } + + // Call the database function to get the weekly report + report, err := gs.Db.GetWeeklyReport(username, projectName, weekInt) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the retrieved weekly report + return c.JSON(report) +} diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go new file mode 100644 index 0000000..c1c8abe --- /dev/null +++ b/backend/internal/handlers/handlers_user_related.go @@ -0,0 +1,121 @@ +package handlers + +import ( + "time" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" +) + +// Register is a simple handler that registers a new user +// +// @Summary Register a new user +// @Description Register a new user +// @Tags User +// @Accept json +// @Produce json +// @Success 200 {string} string "User added" +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /api/register [post] +func (gs *GState) Register(c *fiber.Ctx) error { + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + return c.Status(400).SendString(err.Error()) + } + + if err := gs.Db.AddUser(u.Username, u.Password); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("User added") +} + +// This path should obviously be protected in the future +// UserDelete deletes a user from the database +func (gs *GState) UserDelete(c *fiber.Ctx) error { + // Read from path parameters + username := c.Params("username") + + // Read username from Locals + auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) + + if username != auth_username { + return c.Status(403).SendString("You can only delete yourself") + } + + if err := gs.Db.RemoveUser(username); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("User deleted") +} + +// Login is a simple login handler that returns a JWT token +func (gs *GState) Login(c *fiber.Ctx) error { + // The body type is identical to a NewUser + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + println("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + println("Username:", u.Username) + if !gs.Db.CheckUser(u.Username, u.Password) { + println("User not found") + return c.SendStatus(fiber.StatusUnauthorized) + } + + // Create the Claims + claims := jwt.MapClaims{ + "name": u.Username, + "admin": false, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + println("Token created for user:", u.Username) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + println("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + println("Successfully signed token for user:", u.Username) + return c.JSON(fiber.Map{"token": t}) +} + +// LoginRenew is a simple handler that renews the token +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + renewed := jwt.MapClaims{ + "name": claims["name"], + "admin": claims["admin"], + "exp": claims["exp"], + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + return c.JSON(fiber.Map{"token": t}) +} + +// ListAllUsers is a handler that returns a list of all users in the application database +func (gs *GState) ListAllUsers(c *fiber.Ctx) error { + // Get all users from the database + users, err := gs.Db.GetAllUsersApplication() + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the list of users as JSON + return c.JSON(users) +} diff --git a/backend/internal/types/WeeklyReport.go b/backend/internal/types/WeeklyReport.go index e0ea1ef..b704cc8 100644 --- a/backend/internal/types/WeeklyReport.go +++ b/backend/internal/types/WeeklyReport.go @@ -19,3 +19,26 @@ type NewWeeklyReport struct { // Total time spent on testing TestingTime int `json:"testingTime"` } + +type WeeklyReport struct { + // The ID of the report + ReportId int `json:"reportId" db:"report_id"` + // The user id of the user who submitted the report + UserId int `json:"userId" db:"user_id"` + // The name of the project, as it appears in the database + ProjectId int `json:"projectId" db:"project_id"` + // The week number + Week int `json:"week" db:"week"` + // Total time spent on development + DevelopmentTime int `json:"developmentTime" db:"development_time"` + // Total time spent in meetings + MeetingTime int `json:"meetingTime" db:"meeting_time"` + // Total time spent on administrative tasks + AdminTime int `json:"adminTime" db:"admin_time"` + // Total time spent on personal projects + OwnWorkTime int `json:"ownWorkTime" db:"own_work_time"` + // Total time spent on studying + StudyTime int `json:"studyTime" db:"study_time"` + // Total time spent on testing + TestingTime int `json:"testingTime" db:"testing_time"` +} diff --git a/frontend/src/API/API.test.ts b/frontend/src/API/API.test.ts new file mode 100644 index 0000000..dbae706 --- /dev/null +++ b/frontend/src/API/API.test.ts @@ -0,0 +1,87 @@ +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 6dfbc1e..cfd5b61 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -167,32 +167,29 @@ export const api: API = { token: string, ): Promise> { try { - return fetch("/api/submitWeeklyReport", { + const response = await fetch("/api/submitWeeklyReport", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, body: JSON.stringify(project), - }) - .then((response) => { - if (!response.ok) { - return { - success: false, - message: "Failed to submit weekly report", - }; - } else { - return response.json(); - } - }) - .then((data: Project) => { - return { success: true, data }; - }); + }); + + if (!response.ok) { + return { + success: false, + message: "Failed to submit weekly report", + }; + } + + const data = (await response.json()) as Project; + return { success: true, data }; } catch (e) { - return Promise.resolve({ + return { success: false, message: "Failed to submit weekly report", - }); + }; } }, diff --git a/frontend/src/Components/TimeReport.tsx b/frontend/src/Components/TimeReport.tsx index cb33ad9..e7eb5b7 100644 --- a/frontend/src/Components/TimeReport.tsx +++ b/frontend/src/Components/TimeReport.tsx @@ -1,40 +1,44 @@ import { useState } from "react"; -import { TimeReport } from "../Types/TimeReport"; import { api } from "../API/API"; import { useNavigate } from "react-router-dom"; import Button from "./Button"; +import { NewWeeklyReport } from "../Types/goTypes"; export default function NewTimeReport(): JSX.Element { - const [week, setWeek] = useState(""); - const [development, setDevelopment] = useState("0"); - const [meeting, setMeeting] = useState("0"); - const [administration, setAdministration] = useState("0"); - const [ownwork, setOwnWork] = useState("0"); - const [studies, setStudies] = useState("0"); - const [testing, setTesting] = useState("0"); + const [projectName, setProjectName] = useState("projectName"); // TODO: Get from backend + const [week, setWeek] = useState(NaN); + const [development, setDevelopment] = useState(NaN); + const [meeting, setMeeting] = useState(NaN); + const [administration, setAdministration] = useState(NaN); + const [ownwork, setOwnWork] = useState(NaN); + const [studies, setStudies] = useState(NaN); + const [testing, setTesting] = useState(NaN); const handleNewTimeReport = async (): Promise => { - const newTimeReport: TimeReport = { + const newTimeReport: NewWeeklyReport = { + projectName, week, - development, - meeting, - administration, - ownwork, - studies, - testing, + developmentTime: development, + meetingTime: meeting, + adminTime: administration, + ownWorkTime: ownwork, + studyTime: studies, + testingTime: testing, }; await Promise.resolve(); - // await api.registerTimeReport(newTimeReport); This needs to be implemented! + await api.submitWeeklyReport(newTimeReport, "token"); }; const navigate = useNavigate(); + setProjectName("Something Reasonable"); // This should obviously not be used here + return ( <>
{ - if (week === "") { + if (!week) { alert("Please enter a week number"); e.preventDefault(); return; @@ -50,7 +54,7 @@ export default function NewTimeReport(): JSX.Element { type="week" placeholder="Week" onChange={(e) => { - const weekNumber = e.target.value.split("-W")[1]; + const weekNumber = parseInt(e.target.value.split("-W")[1]); setWeek(weekNumber); }} onKeyDown={(event) => { @@ -81,7 +85,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={development} onChange={(e) => { - setDevelopment(e.target.value); + setDevelopment(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -100,7 +104,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={meeting} onChange={(e) => { - setMeeting(e.target.value); + setMeeting(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -119,7 +123,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={administration} onChange={(e) => { - setAdministration(e.target.value); + setAdministration(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -138,7 +142,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={ownwork} onChange={(e) => { - setOwnWork(e.target.value); + setOwnWork(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -157,7 +161,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={studies} onChange={(e) => { - setStudies(e.target.value); + setStudies(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -176,7 +180,7 @@ export default function NewTimeReport(): JSX.Element { className="border-2 border-black rounded-md text-center w-1/2" value={testing} onChange={(e) => { - setTesting(e.target.value); + setTesting(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; diff --git a/frontend/src/Components/UserListAdmin.tsx b/frontend/src/Components/UserListAdmin.tsx index 42fb094..e25ad5f 100644 --- a/frontend/src/Components/UserListAdmin.tsx +++ b/frontend/src/Components/UserListAdmin.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import { User } from "../Types/Users"; +import { User } from "../Types/goTypes"; /** * The props for the UserProps component @@ -23,9 +23,9 @@ export function UserListAdmin(props: UserProps): JSX.Element {
    {props.users.map((user) => ( - -
  • - {user.userName} + +
  • + {user.username}
  • ))}