parent
b2451fed66
commit
1aa6e28b69
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.rosemyrtle.work/personal-finance/server
|
||||
go 1.21.1
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/deepmap/oapi-codegen/v2 v2.1.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.123.0 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
@ -18,6 +20,7 @@ github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdi
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
|
||||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
|
||||
34
internal/api/converter.go
Normal file
34
internal/api/converter.go
Normal file
@ -0,0 +1,34 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func convert[T any](rows *sql.Rows) []T {
|
||||
var ans []T
|
||||
for rows.Next() {
|
||||
var r T
|
||||
s := reflect.ValueOf(&r).Elem()
|
||||
log.Println(s)
|
||||
|
||||
numCols := s.NumField()
|
||||
columns := make([]interface{}, numCols)
|
||||
log.Println(columns)
|
||||
|
||||
for i := 0; i < numCols; i++ {
|
||||
field := s.Field(i)
|
||||
log.Println(field)
|
||||
columns[i] = field.Addr().Interface()
|
||||
}
|
||||
log.Println(columns)
|
||||
|
||||
if err := rows.Scan(columns...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ans = append(ans, r)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
@ -1,13 +1,18 @@
|
||||
package api
|
||||
|
||||
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=api.cfg.yaml ../../docs/openapi.yaml
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type ServerImpl struct{}
|
||||
type ServerImpl struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (*ServerImpl) GetBanks(ctx echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusNotImplemented)
|
||||
@ -17,8 +22,13 @@ func (*ServerImpl) GetBanksById(ctx echo.Context, bankId int64) error {
|
||||
return echo.NewHTTPError(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (*ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
|
||||
return echo.NewHTTPError(http.StatusNotImplemented)
|
||||
func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
|
||||
rows, err := pf.db.Query("SELECT t.category, t.date, t.description, t.id, tc.amount FROM pfbudget.transactions t LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, convert[Transaction](rows))
|
||||
}
|
||||
|
||||
func (*ServerImpl) GetTransactionsById(ctx echo.Context, transactionId int64) error {
|
||||
|
||||
65
internal/api/impl_test.go
Normal file
65
internal/api/impl_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func TestGetTransactions(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e := echo.New()
|
||||
handlers := ServerImpl{db}
|
||||
RegisterHandlers(e, &handlers)
|
||||
|
||||
t.Run("when successful", func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/transactions", nil)
|
||||
|
||||
date := time.Now()
|
||||
rows := mock.NewRows([]string{"category", "date", "description", "id", "amount"}).
|
||||
AddRow(nil, date, "#1", 1, 1000).
|
||||
AddRow("expense", date, "#2", 2, -1000)
|
||||
mock.ExpectQuery("SELECT t.category, t.date, t.description, t.id, tc.amount FROM pfbudget.transactions t LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id").WillReturnRows(rows)
|
||||
|
||||
ctx := e.NewContext(req, rec)
|
||||
err := handlers.GetTransactions(ctx, GetTransactionsParams{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Error(rec.Code)
|
||||
}
|
||||
|
||||
expected := `[
|
||||
{
|
||||
"date":"` + date.Format(time.DateOnly) + `",
|
||||
"description": "#1",
|
||||
"id":1,
|
||||
"value":1000
|
||||
},
|
||||
{
|
||||
"category": "expense",
|
||||
"date":"` + date.Format(time.DateOnly) + `",
|
||||
"description": "#2",
|
||||
"id":2,
|
||||
"value":-1000
|
||||
}
|
||||
]`
|
||||
expected = strings.Join(strings.Fields(expected), "")
|
||||
log.Println(expected)
|
||||
if ret := strings.TrimRight(rec.Body.String(), "\n"); ret != expected {
|
||||
t.Error(ret, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -7,11 +7,13 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -29,10 +31,25 @@ type Bank struct {
|
||||
// Banks defines model for Banks.
|
||||
type Banks = []Bank
|
||||
|
||||
// Manually added date time that implements the Scanner interface
|
||||
type ScannableDate struct {
|
||||
openapi_types.Date
|
||||
}
|
||||
|
||||
func (d *ScannableDate) Scan(src any) error {
|
||||
switch s := src.(type) {
|
||||
case time.Time:
|
||||
d.Time = s
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("column is not convertible to date")
|
||||
}
|
||||
|
||||
// Transaction defines model for Transaction.
|
||||
type Transaction struct {
|
||||
Category *string `json:"category,omitempty"`
|
||||
Date openapi_types.Date `json:"date"`
|
||||
Date ScannableDate `json:"date"`
|
||||
Description string `json:"description"`
|
||||
Id int64 `json:"id"`
|
||||
Value float32 `json:"value"`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user