Compare commits
	
		
			15 commits
		
	
	
		
			887f31dde0
			...
			e03727613d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e03727613d | ||
|   | 8a34fc07fa | ||
|   | b93df693d2 | ||
|   | 3683552af8 | ||
|   | 9240d5e052 | ||
|   | 3e00a532cf | ||
|   | a77e57e496 | ||
|   | c90d495636 | ||
|   | ec46d29423 | ||
|   | d6fc0594a9 | ||
|   | 1be3ee89c2 | ||
|   | ec36054bd9 | ||
|   | 670ed46d51 | ||
|   | 21cc7ff8a3 | ||
|   | 7c21677310 | 
					 11 changed files with 398 additions and 241 deletions
				
			
		|  | @ -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 | ||||
| 		} | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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) | ||||
| ) | ||||
|   FOREIGN KEY (project_id) REFERENCES projects(id), | ||||
|   FOREIGN KEY (signed_by) REFERENCES users(id) | ||||
| ); | ||||
|  | @ -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 <token>" | ||||
| 	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") | ||||
| } | ||||
|  |  | |||
							
								
								
									
										98
									
								
								backend/internal/handlers/handlers_project_related.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								backend/internal/handlers/handlers_project_related.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
							
								
								
									
										62
									
								
								backend/internal/handlers/handlers_report_related.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								backend/internal/handlers/handlers_report_related.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
							
								
								
									
										124
									
								
								backend/internal/handlers/handlers_user_related.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								backend/internal/handlers/handlers_user_related.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <token>" | ||||
| 	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) | ||||
| } | ||||
|  | @ -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"` | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue