Merge branch 'dev' into frontend

This commit is contained in:
al8763be 2024-03-20 23:37:56 +01:00
commit e839d02cfd
15 changed files with 361 additions and 192 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ bin
database.txt database.txt
plantuml.jar plantuml.jar
db.sqlite3 db.sqlite3
db.sqlite3-journal
diagram.puml diagram.puml
backend/*.png backend/*.png
backend/*.jpg backend/*.jpg

View file

@ -39,6 +39,7 @@ type Database interface {
SignWeeklyReport(reportId int, projectManagerId int) error SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error) IsSiteAdmin(username string) (bool, error)
IsProjectManager(username string, projectname string) (bool, error) IsProjectManager(username string, projectname string) (bool, error)
GetTotalTimePerActivity(projectName string) (map[string]int, error)
} }
// This struct is a wrapper type that holds the database connection // This struct is a wrapper type that holds the database connection
@ -60,14 +61,16 @@ var sampleData embed.FS
// TODO: Possibly break these out into separate files bundled with the embed package? // TODO: Possibly break these out into separate files bundled with the embed package?
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" const projectInsert = "INSERT INTO projects (name, description, owner_user_id) VALUES (?, ?, (SELECT id FROM users WHERE username = ?))"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?) ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" const addUserToProject = `INSERT OR IGNORE INTO user_roles (user_id, project_id, p_role)
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" VALUES ((SELECT id FROM users WHERE username = ?),
(SELECT id FROM projects WHERE name = ?), ?)`
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)"
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id JOIN users u ON ur.user_id = u.id
@ -75,6 +78,11 @@ const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
const deleteProject = `DELETE FROM projects const deleteProject = `DELETE FROM projects
WHERE id = ? AND owner_username = ?` WHERE id = ? AND owner_username = ?`
const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN users ON user_roles.user_id = users.id
JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
// DbConnect connects to the database // DbConnect connects to the database
func DbConnect(dbpath string) Database { func DbConnect(dbpath string) Database {
// Open the database // Open the database
@ -132,41 +140,15 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve
// AddUserToProject adds a user to a project with a specified role. // AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { func (d *Db) AddUserToProject(username string, projectname string, role string) error {
var userid int _, err := d.Exec(addUserToProject, username, projectname, role)
userid, err := d.GetUserId(username) return err
if err != nil {
panic(err)
}
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
_, err3 := d.Exec(addUserToProject, userid, projectid, role)
return err3
} }
// ChangeUserRole changes the role of a user within a project. // ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error { func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Get the user ID
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
// Get the project ID
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
// Execute the SQL query to change the user's role // Execute the SQL query to change the user's role
_, err3 := d.Exec(changeUserRole, role, userid, projectid) _, err := d.Exec(changeUserRole, role, username, projectname)
return err3 return err
} }
// ChangeUserName changes the username of a user. // ChangeUserName changes the username of a user.
@ -215,6 +197,7 @@ func (d *Db) GetProjectId(projectname string) (int, error) {
// Creates a new project in the database, associated with a user // Creates a new project in the database, associated with a user
func (d *Db) AddProject(name string, description string, username string) error { func (d *Db) AddProject(name string, description string, username string) error {
tx := d.MustBegin() tx := d.MustBegin()
// Insert the project into the database
_, err := tx.Exec(projectInsert, name, description, username) _, err := tx.Exec(projectInsert, name, description, username)
if err != nil { if err != nil {
if err := tx.Rollback(); err != nil { if err := tx.Rollback(); err != nil {
@ -222,7 +205,9 @@ func (d *Db) AddProject(name string, description string, username string) error
} }
return err return err
} }
_, err = tx.Exec(changeUserRole, "project_manager", username, name)
// Add creator to project as project manager
_, err = tx.Exec(addUserToProject, username, name, "project_manager")
if err != nil { if err != nil {
if err := tx.Rollback(); err != nil { if err := tx.Rollback(); err != nil {
return err return err
@ -462,23 +447,9 @@ func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.
// IsProjectManager checks if a given username is a project manager for the specified project // IsProjectManager checks if a given username is a project manager for the specified project
func (d *Db) IsProjectManager(username string, projectname string) (bool, error) { func (d *Db) IsProjectManager(username string, projectname string) (bool, error) {
// Define the SQL query to check if the user is a project manager for the project var manager bool
query := ` err := d.Get(&manager, isProjectManagerQuery, username, projectname)
SELECT COUNT(*) FROM user_roles return manager, err
JOIN users ON user_roles.user_id = users.id
JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'
`
// Execute the query
var count int
err := d.Get(&count, query, username, projectname)
if err != nil {
return false, err
}
// If count is greater than 0, the user is a project manager for the project
return count > 0, nil
} }
// MigrateSampleData applies sample data to the database. // MigrateSampleData applies sample data to the database.
@ -519,3 +490,41 @@ func (d *Db) MigrateSampleData() error {
return nil return nil
} }
func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) {
query := `
SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time
FROM weekly_reports
JOIN projects ON weekly_reports.project_id = projects.id
WHERE projects.name = ?
`
rows, err := d.DB.Query(query, projectName)
if err != nil {
return nil, err
}
defer rows.Close()
totalTime := make(map[string]int)
for rows.Next() {
var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int
if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil {
return nil, err
}
totalTime["development"] += developmentTime
totalTime["meeting"] += meetingTime
totalTime["admin"] += adminTime
totalTime["own_work"] += ownWorkTime
totalTime["study"] += studyTime
totalTime["testing"] += testingTime
}
if err := rows.Err(); err != nil {
return nil, err
}
return totalTime, nil
}

View file

@ -1,7 +1,6 @@
package database package database
import ( import (
"fmt"
"testing" "testing"
) )
@ -17,12 +16,61 @@ func setupState() (Database, error) {
return db, nil return db, nil
} }
// This is a more advanced setup that includes more data in the database.
// This is useful for more complex testing scenarios.
func setupAdvancedState() (Database, error) {
db, err := setupState()
if err != nil {
return nil, err
}
// Add a user
if err = db.AddUser("demouser", "password"); err != nil {
return nil, err
}
// Add a project
if err = db.AddProject("projecttest", "description", "demouser"); err != nil {
return nil, err
}
// Add a weekly report
if err = db.AddWeeklyReport("projecttest", "demouser", 1, 1, 1, 1, 1, 1, 1); err != nil {
return nil, err
}
return db, nil
}
// TestDbConnect tests the connection to the database // TestDbConnect tests the connection to the database
func TestDbConnect(t *testing.T) { func TestDbConnect(t *testing.T) {
db := DbConnect(":memory:") db := DbConnect(":memory:")
_ = db _ = db
} }
func TestSetupAdvancedState(t *testing.T) {
db, err := setupAdvancedState()
if err != nil {
t.Error("setupAdvancedState failed:", err)
}
// Check if the user was added
if _, err = db.GetUserId("demouser"); err != nil {
t.Error("GetUserId failed:", err)
}
// Check if the project was added
projects, err := db.GetAllProjects()
if err != nil {
t.Error("GetAllProjects failed:", err)
}
if len(projects) != 1 {
t.Error("GetAllProjects failed: expected 1, got", len(projects))
}
// To be continued...
}
// TestDbAddUser tests the AddUser function of the database // TestDbAddUser tests the AddUser function of the database
func TestDbAddUser(t *testing.T) { func TestDbAddUser(t *testing.T) {
db, err := setupState() db, err := setupState()
@ -58,12 +106,12 @@ func TestDbGetUserId(t *testing.T) {
// TestDbAddProject tests the AddProject function of the database // TestDbAddProject tests the AddProject function of the database
func TestDbAddProject(t *testing.T) { func TestDbAddProject(t *testing.T) {
db, err := setupState() db, err := setupAdvancedState()
if err != nil { if err != nil {
t.Error("setupState failed:", err) t.Error("setupState failed:", err)
} }
err = db.AddProject("test", "description", "test") err = db.AddProject("test", "description", "demouser")
if err != nil { if err != nil {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
} }
@ -168,20 +216,15 @@ func TestChangeUserRole(t *testing.T) {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
} }
err = db.AddUserToProject("testuser", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
role, err := db.GetUserRole("testuser", "testproject") role, err := db.GetUserRole("testuser", "testproject")
if err != nil { if err != nil {
t.Error("GetUserRole failed:", err) t.Error("GetUserRole failed:", err)
} }
if role != "user" { if role != "project_manager" {
t.Error("GetUserRole failed: expected user, got", role) t.Error("GetUserRole failed: expected project_manager, got", role)
} }
err = db.ChangeUserRole("testuser", "testproject", "admin") err = db.ChangeUserRole("testuser", "testproject", "member")
if err != nil { if err != nil {
t.Error("ChangeUserRole failed:", err) t.Error("ChangeUserRole failed:", err)
} }
@ -190,8 +233,8 @@ func TestChangeUserRole(t *testing.T) {
if err != nil { if err != nil {
t.Error("GetUserRole failed:", err) t.Error("GetUserRole failed:", err)
} }
if role != "admin" { if role != "member" {
t.Error("GetUserRole failed: expected admin, got", role) t.Error("GetUserRole failed: expected member, got", role)
} }
} }
@ -480,7 +523,6 @@ func TestSignWeeklyReport(t *testing.T) {
if err != nil { if err != nil {
t.Error("GetUserId failed:", err) t.Error("GetUserId failed:", err)
} }
fmt.Println("Project Manager's ID:", projectManagerID)
// Sign the report with the project manager // Sign the report with the project manager
err = db.SignWeeklyReport(report.ReportId, projectManagerID) err = db.SignWeeklyReport(report.ReportId, projectManagerID)
@ -519,7 +561,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
t.Error("AddUser failed:", err) t.Error("AddUser failed:", err)
} }
// Add project // Add project, projectManager is the owner
err = db.AddProject("testproject", "description", "projectManager") err = db.AddProject("testproject", "description", "projectManager")
if err != nil { if err != nil {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
@ -543,14 +585,25 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
t.Error("GetWeeklyReport failed:", err) t.Error("GetWeeklyReport failed:", err)
} }
anotherManagerID, err := db.GetUserId("projectManager") managerID, err := db.GetUserId("projectManager")
if err != nil { if err != nil {
t.Error("GetUserId failed:", err) t.Error("GetUserId failed:", err)
} }
err = db.SignWeeklyReport(report.ReportId, anotherManagerID) err = db.SignWeeklyReport(report.ReportId, managerID)
if err == nil { if err != nil {
t.Error("Expected SignWeeklyReport to fail with a project manager who is not in the project, but it didn't") t.Error("SignWeeklyReport failed:", err)
}
// Retrieve the report again to check if it's signed
signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1)
if err != nil {
t.Error("GetWeeklyReport failed:", err)
}
// Ensure the report is signed by the project manager
if *signedReport.SignedBy != managerID {
t.Errorf("Expected SignedBy to be %d, got %d", managerID, *signedReport.SignedBy)
} }
} }
@ -676,7 +729,28 @@ func TestIsProjectManager(t *testing.T) {
} }
} }
func TestChangeUserName(t *testing.T) { func TestGetTotalTimePerActivity(t *testing.T) {
// Initialize your test database connection
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Run the query to get total time per activity
totalTime, err := db.GetTotalTimePerActivity("projecttest")
if err != nil {
t.Error("GetTotalTimePerActivity failed:", err)
}
// Check if the totalTime map is not nil
if totalTime == nil {
t.Error("Expected non-nil totalTime map, got nil")
}
// ska lägga till fler assertions
}
func TestEnsureManagerOfCreatedProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
t.Error("setupState failed:", err) t.Error("setupState failed:", err)
@ -688,26 +762,24 @@ func TestChangeUserName(t *testing.T) {
t.Error("AddUser failed:", err) t.Error("AddUser failed:", err)
} }
// Change the user's name // Add a project
err = db.ChangeUserName("testuser", "newname") err = db.AddProject("testproject", "description", "testuser")
if err != nil { if err != nil {
t.Error("ChangeUserName failed:", err) t.Error("AddProject failed:", err)
} }
// Retrieve the user's ID // Set user to a project manager
userID, err := db.GetUserId("newname") // err = db.AddUserToProject("testuser", "testproject", "project_manager")
// if err != nil {
// t.Error("AddUserToProject failed:", err)
// }
managerState, err := db.IsProjectManager("testuser", "testproject")
if err != nil { if err != nil {
t.Error("GetUserId failed:", err) t.Error("IsProjectManager failed:", err)
} }
// Ensure the user's ID matches the expected value if !managerState {
if userID != 1 { t.Error("Expected testuser to be a project manager, but it's not.")
t.Errorf("Expected user ID to be 1, got %d", userID)
}
// Attempt to retrieve the user by the old name
_, err = db.GetUserId("testuser")
if err == nil {
t.Error("Expected GetUserId to fail for the old name, but it didn't")
} }
} }

View file

@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS weekly_reports (
study_time INTEGER, study_time INTEGER,
testing_time INTEGER, testing_time INTEGER,
signed_by INTEGER, signed_by INTEGER,
UNIQUE(user_id, project_id, week),
FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (project_id) REFERENCES projects(id), FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (signed_by) REFERENCES users(id) FOREIGN KEY (signed_by) REFERENCES users(id)

View file

@ -33,3 +33,18 @@ VALUES (3,3,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"project_manager"); VALUES (2,1,"project_manager");
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL);

View file

@ -65,8 +65,8 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
//check token and get username of current user //check token and get username of current user
user := c.Locals("user").(*jwt.Token) user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string) username := claims["name"].(string)
log.Info(projectManagerUsername)
// Extract the necessary parameters from the request // Extract the necessary parameters from the request
data := new(types.RoleChange) data := new(types.RoleChange)
if err := c.BodyParser(data); err != nil { if err := c.BodyParser(data); err != nil {
@ -74,18 +74,19 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
// dubble diping and checcking if current user is log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role)
if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, data.Projectname); err != nil { // Dubble diping and checcking if current user is
if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil {
log.Warn("Error checking if projectmanager:", err) log.Warn("Error checking if projectmanager:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} else if !ismanager { } else if !ismanager {
log.Warn("tried chaning role when not projectmanager:", err) log.Warn("User is not projectmanager")
return c.Status(401).SendString("you can not change role when not projectManager") return c.Status(401).SendString("User is not projectmanager")
} }
// Change the user's role within the project in the database // Change the user's role within the project in the database
if err := gs.Db.ChangeUserRole(data.Username, data.Projectname, data.Role); err != nil { if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
@ -212,9 +213,15 @@ func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error {
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project // IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary parameters from the request query string // Extract necessary parameters from the request query string
username := c.Query("username") projectName := c.Params("projectName")
projectName := c.Query("projectName")
log.Info("Checking if user ", username, " is a project manager for project ", projectName)
// Check if the user is a project manager for the specified project // Check if the user is a project manager for the specified project
isManager, err := gs.Db.IsProjectManager(username, projectName) isManager, err := gs.Db.IsProjectManager(username, projectName)
@ -224,10 +231,5 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
} }
// Return the result as JSON // Return the result as JSON
return c.JSON(map[string]bool{"isProjectManager": isManager}) return c.JSON(fiber.Map{"isProjectManager": isManager})
}
func (gs *GState) CreateTask(c *fiber.Ctx) error {
return nil
} }

View file

@ -32,7 +32,7 @@ func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
} }
if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { 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") log.Info("Error adding weekly report to db:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }

View file

@ -207,8 +207,8 @@ func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce plain // @Produce plain
// @Param NewUser body types.NewUser true "user info" // @Param NewUser body types.NewUser true "user info"
// @Success 200 {json} json "Successfully prometed user" // @Success 200 {json} json "Successfully promoted user"
// @Failure 400 {string} string "bad request" // @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized" // @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error" // @Failure 500 {string} string "Internal server error"
// @Router /promoteToAdmin [post] // @Router /promoteToAdmin [post]

View file

@ -14,9 +14,12 @@ type NewProject struct {
Description string `json:"description"` Description string `json:"description"`
} }
// Used to change the role of a user in a project.
// If name is identical to the name contained in the token, the role can be changed.
// If the name is different, only a project manager can change the role.
type RoleChange struct { type RoleChange struct {
UserName string `json:"username"`
Role string `json:"role" tstype:"'project_manager' | 'user'"` Role string `json:"role" tstype:"'project_manager' | 'user'"`
Username string `json:"username"`
Projectname string `json:"projectname"` Projectname string `json:"projectname"`
} }

View file

@ -98,7 +98,7 @@ func main() {
server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) server.Post("/api/promoteToAdmin", gs.PromoteToAdmin)
server.Get("/api/users/all", gs.ListAllUsers) server.Get("/api/users/all", gs.ListAllUsers)
server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler) server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler)
server.Get("api/checkIfProjectManager", gs.IsProjectManagerHandler) server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler)
server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange)
server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject)

View file

@ -8,52 +8,100 @@ import {
WeeklyReport, WeeklyReport,
} from "../Types/goTypes"; } from "../Types/goTypes";
// This type of pattern should be hard to misuse /**
* Response object returned by API methods.
*/
export interface APIResponse<T> { export interface APIResponse<T> {
/** Indicates whether the API call was successful */
success: boolean; success: boolean;
/** Optional message providing additional information or error description */
message?: string; message?: string;
/** Optional data returned by the API method */
data?: T; data?: T;
} }
// Note that all protected routes also require a token /**
// Defines all the methods that an instance of the API must implement * Interface defining methods that an instance of the API must implement.
*/
interface API { interface API {
/** Register a new user */ /**
* Register a new user
* @param {NewUser} user The user object to be registered
* @returns {Promise<APIResponse<User>>} A promise containing the API response with the user data.
*/
registerUser(user: NewUser): Promise<APIResponse<User>>; registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */
/**
* Removes a user.
* @param {string} username The username of the user to be removed.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<User>>} A promise containing the API response with the removed user data.
*/
removeUser(username: string, token: string): Promise<APIResponse<User>>; removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Check if user is project manager */
/**
* Check if user is project manager.
* @param {string} username The username of the user.
* @param {string} projectName The name of the project.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
*/
checkIfProjectManager( checkIfProjectManager(
username: string, username: string,
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<boolean>>; ): Promise<APIResponse<boolean>>;
/** Login */
/** Logs in a user with the provided credentials.
* @param {NewUser} NewUser The user object containing username and password.
* @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a token.
*/
login(NewUser: NewUser): Promise<APIResponse<string>>; login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Renew the token */
/**
* Renew the token
* @param {string} token The current authentication token.
* @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a renewed token.
*/
renewToken(token: string): Promise<APIResponse<string>>; renewToken(token: string): Promise<APIResponse<string>>;
/** Promote user to admin */ /** Promote user to admin */
/** Create a project */
/** Creates a new project.
* @param {NewProject} project The project object containing name and description.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
*/
createProject( createProject(
project: NewProject, project: NewProject,
token: string, token: string,
): Promise<APIResponse<Project>>; ): Promise<APIResponse<Project>>;
/** Submit a weekly report */
/** Submits a weekly report
* @param {NewWeeklyReport} weeklyReport The weekly report object.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<NewWeeklyReport>>} A promise resolving to an API response with the submitted report.
*/
submitWeeklyReport( submitWeeklyReport(
project: NewWeeklyReport, weeklyReport: NewWeeklyReport,
token: string, token: string,
): Promise<APIResponse<NewWeeklyReport>>; ): Promise<APIResponse<NewWeeklyReport>>;
/**Gets a weekly report*/
/** Gets a weekly report for a specific user, project and week
* @param {string} projectName The name of the project.
* @param {string} week The week number.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
*/
getWeeklyReport( getWeeklyReport(
username: string,
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport>>; ): Promise<APIResponse<WeeklyReport>>;
/** /**
* Returns all the weekly reports for a user in a particular project * Returns all the weekly reports for a user in a particular project
* The username is derived from the token * The username is derived from the token
*
* @param {string} projectName The name of the project * @param {string} projectName The name of the project
* @param {string} token The token of the user * @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports * @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
@ -62,11 +110,23 @@ interface API {
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport[]>>; ): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user*/
/** Gets all the projects of a user
* @param {string} token - The authentication token.
* @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
*/
getUserProjects(token: string): Promise<APIResponse<Project[]>>; getUserProjects(token: string): Promise<APIResponse<Project[]>>;
/** Gets a project from id*/
/** Gets a project by its id.
* @param {number} id The id of the project to retrieve.
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response containing the project data.
*/
getProject(id: number): Promise<APIResponse<Project>>; getProject(id: number): Promise<APIResponse<Project>>;
/** Gets all users*/
/** Gets a list of all users.
* @param {string} token The authentication token of the requesting user.
* @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
*/
getAllUsers(token: string): Promise<APIResponse<string[]>>; getAllUsers(token: string): Promise<APIResponse<string[]>>;
/** Gets all users in a project from name*/ /** Gets all users in a project from name*/
getAllUsersProject( getAllUsersProject(
@ -75,7 +135,7 @@ interface API {
): Promise<APIResponse<UserProjectMember[]>>; ): Promise<APIResponse<UserProjectMember[]>>;
} }
// Export an instance of the API /** An instance of the API */
export const api: API = { export const api: API = {
async registerUser(user: NewUser): Promise<APIResponse<User>> { async registerUser(user: NewUser): Promise<APIResponse<User>> {
try { try {
@ -263,20 +323,21 @@ export const api: API = {
}, },
async getWeeklyReport( async getWeeklyReport(
username: string,
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport>> { ): Promise<APIResponse<WeeklyReport>> {
try { try {
const response = await fetch("/api/getWeeklyReport", { const response = await fetch(
`/api/getWeeklyReport?projectName=${projectName}&week=${week}`,
{
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer " + token, Authorization: "Bearer " + token,
}, },
body: JSON.stringify({ username, projectName, week }), },
}); );
if (!response.ok) { if (!response.ok) {
return { success: false, message: "Failed to get weekly report" }; return { success: false, message: "Failed to get weekly report" };
@ -342,7 +403,6 @@ export const api: API = {
} }
}, },
// Gets a projet by id, currently untested since we have no javascript-based tests
async getProject(id: number): Promise<APIResponse<Project>> { async getProject(id: number): Promise<APIResponse<Project>> {
try { try {
const response = await fetch(`/api/project/${id}`, { const response = await fetch(`/api/project/${id}`, {
@ -368,7 +428,6 @@ export const api: API = {
} }
}, },
// Gets all users
async getAllUsers(token: string): Promise<APIResponse<string[]>> { async getAllUsers(token: string): Promise<APIResponse<string[]>> {
try { try {
const response = await fetch("/api/users/all", { const response = await fetch("/api/users/all", {

View file

@ -7,7 +7,7 @@ import Button from "./Button";
/** /**
* Tries to add a project to the system * Tries to add a project to the system
* @param props - Project name and description * @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not * @returns {boolean} True if created, false if not
*/ */
function CreateProject(props: { name: string; description: string }): boolean { function CreateProject(props: { name: string; description: string }): boolean {
@ -34,8 +34,8 @@ function CreateProject(props: { name: string; description: string }): boolean {
} }
/** /**
* Tries to add a project to the system * Provides UI for adding a project to the system.
* @returns {JSX.Element} UI for project adding * @returns {JSX.Element} - Returns the component UI for adding a project
*/ */
function AddProject(): JSX.Element { function AddProject(): JSX.Element {
const [name, setName] = useState(""); const [name, setName] = useState("");

View file

@ -7,17 +7,19 @@ import { api } from "../API/API";
/** /**
* Renders a component that displays all the time reports for a specific project. * Renders a component that displays all the time reports for a specific project.
* @returns JSX.Element representing the component. * @returns {JSX.Element} representing the component.
*/ */
function AllTimeReportsInProject(): JSX.Element { function AllTimeReportsInProject(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]); const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
// Call getProjects when the component mounts
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => { const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser( const response = await api.getWeeklyReportsForUser(
token,
projectName ?? "", projectName ?? "",
token,
); );
console.log(response); console.log(response);
if (response.success) { if (response.success) {
@ -27,10 +29,8 @@ function AllTimeReportsInProject(): JSX.Element {
} }
}; };
// Call getProjects when the component mounts
useEffect(() => {
void getWeeklyReports(); void getWeeklyReports();
}); }, [projectName]);
return ( return (
<> <>

View file

@ -144,9 +144,14 @@ export interface NewProject {
name: string; name: string;
description: string; description: string;
} }
/**
* Used to change the role of a user in a project.
* If name is identical to the name contained in the token, the role can be changed.
* If the name is different, only a project manager can change the role.
*/
export interface RoleChange { export interface RoleChange {
role: 'project_manager' | 'user';
username: string; username: string;
role: 'project_manager' | 'user';
projectname: string; projectname: string;
} }
export interface NameChange { export interface NameChange {

View file

@ -20,8 +20,8 @@ def randomString(len=10):
# Defined once per test run # Defined once per test run
username = randomString() username = "user_" + randomString()
projectName = randomString() projectName = "project_" + randomString()
# The base URL of the API # The base URL of the API
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
@ -45,30 +45,37 @@ getUsersProjectPath = base_url + "/api/getUsersProject"
#ta bort auth i handlern för att få testet att gå igenom #ta bort auth i handlern för att få testet att gå igenom
def test_ProjectRoleChange(): def test_ProjectRoleChange():
dprint("Testing ProjectRoleChange") dprint("Testing ProjectRoleChange")
project_manager = randomString() localUsername = randomString()
register(project_manager, "project_manager_password") localProjectName = randomString()
register(localUsername, "username_password")
token = login(project_manager, "project_manager_password").json()[ token = login(localUsername, "username_password").json()[
"token" "token"
] ]
# Just checking since this test is built somewhat differently than the others
assert token != None, "Login failed"
response = requests.post( response = requests.post(
addProjectPath, addProjectPath,
json={"name": projectName, "description": "This is a project"}, json={"name": localProjectName, "description": "This is a project"},
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
) )
if response.status_code != 200:
print("Add project failed")
response = requests.post( response = requests.post(
ProjectRoleChangePath, ProjectRoleChangePath,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
json={ json={
"username": username, "projectName": localProjectName,
"projectName": projectName, "role": "project_manager",
"week": 1
}, },
) )
if response.status_code != 200:
print("auth not working, för att man inte kan få tag på pm token atm, för att få igenom det så ta bort auth i handler")
assert response.status_code == 200, "change role successfully" assert response.status_code == 200, "ProjectRoleChange failed"
gprint("test_ProjectRoleChange successful")
def test_get_user_projects(): def test_get_user_projects():
@ -267,7 +274,7 @@ def test_sign_report():
submitReportPath, submitReportPath,
json={ json={
"projectName": projectName, "projectName": projectName,
"week": 1, "week": 2,
"developmentTime": 10, "developmentTime": 10,
"meetingTime": 5, "meetingTime": 5,
"adminTime": 5, "adminTime": 5,
@ -329,42 +336,36 @@ def test_check_if_project_manager():
# Check if the user is a project manager for the project # Check if the user is a project manager for the project
response = requests.get( response = requests.get(
checkIfProjectManagerPath, checkIfProjectManagerPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName},
) )
dprint(response.text) dprint(response.text)
assert response.status_code == 200, "Check if project manager failed" assert response.status_code == 200, "Check if project manager failed"
gprint("test_check_if_project_manager successful") gprint("test_check_if_project_manager successful")
def test_list_all_users_project(): def test_ensure_manager_of_created_project():
# Log in as a user who is a member of the project # Create a new user to add to the project
admin_username = randomString() newUser = "karen_" + randomString()
admin_password = "admin_password2" newProject = "HR_" + randomString()
dprint( register(newUser, "new_user_password")
"Registering with username: ", admin_username, " and password: ", admin_password token = login(newUser, "new_user_password").json()["token"]
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# Log in as the admin # Create a new project
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post( response = requests.post(
promoteToAdminPath, addProjectPath,
json={"username": admin_username}, json={"name": newProject, "description": "This is a project"},
headers={"Authorization": "Bearer " + admin_token}, headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "Add project failed"
# Make a request to list all users associated with the project
response = requests.get( response = requests.get(
getUsersProjectPath + "/" + projectName, checkIfProjectManagerPath + "/" + newProject,
headers={"Authorization": "Bearer " + admin_token}, headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "List all users project failed" assert response.status_code == 200, "Check if project manager failed"
gprint("test_list_all_users_project sucessful") assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful")
if __name__ == "__main__": if __name__ == "__main__":
@ -380,4 +381,5 @@ if __name__ == "__main__":
test_get_weekly_reports_user() test_get_weekly_reports_user()
test_check_if_project_manager() test_check_if_project_manager()
test_ProjectRoleChange() test_ProjectRoleChange()
test_list_all_users_project() #test_list_all_users_project()
test_ensure_manager_of_created_project()