@@ -10,16 +10,20 @@ import (
1010)
1111
1212type AccountListResponse struct {
13- Data []models. Account `json:"data"`
13+ Data []Account `json:"data"`
1414}
1515
1616type AccountResponse struct {
17- Data models.Account `json:"data"`
18- Links AccountLinks `json:"links"`
17+ Data Account `json:"data"`
18+ }
19+
20+ type Account struct {
21+ models.Account
22+ Links AccountLinks `json:"links"`
1923}
2024
2125type AccountLinks struct {
22- Transactions string `json:"transactions " example:"https://example.com/api/v1/budgets/3/ accounts/17/transactions "`
26+ Self string `json:"self " example:"https://example.com/api/v1/accounts/17"`
2327}
2428
2529// RegisterAccountRoutes registers the routes for accounts with
@@ -39,25 +43,6 @@ func RegisterAccountRoutes(r *gin.RouterGroup) {
3943 r .PATCH ("/:accountId" , UpdateAccount )
4044 r .DELETE ("/:accountId" , DeleteAccount )
4145 }
42-
43- // Transactions
44- {
45- r .OPTIONS ("/:accountId/transactions" , OptionsAccountTransactions )
46- r .GET ("/:accountId/transactions" , GetAccountTransactions )
47- }
48- }
49-
50- // @Summary Allowed HTTP verbs
51- // @Description Returns an empty response with the HTTP Header "allow" set to the allowed HTTP verbs
52- // @Tags Accounts
53- // @Success 204
54- // @Failure 400 {object} httputil.HTTPError
55- // @Failure 404
56- // @Param budgetId path uint64 true "ID of the budget"
57- // @Param accountId path uint64 true "ID of the account"
58- // @Router /v1/budgets/{budgetId}/accounts/{accountId}/transactions [options]
59- func OptionsAccountTransactions (c * gin.Context ) {
60- httputil .OptionsGet (c )
6146}
6247
6348// @Summary Allowed HTTP verbs
@@ -66,8 +51,7 @@ func OptionsAccountTransactions(c *gin.Context) {
6651// @Success 204
6752// @Failure 400 {object} httputil.HTTPError
6853// @Failure 404
69- // @Param budgetId path uint64 true "ID of the budget"
70- // @Router /v1/budgets/{budgetId}/accounts [options]
54+ // @Router /v1/accounts [options]
7155func OptionsAccountList (c * gin.Context ) {
7256 httputil .OptionsGetPost (c )
7357}
@@ -78,63 +62,39 @@ func OptionsAccountList(c *gin.Context) {
7862// @Success 204
7963// @Failure 400 {object} httputil.HTTPError
8064// @Failure 404
81- // @Param budgetId path uint64 true "ID of the budget"
8265// @Param accountId path uint64 true "ID of the account"
83- // @Router /v1/budgets/{budgetId}/ accounts/{accountId} [options]
66+ // @Router /v1/accounts/{accountId} [options]
8467func OptionsAccountDetail (c * gin.Context ) {
8568 httputil .OptionsGetPatchDelete (c )
8669}
8770
88- // @Summary List all transactions for an account
89- // @Description Returns a list of all transactions for the account requested
90- // @Tags Accounts
91- // @Produce json
92- // @Success 200 {object} TransactionListResponse
93- // @Failure 400 {object} httputil.HTTPError
94- // @Failure 404
95- // @Failure 500 {object} httputil.HTTPError
96- // @Param budgetId path uint64 true "ID of the budget"
97- // @Param accountId path uint64 true "ID of the account"
98- // @Router /v1/budgets/{budgetId}/accounts/{accountId}/transactions [get]
99- func GetAccountTransactions (c * gin.Context ) {
100- account , err := getAccountResource (c )
101- if err != nil {
102- return
103- }
104-
105- c .JSON (http .StatusOK , TransactionListResponse {
106- Data : account .Transactions (),
107- })
108- }
109-
11071// @Summary Create account
11172// @Description Create a new account for a specific budget
11273// @Tags Accounts
11374// @Produce json
11475// @Success 201 {object} AccountResponse
11576// @Failure 400 {object} httputil.HTTPError
11677// @Failure 404
117- // @Failure 500 {object} httputil.HTTPError
118- // @Param budgetId path uint64 true "ID of the budget"
119- // @Param account body models.AccountCreate true "Account"
120- // @Router /v1/budgets/{budgetId}/accounts [post]
78+ // @Failure 500 {object} httputil.HTTPError
79+ // @Param account body models.AccountCreate true "Account"
80+ // @Router /v1/accounts [post]
12181func CreateAccount (c * gin.Context ) {
122- var data models.Account
82+ var account models.Account
12383
124- if status , err := httputil .BindData (c , & data ); err != nil {
125- httputil .NewError (c , status , err )
84+ if err := httputil .BindData (c , & account ); err != nil {
12685 return
12786 }
12887
129- budget , err := getBudgetResource (c )
88+ // Check if the budget that the account shoud belong to exists
89+ _ , err := getBudget (c , account .BudgetID )
13090 if err != nil {
13191 return
13292 }
13393
134- data .BudgetID = budget .ID
135- models .DB .Create (& data )
94+ models .DB .Create (& account )
13695
137- c .JSON (http .StatusCreated , AccountResponse {Data : data })
96+ accountObject , _ := getAccountObject (c , account .ID )
97+ c .JSON (http .StatusCreated , AccountResponse {Data : accountObject })
13898}
13999
140100// @Summary List accounts
@@ -144,29 +104,24 @@ func CreateAccount(c *gin.Context) {
144104// @Success 200 {object} AccountListResponse
145105// @Failure 400 {object} httputil.HTTPError
146106// @Failure 404
147- // @Failure 500 {object} httputil.HTTPError
148- // @Param budgetId path uint64 true "ID of the budget"
149- // @Router /v1/budgets/{budgetId}/accounts [get]
107+ // @Failure 500 {object} httputil.HTTPError
108+ // @Router /v1/accounts [get]
150109func GetAccounts (c * gin.Context ) {
151110 var accounts []models.Account
152111
153- // Check if the budget exists at all
154- budget , err := getBudgetResource (c )
155- if err != nil {
156- return
157- }
112+ models .DB .Where (& models.Account {}).Find (& accounts )
158113
159- models .DB .Where (& models.Account {
160- AccountCreate : models.AccountCreate {
161- BudgetID : budget .ID ,
162- },
163- }).Find (& accounts )
114+ // When there are no accounts, we want an empty list, not null
115+ // Therefore, we use make to create a slice with zero elements
116+ // which will be marshalled to an empty JSON array
117+ accountObjects := make ([]Account , 0 )
164118
165- for i , account := range accounts {
166- accounts [i ] = account .WithCalculations ()
119+ for _ , account := range accounts {
120+ o , _ := getAccountObject (c , account .ID )
121+ accountObjects = append (accountObjects , o )
167122 }
168123
169- c .JSON (http .StatusOK , AccountListResponse {Data : accounts })
124+ c .JSON (http .StatusOK , AccountListResponse {Data : accountObjects })
170125}
171126
172127// @Summary Get account
@@ -177,16 +132,20 @@ func GetAccounts(c *gin.Context) {
177132// @Failure 400 {object} httputil.HTTPError
178133// @Failure 404
179134// @Failure 500 {object} httputil.HTTPError
180- // @Param budgetId path uint64 true "ID of the budget"
181- // @Param accountId path uint64 true "ID of the account"
182- // @Router /v1/budgets/{budgetId}/accounts/{accountId} [get]
135+ // @Param accountId path uint64 true "ID of the account"
136+ // @Router /v1/accounts/{accountId} [get]
183137func GetAccount (c * gin.Context ) {
184- _ , err := getAccountResource (c )
138+ id , err := httputil .ParseID (c , "accountId" )
139+ if err != nil {
140+ return
141+ }
142+
143+ accountObject , err := getAccountObject (c , id )
185144 if err != nil {
186145 return
187146 }
188147
189- c .JSON (http .StatusOK , newAccountResponse ( c ) )
148+ c .JSON (http .StatusOK , AccountResponse { Data : accountObject } )
190149}
191150
192151// @Summary Update account
@@ -197,24 +156,28 @@ func GetAccount(c *gin.Context) {
197156// @Failure 400 {object} httputil.HTTPError
198157// @Failure 404
199158// @Failure 500 {object} httputil.HTTPError
200- // @Param budgetId path uint64 true "ID of the budget"
201- // @Param accountId path uint64 true "ID of the account"
159+ // @Param accountId path uint64 true "ID of the account"
202160// @Param account body models.AccountCreate true "Account"
203- // @Router /v1/budgets/{budgetId}/ accounts/{accountId} [patch]
161+ // @Router /v1/accounts/{accountId} [patch]
204162func UpdateAccount (c * gin.Context ) {
205- account , err := getAccountResource (c )
163+ id , err := httputil .ParseID (c , "accountId" )
164+ if err != nil {
165+ return
166+ }
167+
168+ account , err := getAccountResource (c , id )
206169 if err != nil {
207170 return
208171 }
209172
210173 var data models.Account
211- if status , err := httputil .BindData (c , & data ); err != nil {
212- httputil .NewError (c , status , err )
174+ if err := httputil .BindData (c , & data ); err != nil {
213175 return
214176 }
215177
216178 models .DB .Model (& account ).Updates (data )
217- c .JSON (http .StatusOK , newAccountResponse (c ))
179+ accountObject , _ := getAccountObject (c , account .ID )
180+ c .JSON (http .StatusOK , AccountResponse {Data : accountObject })
218181}
219182
220183// @Summary Delete account
@@ -225,11 +188,15 @@ func UpdateAccount(c *gin.Context) {
225188// @Failure 400 {object} httputil.HTTPError
226189// @Failure 404
227190// @Failure 500 {object} httputil.HTTPError
228- // @Param budgetId path uint64 true "ID of the budget"
229191// @Param accountId path uint64 true "ID of the account"
230- // @Router /v1/budgets/{budgetId}/ accounts/{accountId} [delete]
192+ // @Router /v1/accounts/{accountId} [delete]
231193func DeleteAccount (c * gin.Context ) {
232- account , err := getAccountResource (c )
194+ id , err := httputil .ParseID (c , "accountId" )
195+ if err != nil {
196+ return
197+ }
198+
199+ account , err := getAccountResource (c , id )
233200 if err != nil {
234201 return
235202 }
@@ -239,28 +206,15 @@ func DeleteAccount(c *gin.Context) {
239206 c .JSON (http .StatusNoContent , gin.H {})
240207}
241208
242- // getAccountResource verifies that the request URI is valid for the account and returns it .
243- func getAccountResource (c * gin.Context ) (models.Account , error ) {
209+ // getAccountResource is the internal helper to verify permissions and return an account .
210+ func getAccountResource (c * gin.Context , id uint64 ) (models.Account , error ) {
244211 var account models.Account
245212
246- budget , err := getBudgetResource (c )
247- if err != nil {
248- return models.Account {}, err
249- }
250-
251- accountID , err := httputil .ParseID (c , "accountId" )
252- if err != nil {
253- return models.Account {}, err
254- }
255-
256- err = models .DB .First (& account , & models.Account {
257- AccountCreate : models.AccountCreate {
258- BudgetID : budget .ID ,
259- },
213+ err := models .DB .Where (& models.Account {
260214 Model : models.Model {
261- ID : accountID ,
215+ ID : id ,
262216 },
263- }).Error
217+ }).First ( & account ). Error
264218 if err != nil {
265219 httputil .FetchErrorHandler (c , err )
266220 return models.Account {}, err
@@ -269,18 +223,26 @@ func getAccountResource(c *gin.Context) (models.Account, error) {
269223 return account , nil
270224}
271225
272- // newAccountResponse creates a response object for an account.
273- func newAccountResponse ( c * gin. Context ) AccountResponse {
274- // When this function is called, all parent resources have already been validated
275- budget , _ := getBudgetResource ( c )
276- account , _ := getAccountResource ( c )
226+ func getAccountObject ( c * gin. Context , id uint64 ) ( Account , error ) {
227+ resource , err := getAccountResource ( c , id )
228+ if err != nil {
229+ return Account {}, err
230+ }
277231
278- url := httputil .RequestPathV1 (c ) + fmt .Sprintf ("/budgets/%d/accounts/%d" , budget .ID , account .ID )
232+ return Account {
233+ resource .WithCalculations (),
234+ getAccountLinks (c , resource .ID ),
235+ }, nil
236+ }
279237
280- return AccountResponse {
281- Data : account .WithCalculations (),
282- Links : AccountLinks {
283- Transactions : url + "/transactions" ,
284- },
238+ // getAccountLinks returns an AccountLinks struct.
239+ //
240+ // This function is only needed for newAccountResponse as we cannot create an instance of Account
241+ // with mixed named and unnamed parameters.
242+ func getAccountLinks (c * gin.Context , id uint64 ) AccountLinks {
243+ url := httputil .RequestPathV1 (c ) + fmt .Sprintf ("/accounts/%d" , id )
244+
245+ return AccountLinks {
246+ Self : url ,
285247 }
286248}
0 commit comments