Compare commits

..

No commits in common. "db6fdf3c2914fb5afd9890d0b180429726406434" and "46eb3c76a8aa83aff5413a5c389c8c019da7c998" have entirely different histories.

31 changed files with 644 additions and 1143 deletions

1
.gitignore vendored
View file

@ -14,7 +14,6 @@ diagram.puml
backend/*.png backend/*.png
backend/*.jpg backend/*.jpg
backend/*.svg backend/*.svg
__pycache__
/go.work.sum /go.work.sum
/package-lock.json /package-lock.json

View file

@ -108,56 +108,6 @@ 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": { "/promoteToAdmin": {
"post": { "post": {
"security": [ "security": [

View file

@ -17,7 +17,6 @@ type Database interface {
AddUser(username string, password string) error AddUser(username string, password string) error
CheckUser(username string, password string) bool CheckUser(username string, password string) bool
RemoveUser(username string) error RemoveUser(username string) error
RemoveUserFromProject(username string, projectname string) error
PromoteToAdmin(username string) error PromoteToAdmin(username string) error
GetUserId(username string) (int, error) GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error AddProject(name string, description string, username string) error
@ -36,7 +35,7 @@ type Database interface {
GetProject(projectId int) (types.Project, error) GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error) GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error)
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error) IsSiteAdmin(username string) (bool, error)
@ -87,10 +86,6 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN projects ON user_roles.project_id = projects.id JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` 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 // DbConnect connects to the database
func DbConnect(dbpath string) Database { func DbConnect(dbpath string) Database {
// Open the database // Open the database
@ -152,11 +147,6 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
return err 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. // ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error { func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Execute the SQL query to change the user's role // Execute the SQL query to change the user's role
@ -473,8 +463,8 @@ func (d *Db) Migrate() error {
return nil return nil
} }
// GetAllWeeklyReports retrieves weekly reports for a specific user and project. // GetWeeklyReportsUser retrieves weekly reports for a specific user and project.
func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) { func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) {
query := ` query := `
SELECT SELECT
wr.week, wr.week,

View file

@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) {
t.Error("AddWeeklyReport failed:", err) t.Error("AddWeeklyReport failed:", err)
} }
reports, err := db.GetAllWeeklyReports("testuser", "testproject") reports, err := db.GetWeeklyReportsUser("testuser", "testproject")
if err != nil { if err != nil {
t.Error("GetWeeklyReportsUser failed:", err) t.Error("GetWeeklyReportsUser failed:", err)
} }
@ -964,3 +964,4 @@ func TestRemoveProject(t *testing.T) {
} }
} }

View file

@ -10,33 +10,42 @@ import (
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role // AddUserToProjectHandler is a handler that adds a user to a project with a specified role
func AddUserToProjectHandler(c *fiber.Ctx) error { 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) user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string) adminUsername := claims["name"].(string)
log.Info("Admin username from claims:", adminUsername)
project := c.Params("projectName") isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil { if err != nil {
log.Info("Error checking if user is project manager:", err) log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
if !isPM { if !isAdmin {
log.Info("User: ", pm_name, " is not a project manager in project: ", project) log.Info("User is not a site admin:", adminUsername)
return c.Status(403).SendString("User is not a project manager") return c.Status(403).SendString("User is not a site admin")
} }
// Add the user to the project with the specified role // Add the user to the project with the specified role
err = db.GetDb(c).AddUserToProject(username, project, "member") err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role)
if err != nil { if err != nil {
log.Info("Error adding user to project:", err) log.Info("Error adding user to project:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
// Return success message // Return success message
log.Info("User : ", username, " added to project: ", project) log.Info("User added to project successfully:", requestData.Username)
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }

View file

@ -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"
)
// @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)
}

View file

@ -1,40 +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 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)
}

View file

@ -1,56 +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"
)
// 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)
}

View file

@ -16,17 +16,11 @@ func GetWeeklyReport(c *fiber.Ctx) error {
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string) username := claims["name"].(string)
log.Info("Getting weekly report for: ", username)
// Extract project name and week from query parameters // Extract project name and week from query parameters
projectName := c.Query("projectName") projectName := c.Query("projectName")
week := c.Query("week") 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 == "" { if projectName == "" || week == "" {
log.Info("Missing project name or week number") log.Info("Missing project name or week number")
@ -40,20 +34,8 @@ func GetWeeklyReport(c *fiber.Ctx) error {
return c.Status(400).SendString("Invalid week number") 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 // Call the database function to get the weekly report
report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt) report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt)
if err != nil { if err != nil {
log.Info("Error getting weekly report from db:", err) log.Info("Error getting weekly report from db:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())

View file

@ -0,0 +1,36 @@
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)
}

View file

@ -119,9 +119,6 @@ func main() {
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
api.Post("/project", projects.CreateProject) api.Post("/project", projects.CreateProject)
api.Post("/ProjectRoleChange", projects.ProjectRoleChange) 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("/removeProject/:projectName", projects.RemoveProject)
api.Delete("/project/:projectID", projects.DeleteProject) api.Delete("/project/:projectID", projects.DeleteProject)
@ -129,9 +126,10 @@ func main() {
// reportGroup := api.Group("/report") // Not currently in use // reportGroup := api.Group("/report") // Not currently in use
api.Get("/getWeeklyReport", reports.GetWeeklyReport) api.Get("/getWeeklyReport", reports.GetWeeklyReport)
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports) api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler)
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
api.Put("/signReport/:reportId", reports.SignReport) api.Put("/signReport/:reportId", reports.SignReport)
api.Put("/addUserToProject", projects.AddUserToProjectHandler)
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
// Announce the port we are listening on and start the server // Announce the port we are listening on and start the server

View file

@ -1,9 +1,8 @@
import { AddMemberInfo } from "../Components/AddMember"; import { NewProjMember } from "../Components/AddMember";
import { ProjectRoleChange } from "../Components/ChangeRole"; import { ProjectRoleChange } from "../Components/ChangeRole";
import { projectTimes } from "../Components/GetProjectTimes"; import { projectTimes } from "../Components/GetProjectTimes";
import { ProjectMember } from "../Components/GetUsersInProject"; import { ProjectMember } from "../Components/GetUsersInProject";
import { import {
UpdateWeeklyReport,
NewWeeklyReport, NewWeeklyReport,
NewUser, NewUser,
User, User,
@ -88,31 +87,16 @@ interface API {
token: string, token: string,
): Promise<APIResponse<string>>; ): Promise<APIResponse<string>>;
/** /** Gets a weekly report for a specific user, project and week
* Updates a weekly report.
* @param {UpdateWeeklyReport} weeklyReport The updated weekly report object.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<string>>} A promise containing the API response with the updated report.
*/
updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>>;
/** 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} projectName The name of the project.
* @param {string} week The week number. * @param {string} week The week number.
* @param {string} token The authentication token. * @param {string} token The authentication token.
* @param {string} targetUser The username of the target user. Defaults to token user.
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report. * @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
*/ */
getWeeklyReport( getWeeklyReport(
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>>; ): Promise<APIResponse<WeeklyReport>>;
/** /**
@ -122,10 +106,9 @@ interface API {
* @param {string} token The token of the user * @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports * @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/ */
getAllWeeklyReportsForUser( getWeeklyReportsForUser(
projectName: string, projectName: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>>; ): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user /** Gets all the projects of a user
@ -164,17 +147,6 @@ interface API {
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<ProjectMember[]>>; ): Promise<APIResponse<ProjectMember[]>>;
/** Gets all unsigned reports in a project.
* @param {string} projectName The name of the project.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<WeeklyReport[]>>} A promise resolving to an API response containing the list of unsigned reports.
*/
getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>>;
/** /**
* Changes the username of a user in the database. * Changes the username of a user in the database.
* @param {StrNameChange} data The object containing the previous and new username. * @param {StrNameChange} data The object containing the previous and new username.
@ -197,13 +169,7 @@ interface API {
): Promise<APIResponse<void>>; ): Promise<APIResponse<void>>;
addUserToProject( addUserToProject(
addMemberInfo: AddMemberInfo, user: NewProjMember,
token: string,
): Promise<APIResponse<void>>;
removeUserFromProject(
user: string,
project: string,
token: string, token: string,
): Promise<APIResponse<void>>; ): Promise<APIResponse<void>>;
@ -220,19 +186,6 @@ interface API {
* @param {string} token The authentication token * @param {string} token The authentication token
*/ */
signReport(reportId: number, token: string): Promise<APIResponse<string>>; signReport(reportId: number, token: string): Promise<APIResponse<string>>;
/**
* 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<APIResponse<string>} A promise resolving to an API response.
*/
promoteToPm(
userName: string,
projectName: string,
token: string,
): Promise<APIResponse<string>>;
} }
/** An instance of the API */ /** An instance of the API */
@ -342,20 +295,18 @@ export const api: API = {
}, },
async addUserToProject( async addUserToProject(
addMemberInfo: AddMemberInfo, user: NewProjMember,
token: string, token: string,
): Promise<APIResponse<void>> { ): Promise<APIResponse<void>> {
try { try {
const response = await fetch( const response = await fetch("/api/addUserToProject", {
`/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, method: "PUT",
{ headers: {
method: "PUT", "Content-Type": "application/json",
headers: { Authorization: "Bearer " + token,
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
}, },
); body: JSON.stringify(user),
});
if (!response.ok) { if (!response.ok) {
return { success: false, message: "Failed to add member" }; return { success: false, message: "Failed to add member" };
@ -367,31 +318,6 @@ export const api: API = {
} }
}, },
async removeUserFromProject(
user: string,
project: string,
token: string,
): Promise<APIResponse<void>> {
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<APIResponse<string>> { async renewToken(token: string): Promise<APIResponse<string>> {
try { try {
const response = await fetch("/api/loginrenew", { const response = await fetch("/api/loginrenew", {
@ -532,46 +458,14 @@ export const api: API = {
} }
}, },
async updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/updateWeeklyReport", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(weeklyReport),
});
if (!response.ok) {
return {
success: false,
message: "Failed to update weekly report",
};
}
const data = await response.text();
return { success: true, message: data };
} catch (e) {
return {
success: false,
message: "Failed to update weekly report",
};
}
},
async getWeeklyReport( async getWeeklyReport(
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>> { ): Promise<APIResponse<WeeklyReport>> {
try { try {
const response = await fetch( const response = await fetch(
`/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, `/api/getWeeklyReport?projectName=${projectName}&week=${week}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
@ -592,22 +486,18 @@ export const api: API = {
} }
}, },
async getAllWeeklyReportsForUser( async getWeeklyReportsForUser(
projectName: string, projectName: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>> { ): Promise<APIResponse<WeeklyReport[]>> {
try { try {
const response = await fetch( const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
`/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, method: "GET",
{ headers: {
method: "GET", "Content-Type": "application/json",
headers: { Authorization: "Bearer " + token,
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
}, },
); });
if (!response.ok) { if (!response.ok) {
return { return {
@ -731,38 +621,6 @@ export const api: API = {
} }
}, },
async getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getUnsignedReports/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return {
success: false,
message:
"Failed to get unsigned reports for project: Response code " +
response.status,
};
} else {
const data = (await response.json()) as WeeklyReport[];
return { success: true, data };
}
} catch (e) {
return {
success: false,
message: "Failed to get unsigned reports for project, unknown error",
};
}
},
async changeUserName( async changeUserName(
data: StrNameChange, data: StrNameChange,
token: string, token: string,
@ -839,35 +697,4 @@ export const api: API = {
return { success: false, message: "Failed to sign report" }; return { success: false, message: "Failed to sign report" };
} }
}, },
async promoteToPm(
userName: string,
projectName: string,
token: string,
): Promise<APIResponse<string>> {
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" };
},
}; };

