diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 7f4a89c..ef365cd 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -2,6 +2,7 @@ package database import ( "embed" + "os" "path/filepath" "ttime/internal/types" @@ -18,7 +19,7 @@ type Database interface { PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error - Migrate() error + Migrate(dirname string) 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 @@ -29,7 +30,6 @@ type Database interface { GetAllProjects() ([]types.Project, error) GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) - GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) } // This struct is a wrapper type that holds the database connection @@ -257,45 +257,15 @@ func (d *Db) GetAllUsersApplication() ([]string, error) { return usernames, nil } -func (d *Db) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) { - var report types.WeeklyReport - query := ` - SELECT - report_id, - user_id, - project_id, - week, - development_time, - meeting_time, - admin_time, - own_work_time, - study_time, - testing_time - FROM - weekly_reports - WHERE - user_id = (SELECT id FROM users WHERE username = ?) - AND project_id = (SELECT id FROM projects WHERE name = ?) - AND week = ? - ` - err := d.Get(&report, query, username, projectName, week) - return report, err -} - // Reads a directory of migration files and applies them to the database. // This will eventually be used on an embedded directory -func (d *Db) Migrate() error { +func (d *Db) Migrate(dirname string) error { // Read the embedded scripts directory files, err := scripts.ReadDir("migrations") if err != nil { return err } - if len(files) == 0 { - println("No migration files found") - return nil - } - tr := d.MustBegin() // Iterate over each SQL file and execute it @@ -305,7 +275,8 @@ func (d *Db) Migrate() error { } // This is perhaps not the most elegant way to do this - sqlBytes, err := scripts.ReadFile("migrations/" + file.Name()) + sqlFile := filepath.Join("migrations", file.Name()) + sqlBytes, err := os.ReadFile(sqlFile) if err != nil { return err } diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index f791066..5438d66 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -8,7 +8,7 @@ import ( func setupState() (Database, error) { db := DbConnect(":memory:") - err := db.Migrate() + err := db.Migrate("../../migrations") if err != nil { return nil, err } @@ -371,42 +371,3 @@ func TestAddProject(t *testing.T) { t.Error("Added project not found") } } - -func TestGetWeeklyReport(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddProject("testproject", "description", "testuser") - if err != nil { - t.Error("AddProject failed:", err) - } - - err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - report, err := db.GetWeeklyReport("testuser", "testproject", 1) - if err != nil { - t.Error("GetWeeklyReport failed:", err) - } - - // Check if the retrieved report matches the expected values - if report.UserId != 1 { - t.Errorf("Expected UserId to be 1, got %d", report.UserId) - } - if report.ProjectId != 1 { - t.Errorf("Expected ProjectId to be 1, got %d", report.ProjectId) - } - if report.Week != 1 { - t.Errorf("Expected Week to be 1, got %d", report.Week) - } - // Check other fields similarly -} diff --git a/backend/internal/database/migrations/0010_users.sql b/backend/internal/database/migrations/0010_users.sql index d2e2dd1..5c9d329 100644 --- a/backend/internal/database/migrations/0010_users.sql +++ b/backend/internal/database/migrations/0010_users.sql @@ -3,7 +3,7 @@ -- username is what is used for login -- password is the hashed password CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INTEGER PRIMARY KEY, userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, username VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index 99bce86..58d8e97 100644 --- a/backend/internal/database/migrations/0020_projects.sql +++ b/backend/internal/database/migrations/0020_projects.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS projects ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, description TEXT NOT NULL, owner_user_id INTEGER NOT NULL, diff --git a/backend/internal/database/migrations/0035_weekly_report.sql b/backend/internal/database/migrations/0035_weekly_report.sql index 366d932..0e29b97 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,16 +1,14 @@ CREATE TABLE weekly_reports ( - report_id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - project_id INTEGER NOT NULL, - week INTEGER NOT NULL, + user_id INTEGER, + project_id INTEGER, + week INTEGER, development_time INTEGER, meeting_time INTEGER, admin_time INTEGER, own_work_time INTEGER, study_time INTEGER, testing_time INTEGER, - signed_by INTEGER, FOREIGN KEY (user_id) REFERENCES users(id), - FOREIGN KEY (project_id) REFERENCES projects(id), - FOREIGN KEY (signed_by) REFERENCES users(id) -); \ No newline at end of file + FOREIGN KEY (project_id) REFERENCES projects(id) + PRIMARY KEY (user_id, project_id, week) +) \ No newline at end of file diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 57a1969..2378f7b 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,9 +1,13 @@ package handlers import ( + "strconv" + "time" "ttime/internal/database" + "ttime/internal/types" "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" ) // The actual interface that we will use @@ -15,7 +19,6 @@ type GlobalState interface { CreateProject(c *fiber.Ctx) error // To create a new project GetUserProjects(c *fiber.Ctx) error // To get all projects SubmitWeeklyReport(c *fiber.Ctx) error - GetWeeklyReport(c *fiber.Ctx) error // GetProject(c *fiber.Ctx) error // To get a specific project // UpdateProject(c *fiber.Ctx) error // To update a project // DeleteProject(c *fiber.Ctx) error // To delete a project @@ -48,6 +51,50 @@ type GState struct { ButtonCount int } +// Register is a simple handler that registers a new user +// +// @Summary Register a new user +// @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] +func (gs *GState) Register(c *fiber.Ctx) error { + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + return c.Status(400).SendString(err.Error()) + } + + if err := gs.Db.AddUser(u.Username, u.Password); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("User added") +} + +// This path should obviously be protected in the future +// UserDelete deletes a user from the database +func (gs *GState) UserDelete(c *fiber.Ctx) error { + // Read from path parameters + username := c.Params("username") + + // Read username from Locals + auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) + + if username != auth_username { + return c.Status(403).SendString("You can only delete yourself") + } + + if err := gs.Db.RemoveUser(username); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("User deleted") +} + func (gs *GState) GetButtonCount(c *fiber.Ctx) error { return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) } @@ -56,3 +103,181 @@ func (gs *GState) IncrementButtonCount(c *fiber.Ctx) error { gs.ButtonCount++ return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) } + +// Login is a simple login handler that returns a JWT token +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 { + return c.Status(400).SendString(err.Error()) + } + + if !gs.Db.CheckUser(u.Username, u.Password) { + println("User not found") + return c.SendStatus(fiber.StatusUnauthorized) + } + + // Create the Claims + claims := jwt.MapClaims{ + "name": u.Username, + "admin": false, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + return c.JSON(fiber.Map{"token": t}) +} + +// LoginRenew is a simple handler that renews the token +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + renewed := jwt.MapClaims{ + "name": claims["name"], + "admin": claims["admin"], + "exp": claims["exp"], + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + return c.JSON(fiber.Map{"token": t}) +} + +// CreateProject is a simple handler that creates a new project +func (gs *GState) CreateProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + p := new(types.NewProject) + if err := c.BodyParser(p); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Get the username from the token and set it as the owner of the project + // This is ugly but + claims := user.Claims.(jwt.MapClaims) + owner := claims["name"].(string) + + if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Project added") +} + +// GetUserProjects returns all projects that the user is a member of +func (gs *GState) GetUserProjects(c *fiber.Ctx) error { + // First we get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Then dip into the database to get the projects + projects, err := gs.Db.GetProjectsForUser(username) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a json serialized list of projects + return c.JSON(projects) +} + +// ListAllUsers is a handler that returns a list of all users in the application database +func (gs *GState) ListAllUsers(c *fiber.Ctx) error { + // Get all users from the database + users, err := gs.Db.GetAllUsersApplication() + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the list of users as JSON + return c.JSON(users) +} + +func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error { + // Extract the project name from the request parameters or body + projectName := c.Params("projectName") + + // Get all users associated with the project from the database + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return the list of users as JSON + return c.JSON(users) +} + +// ProjectRoleChange is a handler that changes a user's role within a project +func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { + // Extract the necessary parameters from the request + username := c.Params("username") + projectName := c.Params("projectName") + role := c.Params("role") + + // Change the user's role within the project in the database + if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} + +// GetProject retrieves a specific project by its ID +func (gs *GState) GetProject(c *fiber.Ctx) error { + // Extract the project ID from the request parameters or body + projectID := c.Params("projectID") + + // Parse the project ID into an integer + projectIDInt, err := strconv.Atoi(projectID) + if err != nil { + 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 { + return c.Status(500).SendString(err.Error()) + } + + // Return the project as JSON + return c.JSON(project) +} + +func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + report := new(types.NewWeeklyReport) + if err := c.BodyParser(report); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if report.Week < 1 || report.Week > 52 { + 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 { + 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 { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Time report added") +} diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go deleted file mode 100644 index 6a430e9..0000000 --- a/backend/internal/handlers/handlers_project_related.go +++ /dev/null @@ -1,98 +0,0 @@ -package handlers - -import ( - "strconv" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" -) - -// CreateProject is a simple handler that creates a new project -func (gs *GState) CreateProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - - p := new(types.NewProject) - if err := c.BodyParser(p); err != nil { - return c.Status(400).SendString(err.Error()) - } - - // Get the username from the token and set it as the owner of the project - // This is ugly but - claims := user.Claims.(jwt.MapClaims) - owner := claims["name"].(string) - - if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("Project added") -} - -// GetUserProjects returns all projects that the user is a member of -func (gs *GState) GetUserProjects(c *fiber.Ctx) error { - // First we get the username from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Then dip into the database to get the projects - projects, err := gs.Db.GetProjectsForUser(username) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a json serialized list of projects - return c.JSON(projects) -} - -// ProjectRoleChange is a handler that changes a user's role within a project -func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { - // Extract the necessary parameters from the request - username := c.Params("username") - projectName := c.Params("projectName") - role := c.Params("role") - - // Change the user's role within the project in the database - if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} - -// GetProject retrieves a specific project by its ID -func (gs *GState) GetProject(c *fiber.Ctx) error { - // Extract the project ID from the request parameters or body - projectID := c.Params("projectID") - - // Parse the project ID into an integer - projectIDInt, err := strconv.Atoi(projectID) - if err != nil { - 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 { - return c.Status(500).SendString(err.Error()) - } - - // Return the project as JSON - return c.JSON(project) -} - -func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error { - // Extract the project name from the request parameters or body - projectName := c.Params("projectName") - - // Get all users associated with the project from the database - users, err := gs.Db.GetAllUsersProject(projectName) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go deleted file mode 100644 index 8754afd..0000000 --- a/backend/internal/handlers/handlers_report_related.go +++ /dev/null @@ -1,62 +0,0 @@ -package handlers - -import ( - "strconv" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" -) - -func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - report := new(types.NewWeeklyReport) - if err := c.BodyParser(report); err != nil { - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if report.Week < 1 || report.Week > 52 { - 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 { - 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 { - return c.Status(500).SendString(err.Error()) - } - - 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 - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract project name and week from query parameters - projectName := c.Query("projectName") - week := c.Query("week") - - // Convert week to integer - weekInt, err := strconv.Atoi(week) - if err != nil { - 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 { - return c.Status(500).SendString(err.Error()) - } - - // Return the retrieved weekly report - return c.JSON(report) -} diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go deleted file mode 100644 index e90abd0..0000000 --- a/backend/internal/handlers/handlers_user_related.go +++ /dev/null @@ -1,116 +0,0 @@ -package handlers - -import ( - "time" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" -) - -// Register is a simple handler that registers a new user -// -// @Summary Register a new user -// @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] -func (gs *GState) Register(c *fiber.Ctx) error { - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - return c.Status(400).SendString(err.Error()) - } - - if err := gs.Db.AddUser(u.Username, u.Password); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("User added") -} - -// This path should obviously be protected in the future -// UserDelete deletes a user from the database -func (gs *GState) UserDelete(c *fiber.Ctx) error { - // Read from path parameters - username := c.Params("username") - - // Read username from Locals - auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) - - if username != auth_username { - return c.Status(403).SendString("You can only delete yourself") - } - - if err := gs.Db.RemoveUser(username); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("User deleted") -} - -// Login is a simple login handler that returns a JWT token -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 { - return c.Status(400).SendString(err.Error()) - } - - if !gs.Db.CheckUser(u.Username, u.Password) { - println("User not found") - return c.SendStatus(fiber.StatusUnauthorized) - } - - // Create the Claims - claims := jwt.MapClaims{ - "name": u.Username, - "admin": false, - "exp": time.Now().Add(time.Hour * 72).Unix(), - } - - // Create token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - - // Generate encoded token and send it as response. - t, err := token.SignedString([]byte("secret")) - if err != nil { - return c.SendStatus(fiber.StatusInternalServerError) - } - - return c.JSON(fiber.Map{"token": t}) -} - -// LoginRenew is a simple handler that renews the token -func (gs *GState) LoginRenew(c *fiber.Ctx) error { - // For testing: curl localhost:3000/restricted -H "Authorization: Bearer " - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - claims["exp"] = time.Now().Add(time.Hour * 72).Unix() - renewed := jwt.MapClaims{ - "name": claims["name"], - "admin": claims["admin"], - "exp": claims["exp"], - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) - t, err := token.SignedString([]byte("secret")) - if err != nil { - return c.SendStatus(fiber.StatusInternalServerError) - } - return c.JSON(fiber.Map{"token": t}) -} - -// ListAllUsers is a handler that returns a list of all users in the application database -func (gs *GState) ListAllUsers(c *fiber.Ctx) error { - // Get all users from the database - users, err := gs.Db.GetAllUsersApplication() - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/types/WeeklyReport.go b/backend/internal/types/WeeklyReport.go index b704cc8..e0ea1ef 100644 --- a/backend/internal/types/WeeklyReport.go +++ b/backend/internal/types/WeeklyReport.go @@ -19,26 +19,3 @@ type NewWeeklyReport struct { // Total time spent on testing TestingTime int `json:"testingTime"` } - -type WeeklyReport struct { - // The ID of the report - ReportId int `json:"reportId" db:"report_id"` - // The user id of the user who submitted the report - UserId int `json:"userId" db:"user_id"` - // The name of the project, as it appears in the database - ProjectId int `json:"projectId" db:"project_id"` - // The week number - Week int `json:"week" db:"week"` - // Total time spent on development - DevelopmentTime int `json:"developmentTime" db:"development_time"` - // Total time spent in meetings - MeetingTime int `json:"meetingTime" db:"meeting_time"` - // Total time spent on administrative tasks - AdminTime int `json:"adminTime" db:"admin_time"` - // Total time spent on personal projects - OwnWorkTime int `json:"ownWorkTime" db:"own_work_time"` - // Total time spent on studying - StudyTime int `json:"studyTime" db:"study_time"` - // Total time spent on testing - TestingTime int `json:"testingTime" db:"testing_time"` -} diff --git a/backend/main.go b/backend/main.go index 7f0f81e..9ba2556 100644 --- a/backend/main.go +++ b/backend/main.go @@ -43,11 +43,6 @@ func main() { // Connect to the database db := database.DbConnect(conf.DbPath) - // Migrate the database - if err = db.Migrate(); err != nil { - fmt.Println("Error migrating database: ", err) - } - // Get our global state gs := handlers.NewGlobalState(db) // Create the server diff --git a/frontend/src/API/API.test.ts b/frontend/src/API/API.test.ts deleted file mode 100644 index e0a93f6..0000000 --- a/frontend/src/API/API.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { describe, expect, test } from "@jest/globals"; -import { api } from "../API/API"; -import { NewUser, NewWeeklyReport } from "../Types/goTypes"; - -describe("API", () => { - test("registerUser", async () => { - const user: NewUser = { - username: "lol", // Add the username property - password: "lol", - }; - - const response = await api.registerUser(user); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("userId"); - }); - - test("createProject", async () => { - const project = { - name: "Project X", - description: "This is a test project", - }; - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.createProject(project, token); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("projectId"); - }); - - test("renewToken", async () => { - const refreshToken = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.renewToken(refreshToken); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("accessToken"); - expect(response.data).toHaveProperty("refreshToken"); - }); - - test("getUserProjects", async () => { - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - const username = "rrgumdzpmc"; - const response = await api.getUserProjects(username, token); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("projects"); - }); - - test("submitWeeklyReport", async () => { - const report: NewWeeklyReport = { - projectName: "vtmosxssst", - week: 2, - developmentTime: 40, - meetingTime: 5, - adminTime: 2, - ownWorkTime: 10, - studyTime: 12, - testingTime: 41, - }; - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTcxMDk0MDIwMywibmFtZSI6InJyZ3VtZHpwbWMifQ.V9NHoYMYV61t"; - - const response = await api.submitWeeklyReport(report, token); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty( - "message", - "Report submitted successfully", - ); - }); - - test("login", async () => { - const user: NewUser = { - username: "rrgumdzpmc", // Add an empty string value for the username property - password: "always_same", - }; - - const response = await api.login(user); - - expect(response.success).toBe(true); - expect(response.data).toHaveProperty("accessToken"); - expect(response.data).toHaveProperty("refreshToken"); - }); -}); diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index d87594c..248ad37 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,10 +1,5 @@ -import { - NewWeeklyReport, - NewUser, - User, - Project, - NewProject, -} from "../Types/goTypes"; +import { NewProject, Project } from "../Types/Project"; +import { NewUser, User } from "../Types/Users"; // This type of pattern should be hard to misuse interface APIResponse { @@ -25,20 +20,8 @@ interface API { project: NewProject, token: string, ): Promise>; - /** Submit a weekly report */ - submitWeeklyReport( - project: NewWeeklyReport, - token: string, - ): Promise>; /** Renew the token */ renewToken(token: string): Promise>; - /** Gets all the projects of a user*/ - getUserProjects( - username: string, - token: string, - ): Promise>; - /** Login */ - login(NewUser: NewUser): Promise>; } // Export an instance of the API @@ -134,83 +117,4 @@ export const api: API = { return { success: false, message: "Failed to renew token" }; } }, - - async getUserProjects(token: string): Promise> { - try { - const response = await fetch("/api/getUserProjects", { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return Promise.resolve({ - success: false, - message: "Failed to get user projects", - }); - } else { - const data = (await response.json()) as Project[]; - return Promise.resolve({ success: true, data }); - } - } catch (e) { - return Promise.resolve({ - success: false, - message: "Failed to get user projects", - }); - } - }, - - async submitWeeklyReport( - project: NewWeeklyReport, - token: string, - ): Promise> { - try { - const response = await fetch("/api/submitWeeklyReport", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(project), - }); - - if (!response.ok) { - return { - success: false, - message: "Failed to submit weekly report", - }; - } - - const data = (await response.json()) as Project; - return { success: true, data }; - } catch (e) { - return { - success: false, - message: "Failed to submit weekly report", - }; - } - }, - - async login(NewUser: NewUser): Promise> { - try { - const response = await fetch("/api/login", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(NewUser), - }); - - if (!response.ok) { - return { success: false, message: "Failed to login" }; - } else { - const data = (await response.json()) as { token: string }; // Update the type of 'data' - return { success: true, data: data.token }; - } - } catch (e) { - return Promise.resolve({ success: false, message: "Failed to login" }); - } - }, }; diff --git a/frontend/src/Components/BackButton.tsx b/frontend/src/Components/BackButton.tsx deleted file mode 100644 index 7a1ac81..0000000 --- a/frontend/src/Components/BackButton.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useNavigate } from "react-router-dom"; - -function BackButton(): JSX.Element { - const navigate = useNavigate(); - const goBack = (): void => { - navigate(-1); - }; - return ( - - ); -} - -export default BackButton; diff --git a/frontend/src/Components/BackgroundAnimation.tsx b/frontend/src/Components/BackgroundAnimation.tsx deleted file mode 100644 index 5f402c0..0000000 --- a/frontend/src/Components/BackgroundAnimation.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect } from "react"; - -const BackgroundAnimation = (): JSX.Element => { - useEffect(() => { - const images = [ - "src/assets/1.jpg", - "src/assets/2.jpg", - "src/assets/3.jpg", - "src/assets/4.jpg", - ]; - - // Pre-load images - for (const i of images) { - console.log(i); - } - - // Start animation - document.body.style.animation = "backgroundTransition 30s infinite"; - }, []); - - return <>; -}; - -export default BackgroundAnimation; diff --git a/frontend/src/Components/InputField.tsx b/frontend/src/Components/InputField.tsx deleted file mode 100644 index 639b4ca..0000000 --- a/frontend/src/Components/InputField.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * A customizable input field - * @param props - Settings for the field - * @returns {JSX.Element} The input field - * @example - * { - * setExample(e.target.value); - * }} - * value={example} - * /> - */ -function InputField(props: { - label: string; - type: string; - value: string; - onChange: (e: React.ChangeEvent) => void; -}): JSX.Element { - return ( -
- - -
- ); -} - -export default InputField; diff --git a/frontend/src/Components/LoginCheck.tsx b/frontend/src/Components/LoginCheck.tsx deleted file mode 100644 index af45d0f..0000000 --- a/frontend/src/Components/LoginCheck.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { NewUser } from "../Types/goTypes"; - -function LoginCheck(props: { username: string; password: string }): number { - //Example users for testing without backend, remove when using backend - const admin: NewUser = { - username: "admin", - password: "123", - }; - const pmanager: NewUser = { - username: "pmanager", - password: "123", - }; - const user: NewUser = { - username: "user", - password: "123", - }; - - //TODO: Compare with db instead when finished - if (props.username === admin.username && props.password === admin.password) { - return 1; - } else if ( - props.username === pmanager.username && - props.password === pmanager.password - ) { - return 2; - } else if ( - props.username === user.username && - props.password === user.password - ) { - return 3; - } - return 0; -} - -export default LoginCheck; diff --git a/frontend/src/Components/LoginField.tsx b/frontend/src/Components/LoginField.tsx deleted file mode 100644 index d7768c0..0000000 --- a/frontend/src/Components/LoginField.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Dispatch, FormEventHandler, SetStateAction } from "react"; -import Button from "./Button"; -import InputField from "./InputField"; - -/** - * A login field complete with input fields - * and a button for submitting the information - * @param props - Settings - * @returns {JSX.Element} A login component - * @example - * - */ -function Login(props: { - handleSubmit: FormEventHandler; - setUsername: Dispatch>; - setPassword: Dispatch>; - username: string; - password: string; -}): JSX.Element { - return ( -
- { - props.setUsername(e.target.value); - }} - value={props.username} - /> - { - props.setPassword(e.target.value); - }} - value={props.password} - /> -