diff --git a/.gitignore b/.gitignore index 281e866..c50fe24 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ diagram.puml backend/*.png backend/*.jpg backend/*.svg +__pycache__ /go.work.sum /package-lock.json diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 7a08b0e..c8b020d 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -108,6 +108,56 @@ const docTemplate = `{ } } }, + "/promote/{projectName}": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Promote a user to project manager", + "consumes": [ + "text/plain" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "Auth" + ], + "summary": "Promote to project manager", + "parameters": [ + { + "type": "string", + "description": "Project name", + "name": "projectName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "User name", + "name": "userName", + "in": "query", + "required": true + } + ], + "responses": { + "403": { + "description": "Forbidden", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/promoteToAdmin": { "post": { "security": [ diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index f4c0f6e..22e11e9 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -17,6 +17,7 @@ type Database interface { AddUser(username string, password string) error CheckUser(username string, password string) bool RemoveUser(username string) error + RemoveUserFromProject(username string, projectname string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error @@ -35,7 +36,7 @@ type Database interface { GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) - GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) + GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) @@ -43,6 +44,7 @@ type Database interface { 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 + GetUserName(id int) (string, error) } // This struct is a wrapper type that holds the database connection @@ -86,6 +88,10 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles JOIN projects ON user_roles.project_id = projects.id WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` +const removeUserFromProjectQuery = `DELETE FROM user_roles + WHERE user_id = (SELECT id FROM users WHERE username = ?) + AND project_id = (SELECT id FROM projects WHERE name = ?)` + // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -147,6 +153,11 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err } +func (d *Db) RemoveUserFromProject(username string, projectname string) error { + _, err := d.Exec(removeUserFromProjectQuery, username, projectname) + return err +} + // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // Execute the SQL query to change the user's role @@ -339,9 +350,14 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } + managerQuery := `SELECT project_id FROM user_roles + WHERE user_id = ? + AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?) + AND p_role = 'project_manager'` + // Retrieve the project ID associated with the project manager var managerProjectID int - err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId) + err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId) if err != nil { return err } @@ -463,8 +479,8 @@ func (d *Db) Migrate() error { return nil } -// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. -func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { +// GetAllWeeklyReports retrieves weekly reports for a specific user and project. +func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) { query := ` SELECT wr.week, @@ -601,3 +617,9 @@ func (d *Db) RemoveProject(projectname string) error { _, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) return err } + +func (d *Db) GetUserName(id int) (string, error) { + var username string + err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id) + return username, err +} diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index fe3e6cd..a691a4d 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) { t.Error("AddWeeklyReport failed:", err) } - reports, err := db.GetWeeklyReportsUser("testuser", "testproject") + reports, err := db.GetAllWeeklyReports("testuser", "testproject") if err != nil { t.Error("GetWeeklyReportsUser failed:", err) } @@ -962,6 +962,5 @@ func TestRemoveProject(t *testing.T) { 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/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index ab74f1a..70499b0 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -21,6 +21,12 @@ VALUES ("projecttest3","test project3", 1); INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) VALUES (1,1,"project_manager"); +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (1,2,"project_manager"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (1,3,"project_manager"); + INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) VALUES (2,1,"member"); diff --git a/backend/internal/handlers/projects/AddUserToProject.go b/backend/internal/handlers/projects/AddUserToProject.go index 702b7dd..3195314 100644 --- a/backend/internal/handlers/projects/AddUserToProject.go +++ b/backend/internal/handlers/projects/AddUserToProject.go @@ -10,42 +10,33 @@ import ( // 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) + pm_name := claims["name"].(string) - isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) + project := c.Params("projectName") + username := c.Query("userName") + + // Check if the user is a project manager + isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) if err != nil { - log.Info("Error checking admin status:", err) + log.Info("Error checking if user is project manager:", 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") + if !isPM { + log.Info("User: ", pm_name, " is not a project manager in project: ", project) + return c.Status(403).SendString("User is not a project manager") } // Add the user to the project with the specified role - err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) + err = db.GetDb(c).AddUserToProject(username, project, "member") 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) + log.Info("User : ", username, " added to project: ", project) return c.SendStatus(fiber.StatusOK) } diff --git a/backend/internal/handlers/projects/PromoteToPm.go b/backend/internal/handlers/projects/PromoteToPm.go new file mode 100644 index 0000000..ffe2215 --- /dev/null +++ b/backend/internal/handlers/projects/PromoteToPm.go @@ -0,0 +1,51 @@ +package projects + +import ( + db "ttime/internal/database" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +// @Summary Promote to project manager +// @Description Promote a user to project manager +// @Tags Auth +// @Security JWT +// @Accept plain +// @Produce plain +// @Param projectName path string true "Project name" +// @Param userName query string true "User name" +// @Failure 500 {string} string "Internal server error" +// @Failure 403 {string} string "Forbidden" +// @Router /promote/{projectName} [put] +// +// Login logs in a user and returns a JWT token +// Promote to project manager +func PromoteToPm(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + pm_name := claims["name"].(string) + + project := c.Params("projectName") + new_pm_name := c.Query("userName") + + // Check if the user is a project manager + isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isPM { + log.Info("User: ", pm_name, " is not a project manager in project: ", project) + return c.Status(403).SendString("User is not a project manager") + } + + // Add the user to the project with the specified role + err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager") + + // Return success message + log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project) + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/internal/handlers/projects/RemoveUserFromProject.go b/backend/internal/handlers/projects/RemoveUserFromProject.go new file mode 100644 index 0000000..7aefcf8 --- /dev/null +++ b/backend/internal/handlers/projects/RemoveUserFromProject.go @@ -0,0 +1,40 @@ +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 RemoveUserFromProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + pm_name := claims["name"].(string) + + project := c.Params("projectName") + username := c.Query("userName") + + // Check if the user is a project manager + isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isPM { + log.Info("User: ", pm_name, " is not a project manager in project: ", project) + return c.Status(403).SendString("User is not a project manager") + } + + // Remove the user from the project + if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil { + log.Info("Error removing user from project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return success message + log.Info("User : ", username, " removed from project: ", project) + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/internal/handlers/reports/GetAllWeeklyReports.go b/backend/internal/handlers/reports/GetAllWeeklyReports.go new file mode 100644 index 0000000..ee81c82 --- /dev/null +++ b/backend/internal/handlers/reports/GetAllWeeklyReports.go @@ -0,0 +1,56 @@ +package reports + +import ( + db "ttime/internal/database" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project +func GetAllWeeklyReports(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 project name and week from query parameters + projectName := c.Params("projectName") + target_user := c.Query("targetUser") // The user whose reports are being requested + + // If the target user is not empty, use it as the username + if target_user == "" { + target_user = username + } + + log.Info(username, " trying to get all weekly reports for: ", target_user) + + if projectName == "" { + log.Info("Missing project name") + return c.Status(400).SendString("Missing project name") + } + + // If the user is not a project manager, they can only view their own reports + pm, err := db.GetDb(c).IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if pm == false && target_user != username { + log.Info("Unauthorized access") + return c.Status(403).SendString("Unauthorized access") + } + + // Retrieve weekly reports for the user in the project from the database + reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName) + if err != nil { + log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning weekly report") + // Return the retrieved weekly report + return c.JSON(reports) +} diff --git a/backend/internal/handlers/reports/GetWeeklyReport.go b/backend/internal/handlers/reports/GetWeeklyReport.go index 422bc0b..04bdc0d 100644 --- a/backend/internal/handlers/reports/GetWeeklyReport.go +++ b/backend/internal/handlers/reports/GetWeeklyReport.go @@ -16,11 +16,17 @@ func GetWeeklyReport(c *fiber.Ctx) error { 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") + target_user := c.Query("targetUser") // The user whose report is being requested + + // If the target user is not empty, use it as the username + if target_user == "" { + target_user = username + } + + log.Info(username, " trying to get weekly report for: ", target_user) if projectName == "" || week == "" { log.Info("Missing project name or week number") @@ -34,8 +40,20 @@ func GetWeeklyReport(c *fiber.Ctx) error { return c.Status(400).SendString("Invalid week number") } + // If the token user is not an admin, check if the target user is the same as the token user + pm, err := db.GetDb(c).IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking if user is project manager:", err) + return c.Status(500).SendString(err.Error()) + } + + if pm == false && target_user != username { + log.Info("Unauthorized access") + return c.Status(403).SendString("Unauthorized access") + } + // Call the database function to get the weekly report - report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt) + report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt) if err != nil { log.Info("Error getting weekly report from db:", err) return c.Status(500).SendString(err.Error()) 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/users/GetUserName.go b/backend/internal/handlers/users/GetUserName.go new file mode 100644 index 0000000..82b6cc8 --- /dev/null +++ b/backend/internal/handlers/users/GetUserName.go @@ -0,0 +1,32 @@ +package users + +import ( + "strconv" + db "ttime/internal/database" + + "github.com/gofiber/fiber/v2" +) + +// Return the username of a user given their user id +func GetUserName(c *fiber.Ctx) error { + // Check the query params for userId + user_id_string := c.Query("userId") + if user_id_string == "" { + return c.Status(400).SendString("Missing user id") + } + + // Convert to int + user_id, err := strconv.Atoi(user_id_string) + if err != nil { + return c.Status(400).SendString("Invalid user id") + } + + // Get the username from the database + username, err := db.GetDb(c).GetUserName(user_id) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Send the nuclear launch codes to north korea + return c.JSON(fiber.Map{"username": username}) +} diff --git a/backend/main.go b/backend/main.go index 6e65386..7b19dd9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -103,6 +103,7 @@ func main() { // userGroup := api.Group("/user") // Not currently in use api.Get("/users/all", users.ListAllUsers) api.Get("/project/getAllUsers", users.GetAllUsersProject) + api.Get("/username", users.GetUserName) api.Post("/login", users.Login) api.Post("/register", users.Register) api.Post("/loginrenew", users.LoginRenew) @@ -119,6 +120,9 @@ func main() { api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) api.Post("/project", projects.CreateProject) api.Post("/ProjectRoleChange", projects.ProjectRoleChange) + api.Put("/promoteToPm/:projectName", projects.PromoteToPm) + api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) + api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/project/:projectID", projects.DeleteProject) @@ -126,10 +130,9 @@ func main() { // 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.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports) api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Put("/signReport/:reportId", reports.SignReport) - api.Put("/addUserToProject", projects.AddUserToProjectHandler) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) // Announce the port we are listening on and start the server diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index c1480fb..86ad6dc 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,4 +1,4 @@ -import { NewProjMember } from "../Components/AddMember"; +import { AddMemberInfo } from "../Components/AddMember"; import { ProjectRoleChange } from "../Components/ChangeRole"; import { projectTimes } from "../Components/GetProjectTimes"; import { ProjectMember } from "../Components/GetUsersInProject"; @@ -99,16 +99,20 @@ interface API { token: string, ): Promise>; - /** Gets a weekly report for a specific user, project and week + /** Gets a weekly report for a specific user, project and week. + * Keep in mind that the user within the token needs to be PM + * of the project to get the report, unless the user is the target user. * @param {string} projectName The name of the project. * @param {string} week The week number. * @param {string} token The authentication token. + * @param {string} targetUser The username of the target user. Defaults to token user. * @returns {Promise>} A promise resolving to an API response with the retrieved report. */ getWeeklyReport( projectName: string, week: string, token: string, + targetUser?: string, ): Promise>; /** @@ -118,9 +122,10 @@ interface API { * @param {string} token The token of the user * @returns {APIResponse} A list of weekly reports */ - getWeeklyReportsForUser( + getAllWeeklyReportsForUser( projectName: string, token: string, + targetUser?: string, ): Promise>; /** Gets all the projects of a user @@ -192,7 +197,13 @@ interface API { ): Promise>; addUserToProject( - user: NewProjMember, + addMemberInfo: AddMemberInfo, + token: string, + ): Promise>; + + removeUserFromProject( + user: string, + project: string, token: string, ): Promise>; @@ -209,6 +220,25 @@ interface API { * @param {string} token The authentication token */ signReport(reportId: number, token: string): Promise>; + + /** + * Promotes a user to project manager within a project. + * + * @param {string} userName The username of the user to promote + * @param {string} projectName The name of the project to promote the user in + * @returns {Promise} A promise resolving to an API response. + */ + promoteToPm( + userName: string, + projectName: string, + token: string, + ): Promise>; + /** + * Get the username from the id + * @param {number} id The id of the user + * @param {string} token Your token + */ + getUsername(id: number, token: string): Promise>; } /** An instance of the API */ @@ -318,18 +348,20 @@ export const api: API = { }, async addUserToProject( - user: NewProjMember, + addMemberInfo: AddMemberInfo, token: string, ): Promise> { try { - const response = await fetch("/api/addUserToProject", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, + const response = await fetch( + `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, + { + 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" }; @@ -341,6 +373,31 @@ export const api: API = { } }, + async removeUserFromProject( + user: string, + project: string, + token: string, + ): Promise> { + try { + const response = await fetch( + `/api/removeUserFromProject/${project}?userName=${user}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }, + ); + if (!response.ok) { + return { success: false, message: "Failed to remove member" }; + } + } catch (e) { + return { success: false, message: "Failed to remove member" }; + } + return { success: true, message: "Removed member" }; + }, + async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -516,10 +573,11 @@ export const api: API = { projectName: string, week: string, token: string, + targetUser?: string, ): Promise> { try { const response = await fetch( - `/api/getWeeklyReport?projectName=${projectName}&week=${week}`, + `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, { method: "GET", headers: { @@ -540,18 +598,22 @@ export const api: API = { } }, - async getWeeklyReportsForUser( + async getAllWeeklyReportsForUser( projectName: string, token: string, + targetUser?: string, ): Promise> { try { - const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, + const response = await fetch( + `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, }, - }); + ); if (!response.ok) { return { @@ -783,4 +845,56 @@ export const api: API = { return { success: false, message: "Failed to sign report" }; } }, + + async promoteToPm( + userName: string, + projectName: string, + token: string, + ): Promise> { + try { + const response = await fetch( + `/api/promoteToPm/${projectName}?userName=${userName}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }, + ); + if (!response.ok) { + return { + success: false, + message: "Failed to promote user to project manager", + }; + } + } catch (e) { + return { + success: false, + message: "Failed to promote user to project manager", + }; + } + return { success: true, message: "User promoted to project manager" }; + }, + + async getUsername(id: number, token: string): Promise> { + try { + const response = await fetch(`/api/username?userId=${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }); + + if (!response.ok) { + return { success: false, message: "Failed to get username" }; + } else { + const data = (await response.json()) as string; + return { success: true, data }; + } + } catch (e) { + return { success: false, message: "Failed to get username" }; + } + }, }; diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx index 194afe8..d8036b7 100644 --- a/frontend/src/Components/AddMember.tsx +++ b/frontend/src/Components/AddMember.tsx @@ -1,44 +1,35 @@ -import { APIResponse, api } from "../API/API"; +import { api } from "../API/API"; -export interface NewProjMember { - username: string; - role: string; - projectname: string; +export interface AddMemberInfo { + userName: string; + projectName: string; } /** * Tries to add a member to a project - * @param {Object} props - A NewProjMember - * @returns {boolean} True if added, false if not + * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name + * @returns {Promise} */ -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; +async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise { + if (props.memberToAdd.userName === "") { + alert("You must choose at least one user to add"); + return; } - api - .addUserToProject( + try { + const response = await 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; + ); + if (response.success) { + alert(`[${props.memberToAdd.userName}] added`); + } else { + alert(`[${props.memberToAdd.userName}] not added`); + console.error(response.message); + } + } catch (error) { + alert(`[${props.memberToAdd.userName}] not added`); + console.error("An error occurred during member add:", error); + } } export default AddMember; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index e2ad8b9..c8a1c66 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,37 +1,10 @@ import { useState } from "react"; -import { APIResponse, api } from "../API/API"; +import { api } from "../API/API"; import { NewProject } from "../Types/goTypes"; import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; -/** - * Tries to add a project to the system - * @param {Object} props - Project name and description - * @returns {boolean} True if created, false if not - */ -function CreateProject(props: { name: string; description: string }): void { - const project: NewProject = { - name: props.name, - description: props.description, - }; - - api - .createProject(project, localStorage.getItem("accessToken") ?? "") - .then((response: APIResponse) => { - if (response.success) { - alert("Project added!"); - } else { - alert("Project NOT added!"); - console.error(response.message); - } - }) - .catch((error) => { - alert("Project NOT added!"); - console.error("An error occurred during creation:", error); - }); -} - /** * Provides UI for adding a project to the system. * @returns {JSX.Element} - Returns the component UI for adding a project @@ -40,6 +13,33 @@ function AddProject(): JSX.Element { const [name, setName] = useState(""); const [description, setDescription] = useState(""); + /** + * Tries to add a project to the system + */ + const handleCreateProject = async (): Promise => { + const project: NewProject = { + name: name.replace(/ /g, ""), + description: description.trim(), + }; + try { + const response = await api.createProject( + project, + localStorage.getItem("accessToken") ?? "", + ); + if (response.success) { + alert(`${project.name} added!`); + setDescription(""); + setName(""); + } else { + alert("Project not added, name could be taken"); + console.error(response.message); + } + } catch (error) { + alert("Project not added"); + console.error(error); + } + }; + return (
@@ -47,10 +47,7 @@ function AddProject(): JSX.Element { className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" onSubmit={(e) => { e.preventDefault(); - CreateProject({ - name: name, - description: description, - }); + void handleCreateProject(); }} > ([]); const [users, setUsers] = useState([]); - const [role, setRole] = useState(""); - GetAllUsers({ setUsersProp: setUsers }); + const [usersProj, setUsersProj] = useState([]); - const handleClick = (): boolean => { - const newMember: NewProjMember = { - username: name, - projectname: props.projectName, - role: role, - }; - return AddMember({ memberToAdd: newMember }); + // Gets all users and project members for filtering + GetAllUsers({ setUsersProp: setUsers }); + GetUsersInProject({ + setUsersProp: setUsersProj, + projectName: props.projectName, + }); + /* + * Filters the members from users so that users who are already + * members are not shown + */ + useEffect(() => { + setUsers((prevUsers) => { + const filteredUsers = prevUsers.filter( + (user) => + !usersProj.some((projectUser) => projectUser.Username === user), + ); + return filteredUsers; + }); + }, [usersProj]); + + // Attempts to add all of the selected users to the project + const handleAddClick = async (): Promise => { + if (names.length === 0) + alert("You have to choose at least one user to add"); + for (const name of names) { + const newMember: AddMemberInfo = { + userName: name, + projectName: props.projectName, + }; + await AddMember({ memberToAdd: newMember }); + } + setNames([]); + location.reload(); + }; + + // Updates the names that have been selected + const handleUserClick = (user: string): void => { + setNames((prevNames): string[] => { + if (!prevNames.includes(user)) { + return [...prevNames, user]; + } + return prevNames.filter((name) => name !== user); + }); }; return ( -
-

- User chosen: [{name}] +

+

+ {props.projectName} +

+

+ Choose users to add:

-

- Role chosen: [{role}] -

-

- Project chosen: [{props.projectName}] -

-

Choose role:

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

Choose user:

-
+
    {users.map((user) => (
  • { - setName(user); + handleUserClick(user); }} > {user} @@ -73,13 +88,16 @@ function AddUserToProject(props: { projectName: string }): JSX.Element { ))}
-
+

+ Number of users to be added: {names.length} +

+
diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index 4fa9ad8..0d5916b 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getWeeklyReportsForUser( + const response = await api.getAllWeeklyReportsForUser( projectName ?? "", token, ); diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx index ef78642..cde9fa7 100644 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx @@ -17,10 +17,10 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getWeeklyReportsForDifferentUser( + const response = await api.getAllWeeklyReportsForUser( projectName ?? "", - username ?? "", token, + username ?? "", ); console.log(response); if (response.success) { @@ -31,7 +31,7 @@ function AllTimeReportsInProject(): JSX.Element { }; void getWeeklyReports(); - }, []); + }, [projectName, username]); return ( <> diff --git a/frontend/src/Components/ChangeRoleView.tsx b/frontend/src/Components/ChangeRoleView.tsx index 30dce3c..782ad8d 100644 --- a/frontend/src/Components/ChangeRoleView.tsx +++ b/frontend/src/Components/ChangeRoleView.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import Button from "./Button"; import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; -export default function ChangeRoles(props: { +export default function ChangeRoleView(props: { projectName: string; username: string; }): JSX.Element { diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 78d7da9..2f73bb6 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -2,8 +2,11 @@ import { APIResponse, api } from "../API/API"; import { StrNameChange } from "../Types/goTypes"; function ChangeUsername(props: { nameChange: StrNameChange }): void { - if (props.nameChange.newName === "") { - alert("You have to select a new name"); + if ( + props.nameChange.newName === "" || + props.nameChange.newName === props.nameChange.prevName + ) { + alert("You have to give a new name\n\nName not changed"); return; } api @@ -13,7 +16,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void { alert("Name changed successfully"); location.reload(); } else { - alert("Name not changed"); + alert("Name not changed, name could be taken"); console.error(response.message); } }) diff --git a/frontend/src/Components/DisplayUnsignedReports.tsx b/frontend/src/Components/DisplayUnsignedReports.tsx index 232cb31..25a1da3 100644 --- a/frontend/src/Components/DisplayUnsignedReports.tsx +++ b/frontend/src/Components/DisplayUnsignedReports.tsx @@ -3,17 +3,14 @@ import { Link, useParams } from "react-router-dom"; import { api } from "../API/API"; import { WeeklyReport } from "../Types/goTypes"; -/** - * 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 [usernames, setUsernames] = useState([]); + const token = localStorage.getItem("accessToken") ?? ""; + useEffect(() => { const getUnsignedReports = async (): Promise => { - const token = localStorage.getItem("accessToken") ?? ""; const response = await api.getUnsignedReportsInProject( projectName ?? "", token, @@ -21,13 +18,21 @@ function DisplayUserProject(): JSX.Element { console.log(response); if (response.success) { setUnsignedReports(response.data ?? []); + const usernamesPromises = (response.data ?? []).map((report) => + api.getUsername(report.userId, token), + ); + const usernamesResponses = await Promise.all(usernamesPromises); + const usernames = usernamesResponses.map( + (res) => (res.data as { username?: string }).username ?? "", + ); + setUsernames(usernames); } else { console.error(response.message); } }; void getUnsignedReports(); - }, [projectName]); // Include 'projectName' in the dependency array + }, [projectName, token]); return ( <> @@ -39,8 +44,8 @@ function DisplayUserProject(): JSX.Element {

- UserID: -

{unsignedReport.userId}

+ Username: +

{usernames[index]}

{" "} Week:

{unsignedReport.week}

Total Time: @@ -58,7 +63,7 @@ function DisplayUserProject(): JSX.Element {

View Report diff --git a/frontend/src/Components/Header.tsx b/frontend/src/Components/Header.tsx index eb4fa5a..9be2f4b 100644 --- a/frontend/src/Components/Header.tsx +++ b/frontend/src/Components/Header.tsx @@ -1,6 +1,6 @@ //info: Header component to display the header of the page including the logo and user information where thr user can logout import { useState } from "react"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import backgroundImage from "../assets/1.jpg"; /** @@ -9,23 +9,33 @@ import backgroundImage from "../assets/1.jpg"; */ function Header(): JSX.Element { const [isOpen, setIsOpen] = useState(false); + const username = localStorage.getItem("username"); + const navigate = useNavigate(); const handleLogout = (): void => { localStorage.clear(); }; + const handleNavigation = (): void => { + if (username === "admin") { + navigate("/admin"); + } else { + navigate("/yourProjects"); + } + }; + return (
- +
TTIME Logo - +
@@ -42,13 +42,16 @@ function MemberInfoModal(props: {

Number of members: {users.length}

-

Total time reported: {totalTime.current}

+

+ Total time reported:{" "} + {Math.floor(totalTime.current / 60 / 24) + " d "} + {Math.floor((totalTime.current / 60) % 24) + " h "} + {(totalTime.current % 60) + " m "} +

Project members:

diff --git a/frontend/src/Components/Register.tsx b/frontend/src/Components/Register.tsx index 68e0979..be35a74 100644 --- a/frontend/src/Components/Register.tsx +++ b/frontend/src/Components/Register.tsx @@ -15,17 +15,21 @@ export default function Register(): JSX.Element { const [errMessage, setErrMessage] = useState(); const handleRegister = async (): Promise => { + if (username === "" || password === "") { + alert("Must provide username and password"); + return; + } const newUser: NewUser = { - username: username ?? "", + username: username?.replace(/ /g, "") ?? "", password: password ?? "", }; const response = await api.registerUser(newUser); if (response.success) { - alert("User added!"); + alert(`${newUser.username} added!`); setPassword(""); setUsername(""); } else { - alert("User not added"); + alert("User not added, name could be taken"); setErrMessage(response.message ?? "Unknown error"); console.error(errMessage); } diff --git a/frontend/src/Components/RemoveUserFromProj.tsx b/frontend/src/Components/RemoveUserFromProj.tsx new file mode 100644 index 0000000..eab79d9 --- /dev/null +++ b/frontend/src/Components/RemoveUserFromProj.tsx @@ -0,0 +1,41 @@ +import { api, APIResponse } from "../API/API"; + +/** + * Removes a user from a project + * @param {string} props.usernameToDelete - The username of user to remove + * @param {string} props.projectName - Project to remove user from + * @returns {void} + * @example + * const exampleUsername = "user"; + * const exampleProjectName "project"; + * RemoveUserFromProj({ userToRemove: exampleUsername, projectName: exampleProjectName }); + */ + +export default function RemoveUserFromProj(props: { + userToRemove: string; + projectName: string; +}): void { + if (props.userToRemove === localStorage.getItem("username")) { + alert("Cannot remove yourself"); + return; + } + api + .removeUserFromProject( + props.userToRemove, + props.projectName, + localStorage.getItem("accessToken") ?? "", + ) + .then((response: APIResponse) => { + if (response.success) { + alert(`${props.userToRemove} has been removed!`); + location.reload(); + } else { + alert(`${props.userToRemove} has not been removed due to an error`); + console.error(response.message); + } + }) + .catch((error) => { + alert(`${props.userToRemove} has not been removed due to an error`); + console.error("An error occurred during deletion:", error); + }); +} diff --git a/frontend/src/Components/TimePerActivity.tsx b/frontend/src/Components/TimePerActivity.tsx index 6175845..c5e4a9f 100644 --- a/frontend/src/Components/TimePerActivity.tsx +++ b/frontend/src/Components/TimePerActivity.tsx @@ -8,12 +8,12 @@ import { projectTimes } from "./GetProjectTimes"; * @returns JSX.Element */ export default function TimePerRole(): JSX.Element { - const [development, setDevelopment] = useState(); - const [meeting, setMeeting] = useState(); - const [admin, setAdmin] = useState(); - const [own_work, setOwnWork] = useState(); - const [study, setStudy] = useState(); - const [testing, setTesting] = useState(); + const [development, setDevelopment] = useState(0); + const [meeting, setMeeting] = useState(0); + const [admin, setAdmin] = useState(0); + const [own_work, setOwnWork] = useState(0); + const [study, setStudy] = useState(0); + const [testing, setTesting] = useState(0); const token = localStorage.getItem("accessToken") ?? ""; const { projectName } = useParams(); diff --git a/frontend/src/Components/UserInfoModal.tsx b/frontend/src/Components/UserInfoModal.tsx index 8cb4c9d..eae011c 100644 --- a/frontend/src/Components/UserInfoModal.tsx +++ b/frontend/src/Components/UserInfoModal.tsx @@ -28,7 +28,7 @@ function UserInfoModal(props: { const handleClickChangeName = (): void => { const nameChange: StrNameChange = { prevName: props.username, - newName: newUsername, + newName: newUsername.replace(/ /g, ""), }; ChangeUsername({ nameChange: nameChange }); }; diff --git a/frontend/src/Components/ViewOtherTimeReport.tsx b/frontend/src/Components/ViewOtherTimeReport.tsx index bde0529..3689854 100644 --- a/frontend/src/Components/ViewOtherTimeReport.tsx +++ b/frontend/src/Components/ViewOtherTimeReport.tsx @@ -31,8 +31,9 @@ export default function GetOtherUsersReport(): JSX.Element { projectName ?? "", fetchedWeek?.toString() ?? "0", token, + username ?? "", ); - + console.log(response); if (response.success) { const report: WeeklyReport = response.data ?? { reportId: 0, @@ -62,25 +63,33 @@ export default function GetOtherUsersReport(): JSX.Element { void fetchUsersWeeklyReport(); }); - const handleSignWeeklyReport = async (): Promise => { - await api.signReport(reportId, token); + const handleSignWeeklyReport = async (): Promise => { + const response = await api.signReport(reportId, token); + if (response.success) { + return true; + } else { + return false; + } }; const navigate = useNavigate(); return ( <> -

- {" "} - UserId: {username}'s Report -

+

{username}'s Report

{ e.preventDefault(); - void handleSignWeeklyReport(); - alert("Report successfully signed!"); - navigate(-1); + void (async (): Promise => { + const success = await handleSignWeeklyReport(); + if (!success) { + alert("Failed to sign report!"); + return; + } + alert("Report successfully signed!"); + navigate(-1); + })(); }} >
diff --git a/testing/helpers.py b/testing/helpers.py new file mode 100644 index 0000000..5f2f367 --- /dev/null +++ b/testing/helpers.py @@ -0,0 +1,151 @@ +import requests +import string +import random +import json + +# Helper function for the TTime API testing suite + +# For style guide, see: +# https://peps.python.org/pep-0008/#function-and-variable-names +# https://google.github.io/styleguide/pyguide.html#316-naming + +################## +## Static Paths ## +################## + +base_url = "http://localhost:8080" + +registerPath = base_url + "/api/register" +loginPath = base_url + "/api/login" +addProjectPath = base_url + "/api/project" +submitReportPath = base_url + "/api/submitWeeklyReport" +getWeeklyReportPath = base_url + "/api/getWeeklyReport" +getProjectPath = base_url + "/api/project" +signReportPath = base_url + "/api/signReport" +addUserToProjectPath = base_url + "/api/addUserToProject" +promoteToAdminPath = base_url + "/api/promoteToAdmin" +getUserProjectsPath = base_url + "/api/getUserProjects" +getAllWeeklyReportsPath = base_url + "/api/getAllWeeklyReports" +checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager" +ProjectRoleChangePath = base_url + "/api/ProjectRoleChange" +getUsersProjectPath = base_url + "/api/getUsersProject" +getUnsignedReportsPath = base_url + "/api/getUnsignedReports" +getChangeUserNamePath = base_url + "/api/changeUserName" +getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport" +removeProjectPath = base_url + "/api/removeProject" +promoteToPmPath = base_url + "/api/promoteToPm" + +debug_output = True + + +def gprint(*args, **kwargs): + print("\033[92m", *args, "\033[00m", **kwargs) + + +def dprint(*args, **kwargs): + if debug_output: + print(*args, **kwargs) + + +def randomString(len=10): + """Generate a random string of fixed length""" + letters = string.ascii_lowercase + return "".join(random.choice(letters) for i in range(len)) + + +############ ############ ############ ############ ############ + + +# Posts the username and password to the register endpoint +def register(username: string, password: string): + dprint("Registering with username: ", username, " and password: ", password) + response = requests.post( + registerPath, json={"username": username, "password": password} + ) + dprint(response.text) + return response + + +# Posts the username and password to the login endpoint +def login(username: string, password: string): + dprint("Logging in with username: ", username, " and password: ", password) + response = requests.post( + loginPath, json={"username": username, "password": password} + ) + dprint(response.text) + return response + + +# Register a user and return the token +def register_and_login(username: string, password: string) -> string: + register(username, password) + response = login(username, password) + return response.json()["token"] + + +def create_project( + token: string, project_name: string, description: string = "Test description" +): + dprint("Creating project with name: ", project_name) + response = requests.post( + addProjectPath, + headers={"Authorization": "Bearer " + token}, + json={"name": project_name, "description": description}, + ) + dprint(response.text) + return response + + +# Add a user to a project, requires the user withing the token to be a project manager of said project +def addToProject(token: string, username: string, project_name: string): + dprint("Adding user with username: ", username, " to project: ", project_name) + response = requests.put( + addUserToProjectPath + "/" + project_name, + headers={"Authorization": "Bearer " + token}, + params={"userName": username}, + ) + dprint(response.text) + return response + + +def promoteToManager(token: string, username: string, project_name: string): + dprint( + "Promoting user with username: ", + username, + " to project manager of project: ", + project_name, + ) + response = requests.put( + promoteToPmPath + "/" + project_name, + headers={"Authorization": "Bearer " + token}, + params={"userName": username}, + ) + dprint(response.text) + return response + + +def submitReport(token: string, report): + dprint("Submitting report: ", report) + response = requests.post( + submitReportPath, + json=report, + headers={"Authorization": "Bearer " + token}, + ) + return response + + +def getReport(token: string, username: string, projectName: string): + # Retrieve the report ID + response = requests.get( + getWeeklyReportPath, + headers={"Authorization": "Bearer " + token}, + params={"username": username, "projectName": projectName, "week": 1}, + ) + return response.json() + + +def signReport(project_manager_token: string, report_id: int): + return requests.put( + signReportPath + "/" + str(report_id), + headers={"Authorization": "Bearer " + project_manager_token}, + ) diff --git a/testing.py b/testing/testing.py similarity index 66% rename from testing.py rename to testing/testing.py index d4594d1..ba38ced 100644 --- a/testing.py +++ b/testing/testing.py @@ -1,61 +1,23 @@ import requests -import string -import random -debug_output = True - -def gprint(*args, **kwargs): - print("\033[92m", *args, "\033[00m", **kwargs) +# This modules contains helper functions for the tests +from helpers import * print("Running Tests...") -def dprint(*args, **kwargs): - if debug_output: - print(*args, **kwargs) - -def randomString(len=10): - """Generate a random string of fixed length""" - letters = string.ascii_lowercase - return "".join(random.choice(letters) for i in range(len)) - - # Defined once per test run username = "user_" + randomString() projectName = "project_" + randomString() -# The base URL of the API -base_url = "http://localhost:8080" -# Endpoint to test -registerPath = base_url + "/api/register" -loginPath = base_url + "/api/login" -addProjectPath = base_url + "/api/project" -submitReportPath = base_url + "/api/submitWeeklyReport" -getWeeklyReportPath = base_url + "/api/getWeeklyReport" -getProjectPath = base_url + "/api/project" -signReportPath = base_url + "/api/signReport" -addUserToProjectPath = base_url + "/api/addUserToProject" -promoteToAdminPath = base_url + "/api/promoteToAdmin" -getUserProjectsPath = base_url + "/api/getUserProjects" -getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser" -checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager" -ProjectRoleChangePath = base_url + "/api/ProjectRoleChange" -getUsersProjectPath = base_url + "/api/getUsersProject" -getUnsignedReportsPath = base_url + "/api/getUnsignedReports" -getChangeUserNamePath = base_url + "/api/changeUserName" -getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport" -removeProjectPath = base_url + "/api/removeProject" - -#ta bort auth i handlern för att få testet att gå igenom +# ta bort auth i handlern för att få testet att gå igenom def test_ProjectRoleChange(): dprint("Testing ProjectRoleChange") localUsername = randomString() localProjectName = randomString() register(localUsername, "username_password") - token = login(localUsername, "username_password").json()[ - "token" - ] + token = login(localUsername, "username_password").json()["token"] # Just checking since this test is built somewhat differently than the others assert token != None, "Login failed" @@ -80,16 +42,17 @@ def test_ProjectRoleChange(): assert response.status_code == 200, "ProjectRoleChange failed" gprint("test_ProjectRoleChange successful") - + def test_get_user_projects(): + username = "user2" + password = "123" dprint("Testing get user projects") - loginResponse = login("user2", "123") + loginResponse = login(username, password) # Check if the user is added to the project response = requests.get( - getUserProjectsPath, - json={"username": "user2"}, + getUserProjectsPath + "/" + username, headers={"Authorization": "Bearer " + loginResponse.json()["token"]}, ) dprint(response.text) @@ -98,26 +61,6 @@ def test_get_user_projects(): gprint("test_get_user_projects successful") -# Posts the username and password to the register endpoint -def register(username: string, password: string): - dprint("Registering with username: ", username, " and password: ", password) - response = requests.post( - registerPath, json={"username": username, "password": password} - ) - dprint(response.text) - return response - - -# Posts the username and password to the login endpoint -def login(username: string, password: string): - dprint("Logging in with username: ", username, " and password: ", password) - response = requests.post( - loginPath, json={"username": username, "password": password} - ) - dprint(response.text) - return response - - # Test function to login def test_login(): response = login(username, "always_same") @@ -133,6 +76,7 @@ def test_create_user(): assert response.status_code == 200, "Registration failed" gprint("test_create_user successful") + # Test function to add a project def test_add_project(): loginResponse = login(username, "always_same") @@ -146,6 +90,7 @@ def test_add_project(): assert response.status_code == 200, "Add project failed" gprint("test_add_project successful") + # Test function to submit a report def test_submit_report(): token = login(username, "always_same").json()["token"] @@ -167,6 +112,7 @@ def test_submit_report(): assert response.status_code == 200, "Submit report failed" gprint("test_submit_report successful") + # Test function to get a weekly report def test_get_weekly_report(): token = login(username, "always_same").json()["token"] @@ -194,91 +140,58 @@ def test_get_project(): # Test function to add a user to a project def test_add_user_to_project(): - # Log in as a site admin - admin_username = randomString() - admin_password = "admin_password" - dprint( - "Registering with username: ", admin_username, " and password: ", admin_password - ) - response = requests.post( - registerPath, json={"username": admin_username, "password": admin_password} - ) - dprint(response.text) + # User to create + pm_user = "user" + randomString() + pm_passwd = "password" - admin_token = login(admin_username, admin_password).json()["token"] - response = requests.post( - promoteToAdminPath, - json={"username": admin_username}, - headers={"Authorization": "Bearer " + admin_token}, - ) - dprint(response.text) - assert response.status_code == 200, "Promote to site admin failed" - dprint("Admin promoted to site admin successfully") + # User to add + member_user = "member" + randomString() + member_passwd = "password" - # Create a new user to add to the project - new_user = randomString() - register(new_user, "new_user_password") + # Name of the project to be created + project_name = "project" + randomString() - # Add the new user to the project as a member - response = requests.put( - addUserToProjectPath, - json={"projectName": projectName, "username": new_user, "role": "member"}, - headers={"Authorization": "Bearer " + admin_token}, - ) + pm_token = register_and_login(pm_user, pm_passwd) + register(member_user, member_passwd) - dprint(response.text) + response = create_project(pm_token, project_name) + assert response.status_code == 200, "Create project failed" + + # Promote the user to project manager + response = addToProject(pm_token, member_user, project_name) assert response.status_code == 200, "Add user to project failed" - gprint("test_add_user_to_project successful") + # Test function to sign a report def test_sign_report(): - # Create a project manager user - project_manager = randomString() - register(project_manager, "project_manager_password") + # Pm user + pm_username = "pm" + randomString() + pm_password = "admin_password2" - # Register an admin - admin_username = randomString() - admin_password = "admin_password2" - dprint( - "Registering with username: ", admin_username, " and password: ", admin_password - ) - response = requests.post( - registerPath, json={"username": admin_username, "password": admin_password} - ) - dprint(response.text) + # User to add + member_user = "member" + randomString() + member_passwd = "password" - # Log in as the admin - admin_token = login(admin_username, admin_password).json()["token"] - response = requests.post( - promoteToAdminPath, - json={"username": admin_username}, - headers={"Authorization": "Bearer " + admin_token}, - ) + # Name of the project to be created + project_name = "project" + randomString() - response = requests.put( - addUserToProjectPath, - json={ - "projectName": projectName, - "username": project_manager, - "role": "project_manager", - }, - headers={"Authorization": "Bearer " + admin_token}, - ) - assert response.status_code == 200, "Add project manager to project failed" - dprint("Project manager added to project successfully") + # Register and get the tokens for both users + pm_token = register_and_login(pm_username, pm_password) + member_token = register_and_login(member_user, member_passwd) - # Log in as the project manager - project_manager_token = login(project_manager, "project_manager_password").json()[ - "token" - ] + # Create the project + response = create_project(pm_token, project_name) + assert response.status_code == 200, "Create project failed" + + # Add the user to the project + response = addToProject(pm_token, member_user, project_name) # Submit a report for the project - token = login(username, "always_same").json()["token"] - response = requests.post( - submitReportPath, - json={ - "projectName": projectName, - "week": 2, + response = submitReport( + member_token, + { + "projectName": project_name, + "week": 1, "developmentTime": 10, "meetingTime": 5, "adminTime": 5, @@ -286,54 +199,40 @@ def test_sign_report(): "studyTime": 10, "testingTime": 10, }, - headers={"Authorization": "Bearer " + token}, ) assert response.status_code == 200, "Submit report failed" - dprint("Submit report successful") # Retrieve the report ID - response = requests.get( - getWeeklyReportPath, - headers={"Authorization": "Bearer " + token}, - params={"username": username, "projectName": projectName, "week": 1}, - ) - dprint(response.text) - report_id = response.json()["reportId"] + report_id = getReport(member_token, member_user, project_name)["reportId"] # Sign the report as the project manager - response = requests.put( - signReportPath + "/" + str(report_id), - headers={"Authorization": "Bearer " + project_manager_token}, - ) + response = signReport(pm_token, report_id) assert response.status_code == 200, "Sign report failed" dprint("Sign report successful") # Retrieve the report ID again for confirmation - response = requests.get( - getWeeklyReportPath, - headers={"Authorization": "Bearer " + token}, - params={"username": username, "projectName": projectName, "week": 1}, - ) - dprint(response.text) + report_id = getReport(member_token, member_user, project_name)["reportId"] + assert report_id != None, "Get report failed" gprint("test_sign_report successful") + # Test function to get weekly reports for a user in a project -def test_get_weekly_reports_user(): +def test_get_all_weekly_reports(): # Log in as the user token = login(username, "always_same").json()["token"] # Get weekly reports for the user in the project response = requests.get( - getWeeklyReportsUserPath + "/" + projectName, + getAllWeeklyReportsPath + "/" + projectName, headers={"Authorization": "Bearer " + token}, + params={"targetUser": username}, ) - + dprint(response.text) assert response.status_code == 200, "Get weekly reports for user failed" gprint("test_get_weekly_reports_user successful") - # Test function to check if a user is a project manager def test_check_if_project_manager(): # Log in as the user @@ -344,11 +243,12 @@ def test_check_if_project_manager(): checkIfProjectManagerPath + "/" + projectName, headers={"Authorization": "Bearer " + token}, ) - + dprint(response.text) assert response.status_code == 200, "Check if project manager failed" gprint("test_check_if_project_manager successful") + def test_ensure_manager_of_created_project(): # Create a new user to add to the project newUser = "karen_" + randomString() @@ -372,6 +272,7 @@ def test_ensure_manager_of_created_project(): assert response.json()["isProjectManager"] == True, "User is not project manager" gprint("test_ensure_admin_of_created_project successful") + def test_change_user_name(): # Register a new user new_user = randomString() @@ -391,7 +292,7 @@ def test_change_user_name(): ) admin_token = login(admin_username, admin_password).json()["token"] - # Promote to admin + # Promote to admin response = requests.post( promoteToAdminPath, json={"username": admin_username}, @@ -409,6 +310,7 @@ def test_change_user_name(): assert response.status_code == 200, "Change user name failed" gprint("test_change_user_name successful") + def test_list_all_users_project(): # Log in as a user who is a member of the project admin_username = randomString() @@ -432,11 +334,12 @@ def test_list_all_users_project(): # Make a request to list all users associated with the project response = requests.get( getUsersProjectPath + "/" + projectName, - headers={"Authorization": "Bearer " + admin_token}, + headers={"Authorization": "Bearer " + admin_token}, ) assert response.status_code == 200, "List all users project failed" gprint("test_list_all_users_project sucessful") + def test_update_weekly_report(): # Log in as the user token = login(username, "always_same").json()["token"] @@ -502,20 +405,99 @@ def test_remove_project(): assert response.status_code == 200, "Remove project failed" gprint("test_remove_project successful") + def test_get_unsigned_reports(): # Log in as the user - token = login("user2", "123").json()["token"] + token = login("user2", "123").json()["token"] - # Make a request to get all unsigned reports - response = requests.get( - getUnsignedReportsPath + "/" + projectName, - headers={"Authorization": "Bearer " + token}, - ) - assert response.status_code == 200, "Get unsigned reports failed" - gprint("test_get_unsigned_reports successful") + # Make a request to get all unsigned reports + response = requests.get( + getUnsignedReportsPath + "/" + projectName, + headers={"Authorization": "Bearer " + token}, + ) + assert response.status_code == 200, "Get unsigned reports failed" + gprint("test_get_unsigned_reports successful") + + +def test_get_other_users_report_as_pm(): + # Create user + user = randomString() + register(user, "password") + + # Create project + project = randomString() + pm_token = login(user, "password").json()["token"] + response = requests.post( + addProjectPath, + json={"name": project, "description": "This is a project"}, + headers={"Authorization": "Bearer " + pm_token}, + ) + assert response.status_code == 200, "Add project failed" + + # Create other user + other_user = randomString() + register(other_user, "password") + user_token = login(other_user, "password").json()["token"] + + # Add other user to project + response = requests.put( + addUserToProjectPath + "/" + project, + headers={"Authorization": "Bearer " + pm_token}, # note pm_token + params={"userName": other_user}, + ) + assert response.status_code == 200, "Add user to project failed" + + # Submit report as other user + response = requests.post( + submitReportPath, + json={ + "projectName": project, + "week": 1, + "developmentTime": 10, + "meetingTime": 5, + "adminTime": 5, + "ownWorkTime": 10, + "studyTime": 10, + "testingTime": 10, + }, + headers={"Authorization": "Bearer " + user_token}, + ) + assert response.status_code == 200, "Submit report failed" + + # Get report as project manager + response = requests.get( + getWeeklyReportPath, + headers={"Authorization": "Bearer " + pm_token}, + params={"targetUser": other_user, "projectName": project, "week": 1}, + ) + assert response.status_code == 200, "Get weekly report failed" + + +def test_promote_to_manager(): + # User to create + pm_user = "user" + randomString() + pm_passwd = "password" + + # User to promote + member_user = "member" + randomString() + member_passwd = "password" + + # Name of the project to be created + project_name = "project" + randomString() + + pm_token = register_and_login(pm_user, pm_passwd) + member_token = register_and_login(member_user, member_passwd) + + response = create_project(pm_token, project_name) + assert response.status_code == 200, "Create project failed" + + # Promote the user to project manager + response = promoteToManager(pm_token, member_user, project_name) + assert response.status_code == 200, "Promote to manager failed" if __name__ == "__main__": + test_promote_to_manager() test_remove_project() test_get_user_projects() test_create_user() @@ -526,7 +508,7 @@ if __name__ == "__main__": test_get_project() test_sign_report() test_add_user_to_project() - test_get_weekly_reports_user() + test_get_all_weekly_reports() test_check_if_project_manager() test_ProjectRoleChange() test_ensure_manager_of_created_project() @@ -534,4 +516,4 @@ if __name__ == "__main__": test_list_all_users_project() test_change_user_name() test_update_weekly_report() - \ No newline at end of file + test_get_other_users_report_as_pm()