Compare commits

..

96 commits

Author SHA1 Message Date
Imbus
7e4e35f597 Silencing python testing, optional verbose output 2024-03-19 00:31:15 +01:00
Imbus
2ffbc2f9fd Merge docs -> dev 2024-03-19 00:10:42 +01:00
Imbus
fe08d01e15 Insanely verbose logging 2024-03-19 00:00:18 +01:00
Imbus
71caf37642 Resolve testing.py 2024-03-18 23:43:14 +01:00
Imbus
108850c20c Merge imbs -> dev 2024-03-18 23:40:56 +01:00
al8763be
9056aafd2e Test for getUserProjectsAdded 2024-03-18 23:34:03 +01:00
al8763be
2cff1d55f9 Fixed migration data 2024-03-18 23:08:38 +01:00
Imbus
7932350980 Exit with non-zero if migration fails 2024-03-18 23:04:08 +01:00
al8763be
bed9381509 Merge branch 'BumBranch' into dev 2024-03-18 22:48:23 +01:00
al8763be
95b09a8ce7 Fixed getProjectForUsers 2024-03-18 22:46:53 +01:00
Samuel Högbom Aronson
f3c5abf4f3 added docs for loginrenew, login, regsiter 2024-03-18 22:40:51 +01:00
al8763be
a5399c9335 Samle data without query 2024-03-18 22:39:43 +01:00
al8763be
7ae6cce6b4 Sample data 2024-03-18 22:39:02 +01:00
Imbus
472940cedc Remove index from userId 2024-03-18 22:20:25 +01:00
Imbus
f5a914330f Removed userId identifier from user table, introducing numerous errors 2024-03-18 22:10:19 +01:00
Imbus
c31f145c35 Database sample data, make target and go code 2024-03-18 22:07:02 +01:00
Imbus
47b60038b4 Merge branch 'frontend' into dev 2024-03-18 21:24:42 +01:00
Imbus
e0de61dd94 Type fixes in frontend, Register & YourProjectsPage 2024-03-18 21:24:26 +01:00
Imbus
f437b25da5 Merge branch 'frontend' into dev 2024-03-18 21:18:49 +01:00
al8763be
8eb23bf7f9 lint bro happ + test for getUserProject 2024-03-18 21:08:33 +01:00
Imbus
2be4afd0e0 Correct ish swagger docstring 2024-03-18 20:05:47 +01:00
Imbus
2aade5d2fe Docs example 2024-03-18 19:59:14 +01:00
al8763be
4392b68397 Removed duplicate getUserProjects 2024-03-18 17:40:53 +01:00
al8763be
d0cc6f2c1b Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-18 17:35:19 +01:00
al8763be
805d05f8a5 Merge branch 'gruppPP' into BumBranch 2024-03-18 17:31:03 +01:00
al8763be
e5904253e3 Merge branch 'gruppdm' into BumBranch 2024-03-18 17:28:57 +01:00
Peter KW
d692165f99 Removed username prop from basicwindow in all pages 2024-03-18 17:27:32 +01:00
Peter KW
388a430613 Tiny fix 2024-03-18 17:22:40 +01:00
Peter KW
2493932f77 Removed username prop, no longer used 2024-03-18 17:20:29 +01:00
al8763be
fbf46b7cd0 Merge branch 'frontend' into BumBranch 2024-03-18 17:14:23 +01:00
pavel Hamawand
b93ef48500 minor fix 2024-03-18 17:07:48 +01:00
pavel Hamawand
0f7f866cde minor fix 2024-03-18 17:06:05 +01:00
pavel Hamawand
3ec0d168eb minor fix 2024-03-18 17:05:37 +01:00
pavel Hamawand
e47b251c14 implement component 2024-03-18 17:03:02 +01:00
pavel Hamawand
39983c7f6f Render Project List 2024-03-18 17:01:11 +01:00
pavel Hamawand
f61ef87d5e Fetch Projects from API - needs fixing 2024-03-18 16:58:46 +01:00
pavel Hamawand
90afe80408 Implement State management 2024-03-18 16:54:16 +01:00
pavel Hamawand
4173003d32 initial component Setup 2024-03-18 16:53:13 +01:00
pavel Hamawand
b8f669e454 new component UserProjectListAdmin 2024-03-18 16:52:40 +01:00
pavel Hamawand
22b9580f51 fix backbutton 2024-03-18 16:52:40 +01:00
pavel Hamawand
8291f4caf3 delete component 2024-03-18 16:52:40 +01:00
pavel Hamawand
576a137038 new component 2024-03-18 16:52:40 +01:00
Peter KW
ace11570a5 Merge branch 'gruppDM' into gruppPP 2024-03-18 16:49:02 +01:00
Peter KW
2bd9878359 Merge branch 'frontend' into gruppPP 2024-03-18 16:44:13 +01:00
Mattias
437520183b removed import 2024-03-18 15:46:49 +01:00
Mattias
b962a856f4 fixes 2024-03-18 15:46:23 +01:00
Mattias
3bcb7a89b8 edited paths 2024-03-18 15:39:29 +01:00
Mattias
43f13dc534 Old paths in main are back 2024-03-18 15:33:19 +01:00
Mattias
1893340c63 Changed name of new component and minor fix 2024-03-18 15:17:53 +01:00
Mattias
3106e0f445 Fixes for viewing-report-component 2024-03-18 14:46:32 +01:00
al8763be
d6ce4a3c57 errMessage fixed 2024-03-18 14:43:28 +01:00
Mattias
b82f73d192 Removed button 2024-03-18 14:42:21 +01:00
Mattias
0634f83689 Now displaying the right component 2024-03-18 14:40:32 +01:00
Mattias
69d4067209 Added new component for viewing weeklyreport 2024-03-18 14:40:20 +01:00
pavel Hamawand
409d973d8a minor fix 2024-03-18 14:35:56 +01:00
Davenludd
3f8d56963b Add ProjectNameContext to NewWeeklyReport and handle project selection in YourProjectsPage 2024-03-18 12:49:58 +01:00
Davenludd
164ff781b3 Add useEffect hook to handle authority navigation and log response in YourProjectsPage 2024-03-18 10:44:15 +01:00
Davenludd
f6b2d17b97 Update Header component to use localStorage for username 2024-03-18 10:44:15 +01:00
Mattias
7a6b875aeb Token is now fetched from local storage 2024-03-18 10:28:20 +01:00
Davenludd
68b25350ef Add username to local storage and retrieve it in YourProjectsPage 2024-03-18 10:22:38 +01:00
Davenludd
3c8b7f9da2 Merge branch 'gruppPP' into gruppDM 2024-03-18 10:10:40 +01:00
Davenludd
5bef01396b Add user-specific project list and API integration 2024-03-18 10:09:12 +01:00
Mattias
711f46f960 fixed import 2024-03-18 09:56:07 +01:00
Davenludd
ba58fea1e3 Merge branch 'frontend' into gruppDM 2024-03-18 09:01:05 +01:00
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
Davenludd
ead1482e50 Merge branch 'frontend' into gruppDM 2024-03-17 21:39:19 +01:00
Davenludd
07aaab5530 Merge branch 'frontend' into gruppDM 2024-03-17 21:22:10 +01:00
Peter KW
888256e9f6 Changed logincheck type and error clarification 2024-03-17 19:38:51 +01:00
Mattias
17a081e816 minor fixes 2024-03-17 16:55:51 +01:00
Mattias
81893ae3e8 Minor fixes 2024-03-17 16:24:09 +01:00
Davenludd
9dd47f3d71 Update import paths for Types 2024-03-17 16:23:37 +01:00
Davenludd
2ba10e837a Refactor API.ts to handle error response in submitWeeklyReport 2024-03-17 16:14:50 +01:00
Davenludd
830c234325 Update login response type in API.ts 2024-03-17 15:50:50 +01:00
Davenludd
c708ec47ca Merge branch 'frontend' into gruppDM 2024-03-17 15:36:41 +01:00
Davenludd
b1f57e5ed8 Merge branch 'frontend' into gruppDM 2024-03-17 15:28:20 +01:00
Davenludd
6d5c25be5c Refactor Register component to use InputField component 2024-03-17 14:49:42 +01:00
Mattias
d6d2b6d170 Visual fixes 2024-03-17 14:49:42 +01:00
Davenludd
46eebee84f Refactor API.ts for improved readability and maintainability 2024-03-17 14:21:38 +01:00
Mattias
f3931f905a Minor changes 2024-03-17 14:19:57 +01:00
Mattias
62f1926305 Corrected NewWeeklyReport (prev. TimeReports) and changed imports 2024-03-17 13:39:16 +01:00
49 changed files with 1117 additions and 299 deletions

