From c4c2d422349f43e200d8c43158a4cfc37a58bc1e Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 22 May 2025 14:26:06 +0200 Subject: [PATCH 1/9] Add lite webpage --- backend/lite.gohtml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/lite.gohtml diff --git a/backend/lite.gohtml b/backend/lite.gohtml new file mode 100644 index 0000000..e69de29 -- 2.47.2 From adff57bf29aa9e70a74f5eabb097f3f4c4a25b50 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 22 May 2025 14:32:13 +0200 Subject: [PATCH 2/9] Add executable to gitignore --- backend/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/.gitignore b/backend/.gitignore index 0f580ed..23487f5 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ *.db3 *.db3-* +/allowance_planner -- 2.47.2 From 6a415ce878049c6cf00cf94cca7ead1d00c3de9d Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 22 May 2025 14:32:43 +0200 Subject: [PATCH 3/9] Tidied models --- backend/go.sum | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend/go.sum b/backend/go.sum index 964d5a6..0a43417 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,7 +1,3 @@ -gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0 h1:kl0VFgvm52UKxJhZpf1hvucxZdOoXY50g/VmzsWH+/8= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.13.0 h1:nqSXu5i5fHB1rrx/kfi8Phn/J6eFa2yh02FiGc9U1yg= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.13.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0 h1:aRItVfUj48fBmuec7rm/jY9KCfvHW2VzJfItVk4t8sw= gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= @@ -218,14 +214,10 @@ 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.6 h1:OhJUhmuJ6MVZdqL5qmnd0/my46DKGFhSX4WOR7ijfyE= -modernc.org/libc v1.65.6/go.mod h1:MOiGAM9lrMBT9L8xT1nO41qYl5eg9gCp9/kWhz5L7WA= modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00= modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= 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= -modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -- 2.47.2 From 19292ec746c28ef121e391f2e0fc061224da0720 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 22 May 2025 14:33:02 +0200 Subject: [PATCH 4/9] fixup! Add lite webpage --- backend/lite.gohtml | 9 +++++++++ backend/main.go | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/lite.gohtml b/backend/lite.gohtml index e69de29..9bb9e0d 100644 --- a/backend/lite.gohtml +++ b/backend/lite.gohtml @@ -0,0 +1,9 @@ + + + Allowance Planner 2000 + + +

Allowance Planner 2000

+

Users

+ + diff --git a/backend/main.go b/backend/main.go index 49a8a0d..f496614 100644 --- a/backend/main.go +++ b/backend/main.go @@ -578,6 +578,10 @@ func getHistory(c *gin.Context) { c.IndentedJSON(http.StatusOK, history) } +func renderLite(c *gin.Context) { + c.HTML(http.StatusOK, "lite.gohtml", nil) +} + /* Initialises the database, and then starts the server. If the context gets cancelled, the server is shutdown and the database is closed. @@ -592,6 +596,10 @@ func start(ctx context.Context, config *ServerConfig) { corsConfig.AllowAllOrigins = true router.Use(cors.New(corsConfig)) + // Web endpoints + router.LoadHTMLFiles("lite.gohtml") + router.GET("/", renderLite) + // API endpoints router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) router.POST("/api/user/:userId/history", postHistory) @@ -641,7 +649,7 @@ func start(ctx context.Context, config *ServerConfig) { func main() { config := ServerConfig{ Datasource: os.Getenv("DB_PATH"), - Addr: ":8080", + Addr: ":8081", } if config.Datasource == "" { config.Datasource = "allowance_planner.db3" -- 2.47.2 From 5330cdd98888e91cadb98b4d2fc43cecaa93436c Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 22 May 2025 15:09:52 +0200 Subject: [PATCH 5/9] Somewhat working first page --- backend/lite.go | 107 ++++++++++++++++++++++++++++++++++++++++++++ backend/lite.gohtml | 79 ++++++++++++++++++++++++++++++++ backend/main.go | 4 -- 3 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 backend/lite.go diff --git a/backend/lite.go b/backend/lite.go new file mode 100644 index 0000000..050c542 --- /dev/null +++ b/backend/lite.go @@ -0,0 +1,107 @@ +package main + +import ( + "errors" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type ViewModel struct { + Users []User + CurrentUser int + Allowances []Allowance + Tasks []Task + History []History +} + +func renderLite(c *gin.Context) { + if c.Query("user") != "" { + c.SetCookie("user", c.Query("user"), 3600, "/", "localhost", false, true) + c.Redirect(http.StatusFound, "/") + return + } + + currentUserStr, err := c.Cookie("user") + if errors.Is(err, http.ErrNoCookie) { + renderNoUser(c) + return + } + + if err != nil { + unsetUserCookie(c) + return + } + currentUser, err := strconv.Atoi(currentUserStr) + if err != nil { + unsetUserCookie(c) + return + } + userExists, err := db.UserExists(currentUser) + if !userExists || err != nil { + unsetUserCookie(c) + return + } + renderWithUser(c, currentUser) +} + +func unsetUserCookie(c *gin.Context) { + c.SetCookie("user", "", -1, "/", "localhost", false, true) + c.Redirect(http.StatusFound, "/") +} + +func renderNoUser(c *gin.Context) { + users, err := db.GetUsers() + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + c.HTML(http.StatusOK, "lite.gohtml", ViewModel{ + Users: users, + }) +} + +func renderWithUser(c *gin.Context, currentUser int) { + users, err := db.GetUsers() + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + allowances, err := db.GetUserAllowances(currentUser) + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + tasks, err := db.GetTasks() + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + history, err := db.GetHistory(currentUser) + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + c.HTML(http.StatusOK, "lite.gohtml", ViewModel{ + Users: users, + CurrentUser: currentUser, + Allowances: allowances, + Tasks: tasks, + History: history, + }) +} diff --git a/backend/lite.gohtml b/backend/lite.gohtml index 9bb9e0d..9d764b6 100644 --- a/backend/lite.gohtml +++ b/backend/lite.gohtml @@ -1,3 +1,4 @@ +{{- /*gotype: allowance_planner.ViewModel*/}} Allowance Planner 2000 @@ -5,5 +6,83 @@

