diff --git a/backend/api_test.go b/backend/api_test.go index 2c4b50e..9a439b0 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -205,3 +205,84 @@ func TestDeleteUserGoalInvalidId(t *testing.T) { Status(400). JSON().Object().Value("error").IsEqual("Invalid goal ID") } + +func TestCreateTask(t *testing.T) { + e := startServer(t) + + // Create a new task without assigned user + requestBody := map[string]interface{}{ + "name": "Test Task", + "reward": 100, + } + + response := e.POST("/tasks"). + WithJSON(requestBody). + Expect(). + Status(201). // Expect Created status + JSON().Object() + + // Verify the response has an ID + response.ContainsKey("id") + taskId := response.Value("id").Number().Raw() + + // Create a new task with assigned user + assignedUserId := 1 + requestBodyWithUser := map[string]interface{}{ + "name": "Test Task Assigned", + "reward": 200, + "assigned": assignedUserId, + } + + responseWithUser := e.POST("/tasks"). + WithJSON(requestBodyWithUser). + Expect(). + Status(201). + JSON().Object() + + responseWithUser.ContainsKey("id") + responseWithUser.Value("id").Number().NotEqual(taskId) // Ensure different ID +} + +func TestCreateTaskNoName(t *testing.T) { + e := startServer(t) + + requestBody := map[string]interface{}{ + "reward": 100, + } + + e.POST("/tasks"). + WithJSON(requestBody). + Expect(). + Status(400). // Expect Bad Request + JSON().Object().Value("error").IsEqual("Task name cannot be empty") +} + +func TestCreateTaskInvalidAssignedUser(t *testing.T) { + e := startServer(t) + + requestBody := map[string]interface{}{ + "name": "Test Task Invalid User", + "reward": 100, + "assigned": 999, // Non-existent user ID + } + + e.POST("/tasks"). + WithJSON(requestBody). + Expect(). + Status(404). // Expect Not Found + JSON().Object().Value("error").IsEqual(ErrUserNotFound) +} + +func TestCreateTaskInvalidRequestBody(t *testing.T) { + e := startServer(t) + + // Test with missing fields (name is required) + invalidRequest := map[string]interface{}{ + "reward": 5000, + } + + e.POST("/tasks"). + WithJSON(invalidRequest). + Expect(). + Status(400) +} diff --git a/backend/db.go b/backend/db.go index 6e68f31..a5ce545 100644 --- a/backend/db.go +++ b/backend/db.go @@ -150,3 +150,35 @@ func (db *Db) DeleteGoal(userId int, goalId int) error { return nil } + +func (db *Db) CreateTask(task *CreateTaskRequest) (int, error) { + tx, err := db.db.Begin() + if err != nil { + return 0, err + } + defer tx.Rollback() + + // Insert the new task + err = tx.Query("insert into tasks (name, reward, assigned) values (?, ?, ?)"). + Bind(task.Name, task.Reward, task.Assigned). + Exec() + + if err != nil { + return 0, err + } + + // Get the last inserted ID + var lastId int + err = tx.Query("select last_insert_rowid()").ScanSingle(&lastId) + if err != nil { + return 0, err + } + + // Commit the transaction + err = tx.Commit() + if err != nil { + return 0, err + } + + return lastId, nil +} diff --git a/backend/dto.go b/backend/dto.go index 1b7ae67..3fe211f 100644 --- a/backend/dto.go +++ b/backend/dto.go @@ -10,11 +10,12 @@ type Allowance struct { Goals []Goal `json:"goals"` } -type Schema struct { - Id int `json:"id"` +// Task represents a task in the system. +type Task struct { + ID int `json:"id"` Name string `json:"name"` Reward int `json:"reward"` - Assigned *int `json:"assigned"` + Assigned *int `json:"assigned"` // Pointer to allow null } type Goal struct { @@ -34,3 +35,15 @@ type CreateGoalRequest struct { type CreateGoalResponse struct { ID int `json:"id"` } + +// CreateTaskRequest represents the request body for creating a new task. +type CreateTaskRequest struct { + Name string `json:"name" binding:"required"` + Reward int `json:"reward"` + Assigned *int `json:"assigned"` +} + +// CreateTaskResponse represents the response body after creating a new task. +type CreateTaskResponse struct { + ID int `json:"id"` +} diff --git a/backend/main.go b/backend/main.go index b3ce37a..e4c36c7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -183,6 +183,44 @@ func deleteUserGoal(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"}) } +func createTask(c *gin.Context) { + var taskRequest CreateTaskRequest + if err := c.ShouldBindJSON(&taskRequest); err != nil { + log.Printf("Error parsing request body: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + return + } + + if taskRequest.Name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Task name cannot be empty"}) + return + } + + // If assigned is not nil, check if user exists + if taskRequest.Assigned != nil { + exists, err := db.UserExists(*taskRequest.Assigned) + if err != nil { + log.Printf("Error checking user existence: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError}) + return + } + if !exists { + c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound}) + return + } + } + + taskId, err := db.CreateTask(&taskRequest) + if err != nil { + log.Printf("Error creating task: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create task"}) + return + } + + response := CreateTaskResponse{ID: taskId} + c.IndentedJSON(http.StatusCreated, response) +} + /* * Initialises the database, and then starts the server. @@ -198,6 +236,7 @@ func start(ctx context.Context, config *ServerConfig) { router.GET("/api/user/:userId/goals", getUserGoals) router.POST("/api/user/:userId/goals", createUserGoal) router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal) + router.POST("/api/tasks", createTask) srv := &http.Server{ Addr: ":" + config.Port,