Skip to content

Commit 1cd0ebe

Browse files
authored
feat: enable filtering of transactions by any account (#288)
1 parent 9de60dd commit 1cd0ebe

File tree

9 files changed

+61
-8
lines changed

9 files changed

+61
-8
lines changed

.vscode/launch.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"cwd": "${workspaceFolder}",
1010
"program": "${workspaceFolder}/main.go",
1111
"env": {
12-
"GIN_MODE": "debug"
12+
"GIN_MODE": "debug",
13+
"API_URL": "http://localhost:8080",
14+
"CORS_ALLOW_ORIGINS": "http://localhost:3000"
1315
},
1416
"console": "integratedTerminal"
1517
}

api/docs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,12 @@ const docTemplate = `{
15891589
"name": "budget",
15901590
"in": "query"
15911591
},
1592+
{
1593+
"type": "string",
1594+
"description": "Filter by ID of associated account, regardeless of source or destination",
1595+
"name": "account",
1596+
"in": "query"
1597+
},
15921598
{
15931599
"type": "string",
15941600
"description": "Filter by source account ID",

api/swagger.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,12 @@
15771577
"name": "budget",
15781578
"in": "query"
15791579
},
1580+
{
1581+
"type": "string",
1582+
"description": "Filter by ID of associated account, regardeless of source or destination",
1583+
"name": "account",
1584+
"in": "query"
1585+
},
15801586
{
15811587
"type": "string",
15821588
"description": "Filter by source account ID",

api/swagger.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,11 @@ paths:
16281628
in: query
16291629
name: budget
16301630
type: string
1631+
- description: Filter by ID of associated account, regardeless of source or
1632+
destination
1633+
in: query
1634+
name: account
1635+
type: string
16311636
- description: Filter by source account ID
16321637
in: query
16331638
name: source

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ require (
2020
gorm.io/gorm v1.23.8
2121
)
2222

23+
require github.com/google/go-cmp v0.5.8 // indirect
24+
2325
require (
2426
github.com/KyleBanks/depth v1.2.1 // indirect
2527
github.com/davecgh/go-spew v1.1.1 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
6464
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
6565
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
6666
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
67-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
6867
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
68+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
69+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
6970
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
7071
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
7172
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -232,7 +233,6 @@ golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4
232233
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
233234
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
234235
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
235-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
236236
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
237237
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
238238
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=

pkg/controllers/transaction.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/gin-gonic/gin"
1313
"github.com/google/uuid"
1414
"github.com/shopspring/decimal"
15+
"gorm.io/gorm"
1516
)
1617

1718
type TransactionListResponse struct {
@@ -40,6 +41,7 @@ type TransactionQueryFilter struct {
4041
DestinationAccountID string `form:"destination"`
4142
EnvelopeID string `form:"envelope"`
4243
Reconciled bool `form:"reconciled"`
44+
AccountID string `form:"account" createField:"false"`
4345
}
4446

4547
func (f TransactionQueryFilter) ToCreate(c *gin.Context) (models.TransactionCreate, error) {
@@ -196,6 +198,7 @@ func CreateTransaction(c *gin.Context) {
196198
// @Param amount query decimal.Decimal false "Filter by amount"
197199
// @Param note query string false "Filter by note"
198200
// @Param budget query string false "Filter by budget ID"
201+
// @Param account query string false "Filter by ID of associated account, regardeless of source or destination"
199202
// @Param source query string false "Filter by source account ID"
200203
// @Param destination query string false "Filter by destination account ID"
201204
// @Param envelope query string false "Filter by envelope ID"
@@ -216,10 +219,30 @@ func GetTransactions(c *gin.Context) {
216219
return
217220
}
218221

219-
var transactions []models.Transaction
220-
database.DB.Order("date(date) DESC").Where(&models.Transaction{
222+
var query *gorm.DB
223+
query = database.DB.Order("date(date) DESC").Where(&models.Transaction{
221224
TransactionCreate: create,
222-
}, queryFields...).Find(&transactions)
225+
}, queryFields...)
226+
227+
if filter.AccountID != "" {
228+
accountID, err := httputil.UUIDFromString(c, filter.AccountID)
229+
if err != nil {
230+
return
231+
}
232+
233+
query = query.Where(&models.Transaction{
234+
TransactionCreate: models.TransactionCreate{
235+
SourceAccountID: accountID,
236+
},
237+
}).Or(&models.Transaction{
238+
TransactionCreate: models.TransactionCreate{
239+
DestinationAccountID: accountID,
240+
},
241+
})
242+
}
243+
244+
var transactions []models.Transaction
245+
query.Find(&transactions)
223246

224247
// When there are no resources, we want an empty list, not null
225248
// Therefore, we use make to create a slice with zero elements

pkg/controllers/transaction_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func (suite *TestSuiteEnv) TestGetTransactionsInvalidQuery() {
7575
"date=A long time ago",
7676
"amount=Seventeen Cents",
7777
"reconciled=I don't think so",
78+
"account=ItIsAHippo!",
7879
}
7980

8081
for _, tt := range tests {
@@ -90,6 +91,7 @@ func (suite *TestSuiteEnv) TestGetTransactionsFilter() {
9091

9192
a1 := createTestAccount(suite.T(), models.AccountCreate{BudgetID: b.Data.ID})
9293
a2 := createTestAccount(suite.T(), models.AccountCreate{BudgetID: b.Data.ID})
94+
a3 := createTestAccount(suite.T(), models.AccountCreate{BudgetID: b.Data.ID})
9395

9496
c := createTestCategory(suite.T(), models.CategoryCreate{BudgetID: b.Data.ID})
9597

@@ -127,7 +129,7 @@ func (suite *TestSuiteEnv) TestGetTransactionsFilter() {
127129
Note: "",
128130
BudgetID: b.Data.ID,
129131
EnvelopeID: e1ID,
130-
SourceAccountID: a1.Data.ID,
132+
SourceAccountID: a3.Data.ID,
131133
DestinationAccountID: a2.Data.ID,
132134
Reconciled: true,
133135
})
@@ -146,6 +148,9 @@ func (suite *TestSuiteEnv) TestGetTransactionsFilter() {
146148
{"Non-existing Source Account", "source=3340a084-acf8-4cb4-8f86-9e7f88a86190", 0},
147149
{"Destination Account", fmt.Sprintf("destination=%s", a2.Data.ID), 2},
148150
{"Reconciled", "reconciled=false", 2},
151+
{"Non-existing Account", "account=534a3562-c5e8-46d1-a2e2-e96c00e7efec", 0},
152+
{"Existing Account 2", fmt.Sprintf("account=%s", a2.Data.ID), 3},
153+
{"Existing Account 1", fmt.Sprintf("account=%s", a1.Data.ID), 2},
149154
}
150155

151156
for _, tt := range tests {

pkg/httputil/query.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ func GetURLFields(url *url.URL, filter any) []any {
2424
field := val.Type().Field(i).Name
2525
param := val.Type().Field(i).Tag.Get("form")
2626

27-
if url.Query().Has(param) {
27+
// createField is a struct tag that allows to specify if the field is part
28+
// of the fields to filter for on the original struct
29+
createField := val.Type().Field(i).Tag.Get("createField")
30+
31+
if url.Query().Has(param) && createField != "false" {
2832
queryFields = append(queryFields, field)
2933
}
3034
}

0 commit comments

Comments
 (0)