Compare commits

..

2 Commits

Author SHA1 Message Date
786bf589d0
refactor: return err from API handlers
Instead of logging + NoContent(http.StatusIntervalServerError).
Gin already logs the error when returned and returns a 500 to the client.
2025-08-24 23:25:24 +01:00
6b5a07aac0
refactor: swap strings with an SQL builder
Adds doug-martin/goqu as dependency.
2025-08-24 23:25:09 +01:00
6 changed files with 179 additions and 128 deletions

View File

@ -9,6 +9,8 @@ import (
"git.rosemyrtle.work/personal-finance/server/internal/api" "git.rosemyrtle.work/personal-finance/server/internal/api"
"git.rosemyrtle.work/personal-finance/server/internal/dal" "git.rosemyrtle.work/personal-finance/server/internal/dal"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -39,7 +41,7 @@ func main() {
} }
// 2. Data Access Layer // 2. Data Access Layer
dal := dal.DalImpl{Db: db} dal := dal.DalImpl{Db: db, Dialect: goqu.Dialect("postgres")}
// 3. HTTP server // 3. HTTP server
e := echo.New() e := echo.New()

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.21.1
require ( require (
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/deepmap/oapi-codegen/v2 v2.1.0 github.com/deepmap/oapi-codegen/v2 v2.1.0
github.com/doug-martin/goqu/v9 v9.0.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.5.5 github.com/jackc/pgx/v5 v5.5.5
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1

26
go.sum
View File

@ -1,3 +1,4 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= 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/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/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
@ -9,16 +10,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ= github.com/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ=
github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8=
github.com/doug-martin/goqu/v9 v9.0.0 h1:de8P6AsHnCPZsdKQl8lrqI8XNxU3K//jrDrI2ZeYcDs=
github.com/doug-martin/goqu/v9 v9.0.0/go.mod h1:GPZzBWikLe6aaCT2uBT5X3MTseGBYevzWiVjMl2wIWk=
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@ -47,6 +52,8 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -54,6 +61,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k= github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k=
@ -70,7 +78,10 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@ -82,29 +93,44 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -21,7 +21,7 @@ func (server *ServerImpl) GetBanks(ctx echo.Context) error {
banks, err := server.Dal.Banks() banks, err := server.Dal.Banks()
if err != nil { if err != nil {
return ctx.NoContent(http.StatusInternalServerError) return err
} }
if len(banks) == 0 { if len(banks) == 0 {
@ -36,8 +36,7 @@ func (server *ServerImpl) GetBankById(ctx echo.Context, bankId string) error {
bank, err := server.Dal.Bank(bankId) bank, err := server.Dal.Bank(bankId)
if err != nil { if err != nil {
log.Printf("%v", err) return err
return ctx.NoContent(http.StatusInternalServerError)
} }
if bank == nil { if bank == nil {
@ -78,8 +77,7 @@ func (server *ServerImpl) GetTransactionById(ctx echo.Context, transactionId int
transaction, err := server.Dal.Transaction(transactionId) transaction, err := server.Dal.Transaction(transactionId)
if err != nil { if err != nil {
log.Printf("%v", err) return err
return ctx.NoContent(http.StatusInternalServerError)
} }
if transaction == nil { if transaction == nil {
@ -98,8 +96,7 @@ func (server *ServerImpl) CreateTransaction(ctx echo.Context) error {
transaction, err := server.Dal.InsertTransaction(transaction2entity(*t)) transaction, err := server.Dal.InsertTransaction(transaction2entity(*t))
if err != nil { if err != nil {
log.Printf("%v", err) return err
return ctx.NoContent(http.StatusInternalServerError)
} }
return ctx.JSON(http.StatusCreated, entity2transaction(transaction)) return ctx.JSON(http.StatusCreated, entity2transaction(transaction))
@ -107,8 +104,7 @@ func (server *ServerImpl) CreateTransaction(ctx echo.Context) error {
func (server *ServerImpl) UpdateTransaction(ctx echo.Context, transactionId int64) error { func (server *ServerImpl) UpdateTransaction(ctx echo.Context, transactionId int64) error {
if exists, err := server.Dal.TransactionExists(entity.TransactionId(transactionId)); err != nil { if exists, err := server.Dal.TransactionExists(entity.TransactionId(transactionId)); err != nil {
log.Printf("%v", err) return err
return ctx.NoContent(http.StatusInternalServerError)
} else if !exists { } else if !exists {
return ctx.NoContent(http.StatusNotFound) return ctx.NoContent(http.StatusNotFound)
} }
@ -121,8 +117,7 @@ func (server *ServerImpl) UpdateTransaction(ctx echo.Context, transactionId int6
success, err := server.Dal.UpdateTransaction(entity.TransactionId(transactionId), update.Category) success, err := server.Dal.UpdateTransaction(entity.TransactionId(transactionId), update.Category)
if err != nil { if err != nil {
log.Printf("%v", err) return err
return ctx.NoContent(http.StatusInternalServerError)
} }
if !success { if !success {
@ -134,7 +129,7 @@ func (server *ServerImpl) UpdateTransaction(ctx echo.Context, transactionId int6
func (server *ServerImpl) GetCategories(ctx echo.Context) error { func (server *ServerImpl) GetCategories(ctx echo.Context) error {
categories, err := server.Dal.Categories() categories, err := server.Dal.Categories()
if err != nil { if err != nil {
return ctx.NoContent(http.StatusInternalServerError) return err
} }
if len(categories) == 0 { if len(categories) == 0 {

View File

@ -5,17 +5,28 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"strings"
"time" "time"
"git.rosemyrtle.work/personal-finance/server/internal/entity" "git.rosemyrtle.work/personal-finance/server/internal/entity"
"github.com/doug-martin/goqu/v9"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
) )
type DalImpl struct { type DalImpl struct {
Db *sql.DB Db *sql.DB
Dialect goqu.DialectWrapper
} }
// Table aliases
var (
schema = goqu.S("pfbudget")
t = schema.Table("transactions")
tc = schema.Table("transactions_categorized")
b = schema.Table("banks")
bn = schema.Table("banks_nordigen")
c = schema.Table("categories")
)
func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error) { func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error) {
log.Printf("DAL::Transaction(%v)", transactionId) log.Printf("DAL::Transaction(%v)", transactionId)
@ -23,15 +34,23 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(
"SELECT t.id, t.date, t.description, t.amount, tc.name", t.Col("id"),
"FROM pfbudget.transactions t", t.Col("date"),
"LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id", t.Col("description"),
"WHERE t.id = $1", t.Col("amount"),
} tc.Col("name"),
stmt := strings.Join(stmts, "\n") + "\n" ).
From(t).
LeftJoin(tc, goqu.On(t.Col("id").Eq(tc.Col("id")))).
Where(t.Col("id").Eq(transactionId))
rows, err := dal.Db.Query(stmt, transactionId) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
log.Panic(err)
}
rows, err := dal.Db.Query(sqlStr, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,27 +70,30 @@ func (dal *DalImpl) Transactions(limit, offset int, category *string) (entity.Tr
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(
"SELECT t.id, t.date, t.description, t.amount, tc.name, count(*) OVER() AS total_count", t.Col("id"),
"FROM pfbudget.transactions t", t.Col("date"),
"LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id", t.Col("description"),
} t.Col("amount"),
args := []any{limit, offset} tc.Col("name"),
goqu.L("count(*) OVER() AS total_count"),
).
From(t).
LeftJoin(tc, goqu.On(t.Col("id").Eq(tc.Col("id")))).
Order(t.Col("date").Desc()).
Limit(uint(limit)).
Offset(uint(offset))
if category != nil { if category != nil {
stmts = append(stmts, "WHERE tc.name SIMILAR TO '%' || $3 || '%'") ds = ds.Where(tc.Col("name").ILike(*category))
args = append(args, *category)
} }
stmts = append(stmts,
"ORDER BY t.date DESC",
"LIMIT $1",
"OFFSET $2",
)
stmt := strings.Join(stmts, "\n") + "\n"
log.Printf("DAL::Transactions::stmt: %s", stmt) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return nil, 0, err
}
rows, err := dal.Db.Query(stmt, args...) rows, err := dal.Db.Query(sqlStr, args...)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -92,33 +114,39 @@ func (dal *DalImpl) Transactions(limit, offset int, category *string) (entity.Tr
var ans entity.Transactions var ans entity.Transactions
for _, twc := range transactions_with_count { for _, twc := range transactions_with_count {
t := entity.Transaction{Id: twc.Id, Date: twc.Date, Description: twc.Description, Value: twc.Value, Category: twc.Category} tx := entity.Transaction{Id: twc.Id, Date: twc.Date, Description: twc.Description, Value: twc.Value, Category: twc.Category}
ans = append(ans, t) ans = append(ans, tx)
} }
return ans, transactions_with_count[0].TotalCount, nil return ans, transactions_with_count[0].TotalCount, nil
} }
func (dal *DalImpl) InsertTransaction(t entity.Transaction) (entity.Transaction, error) { func (dal *DalImpl) InsertTransaction(tx entity.Transaction) (entity.Transaction, error) {
log.Print("DAL::InsertTransaction") log.Print("DAL::InsertTransaction")
if dal.Db == nil { if dal.Db == nil {
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Insert(t).Rows(
"INSERT INTO pfbudget.transactions (date, description, amount)", goqu.Record{
"VALUES ($1, $2, $3)", "date": tx.Date,
"RETURNING id", "description": tx.Description,
} "amount": tx.Value,
stmt := strings.Join(stmts, "\n") + "\n" }).
Returning(t.Col("id"))
id := new(uint64) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err := dal.Db.QueryRow(stmt, t.Date, t.Description, t.Value).Scan(id); err != nil { if err != nil {
return entity.Transaction{}, err return entity.Transaction{}, err
} }
t.Id = *id id := new(uint64)
return t, nil if err := dal.Db.QueryRow(sqlStr, args...).Scan(id); err != nil {
return entity.Transaction{}, err
}
tx.Id = *id
return tx, nil
} }
func (dal *DalImpl) UpdateTransaction(id entity.TransactionId, category *entity.CategoryName) (bool, error) { func (dal *DalImpl) UpdateTransaction(id entity.TransactionId, category *entity.CategoryName) (bool, error) {
@ -132,14 +160,17 @@ func (dal *DalImpl) UpdateTransaction(id entity.TransactionId, category *entity.
return false, errors.New("missing category") return false, errors.New("missing category")
} }
stmts := []string{ // TODO(43): implement upsert logic
"UPDATE pfbudget.transactions_categorized", ds := dal.Dialect.Update(tc).
"SET name = $2", Set(goqu.Record{"name": *category}).
"WHERE id = $1", Where(tc.Col("id").Eq(id))
}
stmt := strings.Join(stmts, "\n") + "\n"
result, err := dal.Db.Exec(stmt, id, *category) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return false, err
}
result, err := dal.Db.Exec(sqlStr, args...)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -164,18 +195,15 @@ func (dal *DalImpl) TransactionExists(id uint64) (bool, error) {
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(goqu.L("EXISTS(SELECT 1 FROM pfbudget.transactions WHERE id = ?)", id))
"SELECT EXISTS(",
" SELECT 1", sqlStr, args, err := ds.Prepared(true).ToSQL()
" FROM pfbudget.transactions", if err != nil {
" WHERE id = $1", return false, err
")",
} }
stmt := strings.Join(stmts, "\n") + "\n"
exists := new(bool) exists := new(bool)
err := dal.Db.QueryRow(stmt, id).Scan(&exists) if err := dal.Db.QueryRow(sqlStr, args...).Scan(exists); err != nil {
if err != nil {
return false, err return false, err
} }
@ -189,15 +217,21 @@ func (dal *DalImpl) Bank(bankId string) (*entity.Bank, error) {
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(
"SELECT b.name, b.name, n.requisition_id", b.Col("name"),
"FROM pfbudget.banks b", b.Col("name"),
"JOIN pfbudget.banks_nordigen n ON b.name = n.name", bn.Col("requisition_id"),
"WHERE b.name = $1", ).
} From(b).
stmt := strings.Join(stmts, "\n") + "\n" Join(bn, goqu.On(b.Col("name").Eq(bn.Col("name")))).
Where(b.Col("name").Eq(bankId))
rows, err := dal.Db.Query(stmt, bankId) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return nil, err
}
rows, err := dal.Db.Query(sqlStr, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -217,14 +251,20 @@ func (dal *DalImpl) Banks() (entity.Banks, error) {
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(
"SELECT b.name, b.name, n.requisition_id", b.Col("name"),
"FROM pfbudget.banks b", b.Col("name"),
"JOIN pfbudget.banks_nordigen n ON b.name = n.name", bn.Col("requisition_id"),
} ).
stmt := strings.Join(stmts, "\n") + "\n" From(b).
Join(bn, goqu.On(b.Col("name").Eq(bn.Col("name"))))
rows, err := dal.Db.Query(stmt) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return entity.Banks{}, err
}
rows, err := dal.Db.Query(sqlStr, args...)
if err != nil { if err != nil {
return entity.Banks{}, err return entity.Banks{}, err
} }
@ -239,13 +279,18 @@ func (dal *DalImpl) Categories() (entity.Categories, error) {
log.Panic("database not available") log.Panic("database not available")
} }
stmts := []string{ ds := dal.Dialect.Select(
"SELECT c.name, c.group", c.Col("name"),
"FROM pfbudget.categories c", c.Col("group"),
} ).
stmt := strings.Join(stmts, "\n") + "\n" From(c)
rows, err := dal.Db.Query(stmt) sqlStr, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return []entity.Category{}, err
}
rows, err := dal.Db.Query(sqlStr, args...)
if err != nil { if err != nil {
return []entity.Category{}, err return []entity.Category{}, err
} }

View File

@ -66,12 +66,7 @@ func TestDalImpl_Transaction(t *testing.T) {
} }
mock. mock.
ExpectQuery(` ExpectQuery("^SELECT.*id.*date.*description.*amount.*name.*WHERE.*id.*= \\?").
^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
WHERE \w+\.id = \$1$`).
WithArgs(tt.args.transactionId). WithArgs(tt.args.transactionId).
WillReturnRows( WillReturnRows(
mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...), mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...),
@ -114,7 +109,7 @@ func TestDalImpl_Transactions(t *testing.T) {
{ {
"SelectTransactions", "SelectTransactions",
fields{db}, fields{db},
args{limit: 30, offset: 0, category: nil}, args{limit: 30, offset: 30, category: nil},
[][]driver.Value{ [][]driver.Value{
{1, date, "income", 1000, nil, 2}, {1, date, "income", 1000, nil, 2},
{2, date, "expense", -10.50, nil, 2}, {2, date, "expense", -10.50, nil, 2},
@ -128,7 +123,7 @@ func TestDalImpl_Transactions(t *testing.T) {
{ {
"SelectTransactionsWithCategory", "SelectTransactionsWithCategory",
fields{db}, fields{db},
args{limit: 30, offset: 0, category: golang.Ptr("C1")}, args{limit: 30, offset: 30, category: golang.Ptr("C1")},
[][]driver.Value{ [][]driver.Value{
{1, date, "income", 1000, "C1", 2}, {1, date, "income", 1000, "C1", 2},
{2, date, "expense", -10.50, nil, 2}, {2, date, "expense", -10.50, nil, 2},
@ -140,7 +135,7 @@ func TestDalImpl_Transactions(t *testing.T) {
false, false,
}, },
{ {
"SelectNoTransactions", fields{db}, args{limit: 30, offset: 0, category: nil}, nil, nil, false, "SelectNoTransactions", fields{db}, args{limit: 30, offset: 30, category: nil}, nil, nil, false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -149,22 +144,14 @@ func TestDalImpl_Transactions(t *testing.T) {
Db: tt.fields.Db, Db: tt.fields.Db,
} }
args := []driver.Value{tt.args.limit, tt.args.offset} args := []driver.Value{}
stmt := ` stmt := "^SELECT.*id.*date.*description.*amount.*name.*count\\(\\*\\) OVER\\(\\) AS total_count"
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name, count\(\*\) OVER\(\) AS total_count
FROM \w+\.transactions \w+
LEFT JOIN \w+\.transactions_categorized \w+
ON \w+\.id = \w+\.id
`
if tt.args.category != nil { if tt.args.category != nil {
stmt += `WHERE \w+\.name SIMILAR TO '%' || \$3 || '%'` stmt += ".*WHERE.*name.*ILIKE \\?"
args = append(args, *tt.args.category) args = append(args, *tt.args.category)
} }
stmt += ` stmt += ".*ORDER BY.*date.*DESC LIMIT \\? OFFSET \\?"
ORDER BY \w+\.date DESC args = append(args, tt.args.limit, tt.args.offset)
LIMIT \$1
OFFSET \$2
`
mock. mock.
ExpectQuery(stmt). ExpectQuery(stmt).
@ -227,7 +214,7 @@ func TestDalImpl_Bank(t *testing.T) {
} }
mock. mock.
ExpectQuery("^SELECT .* FROM .*banks b JOIN .*banks_nordigen n ON b.name = n.name WHERE b.name = \\$1$"). ExpectQuery("^SELECT.*name.*name.*requisition_id.*WHERE.*name.*= \\?").
WithArgs(tt.args.bankId). WithArgs(tt.args.bankId).
WillReturnRows( WillReturnRows(
mock. mock.
@ -290,7 +277,7 @@ func TestDalImpl_Banks(t *testing.T) {
} }
mock. mock.
ExpectQuery("^SELECT .* FROM .*banks b JOIN .*banks_nordigen n ON b.name = n.name$"). ExpectQuery("^SELECT.*name.*name.*requisition_id").
WithoutArgs(). WithoutArgs().
WillReturnRows( WillReturnRows(
mock. mock.
@ -346,10 +333,10 @@ func TestDalImpl_InsertTransaction(t *testing.T) {
mock. mock.
ExpectQuery(` ExpectQuery(`
INSERT INTO .* \(date, description, amount\) INSERT INTO.*.*amount.*date.*description.*
VALUES \(\$1, \$2, \$3\) VALUES.*\?, \?, \?.*
RETURNING id`). RETURNING.*id`).
WithArgs(tt.args.t.Date, tt.args.t.Description, tt.args.t.Value). WithArgs(tt.args.t.Value, tt.args.t.Date, tt.args.t.Description).
WillReturnRows( WillReturnRows(
mock.NewRows([]string{"id"}). mock.NewRows([]string{"id"}).
AddRows([]driver.Value{tt.want.Id}), AddRows([]driver.Value{tt.want.Id}),
@ -413,10 +400,10 @@ func TestDalImpl_UpdateTransaction(t *testing.T) {
mock. mock.
ExpectExec(` ExpectExec(`
UPDATE \w+\.transactions_categorized UPDATE.*transactions_categorized.*
SET name = \$2 SET.*name.*= ?.*
WHERE id = \$1`). WHERE.*id.*= ?`).
WithArgs(tt.args.id, tt.args.category). WithArgs(tt.args.category, tt.args.id).
WillReturnResult(tt.mocks) WillReturnResult(tt.mocks)
got, err := dal.UpdateTransaction(tt.args.id, tt.args.category) got, err := dal.UpdateTransaction(tt.args.id, tt.args.category)
@ -460,12 +447,7 @@ func TestDalImpl_TransactionExists(t *testing.T) {
} }
mock. mock.
ExpectQuery(` ExpectQuery("EXISTS\\(SELECT 1 FROM pfbudget\\.transactions WHERE id = \\?\\)").
SELECT EXISTS\(
SELECT 1
FROM pfbudget.transactions
WHERE id = \$1
\)`).
WithArgs(tt.args.id). WithArgs(tt.args.id).
WillReturnRows( WillReturnRows(
mock.NewRows([]string{"exists"}). mock.NewRows([]string{"exists"}).
@ -529,7 +511,7 @@ func TestDalImpl_Categories(t *testing.T) {
} }
mock. mock.
ExpectQuery(`SELECT \w+\.name, \w+\.group FROM \w+.categories \w+`). ExpectQuery(`SELECT.*name.*group.*FROM.*categories`).
WithoutArgs(). WithoutArgs().
WillReturnRows(mock.NewRows([]string{"name", "group"}).AddRows(tt.mocks...)) WillReturnRows(mock.NewRows([]string{"name", "group"}).AddRows(tt.mocks...))