Implement pagination on /transactions #36
@ -36,7 +36,8 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
default: -1
|
default: 0
|
||||||
|
minimum: 0
|
||||||
- name: bank
|
- name: bank
|
||||||
in: query
|
in: query
|
||||||
description: ID of the bank
|
description: ID of the bank
|
||||||
|
|||||||
@ -49,9 +49,19 @@ func (server *ServerImpl) GetBankById(ctx echo.Context, bankId string) error {
|
|||||||
func (server *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
|
func (server *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
|
||||||
log.Print("GetTransactions")
|
log.Print("GetTransactions")
|
||||||
|
|
||||||
transactions, err := server.Dal.Transactions()
|
limit := 100
|
||||||
|
if params.Limit != nil {
|
||||||
|
limit = int(*params.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := 0
|
||||||
|
if params.Offset != nil {
|
||||||
|
offset = int(*params.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions, err := server.Dal.Transactions(limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.NoContent(http.StatusInternalServerError)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(transactions) == 0 {
|
if len(transactions) == 0 {
|
||||||
|
|||||||
@ -142,14 +142,14 @@ func TestServerImpl_GetTransactions(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"200",
|
"200",
|
||||||
fields{mock_dal},
|
fields{mock_dal},
|
||||||
args{GetTransactionsParams{}},
|
args{GetTransactionsParams{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}},
|
||||||
false,
|
false,
|
||||||
entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}},
|
entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"204",
|
"204",
|
||||||
fields{mock_dal},
|
fields{mock_dal},
|
||||||
args{GetTransactionsParams{}},
|
args{GetTransactionsParams{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}},
|
||||||
false,
|
false,
|
||||||
entity.Transactions{},
|
entity.Transactions{},
|
||||||
},
|
},
|
||||||
@ -164,7 +164,7 @@ func TestServerImpl_GetTransactions(t *testing.T) {
|
|||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
ctx := echo.New().NewContext(req, rec)
|
ctx := echo.New().NewContext(req, rec)
|
||||||
|
|
||||||
mock_dal.EXPECT().Transactions().Return(tt.mocks, nil).Times(1)
|
mock_dal.EXPECT().Transactions(int(*tt.args.params.Limit), int(*tt.args.params.Offset)).Return(tt.mocks, nil).Times(1)
|
||||||
|
|
||||||
if err := pf.GetTransactions(ctx, tt.args.params); (err != nil) != tt.wantErr {
|
if err := pf.GetTransactions(ctx, tt.args.params); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("ServerImpl.GetTransactions() error = %v,", err)
|
t.Errorf("ServerImpl.GetTransactions() error = %v,", err)
|
||||||
|
|||||||
@ -276,24 +276,24 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
var swaggerSpec = []string{
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
"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",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import "git.rosemyrtle.work/personal-finance/server/internal/entity"
|
|||||||
|
|
||||||
type DAL interface {
|
type DAL interface {
|
||||||
Transaction(transactionId int64) (*entity.Transaction, error)
|
Transaction(transactionId int64) (*entity.Transaction, error)
|
||||||
Transactions() (entity.Transactions, error)
|
Transactions(limit, offset int) (entity.Transactions, error)
|
||||||
InsertTransaction(entity.Transaction) (entity.Transaction, error)
|
InsertTransaction(entity.Transaction) (entity.Transaction, error)
|
||||||
UpdateTransaction(entity.TransactionId, *entity.CategoryName) (bool, error)
|
UpdateTransaction(entity.TransactionId, *entity.CategoryName) (bool, error)
|
||||||
TransactionExists(uint64) (bool, error)
|
TransactionExists(uint64) (bool, error)
|
||||||
|
|||||||
@ -41,8 +41,8 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
|
|||||||
return &transactions[0], nil
|
return &transactions[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dal *DalImpl) Transactions() (entity.Transactions, error) {
|
func (dal *DalImpl) Transactions(limit, offset int) (entity.Transactions, error) {
|
||||||
log.Print("DAL::Transactions")
|
log.Print("DAL::Transactions", "limit", limit, "offset", offset)
|
||||||
|
|
||||||
if dal.Db == nil {
|
if dal.Db == nil {
|
||||||
log.Panic("database not available")
|
log.Panic("database not available")
|
||||||
@ -53,9 +53,12 @@ func (dal *DalImpl) Transactions() (entity.Transactions, error) {
|
|||||||
FROM pfbudget.transactions t
|
FROM pfbudget.transactions t
|
||||||
LEFT JOIN pfbudget.transactions_categorized tc
|
LEFT JOIN pfbudget.transactions_categorized tc
|
||||||
ON t.id = tc.id
|
ON t.id = tc.id
|
||||||
|
ORDER BY t.date DESC
|
||||||
|
LIMIT $1
|
||||||
|
OFFSET $2
|
||||||
`
|
`
|
||||||
|
|
||||||
rows, err := dal.Db.Query(stmt)
|
rows, err := dal.Db.Query(stmt, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.Transactions{}, err
|
return entity.Transactions{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,18 +142,23 @@ func TestDalImpl_Transactions(t *testing.T) {
|
|||||||
Db: tt.fields.Db,
|
Db: tt.fields.Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limit, offset := 0, 0
|
||||||
|
|
||||||
mock.
|
mock.
|
||||||
ExpectQuery(`
|
ExpectQuery(`
|
||||||
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name
|
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name
|
||||||
FROM \w+\.transactions \w+
|
FROM \w+\.transactions \w+
|
||||||
LEFT JOIN \w+\.transactions_categorized \w+
|
LEFT JOIN \w+\.transactions_categorized \w+
|
||||||
ON \w+\.id = \w+\.id`).
|
ON \w+\.id = \w+\.id
|
||||||
WithoutArgs().
|
ORDER BY \w+\.date DESC
|
||||||
|
LIMIT \$1
|
||||||
|
OFFSET \$2`).
|
||||||
|
WithArgs(limit, offset).
|
||||||
WillReturnRows(
|
WillReturnRows(
|
||||||
mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...),
|
mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...),
|
||||||
)
|
)
|
||||||
|
|
||||||
got, err := dal.Transactions()
|
got, err := dal.Transactions(limit, offset)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
type MockDAL struct {
|
type MockDAL struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockDALMockRecorder
|
recorder *MockDALMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockDALMockRecorder is the mock recorder for MockDAL.
|
// MockDALMockRecorder is the mock recorder for MockDAL.
|
||||||
@ -40,18 +41,18 @@ func (m *MockDAL) EXPECT() *MockDALMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bank mocks base method.
|
// Bank mocks base method.
|
||||||
func (m *MockDAL) Bank(arg0 string) (*entity.Bank, error) {
|
func (m *MockDAL) Bank(bankId string) (*entity.Bank, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Bank", arg0)
|
ret := m.ctrl.Call(m, "Bank", bankId)
|
||||||
ret0, _ := ret[0].(*entity.Bank)
|
ret0, _ := ret[0].(*entity.Bank)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bank indicates an expected call of Bank.
|
// Bank indicates an expected call of Bank.
|
||||||
func (mr *MockDALMockRecorder) Bank(arg0 any) *gomock.Call {
|
func (mr *MockDALMockRecorder) Bank(bankId any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bank", reflect.TypeOf((*MockDAL)(nil).Bank), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bank", reflect.TypeOf((*MockDAL)(nil).Bank), bankId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Banks mocks base method.
|
// Banks mocks base method.
|
||||||
@ -100,18 +101,18 @@ func (mr *MockDALMockRecorder) InsertTransaction(arg0 any) *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transaction mocks base method.
|
// Transaction mocks base method.
|
||||||
func (m *MockDAL) Transaction(arg0 int64) (*entity.Transaction, error) {
|
func (m *MockDAL) Transaction(transactionId int64) (*entity.Transaction, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Transaction", arg0)
|
ret := m.ctrl.Call(m, "Transaction", transactionId)
|
||||||
ret0, _ := ret[0].(*entity.Transaction)
|
ret0, _ := ret[0].(*entity.Transaction)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction indicates an expected call of Transaction.
|
// Transaction indicates an expected call of Transaction.
|
||||||
func (mr *MockDALMockRecorder) Transaction(arg0 any) *gomock.Call {
|
func (mr *MockDALMockRecorder) Transaction(transactionId any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transaction", reflect.TypeOf((*MockDAL)(nil).Transaction), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transaction", reflect.TypeOf((*MockDAL)(nil).Transaction), transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionExists mocks base method.
|
// TransactionExists mocks base method.
|
||||||
@ -130,18 +131,18 @@ func (mr *MockDALMockRecorder) TransactionExists(arg0 any) *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transactions mocks base method.
|
// Transactions mocks base method.
|
||||||
func (m *MockDAL) Transactions() ([]entity.Transaction, error) {
|
func (m *MockDAL) Transactions(limit, offset int) ([]entity.Transaction, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Transactions")
|
ret := m.ctrl.Call(m, "Transactions", limit, offset)
|
||||||
ret0, _ := ret[0].([]entity.Transaction)
|
ret0, _ := ret[0].([]entity.Transaction)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transactions indicates an expected call of Transactions.
|
// Transactions indicates an expected call of Transactions.
|
||||||
func (mr *MockDALMockRecorder) Transactions() *gomock.Call {
|
func (mr *MockDALMockRecorder) Transactions(limit, offset any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transactions", reflect.TypeOf((*MockDAL)(nil).Transactions))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transactions", reflect.TypeOf((*MockDAL)(nil).Transactions), limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTransaction mocks base method.
|
// UpdateTransaction mocks base method.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user