View file

@ -10,6 +10,7 @@ DB_FILE = db.sqlite3
# Directory containing migration SQL scripts
MIGRATIONS_DIR = internal/database/migrations
SAMPLE_DATA_DIR = internal/database/sample_data
# Build target
build:
@ -54,6 +55,14 @@ migrate:
sqlite3 $(DB_FILE) < $$file; \
done
sampledata:
@echo "If this ever fails, run make clean and try again"
@echo "Migrating database $(DB_FILE) using SQL scripts in $(SAMPLE_DATA_DIR)"
@for file in $(wildcard $(SAMPLE_DATA_DIR)/*.sql); do \
echo "Applying migration: $$file"; \
sqlite3 $(DB_FILE) < $$file; \
done
# Target added primarily for CI/CD to ensure that the database is created before running tests
db.sqlite3:
make migrate

View file

@ -19,19 +19,117 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/register": {
"/login": {
"post": {
"description": "logs the user in and returns a jwt token",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"User"
],
"summary": "login",
"parameters": [
{
"description": "login info",
"name": "NewUser",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.NewUser"
}
}
],
"responses": {
"200": {
"description": "Successfully signed token for user",
"schema": {
"type": "Token"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/loginerenew": {
"post": {
"description": "renews the users token",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"User"
],
"summary": "renews the users token",
"responses": {
"200": {
"description": "Successfully signed token for user",
"schema": {
"type": "Token"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/register": {
"post": {
"description": "Register a new user",
"consumes": [
"application/json"
],
"produces": [
"application/json"
"text/plain"
],
"tags": [
"User"
],
"summary": "Register a new user",
"parameters": [
{
"description": "User to register",
"name": "NewUser",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.NewUser"
}
}
],
"responses": {
"200": {
"description": "User added",
@ -53,6 +151,60 @@ const docTemplate = `{
}
}
}
},
"/userdelete/{username}": {
"delete": {
"description": "UserDelete deletes a user from the database",
"consumes": [
"application/json"
],
"produces": [
"text/plain"
],
"tags": [
"User"
],
"summary": "Deletes a user",
"responses": {
"200": {
"description": "User deleted",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "You can only delete yourself",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"types.NewUser": {
"type": "object",
"properties": {
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
}
},
"externalDocs": {

View file

@ -20,6 +20,7 @@ type Database interface {
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
Migrate() error
MigrateSampleData() error
GetProjectId(projectname string) (int, error)
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
AddUserToProject(username string, projectname string, role string) error
@ -49,6 +50,9 @@ type UserProjectMember struct {
//go:embed migrations
var scripts embed.FS
//go:embed sample_data
var sampleData embed.FS
// TODO: Possibly break these out into separate files bundled with the embed package?
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
@ -60,9 +64,10 @@ const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `SELECT projects.id, projects.name, projects.description, projects.owner_user_id
FROM projects JOIN user_roles ON projects.id = user_roles.project_id
JOIN users ON user_roles.user_id = users.id WHERE users.username = ?;`
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id
WHERE u.username = ?`
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
@ -378,3 +383,42 @@ func (d *Db) Migrate() error {
return nil
}
// MigrateSampleData applies sample data to the database.
func (d *Db) MigrateSampleData() error {
// Insert sample data
files, err := sampleData.ReadDir("sample_data")
if err != nil {
return err
}
if len(files) == 0 {
println("No sample data files found")
}
tr := d.MustBegin()
// Iterate over each SQL file and execute it
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".sql" {
continue
}
// This is perhaps not the most elegant way to do this
sqlBytes, err := sampleData.ReadFile("sample_data/" + file.Name())
if err != nil {
return err
}
sqlQuery := string(sqlBytes)
_, err = tr.Exec(sqlQuery)
if err != nil {
return err
}
}
if tr.Commit() != nil {
return err
}
return nil
}

View file

@ -4,11 +4,9 @@
-- password is the hashed password
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
-- Users are commonly searched by username and userId
CREATE INDEX IF NOT EXISTS users_username_index ON users (username);
CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId);

View file

@ -0,0 +1,35 @@
INSERT OR IGNORE INTO users(username, password)
VALUES ("admin", "123");
INSERT OR IGNORE INTO users(username, password)
VALUES ("user", "123");
INSERT OR IGNORE INTO users(username, password)
VALUES ("user2", "123");
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest","test project", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest2","test project2", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest3","test project3", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,2,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,3,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"project_manager");

View file

@ -68,6 +68,7 @@ func (gs *GState) GetProject(c *fiber.Ctx) error {
// Extract the project ID from the request parameters or body
projectID := c.Params("projectID")
if projectID == "" {
log.Info("No project ID provided")
return c.Status(400).SendString("No project ID provided")
}
log.Info("Getting project with ID: ", projectID)
@ -75,12 +76,14 @@ func (gs *GState) GetProject(c *fiber.Ctx) error {
// Parse the project ID into an integer
projectIDInt, err := strconv.Atoi(projectID)
if err != nil {
log.Info("Invalid project ID")
return c.Status(400).SendString("Invalid project ID")
}
// Get the project from the database by its ID
project, err := gs.Db.GetProject(projectIDInt)
if err != nil {
log.Info("Error getting project:", err)
return c.Status(500).SendString(err.Error())
}
@ -92,13 +95,20 @@ func (gs *GState) GetProject(c *fiber.Ctx) error {
func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error {
// Extract the project name from the request parameters or body
projectName := c.Params("projectName")
if projectName == "" {
log.Info("No project name provided")
return c.Status(400).SendString("No project name provided")
}
// Get all users associated with the project from the database
users, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users for project:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning users for project: ", projectName)
// Return the list of users as JSON
return c.JSON(users)
}

View file

@ -17,50 +17,62 @@ func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
report := new(types.NewWeeklyReport)
if err := c.BodyParser(report); err != nil {
log.Info("Error parsing weekly report")
return c.Status(400).SendString(err.Error())
}
// Make sure all the fields of the report are valid
if report.Week < 1 || report.Week > 52 {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 {
log.Info("Invalid time report")
return c.Status(400).SendString("Invalid time report")
}
if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
log.Info("Error adding weekly report")
return c.Status(500).SendString(err.Error())
}
log.Info("Weekly report added")
return c.Status(200).SendString("Time report added")
}
// Handler for retrieving weekly report
func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the request
log.Info("GetWeeklyReport")
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
log.Info("Getting weekly report for: ", username)
// Extract project name and week from query parameters
projectName := c.Query("projectName")
log.Info(projectName)
week := c.Query("week")
log.Info(week)
if projectName == "" || week == "" {
log.Info("Missing project name or week number")
return c.Status(400).SendString("Missing project name or week number")
}
// Convert week to integer
weekInt, err := strconv.Atoi(week)
if err != nil {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
// Call the database function to get the weekly report
report, err := gs.Db.GetWeeklyReport(username, projectName, weekInt)
if err != nil {
log.Info("Error getting weekly report from db:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly report")
// Return the retrieved weekly report
return c.JSON(report)
}
@ -70,12 +82,13 @@ type ReportId struct {
}
func (gs *GState) SignReport(c *fiber.Ctx) error {
log.Info("Signing report...")
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
log.Info("Signing report for: ", projectManagerUsername)
// Extract report ID from the request query parameters
// reportID := c.Query("reportId")
rid := new(ReportId)
@ -83,22 +96,19 @@ func (gs *GState) SignReport(c *fiber.Ctx) error {
return err
}
log.Info("Signing report for: ", rid.ReportId)
// reportIDInt, err := strconv.Atoi(rid.ReportId)
// log.Info("Signing report for: ", rid.ReportId)
// if err != nil {
// return c.Status(400).SendString("Invalid report ID")
// }
// Get the project manager's ID
projectManagerID, err := gs.Db.GetUserId(projectManagerUsername)
if err != nil {
log.Info("Failed to get project manager ID")
return c.Status(500).SendString("Failed to get project manager ID")
}
log.Info("blabla", projectManagerID)
log.Info("Project manager ID: ", projectManagerID)
// Call the database function to sign the weekly report
err = gs.Db.SignWeeklyReport(rid.ReportId, projectManagerID)
if err != nil {
log.Info("Error signing weekly report:", err)
return c.Status(500).SendString(err.Error())
}

View file

@ -16,11 +16,12 @@ import (
// @Description Register a new user
// @Tags User
// @Accept json
// @Produce json
// @Success 200 {string} string "User added"
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /api/register [post]
// @Produce plain
// @Param NewUser body types.NewUser true "User to register"
// @Success 200 {string} string "User added"
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /register [post]
func (gs *GState) Register(c *fiber.Ctx) error {
u := new(types.NewUser)
if err := c.BodyParser(u); err != nil {
@ -28,9 +29,9 @@ func (gs *GState) Register(c *fiber.Ctx) error {
return c.Status(400).SendString(err.Error())
}
log.Info("Adding user:", u.Username)
log.Info("Adding user:", u.Username)
if err := gs.Db.AddUser(u.Username, u.Password); err != nil {
log.Warn("Error adding user:", err)
return c.Status(500).SendString(err.Error())
}
@ -40,6 +41,17 @@ func (gs *GState) Register(c *fiber.Ctx) error {
// This path should obviously be protected in the future
// UserDelete deletes a user from the database
//
// @Summary Deletes a user
// @Description UserDelete deletes a user from the database
// @Tags User
// @Accept json
// @Produce plain
// @Success 200 {string} string "User deleted"
// @Failure 403 {string} string "You can only delete yourself"
// @Failure 500 {string} string "Internal server error"
// @Failure 401 {string} string "Unauthorized"
// @Router /userdelete/{username} [delete]
func (gs *GState) UserDelete(c *fiber.Ctx) error {
// Read from path parameters
username := c.Params("username")
@ -48,26 +60,42 @@ func (gs *GState) UserDelete(c *fiber.Ctx) error {
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
if username != auth_username {
log.Info("User tried to delete another user")
return c.Status(403).SendString("You can only delete yourself")
}
if err := gs.Db.RemoveUser(username); err != nil {
log.Warn("Error deleting user:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("User deleted:", username)
return c.Status(200).SendString("User deleted")
}
// Login is a simple login handler that returns a JWT token
//
// @Summary login
// @Description logs the user in and returns a jwt token
// @Tags User
// @Accept json
// @Param NewUser body types.NewUser true "login info"
// @Produce plain
// @Success 200 Token types.Token "Successfully signed token for user"
// @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /login [post]
func (gs *GState) Login(c *fiber.Ctx) error {
// The body type is identical to a NewUser
u := new(types.NewUser)
if err := c.BodyParser(u); err != nil {
log.Warn("Error parsing body")
return c.Status(400).SendString(err.Error())
}
log.Info("Username:", u.Username)
log.Info("Username logging in:", u.Username)
if !gs.Db.CheckUser(u.Username, u.Password) {
log.Info("User not found")
return c.SendStatus(fiber.StatusUnauthorized)
@ -91,14 +119,26 @@ func (gs *GState) Login(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
}
log.Info("Successfully signed token for user:", u.Username)
return c.JSON(fiber.Map{"token": t})
println("Successfully signed token for user:", u.Username)
return c.JSON(types.Token{Token: t})
}
// LoginRenew is a simple handler that renews the token
//
// @Summary renews the users token
// @Description renews the users token
// @Tags User
// @Accept json
// @Produce plain
// @Success 200 Token types.Token "Successfully signed token for user"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /loginerenew [post]
func (gs *GState) LoginRenew(c *fiber.Ctx) error {
// For testing: curl localhost:3000/restricted -H "Authorization: Bearer <token>"
user := c.Locals("user").(*jwt.Token)
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
claims := user.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
renewed := jwt.MapClaims{
@ -109,9 +149,12 @@ func (gs *GState) LoginRenew(c *fiber.Ctx) error {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed)
t, err := token.SignedString([]byte("secret"))
if err != nil {
log.Warn("Error signing token")
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(fiber.Map{"token": t})
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
return c.JSON(types.Token{Token: t})
}
// ListAllUsers is a handler that returns a list of all users in the application database
@ -119,9 +162,11 @@ func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
// Get all users from the database
users, err := gs.Db.GetAllUsersApplication()
if err != nil {
log.Info("Error getting users from db:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("Returning all users")
// Return the list of users as JSON
return c.JSON(users)
}

View file

@ -27,3 +27,8 @@ type PublicUser struct {
UserId string `json:"userId"`
Username string `json:"username"`
}
// wrapper type for token
type Token struct {
Token string `json:"token"`
}

View file

@ -47,6 +47,12 @@ func main() {
// Migrate the database
if err = db.Migrate(); err != nil {
fmt.Println("Error migrating database: ", err)
os.Exit(1)
}
if err = db.MigrateSampleData(); err != nil {
fmt.Println("Error migrating sample data: ", err)
os.Exit(1)
}
// Get our global state
@ -56,6 +62,7 @@ func main() {
server.Use(logger.New())
// Mounts the swagger documentation, this is available at /swagger/index.html
server.Get("/swagger/*", swagger.HandlerDefault)
// Mount our static files (Beware of the security implications of this!)

View file

@ -46,6 +46,7 @@ interface API {
username: string,
token: string,
): Promise<APIResponse<Project[]>>;
/** Gets a project from id*/
getProject(id: number): Promise<APIResponse<Project>>;
}
@ -149,7 +150,10 @@ export const api: API = {
}
},
async getUserProjects(token: string): Promise<APIResponse<Project[]>> {
async getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>> {
try {
const response = await fetch("/api/getUserProjects", {
method: "GET",
@ -157,6 +161,7 @@ export const api: API = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username }),
});
if (!response.ok) {

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

@ -2,17 +2,15 @@ import Header from "./Header";
import Footer from "./Footer";
function BasicWindow({
username,
content,
buttons,
}: {
username: string;
content: React.ReactNode;
buttons: React.ReactNode;
}): JSX.Element {
return (
<div className="font-sans flex flex-col h-screen bg-white border-2 border-black overflow-auto pt-[110px]">
<Header username={username} />
<Header />
<div className="flex flex-col items-center flex-grow">{content}</div>
<Footer>{buttons}</Footer>
</div>

View file

@ -0,0 +1,247 @@
import { useState, useEffect } from "react";
import { NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate } from "react-router-dom";
import Button from "./Button";
export default function GetWeeklyReport(): JSX.Element {
const [projectName, setProjectName] = useState("");
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? "";
const username = localStorage.getItem("username") ?? "";
useEffect(() => {
const fetchWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
username,
projectName,
week.toString(),
token,
);
if (response.success) {
const report: NewWeeklyReport = response.data ?? {
projectName: "",
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setProjectName(report.projectName);
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchWeeklyReport();
}, [projectName, token, username, week]);
const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName,
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
};
const navigate = useNavigate();
return (
<>
<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
onSubmit={(e) => {
if (week === 0) {
alert("Please enter a week number");
e.preventDefault();
return;
}
e.preventDefault();
void handleNewWeeklyReport();
navigate("/project");
}}
>
<div className="flex flex-col items-center">
<input
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
type="week"
placeholder="Week"
value={
week === 0 ? "" : `2024-W${week.toString().padStart(2, "0")}`
}
onChange={(e) => {
const weekNumber = parseInt(e.target.value.split("-W")[1]);
setWeek(weekNumber);
}}
onKeyDown={(event) => {
event.preventDefault();
}}
onPaste={(event) => {
event.preventDefault();
}}
/>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">
Activity
</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime}
onChange={(e) => {
setDevelopmentTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime}
onChange={(e) => {
setMeetingTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime}
onChange={(e) => {
setAdminTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime}
onChange={(e) => {
setOwnWorkTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime}
onChange={(e) => {
setStudyTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime}
onChange={(e) => {
setTestingTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
</tbody>
</table>
<Button
text="Submit"
onClick={(): void => {
return;
}}
type="submit"
/>
</div>
</form>
</div>
</>
);
}

View file

@ -1,11 +1,11 @@
import { useState } from "react";
import { Link } from "react-router-dom";
function Header({ username }: { username: string }): JSX.Element {
function Header(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const handleLogout = (): void => {
// Add any logout logic here
localStorage.clear();
};
return (
@ -31,7 +31,7 @@ function Header({ username }: { username: string }): JSX.Element {
}}
>
<button className="mr-4 underline font-bold text-white">
{username}
{localStorage.getItem("username")}
</button>
{isOpen && (

View file

@ -10,17 +10,22 @@ 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);
localStorage.setItem("username", props.username);
//TODO: change so that it checks for user type (admin, user, pm) instead
if (token !== "" && props.username === "admin") {
props.setAuthority((prevAuth) => {
@ -42,14 +47,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

@ -1,50 +1,51 @@
import { useState } from "react";
import { useState, useContext } from "react";
import type { NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate } from "react-router-dom";
import Button from "./Button";
import { NewWeeklyReport } from "../Types/goTypes";
import { ProjectNameContext } from "../Pages/YourProjectsPage";
export default function NewTimeReport(): JSX.Element {
const [projectName, setProjectName] = useState<string>("projectName"); // TODO: Get from backend
const [week, setWeek] = useState<number>(NaN);
const [development, setDevelopment] = useState<number>(NaN);
const [meeting, setMeeting] = useState<number>(NaN);
const [administration, setAdministration] = useState<number>(NaN);
const [ownwork, setOwnWork] = useState<number>(NaN);
const [studies, setStudies] = useState<number>(NaN);
const [testing, setTesting] = useState<number>(NaN);
export default function NewWeeklyReport(): JSX.Element {
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const handleNewTimeReport = async (): Promise<void> => {
const newTimeReport: NewWeeklyReport = {
const projectName = useContext(ProjectNameContext);
const token = localStorage.getItem("accessToken") ?? "";
const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName,
week,
developmentTime: development,
meetingTime: meeting,
adminTime: administration,
ownWorkTime: ownwork,
studyTime: studies,
testingTime: testing,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await Promise.resolve();
await api.submitWeeklyReport(newTimeReport, "token");
await api.submitWeeklyReport(newWeeklyReport, token);
};
const navigate = useNavigate();
setProjectName("Something Reasonable"); // This should obviously not be used here
return (
<>
<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
onSubmit={(e) => {
if (!week) {
if (week === 0) {
alert("Please enter a week number");
e.preventDefault();
return;
}
e.preventDefault();
void handleNewTimeReport();
void handleNewWeeklyReport();
navigate("/project");
}}
>
@ -83,9 +84,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={development}
value={developmentTime}
onChange={(e) => {
setDevelopment(parseInt(e.target.value));
setDevelopmentTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -102,9 +103,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meeting}
value={meetingTime}
onChange={(e) => {
setMeeting(parseInt(e.target.value));
setMeetingTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -121,9 +122,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={administration}
value={adminTime}
onChange={(e) => {
setAdministration(parseInt(e.target.value));
setAdminTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -140,9 +141,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownwork}
value={ownWorkTime}
onChange={(e) => {
setOwnWork(parseInt(e.target.value));
setOwnWorkTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -159,9 +160,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studies}
value={studyTime}
onChange={(e) => {
setStudies(parseInt(e.target.value));
setStudyTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -178,9 +179,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testing}
value={testingTime}
onChange={(e) => {
setTesting(parseInt(e.target.value));
setTestingTime(parseInt(e.target.value));
}}
onKeyDown={(event) => {
const keyValue = event.key;

View file

@ -3,34 +3,9 @@ import { NewUser } from "../Types/goTypes";
import { api } from "../API/API";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
import InputField from "./InputField";
import { useNavigate } from "react-router-dom";
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>
);
}
export default function Register(): JSX.Element {
const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>();
@ -48,6 +23,7 @@ export default function Register(): JSX.Element {
nav("/"); // Instantly navigate to the login page
} else {
setErrMessage(response.message ?? "Unknown error");
console.error(errMessage);
}
};
@ -72,7 +48,7 @@ export default function Register(): JSX.Element {
<InputField
label="Username"
type="text"
value={username}
value={username ?? ""}
onChange={(e) => {
setUsername(e.target.value);
}}
@ -80,48 +56,11 @@ export default function Register(): JSX.Element {
<InputField
label="Password"
type="password"
value={password}
value={password ?? ""}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor="username"
>
Username
</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="username"
type="text"
placeholder="Username"
value={username}
onChange={(e) => {
setUsername(e.target.value);
}}
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor="password"
>
Password
</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="password"
type="password"
placeholder="Choose your password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</div>
{errMessage && <p className="text-red-500 text-xs">{errMessage}</p>}
<div className="flex items-center justify-between">
<Button
text="Register"

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

@ -0,0 +1,43 @@
import React, { useEffect, useState } from "react";
import { api } from "../API/API";
import { Project } from "../Types/goTypes";
const UserProjectListAdmin: React.FC = () => {
const [projects, setProjects] = useState<Project[]>([]);
useEffect(() => {
const fetchProjects = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const username = "NoUser"; // getUsernameFromContext(); // Assuming you have a function to get the username from your context
const response = await api.getUserProjects(username, token);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error("Failed to fetch projects:", response.message);
}
} catch (error) {
console.error("Error fetching projects:", error);
}
};
void fetchProjects();
}, []);
return (
<div>
<h2>User Projects</h2>
<ul>
{projects.map((project) => (
<li key={project.id}>
<span>{project.name}</span>
{/* Add any additional project details you want to display */}
</li>
))}
</ul>
</div>
);
};
export default UserProjectListAdmin;

View file

@ -1,28 +1,16 @@
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 />
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminAddProject;

View file

@ -1,5 +1,5 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import Register from "../../Components/Register";
function AdminAddUser(): JSX.Element {
@ -11,16 +11,10 @@ function AdminAddUser(): JSX.Element {
const buttons = (
<>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminAddUser;

View file

@ -23,6 +23,6 @@ function AdminChangeUsername(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminChangeUsername;

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,23 +8,19 @@ 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 />
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminManageProjects;

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"
/>
@ -36,6 +36,6 @@ function AdminManageUsers(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminManageUsers;

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>
@ -22,6 +22,6 @@ function AdminMenuPage(): JSX.Element {
const buttons = <></>;
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminMenuPage;

View file

@ -23,6 +23,6 @@ function AdminProjectAddMember(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectAddMember;

View file

@ -23,6 +23,6 @@ function AdminProjectChangeUserRole(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectChangeUserRole;

View file

@ -23,6 +23,6 @@ function AdminProjectManageMembers(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectManageMembers;

View file

@ -23,6 +23,6 @@ function AdminProjectPage(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectPage;

View file

@ -16,6 +16,6 @@ function AdminProjectStatistics(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectStatistics;

View file

@ -23,6 +23,6 @@ function AdminProjectViewMemberInfo(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectViewMemberInfo;

View file

@ -1,15 +1,12 @@
import { useLocation } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import UserProjectListAdmin from "../../Components/UserProjectListAdmin";
function AdminViewUserInfo(): JSX.Element {
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">{useLocation().state}</h1>
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<p>Put relevant info on user from database here</p>
</div>
<UserProjectListAdmin />
</>
);
@ -26,6 +23,6 @@ function AdminViewUserInfo(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminViewUserInfo;

View file

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import LoginPage from "./LoginPage";
import { useNavigate } from "react-router-dom";
@ -6,13 +6,16 @@ import { useNavigate } from "react-router-dom";
function App(): JSX.Element {
const navigate = useNavigate();
const [authority, setAuthority] = useState(0);
if (authority === 1) {
navigate("/admin");
} else if (authority === 2) {
navigate("/pm");
} else if (authority === 3) {
navigate("/user");
}
useEffect(() => {
if (authority === 1) {
navigate("/admin");
} else if (authority === 2) {
navigate("/pm");
} else if (authority === 3) {
navigate("/user");
}
}, [authority, navigate]);
return <LoginPage setAuthority={setAuthority} />;
}

View file

@ -18,6 +18,6 @@ function ChangeRole(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default ChangeRole;

View file

@ -10,6 +10,6 @@ function PMOtherUsersTR(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMOtherUsersTR;

View file

@ -30,6 +30,6 @@ function PMProjectMembers(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMProjectMembers;

View file

@ -31,6 +31,6 @@ function PMProjectPage(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={undefined} />;
return <BasicWindow content={content} buttons={undefined} />;
}
export default PMProjectPage;

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow";
import TimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import TimeReport from "../../Components/NewWeeklyReport";
function PMTotalTimeActivity(): JSX.Element {
const content = (
@ -18,6 +18,6 @@ function PMTotalTimeActivity(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMTotalTimeActivity;

View file

@ -10,6 +10,6 @@ function PMTotalTimeRole(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMTotalTimeRole;

View file

@ -10,6 +10,6 @@ function PMUnsignedReports(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMUnsignedReports;

View file

@ -1,7 +1,7 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import TimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
import TimeReport from "../../Components/NewWeeklyReport";
function PMViewUnsignedReport(): JSX.Element {
const content = (
@ -33,6 +33,6 @@ function PMViewUnsignedReport(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMViewUnsignedReport;

View file

@ -1,29 +1,21 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import NewTimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
import EditWeeklyReport from "../../Components/EditWeeklyReport";
function UserEditTimeReportPage(): JSX.Element {
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time Report</h1>
<NewTimeReport />
<EditWeeklyReport />
</>
);
const buttons = (
<>
<Button
text="Save"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default UserEditTimeReportPage;

View file

@ -1,13 +1,13 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import NewTimeReport from "../../Components/TimeReport";
import NewWeeklyReport from "../../Components/NewWeeklyReport";
import { Link } from "react-router-dom";
function UserNewTimeReportPage(): JSX.Element {
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">New Time Report</h1>
<NewTimeReport />
<NewWeeklyReport />
</>
);
@ -25,6 +25,6 @@ function UserNewTimeReportPage(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default UserNewTimeReportPage;

View file

@ -27,6 +27,6 @@ function UserProjectPage(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default UserProjectPage;

View file

@ -15,6 +15,6 @@ function UserViewTimeReportsPage(): JSX.Element {
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default UserViewTimeReportsPage;

View file

@ -1,31 +1,59 @@
import { useState, createContext, useEffect } from "react";
import { Project } from "../Types/goTypes";
import { api } from "../API/API";
import { Link } from "react-router-dom";
import BasicWindow from "../Components/BasicWindow";
import { ProjectListUser } from "../Components/ProjectListUser";
import { Project } from "../Types/Project";
function YourProjectsPage(): JSX.Element {
//TODO: Change so that it reads projects from database
const projects: Project[] = [];
for (let i = 1; i <= 20; i++) {
projects.push({
id: i,
name: "Example Project " + i,
description: "good",
created: "now",
owner: "me",
});
}
export const ProjectNameContext = createContext("");
function UserProjectPage(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]);
const [selectedProject, setSelectedProject] = useState("");
const getProjects = async (): Promise<void> => {
const username = localStorage.getItem("username") ?? ""; // replace with actual username
const token = localStorage.getItem("accessToken") ?? ""; // replace with actual token
const response = await api.getUserProjects(username, token);
console.log(response);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error(response.message);
}
};
// Call getProjects when the component mounts
useEffect(() => {
void getProjects();
}, []);
const handleProjectClick = (projectName: string): void => {
setSelectedProject(projectName);
};
const content = (
<>
<ProjectNameContext.Provider value={selectedProject}>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<ProjectListUser projects={projects} />
<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]">
{projects.map((project, index) => (
<Link
to={`/project/${project.id}`}
onClick={() => {
handleProjectClick(project.name);
}}
key={index}
>
<h1 className="font-bold underline text-[30px] cursor-pointer">
{project.name}
</h1>
</Link>
))}
</div>
</>
</ProjectNameContext.Provider>
);
const buttons = <></>;
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
return <BasicWindow content={content} buttons={buttons} />;
}
export default YourProjectsPage;
export default UserProjectPage;

View file

@ -3,8 +3,32 @@ import ReactDOM from "react-dom/client";
import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./Pages/App";
import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage";
import YourProjectsPage from "./Pages/YourProjectsPage";
import YourProjectsPage from "./Pages/YourProjectsPage.tsx";
import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx";
import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx";
import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
import PMProjectPage from "./Pages/ProjectManagerPages/PMProjectPage.tsx";
import PMTotalTimeActivity from "./Pages/ProjectManagerPages/PMTotalTimeActivity.tsx";
import PMTotalTimeRole from "./Pages/ProjectManagerPages/PMTotalTimeRole.tsx";
import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx";
import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx";
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx";
import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo.tsx";
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
import AdminChangeUsername from "./Pages/AdminPages/AdminChangeUsername.tsx";
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
import AdminProjectChangeUserRole from "./Pages/AdminPages/AdminProjectChangeUserRole.tsx";
import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMembers.tsx";
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
// This is where the routes are mounted
const router = createBrowserRouter([
@ -20,6 +44,110 @@ const router = createBrowserRouter([
path: "/pm",
element: <YourProjectsPage />,
},
{
path: "/user",
element: <YourProjectsPage />,
},
{
path: "/yourProjects",
element: <YourProjectsPage />,
},
{
path: "/editTimeReport",
element: <UserEditTimeReportPage />,
},
{
path: "/newTimeReport",
element: <UserNewTimeReportPage />,
},
{
path: "/project",
element: <UserProjectPage />,
},
{
path: "/projectPage",
element: <UserViewTimeReportsPage />,
},
{
path: "/changeRole",
element: <PMChangeRole />,
},
{
path: "/otherUsersTimeReports",
element: <PMOtherUsersTR />,
},
{
path: "/projectMembers",
element: <PMProjectMembers />,
},
{
path: "/PMProjectPage",
element: <PMProjectPage />,
},
{
path: "/PMTimeActivity",
element: <PMTotalTimeActivity />,
},
{
path: "/PMTimeRole",
element: <PMTotalTimeRole />,
},
{
path: "/PMUnsignedReports",
element: <PMUnsignedReports />,
},
{
path: "/PMViewUnsignedReport",
element: <PMViewUnsignedReport />,
},
{
path: "/adminChangeUsername",
element: <AdminChangeUsername />,
},
{
path: "/adminProjectAddMember",
element: <AdminProjectAddMember />,
},
{
path: "/adminProjectChangeUserRole",
element: <AdminProjectChangeUserRole />,
},
{
path: "/adminProjectManageMembers",
element: <AdminProjectManageMembers />,
},
{
path: "/adminProjectPage",
element: <AdminProjectPage />,
},
{
path: "/adminProjectStatistics",
element: <AdminProjectStatistics />,
},
{
path: "/adminProjectViewMembers",
element: <AdminProjectViewMemberInfo />,
},
{
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

@ -2,6 +2,11 @@ import requests
import string
import random
debug_output = False
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
@ -26,39 +31,58 @@ 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"
def test_get_user_projects():
dprint("Testing get user projects")
loginResponse = login("user2", "123")
# Check if the user is added to the project
response = requests.get(
getUserProjectsPath,
json={"username": "user2"},
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
)
dprint(response.text)
dprint(response.json())
assert response.status_code == 200, "Get user projects failed"
dprint("got user projects successfully")
# Posts the username and password to the register endpoint
def register(username: string, password: string):
print("Registering with username: ", username, " and password: ", password)
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
print(response.text)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
print("Logging in with username: ", username, " and password: ", password)
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
print(response.text)
dprint(response.text)
return response
# Test function to login
def test_login():
response = login(username, "always_same")
assert response.status_code == 200, "Login failed"
print("Login successful")
dprint("Login successful")
return response.json()["token"]
# Test function to create a new user
def test_create_user():
response = register(username, "always_same")
assert response.status_code == 200, "Registration failed"
print("Registration successful")
dprint("Registration successful")
# Test function to add a project
def test_add_project():
@ -69,9 +93,9 @@ def test_add_project():
json={"name": projectName, "description": "This is a project"},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Add project failed"
print("Add project successful")
dprint("Add project successful")
# Test function to submit a report
def test_submit_report():
@ -90,9 +114,9 @@ def test_submit_report():
},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
dprint("Submit report successful")
# Test function to get a weekly report
def test_get_weekly_report():
@ -100,37 +124,45 @@ def test_get_weekly_report():
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName , "week": 1}
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Get weekly report failed"
# Tests getting a project by id
def test_get_project():
token = login(username, "always_same").json()["token"]
response = requests.get(
getProjectPath + "/1", # Assumes that the project with id 1 exists
getProjectPath + "/1", # Assumes that the project with id 1 exists
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Get project failed"
# Test function to add a user to a project
def test_add_user_to_project():
# Log in as a site admin
admin_username = randomString()
admin_password = "admin_password"
print("Registering with username: ", admin_username, " and password: ", admin_password)
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
print(response.text)
dprint(response.text)
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(promoteToAdminPath, json={"username": admin_username}, headers={"Authorization": "Bearer " + admin_token})
print(response.text)
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
dprint(response.text)
assert response.status_code == 200, "Promote to site admin failed"
print("Admin promoted to site admin successfully")
dprint("Admin promoted to site admin successfully")
# Create a new user to add to the project
new_user = randomString()
@ -143,9 +175,9 @@ def test_add_user_to_project():
headers={"Authorization": "Bearer " + admin_token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Add user to project failed"
print("Add user to project successful")
dprint("Add user to project successful")
# Test function to sign a report
def test_sign_report():
@ -156,26 +188,38 @@ def test_sign_report():
# Register an admin
admin_username = randomString()
admin_password = "admin_password2"
print("Registering with username: ", admin_username, " and password: ", admin_password)
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
print(response.text)
dprint(response.text)
# Log in as the admin
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(promoteToAdminPath, json={"username": admin_username}, headers={"Authorization": "Bearer " + admin_token})
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
response = requests.put(
addUserToProjectPath,
json={"projectName": projectName, "username": project_manager, "role": "project_manager"},
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"
print("Project manager added to project successfully")
dprint("Project manager added to project successfully")
# Log in as the project manager
project_manager_token = login(project_manager, "project_manager_password").json()["token"]
project_manager_token = login(project_manager, "project_manager_password").json()[
"token"
]
# Submit a report for the project
token = login(username, "always_same").json()["token"]
@ -194,15 +238,15 @@ def test_sign_report():
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
dprint("Submit report successful")
# Retrieve the report ID
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName , "week": 1}
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
report_id = response.json()["reportId"]
# Sign the report as the project manager
@ -212,18 +256,19 @@ def test_sign_report():
headers={"Authorization": "Bearer " + project_manager_token},
)
assert response.status_code == 200, "Sign report failed"
print("Sign report successful")
dprint("Sign report successful")
# Retrieve the report ID again for confirmation
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName , "week": 1}
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
if __name__ == "__main__":
test_get_user_projects()
test_create_user()
test_login()
test_add_project()