Allowance Planner 2000

Users

+{{range .Users}} + {{if eq $.CurrentUser .ID}} + {{.Name}} + {{else}} + {{.Name}} + {{end}} +{{end}} + +{{if ne .CurrentUser 0}} +

Allowances

+ + + + + + + + + + + {{range .Allowances}} + {{if eq .ID 0}} + + + + + + + {{else}} + + + + + + + {{end}} + {{end}} + +
NameProgressTargetWeight
Total{{.Progress}}{{.Weight}}
{{.Name}}{{.Progress}}{{.Target}}{{.Weight}}
+ +

Tasks

+ + + + + + + + + + {{range .Tasks}} + + + + + + {{end}} + +
NameAssignedReward
{{.Name}}{{.Assigned}}{{.Reward}}
+ +

History

+ + + + + + + + + {{range .History}} + + + + + {{end}} + +
TimestampAllowance
{{.Timestamp}}{{.Allowance}}
+{{end}} diff --git a/backend/main.go b/backend/main.go index f496614..0e84a94 100644 --- a/backend/main.go +++ b/backend/main.go @@ -578,10 +578,6 @@ func getHistory(c *gin.Context) { c.IndentedJSON(http.StatusOK, history) } -func renderLite(c *gin.Context) { - c.HTML(http.StatusOK, "lite.gohtml", nil) -} - /* Initialises the database, and then starts the server. If the context gets cancelled, the server is shutdown and the database is closed. -- 2.47.2 From 885455454c5bfa094f33e65a311b8399734b1a11 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Fri, 23 May 2025 18:37:44 +0200 Subject: [PATCH 6/9] Fix compile error --- backend/api_test.go | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index f1b6f46..e21c0e7 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -623,38 +623,6 @@ func TestCompleteTaskInvalidId(t *testing.T) { e.POST("/task/999/complete").Expect().Status(404) } -func TestCompleteTaskAllowanceWeightsSumTo0(t *testing.T) { - e := startServer(t) - taskId := createTestTaskWithAmount(e, 101) - - e.GET("/tasks").Expect().Status(200).JSON().Array().Length().IsEqual(1) - - // Update rest allowance - e.PUT("/user/1/allowance/0").WithJSON(UpdateAllowanceRequest{ - Weight: 0, - }).Expect().Status(200) - // Create two allowance goals - e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{ - Name: "Test Allowance 1", - Target: 1000, - Weight: 0, - }).Expect().Status(201) - - // Complete the task - e.POST("/task/" + strconv.Itoa(taskId) + "/complete").Expect().Status(200) - - // Verify the task is marked as completed - e.GET("/task/" + strconv.Itoa(taskId)).Expect().Status(404) - - // Verify the allowances are updated for user 1 - allowances := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() - allowances.Length().IsEqual(2) - allowances.Value(0).Object().Value("id").Number().IsEqual(0) - allowances.Value(0).Object().Value("progress").Number().IsEqual(101) - allowances.Value(1).Object().Value("id").Number().IsEqual(1) - allowances.Value(1).Object().Value("progress").Number().IsEqual(0) -} - func TestCompleteAllowance(t *testing.T) { e := startServer(t) createTestTaskWithAmount(e, 100) -- 2.47.2 From 2b6dad709b56af625c5bc478a2225fd779b04a3b Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 24 May 2025 06:11:30 +0200 Subject: [PATCH 7/9] Working on it --- backend/main.go | 3 +- backend/{lite.go => web.go} | 71 +++++++++++++++++++++++++++-- backend/{lite.gohtml => web.gohtml} | 44 +++++++++++------- 3 files changed, 94 insertions(+), 24 deletions(-) rename backend/{lite.go => web.go} (56%) rename backend/{lite.gohtml => web.gohtml} (65%) diff --git a/backend/main.go b/backend/main.go index 0e84a94..1375cd1 100644 --- a/backend/main.go +++ b/backend/main.go @@ -593,8 +593,7 @@ func start(ctx context.Context, config *ServerConfig) { router.Use(cors.New(corsConfig)) // Web endpoints - router.LoadHTMLFiles("lite.gohtml") - router.GET("/", renderLite) + loadWebEndpoints(router) // API endpoints router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) diff --git a/backend/lite.go b/backend/web.go similarity index 56% rename from backend/lite.go rename to backend/web.go index 050c542..97b906a 100644 --- a/backend/lite.go +++ b/backend/web.go @@ -15,13 +15,21 @@ type ViewModel struct { History []History } -func renderLite(c *gin.Context) { +func loadWebEndpoints(router *gin.Engine) { + router.LoadHTMLFiles("web.gohtml") + router.GET("/", renderIndex) + router.GET("/login", renderLogin) + router.POST("/createTask", renderCreateTask) +} + +func renderLogin(c *gin.Context) { if c.Query("user") != "" { c.SetCookie("user", c.Query("user"), 3600, "/", "localhost", false, true) - c.Redirect(http.StatusFound, "/") - return } + c.Redirect(http.StatusFound, "/") +} +func renderIndex(c *gin.Context) { currentUserStr, err := c.Cookie("user") if errors.Is(err, http.ErrNoCookie) { renderNoUser(c) @@ -45,6 +53,59 @@ func renderLite(c *gin.Context) { renderWithUser(c, currentUser) } +func renderCreateTask(c *gin.Context) { + currentUserStr, err := c.Cookie("user") + if errors.Is(err, http.ErrNoCookie) { + c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ + "error": "User not logged in", + }) + return + } + if err != nil { + unsetUserCookie(c) + return + } + currentUser, err := strconv.Atoi(currentUserStr) + if err != nil { + unsetUserCookie(c) + return + } + userExists, err := db.UserExists(currentUser) + if !userExists || err != nil { + unsetUserCookie(c) + return + } + + name := c.PostForm("name") + rewardStr := c.PostForm("reward") + reward, err := strconv.Atoi(rewardStr) + if err != nil { + c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ + "error": "Invalid reward value", + }) + return + } + if name == "" || reward <= 0 { + c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ + "error": "Name and reward must be provided", + }) + return + } + + _, err = db.CreateTask(&CreateTaskRequest{ + Name: name, + Reward: reward, + }) + if err != nil { + c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ + "error": err.Error(), + }) + return + } + + c.Redirect(http.StatusFound, "/") +} + func unsetUserCookie(c *gin.Context) { c.SetCookie("user", "", -1, "/", "localhost", false, true) c.Redirect(http.StatusFound, "/") @@ -59,7 +120,7 @@ func renderNoUser(c *gin.Context) { return } - c.HTML(http.StatusOK, "lite.gohtml", ViewModel{ + c.HTML(http.StatusOK, "web.gohtml", ViewModel{ Users: users, }) } @@ -97,7 +158,7 @@ func renderWithUser(c *gin.Context, currentUser int) { return } - c.HTML(http.StatusOK, "lite.gohtml", ViewModel{ + c.HTML(http.StatusOK, "web.gohtml", ViewModel{ Users: users, CurrentUser: currentUser, Allowances: allowances, diff --git a/backend/lite.gohtml b/backend/web.gohtml similarity index 65% rename from backend/lite.gohtml rename to backend/web.gohtml index 9d764b6..11272a5 100644 --- a/backend/lite.gohtml +++ b/backend/web.gohtml @@ -10,7 +10,7 @@ {{if eq $.CurrentUser .ID}} {{.Name}} {{else}} - {{.Name}} + {{.Name}} {{end}} {{end}} @@ -47,24 +47,34 @@

Tasks

- - - - - - - - - - {{range .Tasks}} + +
NameAssignedReward
+ - - - + + + - {{end}} - -
{{.Name}}{{.Assigned}}{{.Reward}}NameAssignedReward
+ + + {{range .Tasks}} + + {{.Name}} + {{.Assigned}} + {{.Reward}} + + {{end}} + + + + + + + + + + +

History

-- 2.47.2 From 44e148fbb8e096ba02248da2d0b4033ae49c1b7e Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 24 May 2025 06:11:35 +0200 Subject: [PATCH 8/9] First functional lite web thingy --- backend/api_test.go | 10 +-- backend/db.go | 10 ++- backend/web.go | 184 +++++++++++++++++++++++++++++--------------- backend/web.gohtml | 182 +++++++++++++++++++++++++------------------ 4 files changed, 242 insertions(+), 144 deletions(-) diff --git a/backend/api_test.go b/backend/api_test.go index e21c0e7..4bf31d4 100644 --- a/backend/api_test.go +++ b/backend/api_test.go @@ -549,12 +549,12 @@ func TestCompleteTask(t *testing.T) { // Create two allowance goals e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{ Name: "Test Allowance 1", - Target: 1000, + Target: 100, Weight: 50, }).Expect().Status(201) e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{ Name: "Test Allowance 1", - Target: 1000, + Target: 10, Weight: 25, }).Expect().Status(201) @@ -568,11 +568,11 @@ func TestCompleteTask(t *testing.T) { allowances := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array() allowances.Length().IsEqual(3) allowances.Value(0).Object().Value("id").Number().IsEqual(0) - allowances.Value(0).Object().Value("progress").Number().IsEqual(26) + allowances.Value(0).Object().Value("progress").Number().IsEqual(31) allowances.Value(1).Object().Value("id").Number().IsEqual(1) - allowances.Value(1).Object().Value("progress").Number().IsEqual(50) + allowances.Value(1).Object().Value("progress").Number().IsEqual(60) allowances.Value(2).Object().Value("id").Number().IsEqual(2) - allowances.Value(2).Object().Value("progress").Number().IsEqual(25) + allowances.Value(2).Object().Value("progress").Number().IsEqual(10) // And also for user 2 allowances = e.GET("/user/2/allowance").Expect().Status(200).JSON().Array() diff --git a/backend/db.go b/backend/db.go index fe02ac2..0aa5570 100644 --- a/backend/db.go +++ b/backend/db.go @@ -416,16 +416,20 @@ func (db *Db) CompleteTask(taskId int) error { if sumOfWeights > 0 { // Distribute the reward to the allowances - for allowanceRow := range tx.Query("select id, weight from allowances where user_id = ? and weight > 0").Bind(userId).Range(&err) { - var allowanceId int + for allowanceRow := range tx.Query("select id, weight, target, balance from allowances where user_id = ? and weight > 0 order by (target - balance) asc").Bind(userId).Range(&err) { + var allowanceId, allowanceTarget, allowanceBalance int var allowanceWeight float64 - err = allowanceRow.Scan(&allowanceId, &allowanceWeight) + err = allowanceRow.Scan(&allowanceId, &allowanceWeight, &allowanceTarget, &allowanceBalance) if err != nil { return err } // Calculate the amount to add to the allowance amount := int((allowanceWeight / sumOfWeights) * float64(remainingReward)) + if allowanceBalance+amount > allowanceTarget { + // If the amount reaches past the target, set it to the target + amount = allowanceTarget - allowanceBalance + } sumOfWeights -= allowanceWeight err = tx.Query("update allowances set balance = balance + ? where id = ? and user_id = ?"). Bind(amount, allowanceId, userId).Exec() diff --git a/backend/web.go b/backend/web.go index 97b906a..12abbd0 100644 --- a/backend/web.go +++ b/backend/web.go @@ -13,6 +13,7 @@ type ViewModel struct { Allowances []Allowance Tasks []Task History []History + Error string } func loadWebEndpoints(router *gin.Engine) { @@ -20,6 +21,9 @@ func loadWebEndpoints(router *gin.Engine) { router.GET("/", renderIndex) router.GET("/login", renderLogin) router.POST("/createTask", renderCreateTask) + router.GET("/completeTask", renderCompleteTask) + router.POST("/createAllowance", renderCreateAllowance) + router.GET("/completeAllowance", renderCompleteAllowance) } func renderLogin(c *gin.Context) { @@ -30,49 +34,16 @@ func renderLogin(c *gin.Context) { } func renderIndex(c *gin.Context) { - currentUserStr, err := c.Cookie("user") - if errors.Is(err, http.ErrNoCookie) { - renderNoUser(c) + currentUser := getCurrentUser(c) + if currentUser == nil { return } - - if err != nil { - unsetUserCookie(c) - return - } - currentUser, err := strconv.Atoi(currentUserStr) - if err != nil { - unsetUserCookie(c) - return - } - userExists, err := db.UserExists(currentUser) - if !userExists || err != nil { - unsetUserCookie(c) - return - } - renderWithUser(c, currentUser) + renderWithUser(c, *currentUser) } func renderCreateTask(c *gin.Context) { - currentUserStr, err := c.Cookie("user") - if errors.Is(err, http.ErrNoCookie) { - c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ - "error": "User not logged in", - }) - return - } - if err != nil { - unsetUserCookie(c) - return - } - currentUser, err := strconv.Atoi(currentUserStr) - if err != nil { - unsetUserCookie(c) - return - } - userExists, err := db.UserExists(currentUser) - if !userExists || err != nil { - unsetUserCookie(c) + currentUser := getCurrentUser(c) + if currentUser == nil { return } @@ -80,15 +51,11 @@ func renderCreateTask(c *gin.Context) { rewardStr := c.PostForm("reward") reward, err := strconv.Atoi(rewardStr) if err != nil { - c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ - "error": "Invalid reward value", - }) + renderError(c, http.StatusBadRequest, err) return } if name == "" || reward <= 0 { - c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{ - "error": "Name and reward must be provided", - }) + renderError(c, http.StatusBadRequest, err) return } @@ -97,15 +64,112 @@ func renderCreateTask(c *gin.Context) { Reward: reward, }) if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } c.Redirect(http.StatusFound, "/") } +func renderCompleteTask(c *gin.Context) { + taskIDStr := c.Query("task") + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + renderError(c, http.StatusBadRequest, err) + return + } + + err = db.CompleteTask(taskID) + if err != nil { + renderError(c, http.StatusInternalServerError, err) + return + } + + c.Redirect(http.StatusFound, "/") +} + +func renderCreateAllowance(c *gin.Context) { + currentUser := getCurrentUser(c) + if currentUser == nil { + return + } + + name := c.PostForm("name") + targetStr := c.PostForm("target") + target, err := strconv.Atoi(targetStr) + if err != nil { + renderError(c, http.StatusBadRequest, err) + return + } + weightStr := c.PostForm("weight") + weight, err := strconv.ParseFloat(weightStr, 64) + if err != nil { + renderError(c, http.StatusBadRequest, err) + return + } + if name == "" || target <= 0 || weight <= 0 { + renderError(c, http.StatusBadRequest, err) + return + } + + _, err = db.CreateAllowance(*currentUser, &CreateAllowanceRequest{ + Name: name, + Target: target, + Weight: weight, + }) + if err != nil { + renderError(c, http.StatusInternalServerError, err) + return + } + + c.Redirect(http.StatusFound, "/") +} + +func renderCompleteAllowance(c *gin.Context) { + currentUser := getCurrentUser(c) + if currentUser == nil { + return + } + + allowanceIDStr := c.Query("allowance") + allowanceID, err := strconv.Atoi(allowanceIDStr) + if err != nil { + renderError(c, http.StatusBadRequest, err) + return + } + + err = db.CompleteAllowance(*currentUser, allowanceID) + if err != nil { + renderError(c, http.StatusInternalServerError, err) + return + } + + c.Redirect(http.StatusFound, "/") +} + +func getCurrentUser(c *gin.Context) *int { + currentUserStr, err := c.Cookie("user") + if errors.Is(err, http.ErrNoCookie) { + renderNoUser(c) + return nil + } + if err != nil { + unsetUserCookie(c) + return nil + } + currentUser, err := strconv.Atoi(currentUserStr) + if err != nil { + unsetUserCookie(c) + return nil + } + userExists, err := db.UserExists(currentUser) + if !userExists || err != nil { + unsetUserCookie(c) + return nil + } + return ¤tUser +} + func unsetUserCookie(c *gin.Context) { c.SetCookie("user", "", -1, "/", "localhost", false, true) c.Redirect(http.StatusFound, "/") @@ -114,9 +178,7 @@ func unsetUserCookie(c *gin.Context) { func renderNoUser(c *gin.Context) { users, err := db.GetUsers() if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } @@ -128,33 +190,25 @@ func renderNoUser(c *gin.Context) { func renderWithUser(c *gin.Context, currentUser int) { users, err := db.GetUsers() if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } allowances, err := db.GetUserAllowances(currentUser) if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } tasks, err := db.GetTasks() if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } history, err := db.GetHistory(currentUser) if err != nil { - c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{ - "error": err.Error(), - }) + renderError(c, http.StatusInternalServerError, err) return } @@ -166,3 +220,9 @@ func renderWithUser(c *gin.Context, currentUser int) { History: history, }) } + +func renderError(c *gin.Context, statusCode int, err error) { + c.HTML(statusCode, "web.gohtml", ViewModel{ + Error: err.Error(), + }) +} diff --git a/backend/web.gohtml b/backend/web.gohtml index 11272a5..1ed49ee 100644 --- a/backend/web.gohtml +++ b/backend/web.gohtml @@ -2,97 +2,131 @@ Allowance Planner 2000 +

Allowance Planner 2000

-

Users

-{{range .Users}} - {{if eq $.CurrentUser .ID}} - {{.Name}} - {{else}} - {{.Name}} - {{end}} -{{end}} -{{if ne .CurrentUser 0}} -

Allowances

-
- - - - - - - - - - {{range .Allowances}} - {{if eq .ID 0}} - - - - - - - {{else}} - - - - - - - {{end}} +{{if ne .Error ""}} +

Error

+

{{.Error}}

+{{else}} +

Users

+ {{range .Users}} + {{if eq $.CurrentUser .ID}} + {{.Name}} + {{else}} + {{.Name}} {{end}} - -
NameProgressTargetWeight
Total{{.Progress}}{{.Weight}}
{{.Name}}{{.Progress}}{{.Target}}{{.Weight}}
+ {{end}} -

Tasks

-
+ {{if ne .CurrentUser 0}} +

Allowances

+ + + + + + + + + + + + + + + + + + + + {{range .Allowances}} + {{if eq .ID 0}} + + + + + + + {{else}} + + + + + + {{if ge .Progress .Target}} + + {{end}} + + {{end}} + {{end}} + +
NameProgressTargetWeightActions
Total{{.Progress}}{{.Weight}}
{{.Name}} ({{.Progress}}){{.Target}}{{.Weight}} + Mark as completed +
+
+ +

Tasks

+
+ + + + + + + + + + + {{range .Tasks}} + + + + + + + {{end}} + + + + + + + +
NameAssignedRewardActions
{{.Name}} + {{if eq .Assigned nil}} + None + {{else}} + {{.Assigned}} + {{end}} + {{.Reward}} + Mark as completed +
+
+ +

History

- - - + + - {{range .Tasks}} + {{range .History}} - - - + + {{end}} - - - - -
NameAssignedRewardTimestampAllowance
{{.Name}}{{.Assigned}}{{.Reward}}{{.Timestamp}}{{.Allowance}}
- - -
- - -

History

- - - - - - - - - {{range .History}} - - - - - {{end}} - -
TimestampAllowance
{{.Timestamp}}{{.Allowance}}
+ {{end}} {{end}} -- 2.47.2 From 2e28ac0dae832c4a7d736350b839230f5dc2ddc2 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 24 May 2025 06:11:37 +0200 Subject: [PATCH 9/9] Revert port --- backend/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/main.go b/backend/main.go index 1375cd1..e584eb6 100644 --- a/backend/main.go +++ b/backend/main.go @@ -644,7 +644,7 @@ func start(ctx context.Context, config *ServerConfig) { func main() { config := ServerConfig{ Datasource: os.Getenv("DB_PATH"), - Addr: ":8081", + Addr: ":8080", } if config.Datasource == "" { config.Datasource = "allowance_planner.db3" -- 2.47.2