diff --git a/.gitignore b/.gitignore index 281e866..3b1c6d3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ backend/*.svg /go.work.sum /package-lock.json -/backend/docs/swagger.json # Test binary, built with `go test -c` *.test diff --git a/backend/Makefile b/backend/Makefile index 039340c..3443e94 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -104,16 +104,6 @@ default: build docs: swag init -outputTypes go -api: ./docs/swagger.json - npx swagger-typescript-api \ - --api-class-name GenApi \ - --path ./docs/swagger.json \ - --output ../frontend/src/API \ - --name GenApi.ts \ - -./docs/swagger.json: - swag init -outputTypes json - .PHONY: docfmt docfmt: swag fmt diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 0009c17..322c812 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -137,13 +137,13 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successfully promoted user", + "description": "Successfully prometed user", "schema": { "type": "json" } }, "400": { - "description": "Bad request", + "description": "bad request", "schema": { "type": "string" } diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index f4c0f6e..f871755 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -36,13 +36,11 @@ type Database interface { GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) - GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) GetProjectTimes(projectName string) (map[string]int, error) UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error - RemoveProject(projectname string) error } // This struct is a wrapper type that holds the database connection @@ -356,51 +354,6 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } -func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) { - // Define the SQL query to fetch unsigned reports for a given user - query := ` - SELECT - report_id, - user_id, - project_id, - week, - development_time, - meeting_time, - admin_time, - own_work_time, - study_time, - testing_time, - signed_by - FROM - weekly_reports - WHERE - signed_by IS NULL - AND project_id = (SELECT id FROM projects WHERE name = ?) - ` - - // Execute the query - rows, err := d.Queryx(query, projectName) - if err != nil { - return nil, err - } - defer rows.Close() - - // Iterate over the rows and populate the result slice - var reports []types.WeeklyReport - for rows.Next() { - var report types.WeeklyReport - if err := rows.StructScan(&report); err != nil { - return nil, err - } - reports = append(reports, report) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return reports, nil -} - // IsSiteAdmin checks if a given username is a site admin func (d *Db) IsSiteAdmin(username string) (bool, error) { // Define the SQL query to check if the user is a site admin @@ -596,8 +549,3 @@ func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) { return totalTime, nil } - -func (d *Db) RemoveProject(projectname string) error { - _, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) - return err -} diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index fe3e6cd..b68d446 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -470,47 +470,6 @@ func TestGetWeeklyReport(t *testing.T) { // Check other fields similarly } -func TestGetUnsignedWeeklyReports(t *testing.T) { - db, err := setupAdvancedState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddUser("testuser1", "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) - } - - err = db.AddWeeklyReport("testproject", "testuser1", 1, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - reports, err := db.GetUnsignedWeeklyReports("testproject") - if err != nil { - t.Error("GetUnsignedWeeklyReports failed:", err) - } - - if reports == nil { - t.Error("Expected non-nil reports, got nil") - } -} - // TestSignWeeklyReport tests SignWeeklyReport function of the database func TestSignWeeklyReport(t *testing.T) { db, err := setupState() @@ -935,33 +894,3 @@ func TestUpdateWeeklyReport(t *testing.T) { t.Error("UpdateWeeklyReport failed: report not updated correctly") } } - -func TestRemoveProject(t *testing.T) { - db, err := setupAdvancedState() - if err != nil { - t.Error("setupState failed:", err) - } - - // Promote user to Admin - err = db.PromoteToAdmin("demouser") - if err != nil { - t.Error("PromoteToAdmin failed:", err) - } - - // Remove project - err = db.RemoveProject("projecttest") - if err != nil { - t.Error("RemoveProject failed:", err) - } - - // Check if the project was removed - projects, err := db.GetAllProjects() - if err != nil { - t.Error("GetAllProjects failed:", err) - } - if len(projects) != 0 { - t.Error("RemoveProject failed: expected 0, got", len(projects)) - } - -} - \ No newline at end of file diff --git a/backend/internal/database/middleware.go b/backend/internal/database/middleware.go deleted file mode 100644 index 69fa3a2..0000000 --- a/backend/internal/database/middleware.go +++ /dev/null @@ -1,17 +0,0 @@ -package database - -import "github.com/gofiber/fiber/v2" - -// Simple middleware that provides a shared database pool as a local key "db" -func DbMiddleware(db *Database) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - c.Locals("db", db) - return c.Next() - } -} - -// Helper function to get the database from the context, without fiddling with casts -func GetDb(c *fiber.Ctx) Database { - // Dereference a pointer to a local, casted to a pointer to a Database - return *c.Locals("db").(*Database) -} diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index ab74f1a..092fbb0 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -7,8 +7,6 @@ VALUES ("user", "123"); INSERT OR IGNORE INTO users(username, password) VALUES ("user2", "123"); -INSERT OR IGNORE INTO site_admin VALUES (1); - INSERT OR IGNORE INTO projects(name,description,owner_user_id) VALUES ("projecttest","test project", 1); diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go new file mode 100644 index 0000000..49c8c09 --- /dev/null +++ b/backend/internal/handlers/global_state.go @@ -0,0 +1,42 @@ +package handlers + +import ( + "ttime/internal/database" + + "github.com/gofiber/fiber/v2" +) + +// The actual interface that we will use +type GlobalState interface { + Register(c *fiber.Ctx) error // To register a new user + UserDelete(c *fiber.Ctx) error // To delete a user + Login(c *fiber.Ctx) error // To get the token + LoginRenew(c *fiber.Ctx) error // To renew the token + 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 + SignReport(c *fiber.Ctx) error + GetProject(c *fiber.Ctx) error + AddUserToProjectHandler(c *fiber.Ctx) error + PromoteToAdmin(c *fiber.Ctx) error + GetWeeklyReportsUserHandler(c *fiber.Ctx) error + IsProjectManagerHandler(c *fiber.Ctx) error + DeleteProject(c *fiber.Ctx) error // To delete a project // WIP + ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database + ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project + ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project + ChangeUserName(c *fiber.Ctx) error // WIP + GetAllUsersProject(c *fiber.Ctx) error // WIP + UpdateWeeklyReport(c *fiber.Ctx) error +} + +// "Constructor" +func NewGlobalState(db database.Database) GlobalState { + return &GState{Db: db} +} + +// The global state, which implements all the handlers +type GState struct { + Db database.Database +} diff --git a/backend/internal/handlers/global_state_test.go b/backend/internal/handlers/global_state_test.go new file mode 100644 index 0000000..c0b64f7 --- /dev/null +++ b/backend/internal/handlers/global_state_test.go @@ -0,0 +1,15 @@ +package handlers + +import ( + "testing" + "ttime/internal/database" +) + +// The actual interface that we will use +func TestGlobalState(t *testing.T) { + db := database.DbConnect(":memory:") + gs := NewGlobalState(db) + if gs == nil { + t.Error("NewGlobalState returned nil") + } +} diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go new file mode 100644 index 0000000..f64d013 --- /dev/null +++ b/backend/internal/handlers/handlers_project_related.go @@ -0,0 +1,289 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "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") +} + +func (gs *GState) DeleteProject(c *fiber.Ctx) error { + + projectID := c.Params("projectID") + username := c.Params("username") + + if err := gs.Db.DeleteProject(projectID, username); err != nil { + return c.Status(500).SendString((err.Error())) + } + + return c.Status(200).SendString("Project deleted") +} + +// 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 { + + //check token and get username of current user + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Extract the necessary parameters from the request + data := new(types.RoleChange) + if err := c.BodyParser(data); err != nil { + log.Info("error parsing username, project or role") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) + + // 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("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(username, data.Projectname, data.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") + if projectID == "" { + log.Info("No project ID provided") + return c.Status(400).SendString("No project ID provided") + } + log.Info("Getting project with ID: ", projectID) + + // Parse the project ID into an integer + projectIDInt, err := strconv.Atoi(projectID) + if err != nil { + log.Info("Invalid project ID") + 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 { + log.Info("Error getting project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return the project as JSON + log.Info("Returning project: ", project.Name) + 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") + if projectName == "" { + log.Info("No project name provided") + return c.Status(400).SendString("No project name provided") + } + + // Get the user token + userToken := c.Locals("user").(*jwt.Token) + claims := userToken.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Check if the user is a project manager for the specified project + isManager, err := gs.Db.IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking project manager status:", err) + return c.Status(500).SendString(err.Error()) + } + + // If the user is not a project manager, check if the user is a site admin + if !isManager { + isAdmin, err := gs.Db.IsSiteAdmin(username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + if !isAdmin { + log.Info("User is neither a project manager nor a site admin:", username) + return c.Status(403).SendString("User is neither a project manager nor a site admin") + } + } + + // Get all users associated with the project from the database + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users for project:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning users for project: ", projectName) + + // Return the list of users as JSON + return c.JSON(users) +} + +// AddUserToProjectHandler is a handler that adds a user to a project with a specified role +func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error { + // Extract necessary parameters from the request + var requestData struct { + Username string `json:"username"` + ProjectName string `json:"projectName"` + Role string `json:"role"` + } + if err := c.BodyParser(&requestData); err != nil { + log.Info("Error parsing request body:", err) + return c.Status(400).SendString("Bad request") + } + + // Check if the user adding another user to the project is a site admin + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + adminUsername := claims["name"].(string) + log.Info("Admin username from claims:", adminUsername) + + isAdmin, err := gs.Db.IsSiteAdmin(adminUsername) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isAdmin { + log.Info("User is not a site admin:", adminUsername) + return c.Status(403).SendString("User is not a site admin") + } + + // Add the user to the project with the specified role + err = gs.Db.AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) + if err != nil { + log.Info("Error adding user to project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return success message + log.Info("User added to project successfully:", requestData.Username) + return c.SendStatus(fiber.StatusOK) +} + +// 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 + 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) + if err != nil { + log.Info("Error checking project manager status:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return the result as JSON + return c.JSON(fiber.Map{"isProjectManager": isManager}) +} + +func (gs *GState) GetProjectTimesHandler(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) + + // Get project + projectName := c.Params("projectName") + if projectName == "" { + log.Info("No project name provided") + return c.Status(400).SendString("No project name provided") + } + + // Get all users in the project and roles + userProjects, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users in project:", err) + return c.Status(500).SendString(err.Error()) + } + + // If the user is member + isMember := false + for _, userProject := range userProjects { + if userProject.Username == username { + isMember = true + break + } + } + + // If the user is admin + if !isMember { + isAdmin, err := gs.Db.IsSiteAdmin(username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + if !isAdmin { + log.Info("User is neither a project member nor a site admin:", username) + return c.Status(403).SendString("User is neither a project member nor a site admin") + } + } + + // Get project times + projectTimes, err := gs.Db.GetProjectTimes(projectName) + if err != nil { + log.Info("Error getting project times:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return project times as JSON + log.Info("Returning project times for project:", projectName) + return c.JSON(projectTimes) +} \ No newline at end of file diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go new file mode 100644 index 0000000..0e72ead --- /dev/null +++ b/backend/internal/handlers/handlers_report_related.go @@ -0,0 +1,177 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "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 { + log.Info("Error parsing weekly report") + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if report.Week < 1 || report.Week > 52 { + log.Info("Invalid week number") + 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 { + log.Info("Invalid time report") + 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 { + log.Info("Error adding weekly report to db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Weekly report added") + 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) + + log.Info("Getting weekly report for: ", username) + + // Extract project name and week from query parameters + projectName := c.Query("projectName") + week := c.Query("week") + + if projectName == "" || week == "" { + log.Info("Missing project name or week number") + return c.Status(400).SendString("Missing project name or week number") + } + + // Convert week to integer + weekInt, err := strconv.Atoi(week) + if err != nil { + log.Info("Invalid week number") + 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 { + log.Info("Error getting weekly report from db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning weekly report") + // Return the retrieved weekly report + return c.JSON(report) +} + +type ReportId struct { + ReportId int +} + +func (gs *GState) SignReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + projectManagerUsername := claims["name"].(string) + + log.Info("Signing report for: ", projectManagerUsername) + + // Extract report ID from the request query parameters + // reportID := c.Query("reportId") + rid := new(ReportId) + if err := c.BodyParser(rid); err != nil { + return err + } + log.Info("Signing report for: ", rid.ReportId) + + // Get the project manager's ID + projectManagerID, err := gs.Db.GetUserId(projectManagerUsername) + if err != nil { + log.Info("Failed to get project manager ID") + return c.Status(500).SendString("Failed to get project manager ID") + } + log.Info("Project manager ID: ", projectManagerID) + + // Call the database function to sign the weekly report + err = gs.Db.SignWeeklyReport(rid.ReportId, projectManagerID) + if err != nil { + log.Info("Error signing weekly report:", err) + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Weekly report signed successfully") +} + +// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project +func (gs *GState) GetWeeklyReportsUserHandler(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) + + // Extract necessary (path) parameters from the request + projectName := c.Params("projectName") + + // TODO: Here we need to check whether the user is a member of the project + // If not, we should return an error. On the other hand, if the user not a member, + // the returned list of reports will (should) allways be empty. + + // Retrieve weekly reports for the user in the project from the database + reports, err := gs.Db.GetWeeklyReportsUser(username, projectName) + if err != nil { + log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning weekly reports for user:", username, "in project:", projectName) + + // Return the list of reports as JSON + return c.JSON(reports) +} + +func (gs *GState) UpdateWeeklyReport(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) + + // Parse the request body into an UpdateWeeklyReport struct + var updateReport types.UpdateWeeklyReport + if err := c.BodyParser(&updateReport); err != nil { + log.Info("Error parsing weekly report") + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if updateReport.Week < 1 || updateReport.Week > 52 { + log.Info("Invalid week number") + return c.Status(400).SendString("Invalid week number") + } + + if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 { + log.Info("Invalid time report") + return c.Status(400).SendString("Invalid time report") + } + + // Update the weekly report in the database + if err := gs.Db.UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil { + log.Info("Error updating weekly report in db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Weekly report updated") + return c.Status(200).SendString("Weekly report updated") +} diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go new file mode 100644 index 0000000..39788ae --- /dev/null +++ b/backend/internal/handlers/handlers_user_related.go @@ -0,0 +1,269 @@ +package handlers + +import ( + "time" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2/log" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" +) + +// Register is a simple handler that registers a new user +// +// @Summary Register +// @Description Register a new user +// @Tags User +// @Accept json +// @Produce plain +// @Param NewUser body types.NewUser true "User to register" +// @Success 200 {string} string "User added" +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /register [post] +func (gs *GState) Register(c *fiber.Ctx) error { + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + log.Warn("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Adding user:", u.Username) + if err := gs.Db.AddUser(u.Username, u.Password); err != nil { + log.Warn("Error adding user:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("User added:", u.Username) + return c.Status(200).SendString("User added") +} + +// This path should obviously be protected in the future +// UserDelete deletes a user from the database +// +// @Summary UserDelete +// @Description UserDelete deletes a user from the database +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 {string} string "User deleted" +// @Failure 403 {string} string "You can only delete yourself" +// @Failure 500 {string} string "Internal server error" +// @Failure 401 {string} string "Unauthorized" +// @Router /userdelete/{username} [delete] +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 { + log.Info("User tried to delete another user") + return c.Status(403).SendString("You can only delete yourself") + } + + if err := gs.Db.RemoveUser(username); err != nil { + log.Warn("Error deleting user:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("User deleted:", username) + return c.Status(200).SendString("User deleted") +} + +// Login is a simple login handler that returns a JWT token +// +// @Summary login +// @Description logs the user in and returns a jwt token +// @Tags User +// @Accept json +// @Param NewUser body types.NewUser true "login info" +// @Produce plain +// @Success 200 Token types.Token "Successfully signed token for user" +// @Failure 400 {string} string "Bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /login [post] +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 { + log.Warn("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Username logging in:", u.Username) + if !gs.Db.CheckUser(u.Username, u.Password) { + log.Info("User not found") + return c.SendStatus(fiber.StatusUnauthorized) + } + + isAdmin, err := gs.Db.IsSiteAdmin(u.Username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + // Create the Claims + claims := jwt.MapClaims{ + "name": u.Username, + "admin": isAdmin, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + log.Info("Token created for user:", u.Username) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + log.Warn("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + println("Successfully signed token for user:", u.Username) + return c.JSON(types.Token{Token: t}) +} + +// LoginRenew is a simple handler that renews the token +// +// @Summary LoginRenews +// @Description renews the users token +// @Security bererToken +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 Token types.Token "Successfully signed token for user" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /loginerenew [post] +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"]) + + 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 { + log.Warn("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"]) + return c.JSON(types.Token{Token: t}) +} + +// ListAllUsers is a handler that returns a list of all users in the application database +// +// @Summary ListsAllUsers +// @Description lists all users +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 {json} json "Successfully signed token for user" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /users/all [get] +func (gs *GState) ListAllUsers(c *fiber.Ctx) error { + // Get all users from the database + users, err := gs.Db.GetAllUsersApplication() + if err != nil { + log.Info("Error getting users from db:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning all users") + // Return the list of users as JSON + return c.JSON(users) +} + +func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error { + // Get all users from a project + projectName := c.Params("projectName") + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users from project:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning all users") + // Return the list of users as JSON + return c.JSON(users) +} + +// @Summary PromoteToAdmin +// @Description promote chosen user to admin +// @Tags User +// @Accept json +// @Produce plain +// @Param NewUser body types.NewUser true "user info" +// @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] +func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error { + // Extract the username from the request body + var newUser types.NewUser + if err := c.BodyParser(&newUser); err != nil { + return c.Status(400).SendString("Bad request") + } + username := newUser.Username + + log.Info("Promoting user to admin:", username) // Debug print + + // Promote the user to a site admin in the database + if err := gs.Db.PromoteToAdmin(username); err != nil { + log.Info("Error promoting user to admin:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("User promoted to admin successfully:", username) // Debug print + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} + +// ChangeUserName changes a user's username in the database +func (gs *GState) ChangeUserName(c *fiber.Ctx) error { + // Check token and get username of current user + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + adminUsername := claims["name"].(string) + log.Info(adminUsername) + + // Extract the necessary parameters from the request + data := new(types.StrNameChange) + if err := c.BodyParser(data); err != nil { + log.Info("Error parsing username") + return c.Status(400).SendString(err.Error()) + } + + // Check if the current user is an admin + isAdmin, err := gs.Db.IsSiteAdmin(adminUsername) + if err != nil { + log.Warn("Error checking if admin:", err) + return c.Status(500).SendString(err.Error()) + } else if !isAdmin { + log.Warn("Tried changing name when not admin") + return c.Status(401).SendString("You cannot change name unless you are an admin") + } + + // Change the user's name in the database + if err := gs.Db.ChangeUserName(data.PrevName, data.NewName); err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/internal/handlers/projects/AddUserToProject.go b/backend/internal/handlers/projects/AddUserToProject.go deleted file mode 100644 index 702b7dd..0000000 --- a/backend/internal/handlers/projects/AddUserToProject.go +++ /dev/null @@ -1,51 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// AddUserToProjectHandler is a handler that adds a user to a project with a specified role -func AddUserToProjectHandler(c *fiber.Ctx) error { - // Extract necessary parameters from the request - var requestData struct { - Username string `json:"username"` - ProjectName string `json:"projectName"` - Role string `json:"role"` - } - if err := c.BodyParser(&requestData); err != nil { - log.Info("Error parsing request body:", err) - return c.Status(400).SendString("Bad request") - } - - // Check if the user adding another user to the project is a site admin - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - adminUsername := claims["name"].(string) - log.Info("Admin username from claims:", adminUsername) - - isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isAdmin { - log.Info("User is not a site admin:", adminUsername) - return c.Status(403).SendString("User is not a site admin") - } - - // Add the user to the project with the specified role - err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) - if err != nil { - log.Info("Error adding user to project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return success message - log.Info("User added to project successfully:", requestData.Username) - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/projects/CreateProject.go b/backend/internal/handlers/projects/CreateProject.go deleted file mode 100644 index cef2f2b..0000000 --- a/backend/internal/handlers/projects/CreateProject.go +++ /dev/null @@ -1,30 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - "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 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 := db.GetDb(c).AddProject(p.Name, p.Description, owner); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("Project added") -} diff --git a/backend/internal/handlers/projects/DeleteProject.go b/backend/internal/handlers/projects/DeleteProject.go deleted file mode 100644 index 415424a..0000000 --- a/backend/internal/handlers/projects/DeleteProject.go +++ /dev/null @@ -1,19 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" -) - -func DeleteProject(c *fiber.Ctx) error { - - projectID := c.Params("projectID") - username := c.Params("username") - - if err := db.GetDb(c).DeleteProject(projectID, username); err != nil { - return c.Status(500).SendString((err.Error())) - } - - return c.Status(200).SendString("Project deleted") -} diff --git a/backend/internal/handlers/projects/GetProject.go b/backend/internal/handlers/projects/GetProject.go deleted file mode 100644 index 03333ce..0000000 --- a/backend/internal/handlers/projects/GetProject.go +++ /dev/null @@ -1,38 +0,0 @@ -package projects - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// GetProject retrieves a specific project by its ID -func GetProject(c *fiber.Ctx) error { - // Extract the project ID from the request parameters or body - projectID := c.Params("projectID") - if projectID == "" { - log.Info("No project ID provided") - return c.Status(400).SendString("No project ID provided") - } - log.Info("Getting project with ID: ", projectID) - - // Parse the project ID into an integer - projectIDInt, err := strconv.Atoi(projectID) - if err != nil { - log.Info("Invalid project ID") - return c.Status(400).SendString("Invalid project ID") - } - - // Get the project from the database by its ID - project, err := db.GetDb(c).GetProject(projectIDInt) - if err != nil { - log.Info("Error getting project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return the project as JSON - log.Info("Returning project: ", project.Name) - return c.JSON(project) -} diff --git a/backend/internal/handlers/projects/GetProjectTimes.go b/backend/internal/handlers/projects/GetProjectTimes.go deleted file mode 100644 index 573a95e..0000000 --- a/backend/internal/handlers/projects/GetProjectTimes.go +++ /dev/null @@ -1,63 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func GetProjectTimesHandler(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) - - // Get project - projectName := c.Params("projectName") - if projectName == "" { - log.Info("No project name provided") - return c.Status(400).SendString("No project name provided") - } - - // Get all users in the project and roles - userProjects, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users in project:", err) - return c.Status(500).SendString(err.Error()) - } - - // If the user is member - isMember := false - for _, userProject := range userProjects { - if userProject.Username == username { - isMember = true - break - } - } - - // If the user is admin - if !isMember { - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - if !isAdmin { - log.Info("User is neither a project member nor a site admin:", username) - return c.Status(403).SendString("User is neither a project member nor a site admin") - } - } - - // Get project times - projectTimes, err := db.GetDb(c).GetProjectTimes(projectName) - if err != nil { - log.Info("Error getting project times:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return project times as JSON - log.Info("Returning project times for project:", projectName) - return c.JSON(projectTimes) -} diff --git a/backend/internal/handlers/projects/GetUserProject.go b/backend/internal/handlers/projects/GetUserProject.go deleted file mode 100644 index 99ed63b..0000000 --- a/backend/internal/handlers/projects/GetUserProject.go +++ /dev/null @@ -1,25 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" -) - -// GetUserProjects returns all projects that the user is a member of -func 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 := db.GetDb(c).GetProjectsForUser(username) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a json serialized list of projects - return c.JSON(projects) -} diff --git a/backend/internal/handlers/projects/IsProjectManager.go b/backend/internal/handlers/projects/IsProjectManager.go deleted file mode 100644 index 678fad5..0000000 --- a/backend/internal/handlers/projects/IsProjectManager.go +++ /dev/null @@ -1,32 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project -func 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 - 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 := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking project manager status:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return the result as JSON - return c.JSON(fiber.Map{"isProjectManager": isManager}) -} diff --git a/backend/internal/handlers/projects/ListAllUserProjects.go b/backend/internal/handlers/projects/ListAllUserProjects.go deleted file mode 100644 index e0bcaf5..0000000 --- a/backend/internal/handlers/projects/ListAllUserProjects.go +++ /dev/null @@ -1,55 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func ListAllUsersProject(c *fiber.Ctx) error { - // Extract the project name from the request parameters or body - projectName := c.Params("projectName") - if projectName == "" { - log.Info("No project name provided") - return c.Status(400).SendString("No project name provided") - } - - // Get the user token - userToken := c.Locals("user").(*jwt.Token) - claims := userToken.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Check if the user is a project manager for the specified project - isManager, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking project manager status:", err) - return c.Status(500).SendString(err.Error()) - } - - // If the user is not a project manager, check if the user is a site admin - if !isManager { - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - if !isAdmin { - log.Info("User is neither a project manager nor a site admin:", username) - return c.Status(403).SendString("User is neither a project manager nor a site admin") - } - } - - // Get all users associated with the project from the database - users, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users for project:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning users for project: ", projectName) - - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/projects/ProjectRoleChange.go b/backend/internal/handlers/projects/ProjectRoleChange.go deleted file mode 100644 index 266127d..0000000 --- a/backend/internal/handlers/projects/ProjectRoleChange.go +++ /dev/null @@ -1,45 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ProjectRoleChange is a handler that changes a user's role within a project -func ProjectRoleChange(c *fiber.Ctx) error { - - //check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract the necessary parameters from the request - data := new(types.RoleChange) - if err := c.BodyParser(data); err != nil { - log.Info("error parsing username, project or role") - return c.Status(400).SendString(err.Error()) - } - - log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) - - // Dubble diping and checcking if current user is - if ismanager, err := db.GetDb(c).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("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 := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/projects/RemoveProject.go b/backend/internal/handlers/projects/RemoveProject.go deleted file mode 100644 index 7b140dd..0000000 --- a/backend/internal/handlers/projects/RemoveProject.go +++ /dev/null @@ -1,35 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func RemoveProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Check if the user is a site admin - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isAdmin { - log.Info("User is not a site admin:", username) - return c.Status(403).SendString("User is not a site admin") - } - - projectName := c.Params("projectName") - - if err := db.GetDb(c).RemoveProject(projectName); err != nil { - return c.Status(500).SendString((err.Error())) - } - - return c.Status(200).SendString("Project deleted") -} diff --git a/backend/internal/handlers/reports/GetUnsignedReports.go b/backend/internal/handlers/reports/GetUnsignedReports.go deleted file mode 100644 index 9525f55..0000000 --- a/backend/internal/handlers/reports/GetUnsignedReports.go +++ /dev/null @@ -1,45 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func GetUnsignedReports(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - - // Extract project name and week from query parameters - projectName := c.Params("projectName") - - log.Info("Getting unsigned reports for") - - if projectName == "" { - log.Info("Missing project name") - return c.Status(400).SendString("Missing project name") - } - - // Get the project manager's ID - isProjectManager, err := db.GetDb(c).IsProjectManager(projectManagerUsername, projectName) - if err != nil { - log.Info("Failed to get project manager ID") - return c.Status(500).SendString("Failed to get project manager ID") - } - log.Info("User is Project Manager: ", isProjectManager) - - // Call the database function to get the unsigned weekly reports - reports, err := db.GetDb(c).GetUnsignedWeeklyReports(projectName) - if err != nil { - log.Info("Error getting unsigned weekly reports:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning unsigned reports") - // Return the list of unsigned reports - return c.JSON(reports) -} diff --git a/backend/internal/handlers/reports/GetWeeklyReport.go b/backend/internal/handlers/reports/GetWeeklyReport.go deleted file mode 100644 index 422bc0b..0000000 --- a/backend/internal/handlers/reports/GetWeeklyReport.go +++ /dev/null @@ -1,47 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// Handler for retrieving weekly report -func 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) - - log.Info("Getting weekly report for: ", username) - - // Extract project name and week from query parameters - projectName := c.Query("projectName") - week := c.Query("week") - - if projectName == "" || week == "" { - log.Info("Missing project name or week number") - return c.Status(400).SendString("Missing project name or week number") - } - - // Convert week to integer - weekInt, err := strconv.Atoi(week) - if err != nil { - log.Info("Invalid week number") - return c.Status(400).SendString("Invalid week number") - } - - // Call the database function to get the weekly report - report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt) - if err != nil { - log.Info("Error getting weekly report from db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning weekly report") - // Return the retrieved weekly report - return c.JSON(report) -} diff --git a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go b/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go deleted file mode 100644 index da8a90b..0000000 --- a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go +++ /dev/null @@ -1,36 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project -func GetWeeklyReportsUserHandler(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) - - // Extract necessary (path) parameters from the request - projectName := c.Params("projectName") - - // TODO: Here we need to check whether the user is a member of the project - // If not, we should return an error. On the other hand, if the user not a member, - // the returned list of reports will (should) allways be empty. - - // Retrieve weekly reports for the user in the project from the database - reports, err := db.GetDb(c).GetWeeklyReportsUser(username, projectName) - if err != nil { - log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning weekly reports for user:", username, "in project:", projectName) - - // Return the list of reports as JSON - return c.JSON(reports) -} diff --git a/backend/internal/handlers/reports/SignReport.go b/backend/internal/handlers/reports/SignReport.go deleted file mode 100644 index a486ecc..0000000 --- a/backend/internal/handlers/reports/SignReport.go +++ /dev/null @@ -1,41 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func SignReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - - // Extract report ID from the path - reportId, err := strconv.Atoi(c.Params("reportId")) - if err != nil { - log.Info("Invalid report ID") - return c.Status(400).SendString("Invalid report ID") - } - - // Get the project manager's ID - projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername) - if err != nil { - log.Info("Failed to get project manager ID for user: ", projectManagerUsername) - return c.Status(500).SendString("Failed to get project manager ID") - } - - // Call the database function to sign the weekly report - err = db.GetDb(c).SignWeeklyReport(reportId, projectManagerID) - if err != nil { - log.Info("Error signing weekly report:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Project manager ID: ", projectManagerID, " signed report ID: ", reportId) - return c.Status(200).SendString("Weekly report signed successfully") -} diff --git a/backend/internal/handlers/reports/SubmitWeeklyReport.go b/backend/internal/handlers/reports/SubmitWeeklyReport.go deleted file mode 100644 index 900aa03..0000000 --- a/backend/internal/handlers/reports/SubmitWeeklyReport.go +++ /dev/null @@ -1,41 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func 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 { - log.Info("Error parsing weekly report") - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if report.Week < 1 || report.Week > 52 { - log.Info("Invalid week number") - 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 { - log.Info("Invalid time report") - return c.Status(400).SendString("Invalid time report") - } - - if err := db.GetDb(c).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 to db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Weekly report added") - return c.Status(200).SendString("Time report added") -} diff --git a/backend/internal/handlers/reports/UpdateWeeklyReport.go b/backend/internal/handlers/reports/UpdateWeeklyReport.go deleted file mode 100644 index 3ab835d..0000000 --- a/backend/internal/handlers/reports/UpdateWeeklyReport.go +++ /dev/null @@ -1,44 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func UpdateWeeklyReport(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) - - // Parse the request body into an UpdateWeeklyReport struct - var updateReport types.UpdateWeeklyReport - if err := c.BodyParser(&updateReport); err != nil { - log.Info("Error parsing weekly report") - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if updateReport.Week < 1 || updateReport.Week > 52 { - log.Info("Invalid week number") - return c.Status(400).SendString("Invalid week number") - } - - if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 { - log.Info("Invalid time report") - return c.Status(400).SendString("Invalid time report") - } - - // Update the weekly report in the database - if err := db.GetDb(c).UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil { - log.Info("Error updating weekly report in db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Weekly report updated") - return c.Status(200).SendString("Weekly report updated") -} diff --git a/backend/internal/handlers/users/ChangeUserName.go b/backend/internal/handlers/users/ChangeUserName.go deleted file mode 100644 index 75032e4..0000000 --- a/backend/internal/handlers/users/ChangeUserName.go +++ /dev/null @@ -1,44 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ChangeUserName changes a user's username in the database -func ChangeUserName(c *fiber.Ctx) error { - // Check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - adminUsername := claims["name"].(string) - log.Info(adminUsername) - - // Extract the necessary parameters from the request - data := new(types.StrNameChange) - if err := c.BodyParser(data); err != nil { - log.Info("Error parsing username") - return c.Status(400).SendString(err.Error()) - } - - // Check if the current user is an admin - isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) - if err != nil { - log.Warn("Error checking if admin:", err) - return c.Status(500).SendString(err.Error()) - } else if !isAdmin { - log.Warn("Tried changing name when not admin") - return c.Status(401).SendString("You cannot change name unless you are an admin") - } - - // Change the user's name in the database - if err := db.GetDb(c).ChangeUserName(data.PrevName, data.NewName); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/users/GetUsersProjects.go b/backend/internal/handlers/users/GetUsersProjects.go deleted file mode 100644 index 10a6ec6..0000000 --- a/backend/internal/handlers/users/GetUsersProjects.go +++ /dev/null @@ -1,22 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -func GetAllUsersProject(c *fiber.Ctx) error { - // Get all users from a project - projectName := c.Params("projectName") - users, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users from project:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning all users") - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/users/ListAllUsers.go b/backend/internal/handlers/users/ListAllUsers.go deleted file mode 100644 index 1cae76c..0000000 --- a/backend/internal/handlers/users/ListAllUsers.go +++ /dev/null @@ -1,31 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// ListAllUsers is a handler that returns a list of all users in the application database -// @Summary ListsAllUsers -// @Description lists all users -// @Tags User -// @Accept json -// @Produce plain -// @Success 200 {json} json "Successfully signed token for user" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /users/all [get] -func ListAllUsers(c *fiber.Ctx) error { - // Get all users from the database - users, err := db.GetDb(c).GetAllUsersApplication() - if err != nil { - log.Info("Error getting users from db:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning all users") - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/users/Login.go b/backend/internal/handlers/users/Login.go deleted file mode 100644 index c4d6c60..0000000 --- a/backend/internal/handlers/users/Login.go +++ /dev/null @@ -1,65 +0,0 @@ -package users - -import ( - "time" - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// Login is a simple login handler that returns a JWT token -// @Summary login -// @Description logs the user in and returns a jwt token -// @Tags User -// @Accept json -// @Param NewUser body types.NewUser true "login info" -// @Produce plain -// @Success 200 Token types.Token "Successfully signed token for user" -// @Failure 400 {string} string "Bad request" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /login [post] -func Login(c *fiber.Ctx) error { - // The body type is identical to a NewUser - - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - log.Warn("Error parsing body") - return c.Status(400).SendString(err.Error()) - } - - log.Info("Username logging in:", u.Username) - if !db.GetDb(c).CheckUser(u.Username, u.Password) { - log.Info("User not found") - return c.SendStatus(fiber.StatusUnauthorized) - } - - isAdmin, err := db.GetDb(c).IsSiteAdmin(u.Username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - // Create the Claims - claims := jwt.MapClaims{ - "name": u.Username, - "admin": isAdmin, - "exp": time.Now().Add(time.Hour * 72).Unix(), - } - - // Create token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - log.Info("Token created for user:", u.Username) - - // Generate encoded token and send it as response. - t, err := token.SignedString([]byte("secret")) - if err != nil { - log.Warn("Error signing token") - return c.SendStatus(fiber.StatusInternalServerError) - } - - println("Successfully signed token for user:", u.Username) - return c.JSON(types.Token{Token: t}) -} diff --git a/backend/internal/handlers/users/LoginRenew.go b/backend/internal/handlers/users/LoginRenew.go deleted file mode 100644 index 78eadfd..0000000 --- a/backend/internal/handlers/users/LoginRenew.go +++ /dev/null @@ -1,44 +0,0 @@ -package users - -import ( - "time" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// LoginRenew is a simple handler that renews the token -// @Summary LoginRenews -// @Description renews the users token -// @Security bererToken -// @Tags User -// @Accept json -// @Produce plain -// @Success 200 Token types.Token "Successfully signed token for user" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /loginerenew [post] -func LoginRenew(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - - log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"]) - - 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 { - log.Warn("Error signing token") - return c.SendStatus(fiber.StatusInternalServerError) - } - - log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"]) - return c.JSON(types.Token{Token: t}) -} diff --git a/backend/internal/handlers/users/PromoteToAdmin.go b/backend/internal/handlers/users/PromoteToAdmin.go deleted file mode 100644 index 4a21758..0000000 --- a/backend/internal/handlers/users/PromoteToAdmin.go +++ /dev/null @@ -1,42 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// @Summary PromoteToAdmin -// @Description promote chosen user to admin -// @Tags User -// @Accept json -// @Produce plain -// @Param NewUser body types.NewUser true "user info" -// @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] -func PromoteToAdmin(c *fiber.Ctx) error { - // Extract the username from the request body - var newUser types.NewUser - if err := c.BodyParser(&newUser); err != nil { - return c.Status(400).SendString("Bad request") - } - username := newUser.Username - - log.Info("Promoting user to admin:", username) // Debug print - - // Promote the user to a site admin in the database - if err := db.GetDb(c).PromoteToAdmin(username); err != nil { - log.Info("Error promoting user to admin:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("User promoted to admin successfully:", username) // Debug print - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/users/Register.go b/backend/internal/handlers/users/Register.go deleted file mode 100644 index 9977246..0000000 --- a/backend/internal/handlers/users/Register.go +++ /dev/null @@ -1,38 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// Register is a simple handler that registers a new user -// -// @Summary Register -// @Description Register a new user -// @Tags User -// @Accept json -// @Produce plain -// @Param NewUser body types.NewUser true "User to register" -// @Success 200 {string} string "User added" -// @Failure 400 {string} string "Bad request" -// @Failure 500 {string} string "Internal server error" -// @Router /register [post] -func Register(c *fiber.Ctx) error { - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - log.Warn("Error parsing body") - return c.Status(400).SendString(err.Error()) - } - - log.Info("Adding user:", u.Username) - if err := db.GetDb(c).AddUser(u.Username, u.Password); err != nil { - log.Warn("Error adding user:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("User added:", u.Username) - return c.Status(200).SendString("User added") -} diff --git a/backend/internal/handlers/users/UserDelete.go b/backend/internal/handlers/users/UserDelete.go deleted file mode 100644 index 5957c2d..0000000 --- a/backend/internal/handlers/users/UserDelete.go +++ /dev/null @@ -1,43 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// This path should obviously be protected in the future -// UserDelete deletes a user from the database -// -// @Summary UserDelete -// @Description UserDelete deletes a user from the database -// @Tags User -// @Accept json -// @Produce plain -// @Success 200 {string} string "User deleted" -// @Failure 403 {string} string "You can only delete yourself" -// @Failure 500 {string} string "Internal server error" -// @Failure 401 {string} string "Unauthorized" -// @Router /userdelete/{username} [delete] -func 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 { - log.Info("User tried to delete itself") - return c.Status(403).SendString("You can't delete yourself") - } - - if err := db.GetDb(c).RemoveUser(username); err != nil { - log.Warn("Error deleting user:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("User deleted:", username) - return c.Status(200).SendString("User deleted") -} diff --git a/backend/main.go b/backend/main.go index 4c2056e..1967708 100644 --- a/backend/main.go +++ b/backend/main.go @@ -6,9 +6,7 @@ import ( _ "ttime/docs" "ttime/internal/config" "ttime/internal/database" - "ttime/internal/handlers/projects" - "ttime/internal/handlers/reports" - "ttime/internal/handlers/users" + "ttime/internal/handlers" "github.com/BurntSushi/toml" "github.com/gofiber/fiber/v2" @@ -56,28 +54,24 @@ func main() { // Connect to the database db := database.DbConnect(conf.DbPath) - // Migrate the database if err = db.Migrate(); err != nil { fmt.Println("Error migrating database: ", err) os.Exit(1) } - // Migrate sample data, should not be used in production if err = db.MigrateSampleData(); err != nil { fmt.Println("Error migrating sample data: ", err) os.Exit(1) } + // Get our global state + gs := handlers.NewGlobalState(db) // Create the server server := fiber.New() - // We want some logs server.Use(logger.New()) - // Sets up db middleware, accessed as Local "db" key - server.Use(database.DbMiddleware(&db)) - // Mounts the swagger documentation, this is available at /swagger/index.html server.Get("/swagger/*", swagger.HandlerDefault) @@ -85,50 +79,35 @@ func main() { // This will likely be replaced by an embedded filesystem in the future server.Static("/", "./static") - // Create a group for our API - api := server.Group("/api") - // Register our unprotected routes - api.Post("/register", users.Register) - api.Post("/login", users.Login) + server.Post("/api/register", gs.Register) + server.Post("/api/login", gs.Login) - // Every route from here on will require a valid - // JWT bearer token authentication in the header + // Every route from here on will require a valid JWT server.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{Key: []byte("secret")}, })) - // All user related routes - // userGroup := api.Group("/user") // Not currently in use - api.Get("/users/all", users.ListAllUsers) - api.Get("/project/getAllUsers", users.GetAllUsersProject) - api.Post("/login", users.Login) - api.Post("/register", users.Register) - api.Post("/loginrenew", users.LoginRenew) - api.Post("/promoteToAdmin", users.PromoteToAdmin) - api.Put("/changeUserName", users.ChangeUserName) - api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches - - // All project related routes - // projectGroup := api.Group("/project") // Not currently in use - api.Get("/getUserProjects", projects.GetUserProjects) - api.Get("/project/:projectId", projects.GetProject) - api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler) - api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) - api.Post("/project", projects.CreateProject) - api.Post("/ProjectRoleChange", projects.ProjectRoleChange) - api.Delete("/removeProject/:projectName", projects.RemoveProject) - api.Delete("/project/:projectID", projects.DeleteProject) - - // All report related routes - // reportGroup := api.Group("/report") // Not currently in use - api.Get("/getWeeklyReport", reports.GetWeeklyReport) - api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) - api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler) - api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) - api.Put("/signReport/:reportId", reports.SignReport) - api.Put("/addUserToProject", projects.AddUserToProjectHandler) - api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) + // Protected routes (require a valid JWT bearer token authentication header) + server.Post("/api/submitWeeklyReport", gs.SubmitWeeklyReport) + server.Get("/api/getUserProjects", gs.GetUserProjects) + server.Post("/api/loginrenew", gs.LoginRenew) + server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches + server.Delete("api/project/:projectID", gs.DeleteProject) // WIP + server.Post("/api/project", gs.CreateProject) // WIP + server.Get("/api/project/:projectId", gs.GetProject) + server.Get("/api/project/getAllUsers", gs.GetAllUsersProject) + server.Get("/api/getWeeklyReport", gs.GetWeeklyReport) + server.Post("/api/signReport", gs.SignReport) + server.Put("/api/addUserToProject", gs.AddUserToProjectHandler) + server.Put("/api/changeUserName", gs.ChangeUserName) + server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) + server.Get("/api/users/all", gs.ListAllUsers) + server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler) + server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler) + server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) + server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) + server.Put("/api/updateWeeklyReport", gs.UpdateWeeklyReport) // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 886c957..c6cef66 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -6,8 +6,6 @@ import { NewProject, UserProjectMember, WeeklyReport, - StrNameChange, - NewProjMember, } from "../Types/goTypes"; /** @@ -49,6 +47,7 @@ interface API { * @returns {Promise>} A promise containing the API response indicating if the user is a project manager. */ checkIfProjectManager( + username: string, projectName: string, token: string, ): Promise>; @@ -134,37 +133,6 @@ interface API { projectName: string, token: string, ): Promise>; - /** - * Changes the username of a user in the database. - * @param {StrNameChange} data The object containing the previous and new username. - * @param {string} token The authentication token. - * @returns {Promise>} A promise resolving to an API response. - */ - changeUserName( - data: StrNameChange, - token: string, - ): Promise>; - addUserToProject( - user: NewProjMember, - token: string, - ): Promise>; - - removeProject( - projectName: string, - token: string, - ): Promise>; - - /** - * Signs a report. Keep in mind that the user which the token belongs to must be - * the project manager of the project the report belongs to. - * - * @param {number} reportId The id of the report to sign - * @param {string} token The authentication token - */ - signReport( - reportId: number, - token: string, - ): Promise>; } /** An instance of the API */ @@ -202,17 +170,19 @@ export const api: API = { ): Promise> { try { const response = await fetch(`/api/userdelete/${username}`, { - method: "DELETE", + method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, body: JSON.stringify(username), }); + if (!response.ok) { - return { success: false, message: "Could not remove user" }; + return { success: false, message: "Failed to remove user" }; } else { - return { success: true }; + const data = (await response.json()) as User; + return { success: true, data }; } } catch (e) { return { success: false, message: "Failed to remove user" }; @@ -220,20 +190,19 @@ export const api: API = { }, async checkIfProjectManager( + username: string, projectName: string, token: string, ): Promise> { try { - const response = await fetch( - `/api/checkIfProjectManager/${projectName}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, + const response = await fetch("/api/checkIfProjectManager", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, }, - ); + body: JSON.stringify({ username, projectName }), + }); if (!response.ok) { return { @@ -245,7 +214,7 @@ export const api: API = { return { success: true, data }; } } catch (e) { - return { success: false, message: "Failed to check if project manager" }; + return { success: false, message: "fuck" }; } }, @@ -274,30 +243,6 @@ export const api: API = { } }, - async addUserToProject( - user: NewProjMember, - token: string, - ): Promise> { - try { - const response = await fetch("/api/addUserToProject", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(user), - }); - - if (!response.ok) { - return { success: false, message: "Failed to add member" }; - } else { - return { success: true, message: "Added member" }; - } - } catch (e) { - return { success: false, message: "Failed to add member" }; - } - }, - async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -539,81 +484,4 @@ export const api: API = { }); } }, - - async changeUserName( - data: StrNameChange, - token: string, - ): Promise> { - try { - const response = await fetch("/api/changeUserName", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - return { success: false, message: "Failed to change username" }; - } else { - return { success: true }; - } - } catch (e) { - return { success: false, message: "Failed to change username" }; - } - }, - - async removeProject( - projectName: string, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/projectdelete/${projectName}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return Promise.resolve({ - success: false, - message: "Failed to remove project", - }); - } else { - const data = await response.text(); - return Promise.resolve({ success: true, message: data }); - } - } catch (e) { - return Promise.resolve({ - success: false, - message: "Failed to remove project", - }); - } - }, - - async signReport( - reportId: number, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/signReport/${reportId}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to sign report" }; - } else { - return { success: true, message: "Report signed" }; - } - } catch (e) { - return { success: false, message: "Failed to sign report" }; - } - } }; diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx deleted file mode 100644 index d29be68..0000000 --- a/frontend/src/Components/AddMember.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { APIResponse, api } from "../API/API"; -import { NewProjMember } from "../Types/goTypes"; - -/** - * Tries to add a member to a project - * @param {Object} props - A NewProjMember - * @returns {boolean} True if added, false if not - */ -function AddMember(props: { memberToAdd: NewProjMember }): boolean { - let added = false; - if ( - props.memberToAdd.username === "" || - props.memberToAdd.role === "" || - props.memberToAdd.projectname === "" - ) { - alert("All fields must be filled before adding"); - return added; - } - api - .addUserToProject( - props.memberToAdd, - localStorage.getItem("accessToken") ?? "", - ) - .then((response: APIResponse) => { - if (response.success) { - alert("Member added"); - added = true; - } else { - alert("Member not added"); - console.error(response.message); - } - }) - .catch((error) => { - console.error("An error occurred during member add:", error); - }); - return added; -} - -export default AddMember; diff --git a/frontend/src/Components/AddUserToProject.tsx b/frontend/src/Components/AddUserToProject.tsx deleted file mode 100644 index 9f4439b..0000000 --- a/frontend/src/Components/AddUserToProject.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useState } from "react"; -import { NewProjMember } from "../Types/goTypes"; -import Button from "./Button"; -import GetAllUsers from "./GetAllUsers"; -import AddMember from "./AddMember"; -import BackButton from "./BackButton"; - -/** - * Provides UI for adding a member to a project. - * @returns {JSX.Element} - Returns the component UI for adding a member - */ -function AddUserToProject(): JSX.Element { - const [name, setName] = useState(""); - const [users, setUsers] = useState([]); - const [role, setRole] = useState(""); - GetAllUsers({ setUsersProp: setUsers }); - - const handleClick = (): boolean => { - const newMember: NewProjMember = { - username: name, - projectname: localStorage.getItem("projectName") ?? "", - role: role, - }; - return AddMember({ memberToAdd: newMember }); - }; - - return ( -
-

- User chosen: [{name}] -

-

- Role chosen: [{role}] -

-

- Project chosen: [{localStorage.getItem("projectName") ?? ""}] -

-

Choose role:

-
-
    -
  • { - setRole("member"); - }} - > - {"Member"} -
  • -
  • { - setRole("project_manager"); - }} - > - {"Project manager"} -
  • -
-
-

Choose user:

-
-
    -
    - {users.map((user) => ( -
  • { - setName(user); - }} - > - {user} -
  • - ))} -
-
-
-
-

-
- ); -} - -export default AddUserToProject; diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx deleted file mode 100644 index 09ca6dc..0000000 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ /dev/null @@ -1,103 +0,0 @@ -//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 { NewWeeklyReport } from "../Types/goTypes"; -import { Link, useParams } from "react-router-dom"; - -/** - * Renders a component that displays all the time reports for a specific project. - * @returns {JSX.Element} representing the component. - */ -function AllTimeReportsInProject(): JSX.Element { - const { username } = useParams(); - const { projectName } = useParams(); - const [weeklyReports, setWeeklyReports] = useState([]); - - /* // 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); - } - }; */ - // Mock data - const getWeeklyReports = async (): Promise => { - // Simulate a delay - await Promise.resolve(); - const mockWeeklyReports: NewWeeklyReport[] = [ - { - projectName: "Project 1", - week: 1, - developmentTime: 10, - meetingTime: 2, - adminTime: 1, - ownWorkTime: 3, - studyTime: 4, - testingTime: 5, - }, - { - projectName: "Project 1", - week: 2, - developmentTime: 8, - meetingTime: 2, - adminTime: 1, - ownWorkTime: 3, - studyTime: 4, - testingTime: 5, - }, - // Add more reports as needed - ]; - - // Use the mock data instead of the real data - setWeeklyReports(mockWeeklyReports); - }; - useEffect(() => { - void getWeeklyReports(); - }, []); - - return ( - <> -

{username}'s Time Reports

-
- {weeklyReports.map((newWeeklyReport, index) => ( - -
-

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

-

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

-

- {"Signed: "} - NO -

-
- - ))} -
- - ); -} - -export default AllTimeReportsInProject; diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index e297a04..3c35e94 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -1,48 +1,23 @@ import React, { useState } from "react"; import InputField from "./InputField"; -import { api } from "../API/API"; function ChangeUsername(): JSX.Element { const [newUsername, setNewUsername] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); const handleChange = (e: React.ChangeEvent): void => { setNewUsername(e.target.value); }; - const handleSubmit = async (): Promise => { - try { - // Call the API function to change the username - const token = localStorage.getItem("accessToken"); - if (!token) { - throw new Error("Access token not found"); - } - - const response = await api.changeUserName( - { prevName: "currentName", newName: newUsername }, - token, - ); - - if (response.success) { - // Optionally, add a success message or redirect the user - console.log("Username changed successfully"); - } else { - // Handle the error message - console.error("Failed to change username:", response.message); - setErrorMessage(response.message ?? "Failed to change username"); - } - } catch (error) { - console.error("Error changing username:", error); - // Optionally, handle the error - setErrorMessage("Failed to change username"); - } - }; - - const handleButtonClick = (): void => { - handleSubmit().catch((error) => { - console.error("Error in handleSubmit:", error); - }); - }; + // const handleSubmit = async (): Promise => { + // try { + // // Call the API function to update the username + // await api.updateUsername(newUsername); + // // Optionally, add a success message or redirect the user + // } catch (error) { + // console.error("Error updating username:", error); + // // Optionally, handle the error + // } + // }; return (
@@ -52,8 +27,6 @@ function ChangeUsername(): JSX.Element { value={newUsername} onChange={handleChange} /> - {errorMessage &&
{errorMessage}
} -
); } diff --git a/frontend/src/Components/DeleteUser.tsx b/frontend/src/Components/DeleteUser.tsx index d1dbc7f..db49724 100644 --- a/frontend/src/Components/DeleteUser.tsx +++ b/frontend/src/Components/DeleteUser.tsx @@ -11,6 +11,7 @@ import { api, APIResponse } from "../API/API"; */ function DeleteUser(props: { usernameToDelete: string }): boolean { + //console.log(props.usernameToDelete); FOR DEBUG let removed = false; api .removeUser( @@ -19,16 +20,12 @@ function DeleteUser(props: { usernameToDelete: string }): boolean { ) .then((response: APIResponse) => { if (response.success) { - alert("User has been deleted!"); - location.reload(); removed = true; } else { - alert("User has not been deleted"); console.error(response.message); } }) .catch((error) => { - alert("User has not been deleted"); console.error("An error occurred during creation:", error); }); return removed; diff --git a/frontend/src/Components/DisplayUnsignedReports.tsx b/frontend/src/Components/DisplayUnsignedReports.tsx deleted file mode 100644 index 780f20c..0000000 --- a/frontend/src/Components/DisplayUnsignedReports.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useState, useEffect } from "react"; -import { Link, useParams } from "react-router-dom"; - -interface UnsignedReports { - projectName: string; - username: string; - week: number; - signed: boolean; -} - -/** - * Renders a component that displays the projects a user is a part of and links to the projects start-page. - * @returns The JSX element representing the component. - */ -function DisplayUserProject(): JSX.Element { - const { projectName } = useParams(); - const [unsignedReports, setUnsignedReports] = useState([]); - //const navigate = useNavigate(); - - // const getUnsignedReports = async (): Promise => { - // const token = localStorage.getItem("accessToken") ?? ""; - // const response = await api.getUserProjects(token); - // console.log(response); - // if (response.success) { - // setUnsignedReports(response.data ?? []); - // } else { - // console.error(response.message); - // } - // }; - - // const handleReportClick = async (projectName: string): Promise => { - // const username = localStorage.getItem("username") ?? ""; - // const token = localStorage.getItem("accessToken") ?? ""; - // const response = await api.checkIfProjectManager( - // username, - // projectName, - // token, - // ); - // if (response.success) { - // if (response.data) { - // navigate(`/PMProjectPage/${projectName}`); - // } else { - // navigate(`/project/${projectName}`); - // } - // } else { - // // handle error - // console.error(response.message); - // } - // }; - - const getUnsignedReports = async (): Promise => { - // Simulate a delay - await Promise.resolve(); - - // Use mock data - const reports: UnsignedReports[] = [ - { - projectName: "projecttest", - username: "user1", - week: 2, - signed: false, - }, - { - projectName: "projecttest", - username: "user2", - week: 2, - signed: false, - }, - { - projectName: "projecttest", - username: "user3", - week: 2, - signed: false, - }, - { - projectName: "projecttest", - username: "user4", - week: 2, - signed: false, - }, - ]; - - // Set the state with the mock data - setUnsignedReports(reports); - }; - - // Call getProjects when the component mounts - useEffect(() => { - void getUnsignedReports(); - }, []); - - return ( - <> -

- All Unsigned Reports In: {projectName}{" "} -

-
- {unsignedReports.map( - (unsignedReport: UnsignedReports, index: number) => ( -

-
-
-

{unsignedReport.username}

- Week: -

{unsignedReport.week}

- Signed: -

NO

-
-
-
- -

- View Report -

- -
-
-
-

- ), - )} -
- - ); -} - -export default DisplayUserProject; diff --git a/frontend/src/Components/DisplayUserProjects.tsx b/frontend/src/Components/DisplayUserProjects.tsx index 29e4bcb..f4fd782 100644 --- a/frontend/src/Components/DisplayUserProjects.tsx +++ b/frontend/src/Components/DisplayUserProjects.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import { Project } from "../Types/goTypes"; -import { useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { api } from "../API/API"; /** @@ -9,7 +9,6 @@ import { api } from "../API/API"; */ function DisplayUserProject(): JSX.Element { const [projects, setProjects] = useState([]); - const navigate = useNavigate(); const getProjects = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; @@ -22,21 +21,6 @@ function DisplayUserProject(): JSX.Element { } }; - const handleProjectClick = async (projectName: string): Promise => { - const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.checkIfProjectManager(projectName, token); - if (response.success) { - if (response.data) { - navigate(`/PMProjectPage/${projectName}`); - } else { - navigate(`/project/${projectName}`); - } - } else { - // handle error - console.error(response.message); - } - }; - // Call getProjects when the component mounts useEffect(() => { void getProjects(); @@ -46,15 +30,12 @@ function DisplayUserProject(): JSX.Element { <>

Your Projects

- {projects.map((project) => ( -
void handleProjectClick(project.name)} - key={project.id} - > + {projects.map((project, index) => ( +

{project.name}

-
+ ))}
diff --git a/frontend/src/Components/EditWeeklyReport.tsx b/frontend/src/Components/EditWeeklyReport.tsx index 384359e..be96329 100644 --- a/frontend/src/Components/EditWeeklyReport.tsx +++ b/frontend/src/Components/EditWeeklyReport.tsx @@ -18,47 +18,44 @@ export default function GetWeeklyReport(): JSX.Element { const [testingTime, setTestingTime] = useState(0); const token = localStorage.getItem("accessToken") ?? ""; - const { projectName, fetchedWeek } = useParams<{ - projectName: string; - fetchedWeek: string; - }>(); - console.log(projectName, fetchedWeek); + const { projectName } = useParams(); + const { fetchedWeek } = useParams(); + + const fetchWeeklyReport = async (): Promise => { + const response = await api.getWeeklyReport( + projectName ?? "", + fetchedWeek?.toString() ?? "0", + token, + ); + + if (response.success) { + const report: WeeklyReport = response.data ?? { + reportId: 0, + userId: 0, + projectId: 0, + week: 0, + developmentTime: 0, + meetingTime: 0, + adminTime: 0, + ownWorkTime: 0, + studyTime: 0, + testingTime: 0, + }; + setWeek(report.week); + setDevelopmentTime(report.developmentTime); + setMeetingTime(report.meetingTime); + setAdminTime(report.adminTime); + setOwnWorkTime(report.ownWorkTime); + setStudyTime(report.studyTime); + setTestingTime(report.testingTime); + } else { + console.error("Failed to fetch weekly report:", response.message); + } + }; useEffect(() => { - const fetchWeeklyReport = async (): Promise => { - const response = await api.getWeeklyReport( - projectName ?? "", - fetchedWeek ?? "", - token, - ); - - if (response.success) { - const report: WeeklyReport = response.data ?? { - reportId: 0, - userId: 0, - projectId: 0, - week: 0, - developmentTime: 0, - meetingTime: 0, - adminTime: 0, - ownWorkTime: 0, - studyTime: 0, - testingTime: 0, - }; - setWeek(report.week); - setDevelopmentTime(report.developmentTime); - setMeetingTime(report.meetingTime); - setAdminTime(report.adminTime); - setOwnWorkTime(report.ownWorkTime); - setStudyTime(report.studyTime); - setTestingTime(report.testingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - }; - void fetchWeeklyReport(); - }, [projectName, fetchedWeek, token]); + }); const handleNewWeeklyReport = async (): Promise => { const newWeeklyReport: NewWeeklyReport = { @@ -79,7 +76,6 @@ export default function GetWeeklyReport(): JSX.Element { return ( <> -

Edit Time Report

{ @@ -94,10 +90,24 @@ export default function GetWeeklyReport(): JSX.Element { }} >
-
-

Week: {week}

-
- + { + const weekNumber = parseInt(e.target.value.split("-W")[1]); + setWeek(weekNumber); + }} + onKeyDown={(event) => { + event.preventDefault(); + }} + onPaste={(event) => { + event.preventDefault(); + }} + /> @@ -117,14 +127,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={developmentTime === 0 ? "" : developmentTime} + value={developmentTime} onChange={(e) => { - if (e.target.value === "") { - setDevelopmentTime(0); - return; - } else { - setDevelopmentTime(parseInt(e.target.value)); - } + setDevelopmentTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -141,14 +146,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={meetingTime === 0 ? "" : meetingTime} + value={meetingTime} onChange={(e) => { - if (e.target.value === "") { - setMeetingTime(0); - return; - } else { - setMeetingTime(parseInt(e.target.value)); - } + setMeetingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -165,14 +165,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={adminTime === 0 ? "" : adminTime} + value={adminTime} onChange={(e) => { - if (e.target.value === "") { - setAdminTime(0); - return; - } else { - setAdminTime(parseInt(e.target.value)); - } + setAdminTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -189,14 +184,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={ownWorkTime === 0 ? "" : ownWorkTime} + value={ownWorkTime} onChange={(e) => { - if (e.target.value === "") { - setOwnWorkTime(0); - return; - } else { - setOwnWorkTime(parseInt(e.target.value)); - } + setOwnWorkTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -213,14 +203,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={studyTime === 0 ? "" : studyTime} + value={studyTime} onChange={(e) => { - if (e.target.value === "") { - setStudyTime(0); - return; - } else { - setStudyTime(parseInt(e.target.value)); - } + setStudyTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -237,14 +222,9 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={testingTime === 0 ? "" : testingTime} + value={testingTime} onChange={(e) => { - if (e.target.value === "") { - setTestingTime(0); - return; - } else { - setTestingTime(parseInt(e.target.value)); - } + setTestingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; diff --git a/frontend/src/Components/NewWeeklyReport.tsx b/frontend/src/Components/NewWeeklyReport.tsx index f684b0c..a128b8d 100644 --- a/frontend/src/Components/NewWeeklyReport.tsx +++ b/frontend/src/Components/NewWeeklyReport.tsx @@ -12,103 +12,65 @@ import Button from "./Button"; */ export default function NewWeeklyReport(): JSX.Element { const [week, setWeek] = useState(0); - const [developmentTime, setDevelopmentTime] = useState(0); - const [meetingTime, setMeetingTime] = useState(0); - const [adminTime, setAdminTime] = useState(0); - const [ownWorkTime, setOwnWorkTime] = useState(0); - const [studyTime, setStudyTime] = useState(0); - const [testingTime, setTestingTime] = useState(0); + const [developmentTime, setDevelopmentTime] = useState(); + const [meetingTime, setMeetingTime] = useState(); + const [adminTime, setAdminTime] = useState(); + const [ownWorkTime, setOwnWorkTime] = useState(); + const [studyTime, setStudyTime] = useState(); + const [testingTime, setTestingTime] = useState(); const { projectName } = useParams(); const token = localStorage.getItem("accessToken") ?? ""; - const handleNewWeeklyReport = async (): Promise => { + const handleNewWeeklyReport = async (): Promise => { const newWeeklyReport: NewWeeklyReport = { projectName: projectName ?? "", week: week, - developmentTime: developmentTime, - meetingTime: meetingTime, - adminTime: adminTime, - ownWorkTime: ownWorkTime, - studyTime: studyTime, - testingTime: testingTime, + developmentTime: developmentTime ?? 0, + meetingTime: meetingTime ?? 0, + adminTime: adminTime ?? 0, + ownWorkTime: ownWorkTime ?? 0, + studyTime: studyTime ?? 0, + testingTime: testingTime ?? 0, }; - const response = await api.submitWeeklyReport(newWeeklyReport, token); - console.log(response); - if (response.success) { - return true; - } else { - return false; - } + await api.submitWeeklyReport(newWeeklyReport, token); }; const navigate = useNavigate(); - // Check if the browser is Chrome or Edge - const isChromeOrEdge = /Chrome|Edg/.test(navigator.userAgent); return ( <>
{ + if (week === 0) { + alert("Please enter a week number"); + e.preventDefault(); + return; + } e.preventDefault(); - void (async (): Promise => { - if (week === 0 || week > 53 || week < 1) { - alert("Please enter a valid week number"); - return; - } - - const success = await handleNewWeeklyReport(); - if (!success) { - alert( - "A Time Report for this week already exists, please go to the edit page to edit it or change week number.", - ); - return; - } - alert("Weekly report submitted successfully"); - navigate(-1); - })(); + void handleNewWeeklyReport(); + navigate(-1); }} >
- {isChromeOrEdge ? ( - { - const weekNumber = parseInt(e.target.value.split("-W")[1]); - setWeek(weekNumber); - }} - onKeyDown={(event) => { - const keyValue = event.key; - if (!/\d/.test(keyValue) && keyValue !== "Backspace") - event.preventDefault(); - }} - onPaste={(event) => { + { + setWeek(parseInt(e.target.value)); + }} + onKeyDown={(event) => { + const keyValue = event.key; + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); - }} - /> - ) : ( - { - const weekNumber = parseInt(e.target.value); - setWeek(weekNumber); - }} - onKeyDown={(event) => { - const keyValue = event.key; - if (!/\d/.test(keyValue) && keyValue !== "Backspace") - event.preventDefault(); - }} - onPaste={(event) => { - event.preventDefault(); - }} - /> - )} + }} + onPaste={(event) => { + event.preventDefault(); + }} + />
@@ -128,14 +90,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={developmentTime === 0 ? "" : developmentTime} + value={developmentTime} onChange={(e) => { - if (e.target.value === "") { - setDevelopmentTime(0); - return; - } else { - setDevelopmentTime(parseInt(e.target.value)); - } + setDevelopmentTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -152,14 +109,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={meetingTime === 0 ? "" : meetingTime} + value={meetingTime} onChange={(e) => { - if (e.target.value === "") { - setMeetingTime(0); - return; - } else { - setMeetingTime(parseInt(e.target.value)); - } + setMeetingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -176,14 +128,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={adminTime === 0 ? "" : adminTime} + value={adminTime} onChange={(e) => { - if (e.target.value === "") { - setAdminTime(0); - return; - } else { - setAdminTime(parseInt(e.target.value)); - } + setAdminTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -200,14 +147,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={ownWorkTime === 0 ? "" : ownWorkTime} + value={ownWorkTime} onChange={(e) => { - if (e.target.value === "") { - setOwnWorkTime(0); - return; - } else { - setOwnWorkTime(parseInt(e.target.value)); - } + setOwnWorkTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -224,14 +166,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={studyTime === 0 ? "" : studyTime} + value={studyTime} onChange={(e) => { - if (e.target.value === "") { - setStudyTime(0); - return; - } else { - setStudyTime(parseInt(e.target.value)); - } + setStudyTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; @@ -248,14 +185,9 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={testingTime === 0 ? "" : testingTime} + value={testingTime} onChange={(e) => { - if (e.target.value === "") { - setTestingTime(0); - return; - } else { - setTestingTime(parseInt(e.target.value)); - } + setTestingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; diff --git a/frontend/src/Components/OtherUsersTR.tsx b/frontend/src/Components/OtherUsersTR.tsx deleted file mode 100644 index 2b00e16..0000000 --- a/frontend/src/Components/OtherUsersTR.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { useState, useEffect } from "react"; -import { WeeklyReport } from "../Types/goTypes"; -import { api } from "../API/API"; -import { useParams } from "react-router-dom"; - -/** - * Renders the component for editing a weekly report. - * @returns JSX.Element - */ - -//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project. -export default function OtherUsersTR(): JSX.Element { - const [week, setWeek] = useState(0); - const [developmentTime, setDevelopmentTime] = useState(0); - const [meetingTime, setMeetingTime] = useState(0); - const [adminTime, setAdminTime] = useState(0); - const [ownWorkTime, setOwnWorkTime] = useState(0); - const [studyTime, setStudyTime] = useState(0); - const [testingTime, setTestingTime] = useState(0); - - const token = localStorage.getItem("accessToken") ?? ""; - const { projectName } = useParams(); - const { username } = useParams(); - const { fetchedWeek } = useParams(); - - useEffect(() => { - const fetchUsersWeeklyReport = async (): Promise => { - const response = await api.getWeeklyReport( - projectName ?? "", - fetchedWeek?.toString() ?? "0", - token, - ); - - if (response.success) { - const report: WeeklyReport = response.data ?? { - reportId: 0, - userId: 0, - projectId: 0, - week: 0, - developmentTime: 0, - meetingTime: 0, - adminTime: 0, - ownWorkTime: 0, - studyTime: 0, - testingTime: 0, - }; - setWeek(report.week); - setDevelopmentTime(report.developmentTime); - setMeetingTime(report.meetingTime); - setAdminTime(report.adminTime); - setOwnWorkTime(report.ownWorkTime); - setStudyTime(report.studyTime); - setTestingTime(report.testingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - }; - - void fetchUsersWeeklyReport(); - }); - - return ( - <> -

{username}'s Report

-
-
-
-

Week: {week}

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Activity - Total Time (min) -
Development - -
Meeting - -
Administration - -
Own Work - -
Studies - -
Testing - -
-
-
- - ); -} diff --git a/frontend/src/Components/ProjectInfoModal.tsx b/frontend/src/Components/ProjectInfoModal.tsx index 3075b19..b153e9c 100644 --- a/frontend/src/Components/ProjectInfoModal.tsx +++ b/frontend/src/Components/ProjectInfoModal.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import Button from "./Button"; import { UserProjectMember } from "../Types/goTypes"; import GetUsersInProject from "./GetUsersInProject"; -import { Link } from "react-router-dom"; function ProjectInfoModal(props: { isVisible: boolean; @@ -19,12 +18,9 @@ function ProjectInfoModal(props: { className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm flex justify-center items-center" > -
+
-

- {localStorage.getItem("projectName") ?? ""} -

-

Project members:

+

Project members:

    @@ -54,15 +50,6 @@ function ProjectInfoModal(props: { }} type="button" /> - -
- -
- - ); -} diff --git a/frontend/src/Pages/AdminPages/AdminProjectAddMember.tsx b/frontend/src/Pages/AdminPages/AdminProjectAddMember.tsx index 893bdad..712df86 100644 --- a/frontend/src/Pages/AdminPages/AdminProjectAddMember.tsx +++ b/frontend/src/Pages/AdminPages/AdminProjectAddMember.tsx @@ -1,10 +1,22 @@ -import AddUserToProject from "../../Components/AddUserToProject"; +import BackButton from "../../Components/BackButton"; import BasicWindow from "../../Components/BasicWindow"; +import Button from "../../Components/Button"; function AdminProjectAddMember(): JSX.Element { - const content = ; + const content = <>; - const buttons = <>; + const buttons = ( + <> +