Compare commits

...

29 commits

Author SHA1 Message Date
Peter KW
92119dd49e Added path + fixed import 2024-03-18 01:56:04 +01:00
Peter KW
8a2152395f Small fixes 2024-03-18 01:31:58 +01:00
Peter KW
516784c6bb Merge branch 'frontend' into gruppPP 2024-03-18 00:51:34 +01:00
Peter KW
4876038613 Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-03-18 00:48:21 +01:00
Peter KW
f7b8ea7d97 Path fix 2024-03-18 00:46:04 +01:00
Peter KW
bd588048dd Import fix 2024-03-18 00:45:49 +01:00
Peter KW
070e9cc1e5 Link to button 2024-03-18 00:45:22 +01:00
Peter KW
844e94ed26 Small fixes to layout and added 2024-03-18 00:44:56 +01:00
Peter KW
0044b61ac8 Uses input field component instead now 2024-03-18 00:44:11 +01:00
Peter KW
3b49faec2d Import fix 2024-03-18 00:42:22 +01:00
Peter KW
8249678488 Using local storage for token 2024-03-18 00:42:05 +01:00
Peter KW
24e7e68ea0 Logout functionality 2024-03-18 00:41:46 +01:00
Peter KW
200da51633 AddProject component 2024-03-18 00:41:18 +01:00
Peter KW
d97516e0cd Added some paths again 2024-03-18 00:40:42 +01:00
al8763be
953c06212d API Changes 2024-03-18 00:22:32 +01:00
al8763be
7cc74866fc Merge branch 'dev' into frontend 2024-03-18 00:13:04 +01:00
al8763be
b45a20c9f5 Merge branch 'dev' into frontend 2024-03-18 00:06:20 +01:00
Imbus
741ad50ccf Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-17 23:51:52 +01:00
dDogge
f6e4603603 Added handler for SignWeeklyReport named SignReport in handlers_report_related 2024-03-17 23:31:52 +01:00
Imbus
c487b47373 Merge branch 'BumBranch' of github.com:imbus64/TTime into BumBranch 2024-03-17 23:17:18 +01:00
Imbus
9070846b0b Fixing getWeeklyReport tests 2024-03-17 23:17:06 +01:00
al8763be
ff6af1d250 Merge branch 'dev' into BumBranch 2024-03-17 23:11:49 +01:00
al8763be
9f20d46fa6 Test for getWeeklyReport 2024-03-17 22:57:19 +01:00
Imbus
e012b6ff12 Error checking in register component, redirect to login if success 2024-03-17 22:48:38 +01:00
al8763be
c3ce25236f Full implementation of getWeeklyProject 2024-03-17 22:35:51 +01:00
al8763be
6823102b44 Deleted API test 2024-03-17 22:27:21 +01:00
al8763be
06c50e7a42 Merge branch 'frontend' into BumBranch 2024-03-17 22:20:48 +01:00
al8763be
7339a69bce getWeeklyReport 2024-03-17 22:20:34 +01:00
Peter KW
888256e9f6 Changed logincheck type and error clarification 2024-03-17 19:38:51 +01:00
16 changed files with 273 additions and 179 deletions

View file

@ -16,6 +16,7 @@ type GlobalState interface {
GetUserProjects(c *fiber.Ctx) error // To get all projects
SubmitWeeklyReport(c *fiber.Ctx) error
GetWeeklyReport(c *fiber.Ctx) error
SignReport(c *fiber.Ctx) error
// GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project
// DeleteProject(c *fiber.Ctx) error // To delete a project

View file

@ -37,13 +37,16 @@ func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
// Handler for retrieving weekly report
func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the request
println("GetWeeklyReport")
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Query("projectName")
println(projectName)
week := c.Query("week")
println(week)
// Convert week to integer
weekInt, err := strconv.Atoi(week)
@ -60,3 +63,31 @@ func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
// Return the retrieved weekly report
return c.JSON(report)
}
func (gs *GState) SignReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
managerUsername := claims["name"].(string)
// Extract the report ID and project manager ID from request parameters
reportID, err := strconv.Atoi(c.Params("reportId"))
if err != nil {
return c.Status(400).SendString("Invalid report ID")
}
// Call the database function to get the project manager ID
managerID, err := gs.Db.GetUserId(managerUsername)
if err != nil {
return c.Status(500).SendString("Failed to get project manager ID")
}
// Call the database function to sign the weekly report
err = gs.Db.SignWeeklyReport(reportID, managerID)
if err != nil {
return c.Status(500).SendString("Failed to sign the weekly report: " + err.Error())
}
// Return success response
return c.Status(200).SendString("Weekly report signed successfully")
}

