diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index ef365cd..7f4a89c 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -2,7 +2,6 @@ package database import ( "embed" - "os" "path/filepath" "ttime/internal/types" @@ -19,7 +18,7 @@ type Database interface { PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error - Migrate(dirname string) error + Migrate() 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 @@ -30,6 +29,7 @@ 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,15 +257,45 @@ 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(dirname string) error { +func (d *Db) Migrate() 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 @@ -275,8 +305,7 @@ func (d *Db) Migrate(dirname string) error { } // This is perhaps not the most elegant way to do this - sqlFile := filepath.Join("migrations", file.Name()) - sqlBytes, err := os.ReadFile(sqlFile) + sqlBytes, err := scripts.ReadFile("migrations/" + file.Name()) if err != nil { return err } diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 5438d66..f791066 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("../../migrations") + err := db.Migrate() if err != nil { return nil, err } @@ -371,3 +371,42 @@ 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 5c9d329..d2e2dd1 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, + 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 diff --git a/backend/internal/database/migrations/0020_projects.sql b/backend/internal/database/migrations/0020_projects.sql index 58d8e97..99bce86 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, + id INTEGER PRIMARY KEY AUTOINCREMENT, 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 0e29b97..8f76b80 100644 --- a/backend/internal/database/migrations/0035_weekly_report.sql +++ b/backend/internal/database/migrations/0035_weekly_report.sql @@ -1,14 +1,16 @@ -CREATE TABLE weekly_reports ( - user_id INTEGER, - project_id INTEGER, - week INTEGER, +CREATE TABLE IF NOT EXISTS weekly_reports ( + report_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + week INTEGER NOT NULL, 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) - PRIMARY KEY (user_id, project_id, week) -) \ No newline at end of file + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (signed_by) REFERENCES users(id) +); \ No newline at end of file diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 2378f7b..57a1969 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,13 +1,9 @@ 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 @@ -19,6 +15,7 @@ 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 @@ -51,50 +48,6 @@ 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}) } @@ -103,181 +56,3 @@ 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 new file mode 100644 index 0000000..6a430e9 --- /dev/null +++ b/backend/internal/handlers/handlers_project_related.go @@ -0,0 +1,98 @@ +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 new file mode 100644 index 0000000..8754afd --- /dev/null +++ b/backend/internal/handlers/handlers_report_related.go @@ -0,0 +1,62 @@ +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 new file mode 100644 index 0000000..0619ea5 --- /dev/null +++ b/backend/internal/handlers/handlers_user_related.go @@ -0,0 +1,124 @@ +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 { + println("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + println("Adding user:", u.Username) + if err := gs.Db.AddUser(u.Username, u.Password); err != nil { + return c.Status(500).SendString(err.Error()) + } + + println("User added:", u.Username) + 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 { + println("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + println("Username:", u.Username) + 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) + println("Token created for user:", u.Username) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + println("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + println("Successfully signed token for user:", u.Username) + 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 e0ea1ef..b704cc8 100644 --- a/backend/internal/types/WeeklyReport.go +++ b/backend/internal/types/WeeklyReport.go @@ -19,3 +19,26 @@ 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 9ba2556..7f0f81e 100644 --- a/backend/main.go +++ b/backend/main.go @@ -43,6 +43,11 @@ 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