View file

@ -1,35 +1,44 @@
import { api } from "../API/API"; import { APIResponse, api } from "../API/API";
export interface AddMemberInfo { export interface NewProjMember {
userName: string; username: string;
projectName: string; role: string;
projectname: string;
} }
/** /**
* Tries to add a member to a project * Tries to add a member to a project
* @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name * @param {Object} props - A NewProjMember
* @returns {Promise<void>} * @returns {boolean} True if added, false if not
*/ */
async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> { function AddMember(props: { memberToAdd: NewProjMember }): boolean {
if (props.memberToAdd.userName === "") { let added = false;
alert("You must choose at least one user to add"); if (
return; props.memberToAdd.username === "" ||
props.memberToAdd.role === "" ||
props.memberToAdd.projectname === ""
) {
alert("All fields must be filled before adding");
return added;
} }
try { api
const response = await api.addUserToProject( .addUserToProject(
props.memberToAdd, props.memberToAdd,
localStorage.getItem("accessToken") ?? "", localStorage.getItem("accessToken") ?? "",
); )
if (response.success) { .then((response: APIResponse<void>) => {
alert(`[${props.memberToAdd.userName}] added`); if (response.success) {
} else { alert("Member added");
alert(`[${props.memberToAdd.userName}] not added`); added = true;
console.error(response.message); } else {
} alert("Member not added");
} catch (error) { console.error(response.message);
alert(`[${props.memberToAdd.userName}] not added`); }
console.error("An error occurred during member add:", error); })
} .catch((error) => {
console.error("An error occurred during member add:", error);
});
return added;
} }
export default AddMember; export default AddMember;

