Skip to content

Commit a1f16e4

Browse files
committed
feat: add allocations
1 parent 05985c9 commit a1f16e4

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed

internal/controllers/allocation.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package controllers
2+
3+
import (
4+
"errors"
5+
"log"
6+
"net/http"
7+
"strconv"
8+
9+
"github.com/envelope-zero/backend/internal/models"
10+
"github.com/gin-gonic/gin"
11+
"gorm.io/gorm"
12+
)
13+
14+
// RegisterAllocationRoutes registers the routes for allocations with
15+
// the RouterGroup that is passed
16+
func RegisterAllocationRoutes(r *gin.RouterGroup) {
17+
// Root group
18+
{
19+
r.OPTIONS("", func(c *gin.Context) {
20+
c.Header("allow", "GET, POST")
21+
})
22+
r.GET("", GetAllocations)
23+
r.POST("", CreateAllocation)
24+
}
25+
26+
// Transaction with ID
27+
{
28+
r.OPTIONS("/:allocationId", func(c *gin.Context) {
29+
c.Header("allow", "GET, PATCH, DELETE")
30+
})
31+
r.GET("/:allocationId", GetAllocation)
32+
r.PATCH("/:allocationId", UpdateAllocation)
33+
r.DELETE("/:allocationId", DeleteAllocation)
34+
}
35+
}
36+
37+
// CreateAllocation creates a new allocation
38+
func CreateAllocation(c *gin.Context) {
39+
var data models.Allocation
40+
41+
if err := c.ShouldBindJSON(&data); err != nil {
42+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
43+
return
44+
}
45+
46+
data.EnvelopeID, _ = strconv.Atoi(c.Param("envelopeId"))
47+
result := models.DB.Create(&data)
48+
49+
if result.Error != nil {
50+
log.Println(result.Error)
51+
52+
errMessage := "There was an error processing your request, please contact your server administrator"
53+
if result.Error.Error() == "UNIQUE constraint failed: allocations.month, allocations.year" {
54+
errMessage = "You can not create multiple allocations for the same month"
55+
}
56+
57+
c.JSON(http.StatusInternalServerError, gin.H{"error": errMessage})
58+
return
59+
}
60+
61+
c.JSON(http.StatusOK, gin.H{"data": data})
62+
}
63+
64+
// GetAllocations retrieves all allocations
65+
func GetAllocations(c *gin.Context) {
66+
var allocations []models.Allocation
67+
models.DB.Where("envelope_id = ?", c.Param("envelopeId")).Find(&allocations)
68+
69+
c.JSON(http.StatusOK, gin.H{"data": allocations})
70+
}
71+
72+
// GetAllocation retrieves a allocation by its ID
73+
func GetAllocation(c *gin.Context) {
74+
var allocation models.Allocation
75+
err := models.DB.First(&allocation, c.Param("allocationId")).Error
76+
// Return the apporpriate error: 404 if not found, 500 on all others
77+
if err != nil {
78+
if errors.Is(err, gorm.ErrRecordNotFound) {
79+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
80+
} else {
81+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
82+
}
83+
return
84+
}
85+
86+
c.JSON(http.StatusOK, gin.H{"data": allocation})
87+
}
88+
89+
// UpdateAllocation updates a allocation, selected by the ID parameter
90+
func UpdateAllocation(c *gin.Context) {
91+
var allocation models.Allocation
92+
93+
err := models.DB.First(&allocation, c.Param("allocationId")).Error
94+
// Return the apporpriate error: 404 if not found, 500 on all others
95+
if err != nil {
96+
if errors.Is(err, gorm.ErrRecordNotFound) {
97+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
98+
} else {
99+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
100+
}
101+
return
102+
}
103+
104+
var data models.Allocation
105+
err = c.ShouldBindJSON(&data)
106+
if err != nil {
107+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
108+
return
109+
}
110+
111+
models.DB.Model(&allocation).Updates(data)
112+
c.JSON(http.StatusOK, gin.H{"data": allocation})
113+
}
114+
115+
// DeleteAllocation removes a allocation, identified by its ID
116+
func DeleteAllocation(c *gin.Context) {
117+
var allocation models.Allocation
118+
err := models.DB.First(&allocation, c.Param("allocationId")).Error
119+
if err != nil {
120+
if errors.Is(err, gorm.ErrRecordNotFound) {
121+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
122+
} else {
123+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
124+
}
125+
return
126+
}
127+
128+
models.DB.Delete(&allocation)
129+
130+
c.JSON(http.StatusOK, gin.H{"data": true})
131+
}

internal/controllers/envelope.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func RegisterEnvelopeRoutes(r *gin.RouterGroup) {
3131
r.PATCH("/:envelopeId", UpdateEnvelope)
3232
r.DELETE("/:envelopeId", DeleteEnvelope)
3333
}
34+
35+
RegisterAllocationRoutes(r.Group("/:envelopeId/allocations"))
3436
}
3537

3638
// CreateEnvelope creates a new envelope
@@ -70,7 +72,9 @@ func GetEnvelope(c *gin.Context) {
7072
return
7173
}
7274

73-
c.JSON(http.StatusOK, gin.H{"data": envelope})
75+
c.JSON(http.StatusOK, gin.H{"data": envelope, "links": map[string]string{
76+
"allocations": "/allocations",
77+
}})
7478
}
7579

7680
// UpdateEnvelope updates a envelope, selected by the ID parameter

internal/models/allocation.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package models
2+
3+
import (
4+
"github.com/shopspring/decimal"
5+
)
6+
7+
// Allocation represents the allocation of money to an Envelope for a specific month
8+
type Allocation struct {
9+
Model
10+
Month uint8 `json:"month" gorm:"uniqueIndex:year_month;check:month >= 1 AND month <= 12"`
11+
Year uint `json:"year" gorm:"uniqueIndex:year_month"`
12+
Amount decimal.Decimal `json:"amount" gorm:"type:DECIMAL(20,8)"`
13+
EnvelopeID int `json:"envelopeId,omitempty"`
14+
Envelope Envelope `json:"-"`
15+
}

internal/models/database.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func ConnectDatabase() error {
4040
return err
4141
}
4242

43+
err = db.AutoMigrate(Allocation{})
44+
if err != nil {
45+
return err
46+
}
47+
4348
DB = db
4449
return nil
4550
}

0 commit comments

Comments
 (0)