Skip to content

Commit bc0501a

Browse files
committed
feat: add /v1/budgets
1 parent 31e4f55 commit bc0501a

File tree

8 files changed

+279
-0
lines changed

8 files changed

+279
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
tmp
2+
gorm.db

backend.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import (
4+
"github.com/envelope-zero/backend/internal/routing"
5+
)
6+
7+
func main() {
8+
r := routing.Router()
9+
r.Run()
10+
}

go.mod

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
11
module github.com/envelope-zero/backend
22

33
go 1.17
4+
5+
require (
6+
github.com/gin-gonic/gin v1.7.7
7+
gorm.io/driver/sqlite v1.3.1
8+
gorm.io/gorm v1.23.2
9+
)
10+
11+
require (
12+
github.com/gin-contrib/sse v0.1.0 // indirect
13+
github.com/go-playground/locales v0.13.0 // indirect
14+
github.com/go-playground/universal-translator v0.17.0 // indirect
15+
github.com/go-playground/validator/v10 v10.4.1 // indirect
16+
github.com/golang/protobuf v1.3.3 // indirect
17+
github.com/jinzhu/inflection v1.0.0 // indirect
18+
github.com/jinzhu/now v1.1.4 // indirect
19+
github.com/json-iterator/go v1.1.9 // indirect
20+
github.com/leodido/go-urn v1.2.0 // indirect
21+
github.com/mattn/go-isatty v0.0.12 // indirect
22+
github.com/mattn/go-sqlite3 v1.14.9 // indirect
23+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
24+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
25+
github.com/ugorji/go/codec v1.1.7 // indirect
26+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
27+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
28+
gopkg.in/yaml.v2 v2.2.8 // indirect
29+
)

go.sum

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
5+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
6+
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
7+
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
8+
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
9+
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
10+
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
11+
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
12+
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
13+
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
14+
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
15+
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
16+
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
17+
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
18+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
19+
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
20+
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
21+
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
22+
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
23+
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
24+
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
25+
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
26+
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
27+
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
28+
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
29+
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
30+
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
31+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
32+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
33+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
34+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
35+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
36+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
37+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
38+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
39+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
40+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
41+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
42+
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
43+
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
44+
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
45+
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
46+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
47+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
48+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
49+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
50+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
51+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
52+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
53+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
54+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
55+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
56+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
57+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
58+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
59+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
60+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
61+
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
62+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
63+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
64+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
65+
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
66+
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
67+
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
68+
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
69+
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

internal/controllers/budget.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package controllers
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/envelope-zero/backend/internal/database"
8+
"github.com/envelope-zero/backend/internal/models"
9+
"github.com/gin-gonic/gin"
10+
"gorm.io/gorm"
11+
)
12+
13+
// CreateBudget creates a new budget
14+
func CreateBudget(c *gin.Context) {
15+
var data models.CreateBudget
16+
17+
if err := c.ShouldBindJSON(&data); err != nil {
18+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
19+
return
20+
}
21+
22+
budget := models.Budget{Name: data.Name}
23+
database.DB.Create(&budget)
24+
25+
c.JSON(http.StatusOK, gin.H{"data": budget})
26+
}
27+
28+
// GetBudgets retrieves all budgets
29+
func GetBudgets(c *gin.Context) {
30+
var budgets []models.Budget
31+
database.DB.Find(&budgets)
32+
33+
c.JSON(http.StatusOK, gin.H{"data": budgets})
34+
}
35+
36+
// GetBudget retrieves a budget by its ID
37+
func GetBudget(c *gin.Context) {
38+
var budget models.Budget
39+
err := database.DB.First(&budget, c.Param("id")).Error
40+
41+
// Return the apporpriate error: 404 if not found, 500 on all others
42+
if err != nil {
43+
if errors.Is(err, gorm.ErrRecordNotFound) {
44+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
45+
} else {
46+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
47+
}
48+
return
49+
}
50+
51+
c.JSON(http.StatusOK, gin.H{"data": budget})
52+
}
53+
54+
// UpdateBudget updates a budget, selected by the ID parameter
55+
func UpdateBudget(c *gin.Context) {
56+
var budget models.Budget
57+
58+
err := database.DB.First(&budget, c.Param("id")).Error
59+
60+
// Return the apporpriate error: 404 if not found, 500 on all others
61+
if err != nil {
62+
if errors.Is(err, gorm.ErrRecordNotFound) {
63+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
64+
} else {
65+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
66+
}
67+
return
68+
}
69+
70+
var data models.Budget
71+
err = c.ShouldBindJSON(&data)
72+
if err != nil {
73+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
74+
return
75+
}
76+
77+
database.DB.Model(&budget).Updates(data)
78+
c.JSON(http.StatusOK, gin.H{"data": budget})
79+
}
80+
81+
// DeleteBudget removes a budget, identified by its ID
82+
func DeleteBudget(c *gin.Context) {
83+
var budget models.Budget
84+
err := database.DB.First(&budget, c.Param("id")).Error
85+
86+
if err != nil {
87+
if errors.Is(err, gorm.ErrRecordNotFound) {
88+
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
89+
} else {
90+
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
91+
}
92+
return
93+
}
94+
95+
database.DB.Delete(&budget)
96+
97+
c.JSON(http.StatusOK, gin.H{"data": true})
98+
}

internal/database/database.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package database
2+
3+
import (
4+
"github.com/envelope-zero/backend/internal/models"
5+
"gorm.io/driver/sqlite"
6+
"gorm.io/gorm"
7+
)
8+
9+
// DB is the database used by the backend
10+
var DB *gorm.DB
11+
12+
// ConnectDatabase connects to the database DB
13+
func ConnectDatabase() {
14+
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
15+
16+
if err != nil {
17+
panic("Failed to connect to database!")
18+
}
19+
20+
db.AutoMigrate(&models.Budget{})
21+
22+
DB = db
23+
}

internal/models/budget.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package models
2+
3+
import (
4+
"time"
5+
)
6+
7+
// Budget represents a budget
8+
//
9+
// A budget is the highest level of organization in Envelope Zero, all other
10+
// resources reference it directly or transitively.
11+
type Budget struct {
12+
ID uint `json:"id"`
13+
CreatedAt time.Time `json:"createdAt"`
14+
UpdatedAt time.Time `json:"updatedAt"`
15+
Name string `json:"name"`
16+
}
17+
18+
// CreateBudget defines all values required to create a new budget
19+
type CreateBudget struct {
20+
Name string `json:"name" binding:"required"`
21+
}

internal/routing/routing.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package routing
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/envelope-zero/backend/internal/controllers"
7+
"github.com/envelope-zero/backend/internal/database"
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
// Router controls the routes for the API
12+
func Router() *gin.Engine {
13+
r := gin.Default()
14+
15+
r.GET("/", func(c *gin.Context) {
16+
c.JSON(http.StatusOK, gin.H{"data": "hello world"})
17+
})
18+
19+
database.ConnectDatabase()
20+
21+
v1 := r.Group("/v1")
22+
{
23+
v1.GET("/budgets", controllers.GetBudgets)
24+
v1.GET("/budgets/:id", controllers.GetBudget)
25+
v1.POST("/budgets", controllers.CreateBudget)
26+
v1.PATCH("budgets/:id", controllers.UpdateBudget)
27+
v1.DELETE("budgets/:id", controllers.DeleteBudget)
28+
}
29+
30+
return r
31+
}

0 commit comments

Comments
 (0)