From 34d9f74a86e326cc21fd0b08ffeea3bf0899a422 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Tue, 13 May 2025 19:36:29 +0200 Subject: [PATCH 1/4] Remove some whitespace --- backend/api_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/api_test.go b/backend/api_test.go index 0eca0cc..9907ac3 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -402,5 +402,4 @@ func TestPostAllowanceInvalidUserId(t *testing.T) { e.POST("/user/999/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect(). Status(404) - } -- 2.47.2 From 9c4fc3c7c4205fa354e2ee305eb5e75da0e96823 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Tue, 13 May 2025 19:37:50 +0200 Subject: [PATCH 2/4] Initial work towards get allowance endpoint --- backend/api_test.go | 17 ++++++++++ backend/db.go | 21 ++++++++++++- backend/dto.go | 6 ++-- backend/main.go | 27 +++++++++++++--- backend/migrations/1_initial.sql | 2 +- common/api.yaml | 54 +++++++++++++++----------------- 6 files changed, 91 insertions(+), 36 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index 9907ac3..812c267 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -5,6 +5,7 @@ import ( "github.com/gavv/httpexpect/v2" "strconv" "testing" + "time" ) const ( @@ -35,6 +36,7 @@ func TestGetUser(t *testing.T) { result := e.GET("/user/1").Expect().Status(200).JSON().Object() result.Value("name").IsEqual("Seeseemelk") result.Value("id").IsEqual(1) + result.Value("allowance").IsEqual(0) } func TestGetUserUnknown(t *testing.T) { @@ -403,3 +405,18 @@ func TestPostAllowanceInvalidUserId(t *testing.T) { e.POST("/user/999/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect(). Status(404) } + +func TestGetAllowance(t *testing.T) { + e := startServer(t) + + e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect().Status(200) + e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 20}).Expect().Status(200) + e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: -10}).Expect().Status(200) + + response := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() + response.Length().IsEqual(3) + response.Value(0).Object().Value("allowance").Number().IsEqual(100) + response.Value(0).Object().Value("timestamp").Number().NotEqual(0).InDelta(float64(time.Now().Unix()), 2.0) + response.Value(1).Object().Value("allowance").Number().IsEqual(20) + response.Value(2).Object().Value("allowance").Number().IsEqual(-10) +} diff --git a/backend/db.go b/backend/db.go index d70cb41..b37788d 100644 --- a/backend/db.go +++ b/backend/db.go @@ -243,7 +243,7 @@ func (db *Db) AddAllowance(userId int, allowance *PostAllowance) error { } defer tx.MustRollback() - err = tx.Query("insert into history (user_id, date, amount) values (?, ?, ?)"). + err = tx.Query("insert into history (user_id, timestamp, amount) values (?, ?, ?)"). Bind(userId, time.Now().Unix(), allowance.Allowance). Exec() if err != nil { @@ -251,3 +251,22 @@ func (db *Db) AddAllowance(userId int, allowance *PostAllowance) error { } return tx.Commit() } + +func (db *Db) GetHistory(userId int) ([]Allowance, error) { + history := make([]Allowance, 0) + var err error + + for row := range db.db.Query("select amount from history where user_id = ? order by `timestamp` desc"). + Bind(userId).Range(&err) { + allowance := Allowance{} + err = row.Scan(&allowance.Allowance) + if err != nil { + return nil, err + } + history = append(history, allowance) + } + if err != nil { + return nil, err + } + return history, nil +} diff --git a/backend/dto.go b/backend/dto.go index 8c0acfd..00de6ad 100644 --- a/backend/dto.go +++ b/backend/dto.go @@ -1,5 +1,7 @@ package main +import "time" + type User struct { ID int `json:"id"` Name string `json:"name"` @@ -12,8 +14,8 @@ type UserWithAllowance struct { } type Allowance struct { - Allowance int `json:"allowance"` - Goals []Goal `json:"goals"` + Allowance int `json:"allowance"` + Timestamp time.Time } type PostAllowance struct { diff --git a/backend/main.go b/backend/main.go index ae64cbe..03cd7c9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -185,7 +185,7 @@ func deleteUserGoal(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"}) + c.IndentedJSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"}) } func createTask(c *gin.Context) { @@ -233,7 +233,7 @@ func getTasks(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError}) return } - c.JSON(http.StatusOK, &response) + c.IndentedJSON(http.StatusOK, &response) } func getTask(c *gin.Context) { @@ -325,8 +325,26 @@ func postAllowance(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Allowance updated successfully"}) } +func getAllowance(c *gin.Context) { + userIdStr := c.Param("userId") + userId, err := strconv.Atoi(userIdStr) + if err != nil { + log.Printf("Invalid user ID: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + history, err := db.GetHistory(userId) + if err != nil { + log.Printf("Error getting history: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError}) + return + } + + c.IndentedJSON(http.StatusOK, history) +} + /* -* Initialises the database, and then starts the server. If the context gets cancelled, the server is shutdown and the database is closed. */ @@ -337,6 +355,8 @@ func start(ctx context.Context, config *ServerConfig) { router := gin.Default() router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) + router.POST("/api/user/:userId/allowance", postAllowance) + //router.GET("/api/user/:userId/allowance", getAllowance) router.GET("/api/user/:userId/goals", getUserGoals) router.POST("/api/user/:userId/goals", createUserGoal) router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal) @@ -344,7 +364,6 @@ func start(ctx context.Context, config *ServerConfig) { router.GET("/api/tasks", getTasks) router.GET("/api/task/:taskId", getTask) router.PUT("/api/task/:taskId", putTask) - router.POST("/api/user/:userId/allowance", postAllowance) srv := &http.Server{ Addr: config.Addr, diff --git a/backend/migrations/1_initial.sql b/backend/migrations/1_initial.sql index 14866d5..9c3500e 100644 --- a/backend/migrations/1_initial.sql +++ b/backend/migrations/1_initial.sql @@ -8,7 +8,7 @@ create table history ( id integer primary key, user_id integer not null, - date date not null, + timestamp date not null, amount integer not null ); diff --git a/common/api.yaml b/common/api.yaml index 3a6dba6..240255a 100644 --- a/common/api.yaml +++ b/common/api.yaml @@ -60,6 +60,32 @@ paths: description: The users could not be found. /user/{userId}/allowance: + get: + summary: Gets the allowance history of a user + parameters: + - in: path + name: userId + description: The user ID + required: true + schema: + type: integer + responses: + 200: + description: Information about the allowance history of the user + content: + application/json: + schema: + type: array + items: + type: object + properties: + date: + type: string + format: date-time + description: The date of the allowance or expense. + amount: + type: integer + description: The amount of the allowance to be added, in cents. A negative value post: summary: Updates the allowance of a user parameters: @@ -88,34 +114,6 @@ paths: 400: description: The allowance could not be updated. - /user/{userId}/history: - get: - summary: Gets the allowance history of a user - parameters: - - in: path - name: userId - description: The user ID - required: true - schema: - type: integer - responses: - 200: - description: Information about the allowance history of the user - content: - application/json: - schema: - type: array - items: - type: object - properties: - date: - type: string - format: date-time - description: The date of the allowance or expense. - amount: - type: integer - description: The amount of the allowance to be added, in cents. A negative value - /user/{userId}/goals: get: summary: Gets all goals -- 2.47.2 From 2d476fb59804cfa062c0fe2d21aa86a8ce92f838 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 14 May 2025 15:23:09 +0200 Subject: [PATCH 3/4] Functional get history endpoint --- backend/api_test.go | 8 +++++++- backend/db.go | 6 ++++-- backend/dto.go | 4 ++-- backend/go.mod | 4 ++-- backend/go.sum | 5 +++++ backend/main.go | 2 +- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index 812c267..cfff1c9 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -416,7 +416,13 @@ func TestGetAllowance(t *testing.T) { response := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() response.Length().IsEqual(3) response.Value(0).Object().Value("allowance").Number().IsEqual(100) - response.Value(0).Object().Value("timestamp").Number().NotEqual(0).InDelta(float64(time.Now().Unix()), 2.0) + response.Value(0).Object().Value("timestamp").String().AsDateTime().InRange(getDelta(time.Now(), 2.0)) response.Value(1).Object().Value("allowance").Number().IsEqual(20) response.Value(2).Object().Value("allowance").Number().IsEqual(-10) } + +func getDelta(base time.Time, delta float64) (time.Time, time.Time) { + start := base.Add(-time.Duration(delta) * time.Second) + end := base.Add(time.Duration(delta) * time.Second) + return start, end +} diff --git a/backend/db.go b/backend/db.go index b37788d..120143b 100644 --- a/backend/db.go +++ b/backend/db.go @@ -256,13 +256,15 @@ func (db *Db) GetHistory(userId int) ([]Allowance, error) { history := make([]Allowance, 0) var err error - for row := range db.db.Query("select amount from history where user_id = ? order by `timestamp` desc"). + for row := range db.db.Query("select amount, `timestamp` from history where user_id = ? order by `timestamp` desc"). Bind(userId).Range(&err) { allowance := Allowance{} - err = row.Scan(&allowance.Allowance) + var timestamp int64 + err = row.Scan(&allowance.Allowance, ×tamp) if err != nil { return nil, err } + allowance.Timestamp = time.Unix(timestamp, 0) history = append(history, allowance) } if err != nil { diff --git a/backend/dto.go b/backend/dto.go index 00de6ad..225e28d 100644 --- a/backend/dto.go +++ b/backend/dto.go @@ -14,8 +14,8 @@ type UserWithAllowance struct { } type Allowance struct { - Allowance int `json:"allowance"` - Timestamp time.Time + Allowance int `json:"allowance"` + Timestamp time.Time `json:"timestamp"` } type PostAllowance struct { diff --git a/backend/go.mod b/backend/go.mod index 510e8c9..37bb671 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,7 +3,7 @@ module allowance_planner go 1.24.2 require ( - gitea.seeseepuff.be/seeseemelk/mysqlite v0.11.1 + gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0 github.com/gavv/httpexpect/v2 v2.17.0 github.com/gin-gonic/gin v1.10.0 ) @@ -67,7 +67,7 @@ require ( gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.65.2 // indirect + modernc.org/libc v1.65.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.10.0 // indirect modernc.org/sqlite v1.37.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 0880315..75922a4 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,5 +1,7 @@ gitea.seeseepuff.be/seeseemelk/mysqlite v0.11.1 h1:5s0r2IRpomGJC6pjirdMk7HAcAYEydLK5AhBZy+V1Ys= gitea.seeseepuff.be/seeseemelk/mysqlite v0.11.1/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0 h1:kl0VFgvm52UKxJhZpf1hvucxZdOoXY50g/VmzsWH+/8= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= @@ -204,12 +206,15 @@ modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.27.1 h1:emhLB4uoOmkZUnTDFcMI3AbkmU/Evjuerit9Taqe6Ss= modernc.org/ccgo/v4 v4.27.1/go.mod h1:543Q0qQhJWekKVS5P6yL5fO6liNhla9Lbm2/B3rEKDE= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/libc v1.65.2 h1:drWL1QO9fKXr3kXDN8y+4lKyBr8bA3mtUBQpftq3IJw= modernc.org/libc v1.65.2/go.mod h1:VI3V2S5mNka4deJErQ0jsMXe7jgxojE2fOB/mWoHlbc= +modernc.org/libc v1.65.6 h1:OhJUhmuJ6MVZdqL5qmnd0/my46DKGFhSX4WOR7ijfyE= +modernc.org/libc v1.65.6/go.mod h1:MOiGAM9lrMBT9L8xT1nO41qYl5eg9gCp9/kWhz5L7WA= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= diff --git a/backend/main.go b/backend/main.go index 03cd7c9..8006f5c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -356,7 +356,7 @@ func start(ctx context.Context, config *ServerConfig) { router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) router.POST("/api/user/:userId/allowance", postAllowance) - //router.GET("/api/user/:userId/allowance", getAllowance) + router.GET("/api/user/:userId/allowance", getAllowance) router.GET("/api/user/:userId/goals", getUserGoals) router.POST("/api/user/:userId/goals", createUserGoal) router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal) -- 2.47.2 From 58a9fac93422b5da68307a81f7eef1ab9b2d42a5 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 14 May 2025 15:23:58 +0200 Subject: [PATCH 4/4] Rename from allowance to history --- backend/api_test.go | 4 ++-- backend/main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index cfff1c9..c74919f 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -406,14 +406,14 @@ func TestPostAllowanceInvalidUserId(t *testing.T) { Status(404) } -func TestGetAllowance(t *testing.T) { +func TestGetHistory(t *testing.T) { e := startServer(t) e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect().Status(200) e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 20}).Expect().Status(200) e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: -10}).Expect().Status(200) - response := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() + response := e.GET("/user/1/history").Expect().Status(200).JSON().Array() response.Length().IsEqual(3) response.Value(0).Object().Value("allowance").Number().IsEqual(100) response.Value(0).Object().Value("timestamp").String().AsDateTime().InRange(getDelta(time.Now(), 2.0)) diff --git a/backend/main.go b/backend/main.go index 8006f5c..8608846 100644 --- a/backend/main.go +++ b/backend/main.go @@ -325,7 +325,7 @@ func postAllowance(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Allowance updated successfully"}) } -func getAllowance(c *gin.Context) { +func getHistory(c *gin.Context) { userIdStr := c.Param("userId") userId, err := strconv.Atoi(userIdStr) if err != nil { @@ -356,7 +356,7 @@ func start(ctx context.Context, config *ServerConfig) { router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) router.POST("/api/user/:userId/allowance", postAllowance) - router.GET("/api/user/:userId/allowance", getAllowance) + router.GET("/api/user/:userId/history", getHistory) router.GET("/api/user/:userId/goals", getUserGoals) router.POST("/api/user/:userId/goals", createUserGoal) router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal) -- 2.47.2