View file

@ -1,10 +1,37 @@
import { useState } from "react"; import { useState } from "react";
import { api } from "../API/API"; import { APIResponse, api } from "../API/API";
import { NewProject } from "../Types/goTypes"; import { NewProject } from "../Types/goTypes";
import InputField from "./InputField"; import InputField from "./InputField";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; 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<void>) => {
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. * Provides UI for adding a project to the system.
* @returns {JSX.Element} - Returns the component UI for adding a project * @returns {JSX.Element} - Returns the component UI for adding a project
@ -13,33 +40,6 @@ function AddProject(): JSX.Element {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
/**
* Tries to add a project to the system
*/
const handleCreateProject = async (): Promise<void> => {
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 ( return (
<div className="flex flex-col h-fit w-screen items-center justify-center"> <div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
@ -47,7 +47,10 @@ 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" 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) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleCreateProject(); CreateProject({
name: name,
description: description,
});
}} }}
> >
<img <img

View file

@ -1,86 +1,71 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import Button from "./Button"; import Button from "./Button";
import AddMember, { AddMemberInfo } from "./AddMember";
import BackButton from "./BackButton";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetAllUsers from "./GetAllUsers"; import GetAllUsers from "./GetAllUsers";
import AddMember, { NewProjMember } from "./AddMember";
import BackButton from "./BackButton";
/** /**
* Provides UI for adding a member to a project. * Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member * @returns {JSX.Element} - Returns the component UI for adding a member
*/ */
function AddUserToProject(props: { projectName: string }): JSX.Element { function AddUserToProject(props: { projectName: string }): JSX.Element {
const [names, setNames] = useState<string[]>([]); const [name, setName] = useState("");
const [users, setUsers] = useState<string[]>([]); const [users, setUsers] = useState<string[]>([]);
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]); const [role, setRole] = useState("");
// Gets all users and project members for filtering
GetAllUsers({ setUsersProp: setUsers }); 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 handleClick = (): boolean => {
const handleAddClick = async (): Promise<void> => { const newMember: NewProjMember = {
if (names.length === 0) username: name,
alert("You have to choose at least one user to add"); projectname: props.projectName,
for (const name of names) { role: role,
const newMember: AddMemberInfo = { };
userName: name, return AddMember({ memberToAdd: newMember });
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 ( return (
<div className="border-4 border-black bg-white flex flex-col items-center pt-10 rounded-3xl content-center pl-20 pr-20 h-[63vh] w-[50] overflow-auto"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center rounded-3xl content-center pl-20 pr-20 h-[75vh] w-[50vh]">
<h1 className="text-center font-bold text-[36px] pb-10"> <p className="pb-4 mb-2 text-center font-bold text-[18px]">
{props.projectName} User chosen: [{name}]
</h1>
<p className="p-1 text-center font-bold text-[26px]">
Choose users to add:
</p> </p>
<div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto h-[26vh] w-[26vh]"> <p className="pb-4 mb-2 text-center font-bold text-[18px]">
Role chosen: [{role}]
</p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
Project chosen: [{props.projectName}]
</p>
<p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16] overflow-auto">
<ul className="text-center items-center font-medium space-y-2">
<li
className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("member");
}}
>
{"Member"}
</li>
<li
className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("project_manager");
}}
>
{"Project manager"}
</li>
</ul>
</div>
<p className="p-1">Choose user:</p>
<div className="border-2 border-black p-2 rounded-xl text-center overflow-scroll h-[26vh] w-[26vh]">
<ul className="text-center font-medium space-y-2"> <ul className="text-center font-medium space-y-2">
<div></div> <div></div>
{users.map((user) => ( {users.map((user) => (
<li <li
className={ className="items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
names.includes(user)
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 hover:text-slate-100 hover:cursor-pointer"
}
key={user} key={user}
value={user} value={user}
onClick={() => { onClick={() => {
handleUserClick(user); setName(user);
}} }}
> >
<span>{user}</span> <span>{user}</span>
@ -88,16 +73,13 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
))} ))}
</ul> </ul>
</div> </div>
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]"> <div className="flex space-x-5 items-center justify-between">
Number of users to be added: {names.length}
</p>
<div className="space-x-10 items-center">
<Button <Button
text="Add" text="Add"
onClick={(): void => { onClick={(): void => {
void handleAddClick(); handleClick();
}} }}
type="button" type="submit"
/> />
<BackButton /> <BackButton />
</div> </div>