View file

@ -78,6 +78,7 @@ func main() {
server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject)
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
// Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port))

View file

@ -1,87 +0,0 @@
import { describe, expect, test } from "@jest/globals";
import { api } from "../API/API";
import { NewUser, NewWeeklyReport } from "../Types/goTypes";
describe("API", () => {
test("registerUser", async () => {
const user: NewUser = {
username: "lol", // Add the username property
password: "lol",
};
const response = await api.registerUser(user);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("userId");
});
test("createProject", async () => {
const project = {
name: "Project X",
description: "This is a test project",
};
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.createProject(project, token);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("projectId");
});
test("renewToken", async () => {
const refreshToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.renewToken(refreshToken);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("accessToken");
expect(response.data).toHaveProperty("refreshToken");
});
test("getUserProjects", async () => {
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const username = "rrgumdzpmc";
const response = await api.getUserProjects(username, token);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("projects");
});
test("submitWeeklyReport", async () => {
const report: NewWeeklyReport = {
projectName: "vtmosxssst",
week: 2,
developmentTime: 40,
meetingTime: 5,
adminTime: 2,
ownWorkTime: 10,
studyTime: 12,
testingTime: 41,
};
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t";
const response = await api.submitWeeklyReport(report, token);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty(
"message",
"Report submitted successfully",
);
});
test("login", async () => {
const user: NewUser = {
username: "rrgumdzpmc", // Add an empty string value for the username property
password: "always_same",
};
const response = await api.login(user);
console.log(response.message);
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("accessToken");
expect(response.data).toHaveProperty("refreshToken");
});
});

View file

@ -20,25 +20,32 @@ interface API {
registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */
removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Login */
login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>;
/** Create a project */
createProject(
project: NewProject,
token: string,
): Promise<APIResponse<Project>>;
/** Submit a weekly report */
submitWeeklyReport(
project: NewWeeklyReport,
token: string,
): Promise<APIResponse<Project>>;
/** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>;
/** Gets all the projects of a user*/
getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>>;
/** Login */
login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Submit a weekly report */
submitWeeklyReport(
project: NewWeeklyReport,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
/**Gets a weekly report*/
getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
}
// Export an instance of the API
@ -54,13 +61,19 @@ export const api: API = {
});
if (!response.ok) {
return { success: false, message: "Failed to register user" };
return {
success: false,
message: "Failed to register user: " + response.status,
};
} else {
const data = (await response.json()) as User;
return { success: true, data };
// const data = (await response.json()) as User; // The API does not currently return the user
return { success: true };
}
} catch (e) {
return { success: false, message: "Failed to register user" };
return {
success: false,
message: "Unknown error while registering user",
};
}
},
@ -163,9 +176,9 @@ export const api: API = {
},
async submitWeeklyReport(
project: NewWeeklyReport,
weeklyReport: NewWeeklyReport,
token: string,
): Promise<APIResponse<Project>> {
): Promise<APIResponse<NewWeeklyReport>> {
try {
const response = await fetch("/api/submitWeeklyReport", {
method: "POST",
@ -173,7 +186,7 @@ export const api: API = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(project),
body: JSON.stringify(weeklyReport),
});
if (!response.ok) {
@ -183,7 +196,7 @@ export const api: API = {
};
}
const data = (await response.json()) as Project;
const data = (await response.json()) as NewWeeklyReport;
return { success: true, data };
} catch (e) {
return {
@ -193,6 +206,33 @@ export const api: API = {
}
},
async getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>> {
try {
const response = await fetch("/api/getWeeklyReport", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username, projectName, week }),
});
if (!response.ok) {
return { success: false, message: "Failed to get weekly report" };
} else {
const data = (await response.json()) as NewWeeklyReport;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get weekly report" };
}
},
async login(NewUser: NewUser): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/login", {

