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
-
-
-
- Name |
- Progress |
- Target |
- Weight |
-
-
-
- {{range .Allowances}}
- {{if eq .ID 0}}
-
- Total |
- {{.Progress}} |
- |
- {{.Weight}} |
-
- {{else}}
-
- {{.Name}} |
- {{.Progress}} |
- {{.Target}} |
- {{.Weight}} |
-
- {{end}}
+{{if ne .Error ""}}
+ Error
+ {{.Error}}
+{{else}}
+ Users
+ {{range .Users}}
+ {{if eq $.CurrentUser .ID}}
+ {{.Name}}
+ {{else}}
+ {{.Name}}
{{end}}
-
-
+ {{end}}
- Tasks
-
+
+ Tasks
+
+
+ History
-
-
- History
-
-
-
- Timestamp |
- Allowance |
-
-
-
- {{range .History}}
-
- {{.Timestamp}} |
- {{.Allowance}} |
-
- {{end}}
-
-
+ {{end}}
{{end}}