Implement pagination on /transactions
using limit and offset. While not the best, it's the simpler to implement. Issue: #11
This commit is contained in:
parent
708a8d0588
commit
5552b0622a
@ -36,7 +36,8 @@ paths:
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
default: -1
|
||||
default: 0
|
||||
minimum: 0
|
||||
- name: bank
|
||||
in: query
|
||||
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 {
|
||||
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 {
|
||||
return ctx.NoContent(http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(transactions) == 0 {
|
||||
|
||||
@ -142,14 +142,14 @@ func TestServerImpl_GetTransactions(t *testing.T) {
|
||||
{
|
||||
"200",
|
||||
fields{mock_dal},
|
||||
args{GetTransactionsParams{}},
|
||||
args{GetTransactionsParams{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}},
|
||||
false,
|
||||
entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}},
|
||||
},
|
||||
{
|
||||
"204",
|
||||
fields{mock_dal},
|
||||
args{GetTransactionsParams{}},
|
||||
args{GetTransactionsParams{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}},
|
||||
false,
|
||||
entity.Transactions{},
|
||||
},
|
||||
@ -164,7 +164,7 @@ func TestServerImpl_GetTransactions(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
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 {
|
||||
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
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Transaction(transactionId int64) (*entity.Transaction, error)
|
||||
Transactions() (entity.Transactions, error)
|
||||
Transactions(limit, offset int) (entity.Transactions, error)
|
||||
InsertTransaction(entity.Transaction) (entity.Transaction, error)
|
||||
UpdateTransaction(entity.TransactionId, *entity.CategoryName) (bool, error)
|
||||
TransactionExists(uint64) (bool, error)
|
||||
|
||||
@ -41,8 +41,8 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
|
||||
return &transactions[0], nil
|
||||
}
|
||||
|
||||
func (dal *DalImpl) Transactions() (entity.Transactions, error) {
|
||||
log.Print("DAL::Transactions")
|
||||
func (dal *DalImpl) Transactions(limit, offset int) (entity.Transactions, error) {
|
||||
log.Print("DAL::Transactions", "limit", limit, "offset", offset)
|
||||
|
||||
if dal.Db == nil {
|
||||
log.Panic("database not available")
|
||||
@ -53,9 +53,12 @@ func (dal *DalImpl) Transactions() (entity.Transactions, error) {
|
||||
FROM pfbudget.transactions t
|
||||
LEFT JOIN pfbudget.transactions_categorized tc
|
||||
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 {
|
||||
return entity.Transactions{}, err
|
||||
}
|
||||
|
||||
@ -142,18 +142,23 @@ func TestDalImpl_Transactions(t *testing.T) {
|
||||
Db: tt.fields.Db,
|
||||
}
|
||||
|
||||
limit, offset := 0, 0
|
||||
|
||||
mock.
|
||||
ExpectQuery(`
|
||||
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name
|
||||
FROM \w+\.transactions \w+
|
||||
LEFT JOIN \w+\.transactions_categorized \w+
|
||||
ON \w+\.id = \w+\.id`).
|
||||
WithoutArgs().
|
||||
ON \w+\.id = \w+\.id
|
||||
ORDER BY \w+\.date DESC
|
||||
LIMIT \$1
|
||||
OFFSET \$2`).
|
||||
WithArgs(limit, offset).
|
||||
WillReturnRows(
|
||||
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 {
|
||||
t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
type MockDAL struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDALMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockDALMockRecorder is the mock recorder for MockDAL.
|
||||
@ -40,18 +41,18 @@ func (m *MockDAL) EXPECT() *MockDALMockRecorder {
|
||||
}
|
||||
|
||||
// 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()
|
||||
ret := m.ctrl.Call(m, "Bank", arg0)
|
||||
ret := m.ctrl.Call(m, "Bank", bankId)
|
||||
ret0, _ := ret[0].(*entity.Bank)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// 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()
|
||||
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.
|
||||
@ -100,18 +101,18 @@ func (mr *MockDALMockRecorder) InsertTransaction(arg0 any) *gomock.Call {
|
||||
}
|
||||
|
||||
// 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()
|
||||
ret := m.ctrl.Call(m, "Transaction", arg0)
|
||||
ret := m.ctrl.Call(m, "Transaction", transactionId)
|
||||
ret0, _ := ret[0].(*entity.Transaction)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// 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()
|
||||
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.
|
||||
@ -130,18 +131,18 @@ func (mr *MockDALMockRecorder) TransactionExists(arg0 any) *gomock.Call {
|
||||
}
|
||||
|
||||
// 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()
|
||||
ret := m.ctrl.Call(m, "Transactions")
|
||||
ret := m.ctrl.Call(m, "Transactions", limit, offset)
|
||||
ret0, _ := ret[0].([]entity.Transaction)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// 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()
|
||||
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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user