diff --git a/backend/docs/docs.go b/backend/docs/docs.go index c8b020d..7a08b0e 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -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": { "post": { "security": [ diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 22e11e9..6bf6fba 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -17,7 +17,6 @@ type Database interface { AddUser(username string, password string) error CheckUser(username string, password string) bool RemoveUser(username string) error - RemoveUserFromProject(username string, projectname string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error @@ -44,7 +43,6 @@ type Database interface { GetProjectTimes(projectName string) (map[string]int, error) UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error RemoveProject(projectname string) error - GetUserName(id int) (string, error) } // This struct is a wrapper type that holds the database connection @@ -88,10 +86,6 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles JOIN projects ON user_roles.project_id = projects.id WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` -const removeUserFromProjectQuery = `DELETE FROM user_roles - WHERE user_id = (SELECT id FROM users WHERE username = ?) - AND project_id = (SELECT id FROM projects WHERE name = ?)` - // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -153,11 +147,6 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err } -func (d *Db) RemoveUserFromProject(username string, projectname string) error { - _, err := d.Exec(removeUserFromProjectQuery, username, projectname) - return err -} - // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // Execute the SQL query to change the user's role @@ -350,14 +339,9 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } - managerQuery := `SELECT project_id FROM user_roles - WHERE user_id = ? - AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?) - AND p_role = 'project_manager'` - // Retrieve the project ID associated with the project manager var managerProjectID int - err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId) + err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId) if err != nil { return err } @@ -617,9 +601,3 @@ func (d *Db) RemoveProject(projectname string) error { _, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) return err } - -func (d *Db) GetUserName(id int) (string, error) { - var username string - err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id) - return username, err -} diff --git a/backend/internal/handlers/projects/RemoveUserFromProject.go b/backend/internal/handlers/projects/RemoveUserFromProject.go deleted file mode 100644 index 7aefcf8..0000000 --- a/backend/internal/handlers/projects/RemoveUserFromProject.go +++ /dev/null @@ -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) -} diff --git a/backend/internal/handlers/users/GetUserName.go b/backend/internal/handlers/users/GetUserName.go deleted file mode 100644 index 82b6cc8..0000000 --- a/backend/internal/handlers/users/GetUserName.go +++ /dev/null @@ -1,32 +0,0 @@ -package users - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" -) - -// Return the username of a user given their user id -func GetUserName(c *fiber.Ctx) error { - // Check the query params for userId - user_id_string := c.Query("userId") - if user_id_string == "" { - return c.Status(400).SendString("Missing user id") - } - - // Convert to int - user_id, err := strconv.Atoi(user_id_string) - if err != nil { - return c.Status(400).SendString("Invalid user id") - } - - // Get the username from the database - username, err := db.GetDb(c).GetUserName(user_id) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Send the nuclear launch codes to north korea - return c.JSON(fiber.Map{"username": username}) -} diff --git a/backend/main.go b/backend/main.go index 7b19dd9..b5ecacf 100644 --- a/backend/main.go +++ b/backend/main.go @@ -103,7 +103,6 @@ func main() { // userGroup := api.Group("/user") // Not currently in use api.Get("/users/all", users.ListAllUsers) api.Get("/project/getAllUsers", users.GetAllUsersProject) - api.Get("/username", users.GetUserName) api.Post("/login", users.Login) api.Post("/register", users.Register) api.Post("/loginrenew", users.LoginRenew) @@ -122,7 +121,6 @@ func main() { api.Post("/ProjectRoleChange", projects.ProjectRoleChange) api.Put("/promoteToPm/:projectName", projects.PromoteToPm) api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) - api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/project/:projectID", projects.DeleteProject) diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 86ad6dc..c4cf445 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,4 +1,4 @@ -import { AddMemberInfo } from "../Components/AddMember"; +import { NewProjMember } from "../Components/AddMember"; import { ProjectRoleChange } from "../Components/ChangeRole"; import { projectTimes } from "../Components/GetProjectTimes"; import { ProjectMember } from "../Components/GetUsersInProject"; @@ -197,13 +197,7 @@ interface API { ): Promise>; addUserToProject( - addMemberInfo: AddMemberInfo, - token: string, - ): Promise>; - - removeUserFromProject( - user: string, - project: string, + user: NewProjMember, token: string, ): Promise>; @@ -233,12 +227,6 @@ interface API { projectName: string, token: string, ): Promise>; - /** - * Get the username from the id - * @param {number} id The id of the user - * @param {string} token Your token - */ - getUsername(id: number, token: string): Promise>; } /** An instance of the API */ @@ -348,20 +336,18 @@ export const api: API = { }, async addUserToProject( - addMemberInfo: AddMemberInfo, + user: NewProjMember, token: string, ): Promise> { try { - const response = await fetch( - `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, + const response = await fetch("/api/addUserToProject", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, }, - ); + body: JSON.stringify(user), + }); if (!response.ok) { return { success: false, message: "Failed to add member" }; @@ -373,31 +359,6 @@ export const api: API = { } }, - async removeUserFromProject( - user: string, - project: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/removeUserFromProject/${project}?userName=${user}`, - { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - if (!response.ok) { - return { success: false, message: "Failed to remove member" }; - } - } catch (e) { - return { success: false, message: "Failed to remove member" }; - } - return { success: true, message: "Removed member" }; - }, - async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -577,7 +538,7 @@ export const api: API = { ): Promise> { try { const response = await fetch( - `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, + `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser}`, { method: "GET", headers: { @@ -605,7 +566,7 @@ export const api: API = { ): Promise> { try { const response = await fetch( - `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, + `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`, { method: "GET", headers: { @@ -876,25 +837,4 @@ export const api: API = { } return { success: true, message: "User promoted to project manager" }; }, - - async getUsername(id: number, token: string): Promise> { - try { - const response = await fetch(`/api/username?userId=${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to get username" }; - } else { - const data = (await response.json()) as string; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to get username" }; - } - }, }; diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx index d8036b7..194afe8 100644 --- a/frontend/src/Components/AddMember.tsx +++ b/frontend/src/Components/AddMember.tsx @@ -1,35 +1,44 @@ -import { api } from "../API/API"; +import { APIResponse, api } from "../API/API"; -export interface AddMemberInfo { - userName: string; - projectName: string; +export interface NewProjMember { + username: string; + role: string; + projectname: string; } /** * Tries to add a member to a project - * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name - * @returns {Promise} + * @param {Object} props - A NewProjMember + * @returns {boolean} True if added, false if not */ -async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise { - if (props.memberToAdd.userName === "") { - alert("You must choose at least one user to add"); - return; +function AddMember(props: { memberToAdd: NewProjMember }): boolean { + let added = false; + if ( + props.memberToAdd.username === "" || + props.memberToAdd.role === "" || + props.memberToAdd.projectname === "" + ) { + alert("All fields must be filled before adding"); + return added; } - try { - const response = await api.addUserToProject( + api + .addUserToProject( props.memberToAdd, localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`[${props.memberToAdd.userName}] added`); - } else { - alert(`[${props.memberToAdd.userName}] not added`); - console.error(response.message); - } - } catch (error) { - alert(`[${props.memberToAdd.userName}] not added`); - console.error("An error occurred during member add:", error); - } + ) + .then((response: APIResponse) => { + if (response.success) { + alert("Member added"); + added = true; + } else { + alert("Member not added"); + console.error(response.message); + } + }) + .catch((error) => { + console.error("An error occurred during member add:", error); + }); + return added; } export default AddMember; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index c8a1c66..e2ad8b9 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,10 +1,37 @@ import { useState } from "react"; -import { api } from "../API/API"; +import { APIResponse, api } from "../API/API"; import { NewProject } from "../Types/goTypes"; import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; +/** + * Tries to add a project to the system + * @param {Object} props - Project name and description + * @returns {boolean} True if created, false if not + */ +function CreateProject(props: { name: string; description: string }): void { + const project: NewProject = { + name: props.name, + description: props.description, + }; + + api + .createProject(project, localStorage.getItem("accessToken") ?? "") + .then((response: APIResponse) => { + if (response.success) { + alert("Project added!"); + } else { + alert("Project NOT added!"); + console.error(response.message); + } + }) + .catch((error) => { + alert("Project NOT added!"); + console.error("An error occurred during creation:", error); + }); +} + /** * Provides UI for adding a project to the system. * @returns {JSX.Element} - Returns the component UI for adding a project @@ -13,33 +40,6 @@ function AddProject(): JSX.Element { const [name, setName] = useState(""); const [description, setDescription] = useState(""); - /** - * Tries to add a project to the system - */ - const handleCreateProject = async (): Promise => { - const project: NewProject = { - name: name.replace(/ /g, ""), - description: description.trim(), - }; - try { - const response = await api.createProject( - project, - localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`${project.name} added!`); - setDescription(""); - setName(""); - } else { - alert("Project not added, name could be taken"); - console.error(response.message); - } - } catch (error) { - alert("Project not added"); - console.error(error); - } - }; - return (
@@ -47,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" onSubmit={(e) => { e.preventDefault(); - void handleCreateProject(); + CreateProject({ + name: name, + description: description, + }); }} > ([]); + const [name, setName] = useState(""); const [users, setUsers] = useState([]); - const [usersProj, setUsersProj] = useState([]); - - // Gets all users and project members for filtering + const [role, setRole] = useState(""); GetAllUsers({ setUsersProp: setUsers }); - GetUsersInProject({ - setUsersProp: setUsersProj, - projectName: props.projectName, - }); - /* - * Filters the members from users so that users who are already - * members are not shown - */ - useEffect(() => { - setUsers((prevUsers) => { - const filteredUsers = prevUsers.filter( - (user) => - !usersProj.some((projectUser) => projectUser.Username === user), - ); - return filteredUsers; - }); - }, [usersProj]); - // Attempts to add all of the selected users to the project - const handleAddClick = async (): Promise => { - if (names.length === 0) - alert("You have to choose at least one user to add"); - for (const name of names) { - const newMember: AddMemberInfo = { - userName: name, - projectName: props.projectName, - }; - await AddMember({ memberToAdd: newMember }); - } - setNames([]); - location.reload(); - }; - - // Updates the names that have been selected - const handleUserClick = (user: string): void => { - setNames((prevNames): string[] => { - if (!prevNames.includes(user)) { - return [...prevNames, user]; - } - return prevNames.filter((name) => name !== user); - }); + const handleClick = (): boolean => { + const newMember: NewProjMember = { + username: name, + projectname: props.projectName, + role: role, + }; + return AddMember({ memberToAdd: newMember }); }; return ( -
-

- {props.projectName} -

-

- Choose users to add: +

+

+ User chosen: [{name}]

-
+

+ Role chosen: [{role}] +

+

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

+

Choose role:

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

Choose user:

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

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

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

- Username: -

{usernames[index]}

{" "} + UserID: +

{unsignedReport.userId}

Week:

{unsignedReport.week}

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

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