View file

@ -0,0 +1,94 @@
import { useState } from "react";
import { APIResponse, api } from "../API/API";
import { NewProject, Project } 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 props - Project name and description
* @returns {boolean} True if created, false if not
*/
function CreateProject(props: { name: string; description: string }): boolean {
const project: NewProject = {
name: props.name,
description: props.description,
};
let created = false;
api
.createProject(project, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<Project>) => {
if (response.success) {
created = true;
} else {
console.error(response.message);
}
})
.catch((error) => {
console.error("An error occurred during creation:", error);
});
return created;
}
/**
* Tries to add a project to the system
* @returns {JSX.Element} UI for project adding
*/
function AddProject(): JSX.Element {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
return (
<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">
<form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => {
e.preventDefault();
CreateProject({ name: name, description: description });
}}
>
<img
src={Logo}
className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo"
/>
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Create a new project
</h3>
<InputField
label="Name"
type="text"
value={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
<InputField
label="Description"
type="text"
value={description}
onChange={(e) => {
setDescription(e.target.value);
}}
/>
<div className="flex items-center justify-between">
<Button
text="Create"
onClick={(): void => {
return;
}}
type="submit"
/>
</div>
</form>
<p className="text-center text-gray-500 text-xs"></p>
</div>
</div>
);
}
export default AddProject;

View file

