diff --git a/go.mod b/go.mod index 1b43872..1678f1b 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 93975bb..e56ab7f 100644 --- a/go.sum +++ b/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= diff --git a/internal/api/impl.go b/internal/api/impl.go index 1e35bc0..e45f4fb 100644 --- a/internal/api/impl.go +++ b/internal/api/impl.go @@ -2,12 +2,19 @@ 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" + "errors" + "log" "net/http" + "reflect" "github.com/labstack/echo/v4" + openapi_types "github.com/oapi-codegen/runtime/types" ) -type PersonalFinanceImpl struct{} +type PersonalFinanceImpl struct { + db *sql.DB +} func (*PersonalFinanceImpl) GetBanks(ctx echo.Context) error { return echo.NewHTTPError(http.StatusNotImplemented) @@ -17,10 +24,48 @@ func (*PersonalFinanceImpl) GetBanksById(ctx echo.Context, bankId int64) error { return echo.NewHTTPError(http.StatusNotImplemented) } -func (*PersonalFinanceImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error { - return echo.NewHTTPError(http.StatusNotImplemented) +func (pf *PersonalFinanceImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error { + rows, err := pf.db.Query("SELECT t.id, t.date, t.description, t.amount, tc.category 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 (*PersonalFinanceImpl) GetTransactionsById(ctx echo.Context, transactionId int64) error { return echo.NewHTTPError(http.StatusNotImplemented) } + +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) + columns[i] = field.Addr().Interface() + } + + if err := rows.Scan(columns...); err != nil { + log.Fatal(err) + } + + ans = append(ans, r) + } + return ans +} + +func (d *openapi_types.Date) Scan(value interface{}) error { + if value == nil { + return errors.New("Null date") + } + + // if bv, err := driver. +} diff --git a/internal/api/impl_test.go b/internal/api/impl_test.go new file mode 100644 index 0000000..676d266 --- /dev/null +++ b/internal/api/impl_test.go @@ -0,0 +1,61 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "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 := PersonalFinanceImpl{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{"id", "date", "description", "amount", "category"}). + AddRow(1, date, "#1", 1000, nil). + AddRow(2, date, "#2", -1000, "expense") + mock.ExpectQuery("SELECT t.id, t.date, t.description, t.amount, tc.category 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 := `[ + { + "id":1, + "date":"` + date.Format(time.DateOnly) + `", + description": "#1", + "amount":1000 + }, + { + "id":2, + "date":"` + date.Format(time.DateOnly) + `", + description": "#2", + "amount":-1000, + "category": "expense" + }, + ]` + if ret := rec.Body.String(); ret != expected { + t.Error(ret) + } + }) +}