View file

@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element {
useEffect(() => { useEffect(() => {
const getWeeklyReports = async (): Promise<void> => { const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllWeeklyReportsForUser( const response = await api.getWeeklyReportsForUser(
projectName ?? "", projectName ?? "",
token, token,
); );

View file

@ -1,9 +1,8 @@
//Info: This component is used to display all the time reports for a project. It will display the week number, //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. //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 { useEffect, useState } from "react";
import { WeeklyReport } from "../Types/goTypes"; import { NewWeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/** /**
* Renders a component that displays all the time reports for a specific project. * Renders a component that displays all the time reports for a specific project.
@ -12,15 +11,15 @@ import { api } from "../API/API";
function AllTimeReportsInProject(): JSX.Element { function AllTimeReportsInProject(): JSX.Element {
const { username } = useParams(); const { username } = useParams();
const { projectName } = useParams(); const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]); const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]);
/* // Call getProjects when the component mounts
useEffect(() => { useEffect(() => {
const getWeeklyReports = async (): Promise<void> => { const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllWeeklyReportsForUser( const response = await api.getWeeklyReportsForUser(
projectName ?? "", projectName ?? "",
token, token,
username ?? "",
); );
console.log(response); console.log(response);
if (response.success) { if (response.success) {
@ -28,10 +27,41 @@ function AllTimeReportsInProject(): JSX.Element {
} else { } else {
console.error(response.message); console.error(response.message);
} }
}; }; */
// Mock data
const getWeeklyReports = async (): Promise<void> => {
// 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(); void getWeeklyReports();
}, [projectName, username]); }, []);
return ( return (
<> <>

View file

@ -2,7 +2,7 @@ import { useState } from "react";
import Button from "./Button"; import Button from "./Button";
import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoleView(props: { export default function ChangeRoles(props: {
projectName: string; projectName: string;
username: string; username: string;
}): JSX.Element { }): JSX.Element {

View file

@ -2,11 +2,8 @@ import { APIResponse, api } from "../API/API";
import { StrNameChange } from "../Types/goTypes"; import { StrNameChange } from "../Types/goTypes";
function ChangeUsername(props: { nameChange: StrNameChange }): void { function ChangeUsername(props: { nameChange: StrNameChange }): void {
if ( if (props.nameChange.newName === "") {
props.nameChange.newName === "" || alert("You have to select a new name");
props.nameChange.newName === props.nameChange.prevName
) {
alert("You have to give a new name\n\nName not changed");
return; return;
} }
api api
@ -16,7 +13,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void {
alert("Name changed successfully"); alert("Name changed successfully");
location.reload(); location.reload();
} else { } else {
alert("Name not changed, name could be taken"); alert("Name not changed");
console.error(response.message); console.error(response.message);
} }
}) })

View file

@ -1,7 +1,12 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
import { WeeklyReport } from "../Types/goTypes"; 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. * Renders a component that displays the projects a user is a part of and links to the projects start-page.
@ -9,25 +14,80 @@ import { WeeklyReport } from "../Types/goTypes";
*/ */
function DisplayUserProject(): JSX.Element { function DisplayUserProject(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]); const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]);
//const navigate = useNavigate(); //const navigate = useNavigate();
useEffect(() => {
const getUnsignedReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUnsignedReportsInProject(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setUnsignedReports(response.data ?? []);
} else {
console.error(response.message);
}
};
// const getUnsignedReports = async (): Promise<void> => {
// 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<void> => {
// 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<void> => {
// 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(); void getUnsignedReports();
}, [projectName]); // Include 'projectName' in the dependency array }, []);
return ( return (
<> <>
@ -35,40 +95,32 @@ function DisplayUserProject(): JSX.Element {
All Unsigned Reports In: {projectName}{" "} All Unsigned Reports In: {projectName}{" "}
</h1> </h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{unsignedReports.map((unsignedReport: WeeklyReport, index: number) => ( {unsignedReports.map(
<h1 key={index} className="border-b-2 border-black w-full"> (unsignedReport: UnsignedReports, index: number) => (
<div className="flex justify-between"> <h1 key={index} className="border-b-2 border-black w-full">
<div className="flex"> <div className="flex justify-between">
<span className="ml-6 mr-2 font-bold">UserID:</span> <div className="flex">
<h1>{unsignedReport.userId}</h1> <h1>{unsignedReport.username}</h1>
<span className="ml-6 mr-2 font-bold">Week:</span> <span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1> <h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Total Time:</span> <span className="ml-6 mr-2 font-bold">Signed:</span>
<h1> <h1>NO</h1>
{unsignedReport.developmentTime + </div>
unsignedReport.meetingTime + <div className="flex">
unsignedReport.adminTime + <div className="ml-auto flex space-x-4">
unsignedReport.ownWorkTime + <Link
unsignedReport.studyTime + to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`}
unsignedReport.testingTime} >
</h1> <h1 className="underline cursor-pointer font-bold">
<span className="ml-6 mr-2 font-bold">Signed:</span> View Report
<h1>NO</h1> </h1>
</div> </Link>
<div className="flex"> </div>
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.userId}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report
</h1>
</Link>
</div> </div>
</div> </div>
</div> </h1>
</h1> ),
))} )}
</div> </div>
</> </>
); );

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes"; import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
@ -22,7 +22,6 @@ export default function GetWeeklyReport(): JSX.Element {
projectName: string; projectName: string;
fetchedWeek: string; fetchedWeek: string;
}>(); }>();
const username = localStorage.getItem("userName") ?? "";
console.log(projectName, fetchedWeek); console.log(projectName, fetchedWeek);
useEffect(() => { useEffect(() => {
@ -61,9 +60,8 @@ export default function GetWeeklyReport(): JSX.Element {
void fetchWeeklyReport(); void fetchWeeklyReport();
}, [projectName, fetchedWeek, token]); }, [projectName, fetchedWeek, token]);
const handleUpdateWeeklyReport = async (): Promise<void> => { const handleNewWeeklyReport = async (): Promise<void> => {
const updateWeeklyReport: UpdateWeeklyReport = { const newWeeklyReport: NewWeeklyReport = {
userName: username,
projectName: projectName ?? "", projectName: projectName ?? "",
week, week,
developmentTime, developmentTime,
@ -74,7 +72,7 @@ export default function GetWeeklyReport(): JSX.Element {
testingTime, testingTime,
}; };
await api.updateWeeklyReport(updateWeeklyReport, token); await api.submitWeeklyReport(newWeeklyReport, token);
}; };
const navigate = useNavigate(); const navigate = useNavigate();
@ -91,8 +89,7 @@ export default function GetWeeklyReport(): JSX.Element {
return; return;
} }
e.preventDefault(); e.preventDefault();
void handleUpdateWeeklyReport(); void handleNewWeeklyReport();
alert("Changes submitted");
navigate(-1); navigate(-1);
}} }}
> >
@ -131,12 +128,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -160,12 +152,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -189,12 +176,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -218,12 +200,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -247,12 +224,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -276,12 +248,7 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />

View file

@ -1,8 +1,8 @@
import Button from "./Button"; import Button from "./Button";
import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin"; import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react"; import { useState } from "react";
import ChangeRoleView from "./ChangeRoleView"; import ChangeRoleView from "./ChangeRoleView";
import RemoveUserFromProj from "./RemoveUserFromProj";
function MemberInfoModal(props: { function MemberInfoModal(props: {
projectName: string; projectName: string;
@ -20,7 +20,7 @@ function MemberInfoModal(props: {
}; };
return ( return (
<div <div
className="fixed inset-10 bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col"> <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
@ -42,16 +42,13 @@ function MemberInfoModal(props: {
<UserProjectListAdmin username={props.username} /> <UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6"> <div className="items-center space-x-6">
<Button <Button
text={"Remove"} text={"Delete"}
onClick={function (): void { onClick={function (): void {
if ( if (
window.confirm( window.confirm("Are you sure you want to delete this user?")
"Are you sure you want to remove this user from the project?",
)
) { ) {
RemoveUserFromProj({ DeleteUser({
userToRemove: props.username, usernameToDelete: props.username,
projectName: props.projectName,
}); });
} }
}} }}

View file

@ -139,12 +139,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -168,12 +163,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -197,12 +187,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -226,12 +211,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -255,12 +235,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -284,12 +259,7 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if ( if (!/\d/.test(keyValue) && keyValue !== "Backspace")
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />

View file

@ -29,7 +29,6 @@ export default function OtherUsersTR(): JSX.Element {
projectName ?? "", projectName ?? "",
fetchedWeek?.toString() ?? "0", fetchedWeek?.toString() ?? "0",
token, token,
username ?? "",
); );
if (response.success) { if (response.success) {
@ -87,7 +86,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime} value={developmentTime === 0 ? "" : developmentTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -99,7 +97,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime} value={meetingTime === 0 ? "" : meetingTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -111,7 +108,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime} value={adminTime === 0 ? "" : adminTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -123,7 +119,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime} value={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -135,7 +130,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime} value={studyTime === 0 ? "" : studyTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -147,7 +141,6 @@ export default function OtherUsersTR(): JSX.Element {
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime} value={testingTime === 0 ? "" : testingTime}
readOnly
/> />
</td> </td>
</tr> </tr>

View file

@ -15,21 +15,17 @@ export default function Register(): JSX.Element {
const [errMessage, setErrMessage] = useState<string>(); const [errMessage, setErrMessage] = useState<string>();
const handleRegister = async (): Promise<void> => { const handleRegister = async (): Promise<void> => {
if (username === "" || password === "") {
alert("Must provide username and password");
return;
}
const newUser: NewUser = { const newUser: NewUser = {
username: username?.replace(/ /g, "") ?? "", username: username ?? "",
password: password ?? "", password: password ?? "",
}; };
const response = await api.registerUser(newUser); const response = await api.registerUser(newUser);
if (response.success) { if (response.success) {
alert(`${newUser.username} added!`); alert("User added!");
setPassword(""); setPassword("");
setUsername(""); setUsername("");
} else { } else {
alert("User not added, name could be taken"); alert("User not added");
setErrMessage(response.message ?? "Unknown error"); setErrMessage(response.message ?? "Unknown error");
console.error(errMessage); console.error(errMessage);
} }

View file

@ -1,41 +0,0 @@
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<void>) => {
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);
});
}

View file

@ -1,45 +1,70 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { api } from "../API/API";
import { projectTimes } from "./GetProjectTimes";
/** /**
* Renders the component for showing total time per role in a project. * Renders the component for showing total time per role in a project.
* @returns JSX.Element * @returns JSX.Element
*/ */
export default function TimePerRole(): JSX.Element { export default function TimePerRole(): JSX.Element {
const [development, setDevelopment] = useState<number>(0); const [developmentTime, setDevelopmentTime] = useState<number>();
const [meeting, setMeeting] = useState<number>(0); const [meetingTime, setMeetingTime] = useState<number>();
const [admin, setAdmin] = useState<number>(0); const [adminTime, setAdminTime] = useState<number>();
const [own_work, setOwnWork] = useState<number>(0); const [ownWorkTime, setOwnWorkTime] = useState<number>();
const [study, setStudy] = useState<number>(0); const [studyTime, setStudyTime] = useState<number>();
const [testing, setTesting] = useState<number>(0); const [testingTime, setTestingTime] = useState<number>();
const token = localStorage.getItem("accessToken") ?? ""; // const token = localStorage.getItem("accessToken") ?? "";
// const username = localStorage.getItem("username") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
// const fetchTimePerRole = async (): Promise<void> => {
// const response = await api.getTimePerRole(
// username,
// projectName ?? "",
// token,
// );
// {
// if (response.success) {
// const report: TimePerRole = response.data ?? {
// PManagerTime: 0,
// SManagerTime: 0,
// DeveloperTime: 0,
// TesterTime: 0,
// };
// } else {
// console.error("Failed to fetch weekly report:", response.message);
// }
// }
interface TimePerActivity {
developmentTime: number;
meetingTime: number;
adminTime: number;
ownWorkTime: number;
studyTime: number;
testingTime: number;
}
const fetchTimePerActivity = async (): Promise<void> => { const fetchTimePerActivity = async (): Promise<void> => {
const response = await api.getProjectTimes(projectName ?? "", token); // Use mock data
{ const report: TimePerActivity = {
if (response.success) { developmentTime: 100,
const report: projectTimes = response.data ?? { meetingTime: 200,
development: 0, adminTime: 300,
meeting: 0, ownWorkTime: 50,
admin: 0, studyTime: 75,
own_work: 0, testingTime: 110,
study: 0, };
testing: 0,
}; // Set the state with the mock data
setDevelopment(report.development); setDevelopmentTime(report.developmentTime);
setMeeting(report.meeting); setMeetingTime(report.meetingTime);
setAdmin(report.admin); setAdminTime(report.adminTime);
setOwnWork(report.own_work); setOwnWorkTime(report.ownWorkTime);
setStudy(report.study); setStudyTime(report.studyTime);
setTesting(report.testing); setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message); await Promise.resolve();
}
}
}; };
useEffect(() => { useEffect(() => {
@ -69,8 +94,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={development} value={developmentTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -80,8 +107,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={meeting} value={meetingTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -91,8 +120,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={admin} value={adminTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -102,8 +133,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={own_work} value={ownWorkTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -113,8 +146,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={study} value={studyTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -124,8 +159,10 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={testing} value={testingTime}
readOnly onKeyDown={(event) => {
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>

View file

@ -28,7 +28,7 @@ function UserInfoModal(props: {
const handleClickChangeName = (): void => { const handleClickChangeName = (): void => {
const nameChange: StrNameChange = { const nameChange: StrNameChange = {
prevName: props.username, prevName: props.username,
newName: newUsername.replace(/ /g, ""), newName: newUsername,
}; };
ChangeUsername({ nameChange: nameChange }); ChangeUsername({ nameChange: nameChange });
}; };

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport } from "../Types/goTypes"; import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
@ -18,7 +18,6 @@ export default function GetOtherUsersReport(): JSX.Element {
const [ownWorkTime, setOwnWorkTime] = useState(0); const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0); const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const [reportId, setReportId] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
@ -46,7 +45,6 @@ export default function GetOtherUsersReport(): JSX.Element {
studyTime: 0, studyTime: 0,
testingTime: 0, testingTime: 0,
}; };
setReportId(report.reportId);
setWeek(report.week); setWeek(report.week);
setDevelopmentTime(report.developmentTime); setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime); setMeetingTime(report.meetingTime);
@ -63,23 +61,30 @@ export default function GetOtherUsersReport(): JSX.Element {
}); });
const handleSignWeeklyReport = async (): Promise<void> => { const handleSignWeeklyReport = async (): Promise<void> => {
await api.signReport(reportId, token); const newWeeklyReport: NewWeeklyReport = {
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
}; };
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<h1 className="text-[30px] font-bold"> <h1 className="text-[30px] font-bold">{username}&apos;s Report</h1>
{" "}
UserId: {username}&apos;s Report
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleSignWeeklyReport(); void handleSignWeeklyReport();
alert("Report successfully signed!");
navigate(-1); navigate(-1);
}} }}
> >
@ -107,10 +112,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={ value={developmentTime === 0 ? "" : developmentTime}
developmentTime === 0 ? "" : developmentTime
}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -121,8 +123,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={meetingTime === 0 ? "" : meetingTime} value={meetingTime === 0 ? "" : meetingTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -133,8 +134,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={adminTime === 0 ? "" : adminTime} value={adminTime === 0 ? "" : adminTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -145,8 +145,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={ownWorkTime === 0 ? "" : ownWorkTime} value={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -157,8 +156,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={studyTime === 0 ? "" : studyTime} value={studyTime === 0 ? "" : studyTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -169,8 +167,7 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
defaultValue={testingTime === 0 ? "" : testingTime} value={testingTime === 0 ? "" : testingTime}
readOnly
/> />
</td> </td>
</tr> </tr>

View file

@ -1,23 +1,61 @@
import requests import requests
import string
import random
# This modules contains helper functions for the tests debug_output = True
from helpers import *
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
print("Running Tests...") 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 # Defined once per test run
username = "user_" + randomString() username = "user_" + randomString()
projectName = "project_" + randomString() projectName = "project_" + randomString()
# The base URL of the API
base_url = "http://localhost:8080"
# ta bort auth i handlern för att få testet att gå igenom # 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
def test_ProjectRoleChange(): def test_ProjectRoleChange():
dprint("Testing ProjectRoleChange") dprint("Testing ProjectRoleChange")
localUsername = randomString() localUsername = randomString()
localProjectName = randomString() localProjectName = randomString()
register(localUsername, "username_password") 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 # Just checking since this test is built somewhat differently than the others
assert token != None, "Login failed" assert token != None, "Login failed"
@ -45,14 +83,13 @@ def test_ProjectRoleChange():
def test_get_user_projects(): def test_get_user_projects():
username = "user2"
password = "123"
dprint("Testing get user projects") dprint("Testing get user projects")
loginResponse = login(username, password) loginResponse = login("user2", "123")
# Check if the user is added to the project # Check if the user is added to the project
response = requests.get( response = requests.get(
getUserProjectsPath + "/" + username, getUserProjectsPath,
json={"username": "user2"},
headers={"Authorization": "Bearer " + loginResponse.json()["token"]}, headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
) )
dprint(response.text) dprint(response.text)
@ -61,6 +98,26 @@ def test_get_user_projects():
gprint("test_get_user_projects successful") 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 # Test function to login
def test_login(): def test_login():
response = login(username, "always_same") response = login(username, "always_same")
@ -76,7 +133,6 @@ def test_create_user():
assert response.status_code == 200, "Registration failed" assert response.status_code == 200, "Registration failed"
gprint("test_create_user successful") gprint("test_create_user successful")
# Test function to add a project # Test function to add a project
def test_add_project(): def test_add_project():
loginResponse = login(username, "always_same") loginResponse = login(username, "always_same")
@ -90,7 +146,6 @@ def test_add_project():
assert response.status_code == 200, "Add project failed" assert response.status_code == 200, "Add project failed"
gprint("test_add_project successful") gprint("test_add_project successful")
# Test function to submit a report # Test function to submit a report
def test_submit_report(): def test_submit_report():
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -112,7 +167,6 @@ def test_submit_report():
assert response.status_code == 200, "Submit report failed" assert response.status_code == 200, "Submit report failed"
gprint("test_submit_report successful") gprint("test_submit_report successful")
# Test function to get a weekly report # Test function to get a weekly report
def test_get_weekly_report(): def test_get_weekly_report():
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -140,58 +194,91 @@ def test_get_project():
# Test function to add a user to a project # Test function to add a user to a project
def test_add_user_to_project(): def test_add_user_to_project():
# User to create # Log in as a site admin
pm_user = "user" + randomString() admin_username = randomString()
pm_passwd = "password" 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 add admin_token = login(admin_username, admin_password).json()["token"]
member_user = "member" + randomString() response = requests.post(
member_passwd = "password" 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")
# Name of the project to be created # Create a new user to add to the project
project_name = "project" + randomString() new_user = randomString()
register(new_user, "new_user_password")
pm_token = register_and_login(pm_user, pm_passwd) # Add the new user to the project as a member
register(member_user, member_passwd) response = requests.put(
addUserToProjectPath,
json={"projectName": projectName, "username": new_user, "role": "member"},
headers={"Authorization": "Bearer " + admin_token},
)
response = create_project(pm_token, project_name) dprint(response.text)
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" assert response.status_code == 200, "Add user to project failed"
gprint("test_add_user_to_project successful")
# Test function to sign a report # Test function to sign a report
def test_sign_report(): def test_sign_report():
# Pm user # Create a project manager user
pm_username = "pm" + randomString() project_manager = randomString()
pm_password = "admin_password2" register(project_manager, "project_manager_password")
# User to add # Register an admin
member_user = "member" + randomString() admin_username = randomString()
member_passwd = "password" 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)
# Name of the project to be created # Log in as the admin
project_name = "project" + randomString() admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
# Register and get the tokens for both users response = requests.put(
pm_token = register_and_login(pm_username, pm_password) addUserToProjectPath,
member_token = register_and_login(member_user, member_passwd) 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")
# Create the project # Log in as the project manager
response = create_project(pm_token, project_name) project_manager_token = login(project_manager, "project_manager_password").json()[
assert response.status_code == 200, "Create project failed" "token"
]
# Add the user to the project
response = addToProject(pm_token, member_user, project_name)
# Submit a report for the project # Submit a report for the project
response = submitReport( token = login(username, "always_same").json()["token"]
member_token, response = requests.post(
{ submitReportPath,
"projectName": project_name, json={
"week": 1, "projectName": projectName,
"week": 2,
"developmentTime": 10, "developmentTime": 10,
"meetingTime": 5, "meetingTime": 5,
"adminTime": 5, "adminTime": 5,
@ -199,33 +286,46 @@ def test_sign_report():
"studyTime": 10, "studyTime": 10,
"testingTime": 10, "testingTime": 10,
}, },
headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "Submit report failed" assert response.status_code == 200, "Submit report failed"
dprint("Submit report successful")
# Retrieve the report ID # Retrieve the report ID
report_id = getReport(member_token, member_user, project_name)["reportId"] response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
report_id = response.json()["reportId"]
# Sign the report as the project manager # Sign the report as the project manager
response = signReport(pm_token, report_id) response = requests.put(
signReportPath + "/" + str(report_id),
headers={"Authorization": "Bearer " + project_manager_token},
)
assert response.status_code == 200, "Sign report failed" assert response.status_code == 200, "Sign report failed"
dprint("Sign report successful") dprint("Sign report successful")
# Retrieve the report ID again for confirmation # Retrieve the report ID again for confirmation
report_id = getReport(member_token, member_user, project_name)["reportId"] response = requests.get(
assert report_id != None, "Get report failed" getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
gprint("test_sign_report successful") gprint("test_sign_report successful")
# Test function to get weekly reports for a user in a project # Test function to get weekly reports for a user in a project
def test_get_all_weekly_reports(): def test_get_weekly_reports_user():
# Log in as the user # Log in as the user
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
# Get weekly reports for the user in the project # Get weekly reports for the user in the project
response = requests.get( response = requests.get(
getAllWeeklyReportsPath + "/" + projectName, getWeeklyReportsUserPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"targetUser": username},
) )
dprint(response.text) dprint(response.text)
@ -233,6 +333,7 @@ def test_get_all_weekly_reports():
gprint("test_get_weekly_reports_user successful") gprint("test_get_weekly_reports_user successful")
# Test function to check if a user is a project manager # Test function to check if a user is a project manager
def test_check_if_project_manager(): def test_check_if_project_manager():
# Log in as the user # Log in as the user
@ -248,7 +349,6 @@ def test_check_if_project_manager():
assert response.status_code == 200, "Check if project manager failed" assert response.status_code == 200, "Check if project manager failed"
gprint("test_check_if_project_manager successful") gprint("test_check_if_project_manager successful")
def test_ensure_manager_of_created_project(): def test_ensure_manager_of_created_project():
# Create a new user to add to the project # Create a new user to add to the project
newUser = "karen_" + randomString() newUser = "karen_" + randomString()
@ -272,7 +372,6 @@ def test_ensure_manager_of_created_project():
assert response.json()["isProjectManager"] == True, "User is not project manager" assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful") gprint("test_ensure_admin_of_created_project successful")
def test_change_user_name(): def test_change_user_name():
# Register a new user # Register a new user
new_user = randomString() new_user = randomString()
@ -310,7 +409,6 @@ def test_change_user_name():
assert response.status_code == 200, "Change user name failed" assert response.status_code == 200, "Change user name failed"
gprint("test_change_user_name successful") gprint("test_change_user_name successful")
def test_list_all_users_project(): def test_list_all_users_project():
# Log in as a user who is a member of the project # Log in as a user who is a member of the project
admin_username = randomString() admin_username = randomString()
@ -339,7 +437,6 @@ def test_list_all_users_project():
assert response.status_code == 200, "List all users project failed" assert response.status_code == 200, "List all users project failed"
gprint("test_list_all_users_project sucessful") gprint("test_list_all_users_project sucessful")
def test_update_weekly_report(): def test_update_weekly_report():
# Log in as the user # Log in as the user
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -405,99 +502,20 @@ def test_remove_project():
assert response.status_code == 200, "Remove project failed" assert response.status_code == 200, "Remove project failed"
gprint("test_remove_project successful") gprint("test_remove_project successful")
def test_get_unsigned_reports(): def test_get_unsigned_reports():
# Log in as the user # 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 # Make a request to get all unsigned reports
response = requests.get( response = requests.get(
getUnsignedReportsPath + "/" + projectName, getUnsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "Get unsigned reports failed" assert response.status_code == 200, "Get unsigned reports failed"
gprint("test_get_unsigned_reports successful") 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__": if __name__ == "__main__":
test_promote_to_manager()
test_remove_project() test_remove_project()
test_get_user_projects() test_get_user_projects()
test_create_user() test_create_user()
@ -508,7 +526,7 @@ if __name__ == "__main__":
test_get_project() test_get_project()
test_sign_report() test_sign_report()
test_add_user_to_project() test_add_user_to_project()
test_get_all_weekly_reports() test_get_weekly_reports_user()
test_check_if_project_manager() test_check_if_project_manager()
test_ProjectRoleChange() test_ProjectRoleChange()
test_ensure_manager_of_created_project() test_ensure_manager_of_created_project()
@ -516,4 +534,4 @@ if __name__ == "__main__":
test_list_all_users_project() test_list_all_users_project()
test_change_user_name() test_change_user_name()
test_update_weekly_report() test_update_weekly_report()
test_get_other_users_report_as_pm()

View file

@ -1,151 +0,0 @@
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},
)