From 2c133e0d85adc2c0bb4b985ec37b0041192e8cef Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Tue, 13 May 2025 10:36:24 +0200 Subject: [PATCH] Add get tasks endpoint (#29) Closes #20 Reviewed-on: https://gitea.seeseepuff.be/seeseemelk/allowance_planner_2000/pulls/29 --- backend/api_test.go | 58 +++++++++++++++++++++++++++++++++++++++++---- backend/db.go | 18 ++++++++++++++ backend/main.go | 37 ++++++++++++++++++++++------- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index ebb1f66..ef66dde 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -1,10 +1,10 @@ package main import ( + "fmt" + "github.com/gavv/httpexpect/v2" "strconv" "testing" - - "github.com/gavv/httpexpect/v2" ) const ( @@ -14,12 +14,12 @@ const ( func startServer(t *testing.T) *httpexpect.Expect { config := ServerConfig{ Datasource: ":memory:", - Port: "8181", + Addr: ":0", Started: make(chan bool), } go start(t.Context(), &config) <-config.Started - return httpexpect.Default(t, "http://localhost:8181/api") + return httpexpect.Default(t, fmt.Sprintf("http://localhost:%d/api", config.Port)) } func TestGetUsers(t *testing.T) { @@ -53,6 +53,29 @@ func TestGetUserGoalsWhenNoGoalsPresent(t *testing.T) { result.Length().IsEqual(0) } +func TestGetUserGoals(t *testing.T) { + e := startServer(t) + + // Create a new goal + requestBody := map[string]interface{}{ + "name": TestGoalName, + "target": 5000, + "weight": 10, + } + e.POST("/user/1/goals").WithJSON(requestBody).Expect().Status(201) + + // Validate goal + result := e.GET("/user/1/goals").Expect().Status(200).JSON().Array() + result.Length().IsEqual(1) + item := result.Value(0).Object() + item.Value("id").IsEqual(1) + item.Value("name").IsEqual(TestGoalName) + item.Value("target").IsEqual(5000) + item.Value("weight").IsEqual(10) + item.Value("progress").IsEqual(0) + item.NotContainsKey("user_id") +} + func TestGetUserGoalsNoUser(t *testing.T) { e := startServer(t) e.GET("/user/999/goals").Expect().Status(404) @@ -282,3 +305,30 @@ func TestCreateTaskInvalidRequestBody(t *testing.T) { Expect(). Status(400) } + +func TestGetTaskWhenNoTasks(t *testing.T) { + e := startServer(t) + + result := e.GET("/tasks").Expect().Status(200).JSON().Array() + result.Length().IsEqual(0) +} + +func TestGetTaskWhenTasks(t *testing.T) { + e := startServer(t) + + // Create a new task + requestBody := map[string]interface{}{ + "name": "Test Task", + "reward": 100, + } + e.POST("/tasks").WithJSON(requestBody).Expect().Status(201) + + // Get the task + result := e.GET("/tasks").Expect().Status(200).JSON().Array() + result.Length().IsEqual(1) + item := result.Value(0).Object() + item.Value("id").IsEqual(1) + item.Value("name").IsEqual("Test Task") + item.Value("reward").IsEqual(100) + item.Value("assigned").IsNull() +} diff --git a/backend/db.go b/backend/db.go index a5ce545..9a7b934 100644 --- a/backend/db.go +++ b/backend/db.go @@ -182,3 +182,21 @@ func (db *Db) CreateTask(task *CreateTaskRequest) (int, error) { return lastId, nil } + +func (db *Db) GetTasks() ([]Task, error) { + tasks := make([]Task, 0) + var err error + + for row := range db.db.Query("select id, name, reward, assigned from tasks").Range(&err) { + task := Task{} + err = row.Scan(&task.ID, &task.Name, &task.Reward, &task.Assigned) + if err != nil { + return nil, err + } + tasks = append(tasks, task) + } + if err != nil { + return nil, err + } + return tasks, nil +} diff --git a/backend/main.go b/backend/main.go index e4c36c7..c8506f9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,8 +3,8 @@ package main import ( "context" "embed" - "errors" "log" + "net" "net/http" "os" "strconv" @@ -31,7 +31,10 @@ type ServerConfig struct { // The port to listen on. // Use an empty string to listen on a random port. - Port string + Addr string + + // The port that is actually being listened on. + Port int // The channel that gets signaled when the server has started. Started chan bool @@ -221,6 +224,16 @@ func createTask(c *gin.Context) { c.IndentedJSON(http.StatusCreated, response) } +func getTasks(c *gin.Context) { + response, err := db.GetTasks() + if err != nil { + log.Printf("Error getting tasks: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError}) + return + } + c.JSON(http.StatusOK, &response) +} + /* * Initialises the database, and then starts the server. @@ -237,21 +250,29 @@ func start(ctx context.Context, config *ServerConfig) { router.POST("/api/user/:userId/goals", createUserGoal) router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal) router.POST("/api/tasks", createTask) + router.GET("/api/tasks", getTasks) srv := &http.Server{ - Addr: ":" + config.Port, + Addr: config.Addr, Handler: router.Handler(), } go func() { - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + l, err := net.Listen("tcp", srv.Addr) + if err != nil { + log.Fatalf("listen: %s\n", err) + } + config.Port = l.Addr().(*net.TCPAddr).Port + + log.Printf("Running server on port %s\n", l.Addr().String()) + if config.Started != nil { + config.Started <- true + } + + if err := http.Serve(l, router.Handler()); err != nil { log.Fatalf("listen: %s\n", err) } }() - log.Printf("Running server on port %s\n", config.Port) - if config.Started != nil { - config.Started <- true - } <-ctx.Done() log.Println("Shutting down") if err := srv.Shutdown(context.Background()); err != nil {