@@ -2,6 +2,7 @@ package controllers
22
33import (
44 "errors"
5+ "fmt"
56 "net/http"
67
78 "github.com/envelope-zero/backend/internal/httputil"
@@ -11,11 +12,20 @@ import (
1112)
1213
1314type TransactionListResponse struct {
14- Data []models. Transaction `json:"data"`
15+ Data []Transaction `json:"data"`
1516}
1617
1718type TransactionResponse struct {
18- Data models.Transaction `json:"data"`
19+ Data Transaction `json:"data"`
20+ }
21+
22+ type Transaction struct {
23+ models.Transaction
24+ Links TransactionLinks `json:"links"`
25+ }
26+
27+ type TransactionLinks struct {
28+ Self string `json:"self" example:"https://example.com/api/v1/transactions/1741"`
1929}
2030
2131// RegisterTransactionRoutes registers the routes for transactions with
@@ -41,8 +51,7 @@ func RegisterTransactionRoutes(r *gin.RouterGroup) {
4151// @Description Returns an empty response with the HTTP Header "allow" set to the allowed HTTP verbs
4252// @Tags Transactions
4353// @Success 204
44- // @Param budgetId path uint64 true "ID of the budget"
45- // @Router /v1/budgets/{budgetId}/transactions [options]
54+ // @Router /v1/transactions [options]
4655func OptionsTransactionList (c * gin.Context ) {
4756 httputil .OptionsGetPost (c )
4857}
@@ -51,45 +60,44 @@ func OptionsTransactionList(c *gin.Context) {
5160// @Description Returns an empty response with the HTTP Header "allow" set to the allowed HTTP verbs
5261// @Tags Transactions
5362// @Success 204
54- // @Param budgetId path uint64 true "ID of the budget"
5563// @Param transactionId path uint64 true "ID of the transaction"
56- // @Router /v1/budgets/{budgetId}/ transactions/{transactionId} [options]
64+ // @Router /v1/transactions/{transactionId} [options]
5765func OptionsTransactionDetail (c * gin.Context ) {
5866 httputil .OptionsGetPatchDelete (c )
5967}
6068
6169// @Summary Create transaction
62- // @Description Create a new transaction for the specified budget
70+ // @Description Create a new transaction
6371// @Tags Transactions
6472// @Produce json
6573// @Success 201 {object} TransactionResponse
6674// @Failure 400 {object} httputil.HTTPError
6775// @Failure 404
6876// @Failure 500 {object} httputil.HTTPError
69- // @Param budgetId path uint64 true "ID of the budget"
7077// @Param transaction body models.TransactionCreate true "Transaction"
71- // @Router /v1/budgets/{budgetId}/ transactions [post]
78+ // @Router /v1/transactions [post]
7279func CreateTransaction (c * gin.Context ) {
73- var data models.Transaction
80+ var transaction models.Transaction
7481
75- err := httputil .BindData (c , & data )
76- if err != nil {
82+ if err := httputil .BindData (c , & transaction ); err != nil {
7783 return
7884 }
7985
80- // Convert and validate data
81- data . BudgetID , err = httputil . ParseID (c , "budgetId" )
86+ // Check if the budget that the transaction shoud belong to exists
87+ _ , err := getBudgetResource (c , transaction . BudgetID )
8288 if err != nil {
8389 return
8490 }
8591
86- if ! decimal .Decimal .IsPositive (data .Amount ) {
92+ if ! decimal .Decimal .IsPositive (transaction .Amount ) {
8793 httputil .NewError (c , http .StatusBadRequest , errors .New ("The transaction amount must be positive" ))
8894 return
8995 }
9096
91- models .DB .Create (& data )
92- c .JSON (http .StatusCreated , TransactionResponse {Data : data })
97+ models .DB .Create (& transaction )
98+
99+ transactionObject , _ := getTransactionObject (c , transaction .ID )
100+ c .JSON (http .StatusCreated , TransactionResponse {Data : transactionObject })
93101}
94102
95103// @Summary Get all transactions
@@ -99,27 +107,23 @@ func CreateTransaction(c *gin.Context) {
99107// @Success 200 {object} TransactionListResponse
100108// @Failure 400 {object} httputil.HTTPError
101109// @Failure 404
102- // @Failure 500 {object} httputil.HTTPError
103- // @Param budgetId path uint64 true "ID of the budget"
104- // @Router /v1/budgets/{budgetId}/transactions [get]
110+ // @Failure 500 {object} httputil.HTTPError
111+ // @Router /v1/transactions [get]
105112func GetTransactions (c * gin.Context ) {
106113 var transactions []models.Transaction
107114
108- budgetID , _ := httputil . ParseID ( c , "budgetId" )
115+ models . DB . Find ( & transactions )
109116
110- // Check if the budget exists at all
111- budget , err := getBudgetResource (c , budgetID )
112- if err != nil {
113- return
117+ // When there are no resources, we want an empty list, not null
118+ // Therefore, we use make to create a slice with zero elements
119+ // which will be marshalled to an empty JSON array
120+ transactionObjects := make ([]Transaction , 0 )
121+ for _ , transaction := range transactions {
122+ o , _ := getTransactionObject (c , transaction .ID )
123+ transactionObjects = append (transactionObjects , o )
114124 }
115125
116- models .DB .Where (& models.Category {
117- CategoryCreate : models.CategoryCreate {
118- BudgetID : budget .ID ,
119- },
120- }).Find (& transactions )
121-
122- c .JSON (http .StatusOK , TransactionListResponse {Data : transactions })
126+ c .JSON (http .StatusOK , TransactionListResponse {Data : transactionObjects })
123127}
124128
125129// @Summary Get transaction
@@ -130,16 +134,20 @@ func GetTransactions(c *gin.Context) {
130134// @Failure 400 {object} httputil.HTTPError
131135// @Failure 404
132136// @Failure 500 {object} httputil.HTTPError
133- // @Param budgetId path uint64 true "ID of the budget"
134137// @Param transactionId path uint64 true "ID of the transaction"
135- // @Router /v1/budgets/{budgetId}/ transactions/{transactionId} [get]
138+ // @Router /v1/transactions/{transactionId} [get]
136139func GetTransaction (c * gin.Context ) {
137- t , err := getTransactionResource (c )
140+ id , err := httputil .ParseID (c , "transactionId" )
141+ if err != nil {
142+ return
143+ }
144+
145+ transactionObject , err := getTransactionObject (c , id )
138146 if err != nil {
139147 return
140148 }
141149
142- c .JSON (http .StatusOK , TransactionResponse {Data : t })
150+ c .JSON (http .StatusOK , TransactionResponse {Data : transactionObject })
143151}
144152
145153// @Summary Update a transaction
@@ -151,12 +159,16 @@ func GetTransaction(c *gin.Context) {
151159// @Failure 400 {object} httputil.HTTPError
152160// @Failure 404
153161// @Failure 500 {object} httputil.HTTPError
154- // @Param budgetId path uint64 true "ID of the budget"
155162// @Param transactionId path uint64 true "ID of the transaction"
156163// @Param transaction body models.TransactionCreate true "Transaction"
157- // @Router /v1/budgets/{budgetId}/ transactions/{transactionId} [patch]
164+ // @Router /v1/transactions/{transactionId} [patch]
158165func UpdateTransaction (c * gin.Context ) {
159- transaction , err := getTransactionResource (c )
166+ id , err := httputil .ParseID (c , "transactionId" )
167+ if err != nil {
168+ return
169+ }
170+
171+ transaction , err := getTransactionResource (c , id )
160172 if err != nil {
161173 return
162174 }
@@ -178,7 +190,8 @@ func UpdateTransaction(c *gin.Context) {
178190 }
179191
180192 models .DB .Model (& transaction ).Updates (data )
181- c .JSON (http .StatusOK , TransactionResponse {Data : transaction })
193+ transactionObject , _ := getTransactionObject (c , id )
194+ c .JSON (http .StatusOK , TransactionResponse {Data : transactionObject })
182195}
183196
184197// @Summary Delete a transaction
@@ -188,11 +201,15 @@ func UpdateTransaction(c *gin.Context) {
188201// @Failure 400 {object} httputil.HTTPError
189202// @Failure 404
190203// @Failure 500 {object} httputil.HTTPError
191- // @Param budgetId path uint64 true "ID of the budget"
192204// @Param transactionId path uint64 true "ID of the transaction"
193- // @Router /v1/budgets/{budgetId}/ transactions/{transactionId} [delete]
205+ // @Router /v1/transactions/{transactionId} [delete]
194206func DeleteTransaction (c * gin.Context ) {
195- transaction , err := getTransactionResource (c )
207+ id , err := httputil .ParseID (c , "transactionId" )
208+ if err != nil {
209+ return
210+ }
211+
212+ transaction , err := getTransactionResource (c , id )
196213 if err != nil {
197214 return
198215 }
@@ -203,27 +220,12 @@ func DeleteTransaction(c *gin.Context) {
203220}
204221
205222// getTransactionResource verifies that the request URI is valid for the transaction and returns it.
206- func getTransactionResource (c * gin.Context ) (models.Transaction , error ) {
223+ func getTransactionResource (c * gin.Context , id uint64 ) (models.Transaction , error ) {
207224 var transaction models.Transaction
208225
209- budgetID , _ := httputil .ParseID (c , "budgetId" )
210-
211- budget , err := getBudgetResource (c , budgetID )
212- if err != nil {
213- return models.Transaction {}, err
214- }
215-
216- accountID , err := httputil .ParseID (c , "transactionId" )
217- if err != nil {
218- return models.Transaction {}, err
219- }
220-
221- err = models .DB .First (& transaction , & models.Transaction {
222- TransactionCreate : models.TransactionCreate {
223- BudgetID : budget .ID ,
224- },
226+ err := models .DB .First (& transaction , & models.Transaction {
225227 Model : models.Model {
226- ID : accountID ,
228+ ID : id ,
227229 },
228230 }).Error
229231 if err != nil {
@@ -233,3 +235,27 @@ func getTransactionResource(c *gin.Context) (models.Transaction, error) {
233235
234236 return transaction , nil
235237}
238+
239+ func getTransactionObject (c * gin.Context , id uint64 ) (Transaction , error ) {
240+ resource , err := getTransactionResource (c , id )
241+ if err != nil {
242+ return Transaction {}, err
243+ }
244+
245+ return Transaction {
246+ resource ,
247+ getTransactionLinks (c , id ),
248+ }, nil
249+ }
250+
251+ // getTransactionLinks returns a TransactionLinks struct.
252+ //
253+ // This function is only needed for getTransactionObject as we cannot create an instance of Transaction
254+ // with mixed named and unnamed parameters.
255+ func getTransactionLinks (c * gin.Context , id uint64 ) TransactionLinks {
256+ url := httputil .RequestPathV1 (c ) + fmt .Sprintf ("/transactions/%d" , id )
257+
258+ return TransactionLinks {
259+ Self : url ,
260+ }
261+ }
0 commit comments