@ -5,7 +5,7 @@ function Header({ username }: { username: string }): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const handleLogout = (): void => {
// Add any logout logic here
localStorage.clear();
};
return (

View file

@ -10,17 +10,21 @@ function LoginCheck(props: {
username: string;
password: string;
setAuthority: Dispatch<SetStateAction<number>>;
}): number {
}): void {
const user: NewUser = {
username: props.username,
password: props.password,
};
localStorage.clear();
api
.login(user)
.then((response: APIResponse<string>) => {
if (response.success) {
if (response.data !== undefined) {
const token = response.data;
localStorage.setItem("accessToken", token);
//TODO: change so that it checks for user type (admin, user, pm) instead
if (token !== "" && props.username === "admin") {
props.setAuthority((prevAuth) => {
@ -42,14 +46,12 @@ function LoginCheck(props: {
console.error("Token was undefined");
}
} else {
console.error("Token could not be fetched");
console.error("Token could not be fetched/No such user");
}
})
.catch((error) => {
console.error("An error occurred during login:", error);
});
return 0;
}
export default LoginCheck;

View file

@ -3,40 +3,27 @@ import { NewUser } from "../Types/goTypes";
import { api } from "../API/API";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
function InputField(props: {
label: string;
type: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
return (
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor={props.label}
>
{props.label}
</label>
<input
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id={props.label}
type={props.type}
placeholder={props.label}
value={props.value}
onChange={props.onChange}
/>
</div>
);
}
import InputField from "./InputField";
import { useNavigate } from "react-router-dom";
export default function Register(): JSX.Element {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>();
const [errMessage, setErrMessage] = useState<string>();
const nav = useNavigate();
const handleRegister = async (): Promise<void> => {
const newUser: NewUser = { username: username, password };
await api.registerUser(newUser); // TODO: Handle errors
const newUser: NewUser = {
username: username ?? "",
password: password ?? "",
};
const response = await api.registerUser(newUser);
if (response.success) {
nav("/"); // Instantly navigate to the login page
} else {
setErrMessage(response.message ?? "Unknown error");
}
};
return (

View file

@ -1,11 +1,11 @@
import { Link } from "react-router-dom";
import { User } from "../Types/goTypes";
import { PublicUser } from "../Types/goTypes";
/**
* The props for the UserProps component
*/
interface UserProps {
users: User[];
users: PublicUser[];
}
/**
@ -23,7 +23,7 @@ export function UserListAdmin(props: UserProps): JSX.Element {
<div>
<ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.users.map((user) => (
<Link to="/admin-view-user" key={user.userId} state={user.username}>
<Link to="/adminUserInfo" key={user.userId} state={user.username}>
<li className="pt-5" key={user.userId}>
{user.username}
</li>

View file

@ -1,25 +1,13 @@
import AddProject from "../../Components/AddProject";
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminAddProject(): JSX.Element {
const content = <></>;
const content = <AddProject />;
const buttons = (
<>
<Button
text="Finish"
onClick={(): void => {
return;
}}
type="button"
/>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);

View file

@ -1,3 +1,5 @@
import { Link } from "react-router-dom";
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
@ -6,20 +8,16 @@ function AdminManageProjects(): JSX.Element {
const buttons = (
<>
<Button
text="Add Project"
onClick={(): void => {
return;
}}
type="button"
/>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
<Link to="/addProject">
<Button
text="Add Project"
onClick={(): void => {
return;
}}
type="button"
/>
</Link>
<BackButton />
</>
);

View file

@ -2,14 +2,14 @@ import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import { UserListAdmin } from "../../Components/UserListAdmin";
import { User } from "../../Types/Users";
import { PublicUser } from "../../Types/goTypes";
import { useNavigate } from "react-router-dom";
function AdminManageUsers(): JSX.Element {
//TODO: Change so that it reads users from database
const users: User[] = [];
const users: PublicUser[] = [];
for (let i = 1; i <= 20; i++) {
users.push({ id: i, userName: "Example User " + i });
users.push({ userId: "id" + i, username: "Example User " + i });
}
const navigate = useNavigate();
@ -28,7 +28,7 @@ function AdminManageUsers(): JSX.Element {
<Button
text="Add User"
onClick={(): void => {
navigate("/admin-add-user");
navigate("/adminAddUser");
}}
type="button"
/>

View file

@ -6,12 +6,12 @@ function AdminMenuPage(): JSX.Element {
<>
<h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to="/admin-manage-users">
<Link to="/adminManageUser">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Manage Users
</h1>
</Link>
<Link to="/admin-manage-projects">
<Link to="/adminManageProject">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Manage Projects
</h1>

View file

@ -5,6 +5,11 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./Pages/App";
import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage";
import YourProjectsPage from "./Pages/YourProjectsPage";
import AdminAddProject from "./Pages/AdminPages/AdminAddProject";
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects";
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers";
import AdminAddUser from "./Pages/AdminPages/AdminAddUser";
import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo";
// This is where the routes are mounted
const router = createBrowserRouter([
@ -20,6 +25,30 @@ const router = createBrowserRouter([
path: "/pm",
element: <YourProjectsPage />,
},
{
path: "/user",
element: <YourProjectsPage />,
},
{
path: "/addProject",
element: <AdminAddProject />,
},
{
path: "/adminAddUser",
element: <AdminAddUser />,
},
{
path: "/adminUserInfo",
element: <AdminViewUserInfo />,
},
{
path: "/adminManageProject",
element: <AdminManageProjects />,
},
{
path: "/adminManageUser",
element: <AdminManageUsers />,
},
]);
// Semi-hacky way to get the root element

View file

@ -21,6 +21,7 @@ registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
# Posts the username and password to the register endpoint
@ -74,7 +75,7 @@ def test_submit_report():
response = requests.post(
submitReportPath,
json={
"projectName": "report1",
"projectName": projectName,
"week": 1,
"developmentTime": 10,
"meetingTime": 5,
@ -89,9 +90,18 @@ def test_submit_report():
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
def test_get_weekly_report():
token = login(username, "always_same").json()["token"]
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName , "week": 1}
)
print(response.text)
if __name__ == "__main__":
test_create_user()
test_login()
test_add_project()
test_submit_report()
test_get_weekly_report()