Skip to content

Commit 47a3134

Browse files
authored
feat!: categories, envelopes and allocations now in new API structure (#147)
1 parent c5541d7 commit 47a3134

File tree

13 files changed

+1471
-2367
lines changed

13 files changed

+1471
-2367
lines changed

internal/controllers/allocation.go

Lines changed: 87 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controllers
22

33
import (
44
"errors"
5+
"fmt"
56
"net/http"
67
"strings"
78

@@ -14,11 +15,20 @@ import (
1415
)
1516

1617
type AllocationResponse struct {
17-
Data models.Allocation `json:"data"`
18+
Data Allocation `json:"data"`
1819
}
1920

2021
type AllocationListResponse struct {
21-
Data []models.Allocation `json:"data"`
22+
Data []Allocation `json:"data"`
23+
}
24+
25+
type Allocation struct {
26+
models.Allocation
27+
Links AllocationLinks `json:"links"`
28+
}
29+
30+
type AllocationLinks struct {
31+
Self string `json:"self" example:"https://example.com/api/v1/allocations/47"`
2232
}
2333

2434
// RegisterAllocationRoutes registers the routes for allocations with
@@ -44,10 +54,7 @@ func RegisterAllocationRoutes(r *gin.RouterGroup) {
4454
// @Description Returns an empty response with the HTTP Header "allow" set to the allowed HTTP verbs
4555
// @Tags Allocations
4656
// @Success 204
47-
// @Param budgetId path uint64 true "ID of the budget"
48-
// @Param categoryId path uint64 true "ID of the category"
49-
// @Param envelopeId path uint64 true "ID of the envelope"
50-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations [options]
57+
// @Router /v1/allocations [options]
5158
func OptionsAllocationList(c *gin.Context) {
5259
httputil.OptionsGetPost(c)
5360
}
@@ -56,11 +63,8 @@ func OptionsAllocationList(c *gin.Context) {
5663
// @Description Returns an empty response with the HTTP Header "allow" set to the allowed HTTP verbs
5764
// @Tags Allocations
5865
// @Success 204
59-
// @Param budgetId path uint64 true "ID of the budget"
60-
// @Param categoryId path uint64 true "ID of the category"
61-
// @Param envelopeId path uint64 true "ID of the envelope"
6266
// @Param allocationId path uint64 true "ID of the allocation"
63-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations/{allocationId} [options]
67+
// @Router /v1/allocations/{allocationId} [options]
6468
func OptionsAllocationDetail(c *gin.Context) {
6569
httputil.OptionsGetPatchDelete(c)
6670
}
@@ -73,24 +77,22 @@ func OptionsAllocationDetail(c *gin.Context) {
7377
// @Failure 400 {object} httputil.HTTPError
7478
// @Failure 404
7579
// @Failure 500 {object} httputil.HTTPError
76-
// @Param budgetId path uint64 true "ID of the budget"
77-
// @Param categoryId path uint64 true "ID of the category"
78-
// @Param envelopeId path uint64 true "ID of the envelope"
7980
// @Param allocation body models.AllocationCreate true "Allocation"
80-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations [post]
81+
// @Router /v1/allocations [post]
8182
func CreateAllocation(c *gin.Context) {
82-
var data models.Allocation
83+
var allocation models.Allocation
8384

84-
err := httputil.BindData(c, &data)
85+
err := httputil.BindData(c, &allocation)
8586
if err != nil {
8687
return
8788
}
8889

89-
data.EnvelopeID, err = httputil.ParseID(c, "envelopeId")
90+
_, err = getEnvelopeResource(c, allocation.EnvelopeID)
9091
if err != nil {
9192
return
9293
}
93-
result := models.DB.Create(&data)
94+
95+
result := models.DB.Create(&allocation)
9496

9597
if result.Error != nil {
9698
// By default, we assume a server error
@@ -115,7 +117,8 @@ func CreateAllocation(c *gin.Context) {
115117
return
116118
}
117119

118-
c.JSON(http.StatusCreated, AllocationResponse{Data: data})
120+
allocationObject, _ := getAllocationObject(c, allocation.ID)
121+
c.JSON(http.StatusCreated, AllocationResponse{Data: allocationObject})
119122
}
120123

121124
// @Summary Get all allocations for an envelope
@@ -125,27 +128,24 @@ func CreateAllocation(c *gin.Context) {
125128
// @Success 200 {object} AllocationListResponse
126129
// @Failure 400 {object} httputil.HTTPError
127130
// @Failure 404
128-
// @Failure 500 {object} httputil.HTTPError
129-
// @Param budgetId path uint64 true "ID of the budget"
130-
// @Param categoryId path uint64 true "ID of the category"
131-
// @Param envelopeId path uint64 true "ID of the envelope"
132-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations [get]
131+
// @Failure 500 {object} httputil.HTTPError
132+
// @Router /v1/allocations [get]
133133
func GetAllocations(c *gin.Context) {
134134
var allocations []models.Allocation
135135

136-
// Check if the envelope exists
137-
envelope, err := getEnvelopeResource(c)
138-
if err != nil {
139-
return
140-
}
136+
models.DB.Find(&allocations)
141137

142-
models.DB.Where(&models.Allocation{
143-
AllocationCreate: models.AllocationCreate{
144-
EnvelopeID: envelope.ID,
145-
},
146-
}).Find(&allocations)
138+
// When there are no resources, we want an empty list, not null
139+
// Therefore, we use make to create a slice with zero elements
140+
// which will be marshalled to an empty JSON array
141+
allocationObjects := make([]Allocation, 0)
147142

148-
c.JSON(http.StatusOK, AllocationListResponse{Data: allocations})
143+
for _, allocation := range allocations {
144+
o, _ := getAllocationObject(c, allocation.ID)
145+
allocationObjects = append(allocationObjects, o)
146+
}
147+
148+
c.JSON(http.StatusOK, AllocationListResponse{Data: allocationObjects})
149149
}
150150

151151
// @Summary Get allocation
@@ -156,18 +156,20 @@ func GetAllocations(c *gin.Context) {
156156
// @Failure 400 {object} httputil.HTTPError
157157
// @Failure 404
158158
// @Failure 500 {object} httputil.HTTPError
159-
// @Param budgetId path uint64 true "ID of the budget"
160-
// @Param categoryId path uint64 true "ID of the category"
161-
// @Param envelopeId path uint64 true "ID of the envelope"
162159
// @Param allocationId path uint64 true "ID of the allocation"
163-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations/{allocationId} [get]
160+
// @Router /v1/allocations/{allocationId} [get]
164161
func GetAllocation(c *gin.Context) {
165-
allocation, err := getAllocationResource(c)
162+
id, err := httputil.ParseID(c, "allocationId")
163+
if err != nil {
164+
return
165+
}
166+
167+
allocationObject, err := getAllocationObject(c, id)
166168
if err != nil {
167169
return
168170
}
169171

170-
c.JSON(http.StatusOK, AllocationResponse{Data: allocation})
172+
c.JSON(http.StatusOK, AllocationResponse{Data: allocationObject})
171173
}
172174

173175
// @Summary Update an allocation
@@ -179,14 +181,16 @@ func GetAllocation(c *gin.Context) {
179181
// @Failure 400 {object} httputil.HTTPError
180182
// @Failure 404
181183
// @Failure 500 {object} httputil.HTTPError
182-
// @Param budgetId path uint64 true "ID of the budget"
183-
// @Param categoryId path uint64 true "ID of the category"
184-
// @Param envelopeId path uint64 true "ID of the envelope"
185184
// @Param allocationId path uint64 true "ID of the allocation"
186185
// @Param allocation body models.AllocationCreate true "Allocation"
187-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations/{allocationId} [patch]
186+
// @Router /v1/allocations/{allocationId} [patch]
188187
func UpdateAllocation(c *gin.Context) {
189-
allocation, err := getAllocationResource(c)
188+
id, err := httputil.ParseID(c, "allocationId")
189+
if err != nil {
190+
return
191+
}
192+
193+
allocation, err := getAllocationResource(c, id)
190194
if err != nil {
191195
return
192196
}
@@ -197,7 +201,9 @@ func UpdateAllocation(c *gin.Context) {
197201
}
198202

199203
models.DB.Model(&allocation).Updates(data)
200-
c.JSON(http.StatusOK, AllocationResponse{Data: allocation})
204+
allocationObject, _ := getAllocationObject(c, allocation.ID)
205+
206+
c.JSON(http.StatusOK, AllocationResponse{Data: allocationObject})
201207
}
202208

203209
// @Summary Delete an allocation
@@ -207,13 +213,15 @@ func UpdateAllocation(c *gin.Context) {
207213
// @Failure 400 {object} httputil.HTTPError
208214
// @Failure 404
209215
// @Failure 500 {object} httputil.HTTPError
210-
// @Param budgetId path uint64 true "ID of the budget"
211-
// @Param categoryId path uint64 true "ID of the category"
212-
// @Param envelopeId path uint64 true "ID of the envelope"
213216
// @Param allocationId path uint64 true "ID of the allocation"
214-
// @Router /v1/budgets/{budgetId}/categories/{categoryId}/envelopes/{envelopeId}/allocations/{allocationId} [delete]
217+
// @Router /v1/allocations/{allocationId} [delete]
215218
func DeleteAllocation(c *gin.Context) {
216-
allocation, err := getAllocationResource(c)
219+
id, err := httputil.ParseID(c, "allocationId")
220+
if err != nil {
221+
return
222+
}
223+
224+
allocation, err := getAllocationResource(c, id)
217225
if err != nil {
218226
return
219227
}
@@ -224,25 +232,12 @@ func DeleteAllocation(c *gin.Context) {
224232
}
225233

226234
// getAllocationResource verifies that the request URI is valid for the transaction and returns it.
227-
func getAllocationResource(c *gin.Context) (models.Allocation, error) {
235+
func getAllocationResource(c *gin.Context, id uint64) (models.Allocation, error) {
228236
var allocation models.Allocation
229237

230-
envelope, err := getEnvelopeResource(c)
231-
if err != nil {
232-
return models.Allocation{}, err
233-
}
234-
235-
allocationID, err := httputil.ParseID(c, "allocationId")
236-
if err != nil {
237-
return models.Allocation{}, err
238-
}
239-
240-
err = models.DB.First(&allocation, &models.Allocation{
241-
AllocationCreate: models.AllocationCreate{
242-
EnvelopeID: envelope.ID,
243-
},
238+
err := models.DB.First(&allocation, &models.Allocation{
244239
Model: models.Model{
245-
ID: allocationID,
240+
ID: id,
246241
},
247242
}).Error
248243
if err != nil {
@@ -252,3 +247,27 @@ func getAllocationResource(c *gin.Context) (models.Allocation, error) {
252247

253248
return allocation, nil
254249
}
250+
251+
func getAllocationObject(c *gin.Context, id uint64) (Allocation, error) {
252+
resource, err := getAllocationResource(c, id)
253+
if err != nil {
254+
return Allocation{}, err
255+
}
256+
257+
return Allocation{
258+
resource,
259+
getAllocationLinks(c, id),
260+
}, nil
261+
}
262+
263+
// getAllocationLinks returns a BudgetLinks struct.
264+
//
265+
// This function is only needed for getAllocationObject as we cannot create an instance of Allocation
266+
// with mixed named and unnamed parameters.
267+
func getAllocationLinks(c *gin.Context, id uint64) AllocationLinks {
268+
url := httputil.RequestPathV1(c) + fmt.Sprintf("/allocations/%d", id)
269+
270+
return AllocationLinks{
271+
Self: url,
272+
}
273+
}

0 commit comments

Comments
 (0)