From 8f6974e151de9449261436245549ee82d894e5c3 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 09:30:54 +0000 Subject: [PATCH 01/57] Add golang-ci to dev image --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 83ef4e2..4c930c3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,15 +2,16 @@ FROM alpine as godeps RUN apk add go RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest RUN go install github.com/go-task/task/v3/cmd/task@latest +RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest FROM alpine RUN apk add go RUN apk add nodejs yarn bash curl git git-perl tmux ADD docker/build.sh / -COPY --from=godeps /root/go/bin/task /root/go/bin/sqlc /usr/local/bin/ RUN yarn global add @vue/cli ENV PATH="/root/.yarn/bin/:${PATH}" WORKDIR /src ADD web/package.json /src/web/ RUN yarn +COPY --from=godeps /root/go/bin/task /root/go/bin/sqlc /root/go/bin/golangci-lint /usr/local/bin/ CMD /build.sh -- 2.47.2 From 4bbbc0be13325fb3cbec0d74d48f405455dbac08 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 09:33:17 +0000 Subject: [PATCH 02/57] Add vet fmt and linters to build --- Taskfile.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 3306503..3768d3c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -58,6 +58,9 @@ tasks: desc: Build budgeteer in prod mode deps: [gomod, sqlc, frontend] cmds: + - go vet + - go fmt + - golang-ci run - task: build frontend: -- 2.47.2 From 835a15ec08867454b1a208122c357b990ea70a31 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 11:51:38 +0000 Subject: [PATCH 03/57] Ad config for golangci --- .golangci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6fb6306 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,8 @@ +linters-settings: + errcheck: + exclude-functions: + - io/ioutil.ReadFile + - io.Copy(*bytes.Buffer) + - (*github.com/gin-gonic/gin.Context).AbortWithError + - (*github.com/gin-gonic/gin.Context).AbortWithError + - io.Copy(os.Stdout) \ No newline at end of file -- 2.47.2 From 38dfa540b405883f0bfa219b9cd5da97d4c3dcb4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 11:52:06 +0000 Subject: [PATCH 04/57] Fix issues from golangci --- http/http.go | 9 +++---- http/session.go | 20 +++++++++++--- http/transaction.go | 9 ++++--- http/util.go | 56 --------------------------------------- postgres/budgetservice.go | 9 ++++++- 5 files changed, 33 insertions(+), 70 deletions(-) delete mode 100644 http/util.go diff --git a/http/http.go b/http/http.go index ade5c22..ccb53a3 100644 --- a/http/http.go +++ b/http/http.go @@ -24,15 +24,14 @@ type Handler struct { StaticFS http.FileSystem } -const ( - expiration = 72 -) - // Serve starts the http server func (h *Handler) Serve() { router := gin.Default() h.LoadRoutes(router) - router.Run(":1323") + err := router.Run(":1323") + if err != nil { + panic(err) + } } // LoadRoutes initializes all the routes diff --git a/http/session.go b/http/session.go index a76298a..9bd0f75 100644 --- a/http/session.go +++ b/http/session.go @@ -8,6 +8,7 @@ import ( "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/postgres" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) func (h *Handler) verifyLogin(c *gin.Context) (budgeteer.Token, error) { @@ -77,7 +78,7 @@ func (h *Handler) loginPost(c *gin.Context) { c.AbortWithError(http.StatusUnauthorized, err) } - go h.Service.UpdateLastLogin(context.Background(), user.ID) + go h.UpdateLastLogin(user.ID) budgets, err := h.Service.GetBudgetsForUser(c.Request.Context(), user.ID) if err != nil { @@ -101,14 +102,18 @@ type registerInformation struct { func (h *Handler) registerPost(c *gin.Context) { var register registerInformation - c.BindJSON(®ister) + err := c.BindJSON(®ister) + if err != nil { + c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse body: %w", err)) + return + } if register.Email == "" || register.Password == "" || register.Name == "" { c.AbortWithError(http.StatusBadRequest, fmt.Errorf("e-mail, password and name are required")) return } - _, err := h.Service.GetUserByUsername(c.Request.Context(), register.Email) + _, err = h.Service.GetUserByUsername(c.Request.Context(), register.Email) if err == nil { c.AbortWithError(http.StatusBadRequest, fmt.Errorf("email is already taken")) return @@ -135,7 +140,7 @@ func (h *Handler) registerPost(c *gin.Context) { c.AbortWithError(http.StatusUnauthorized, err) } - go h.Service.UpdateLastLogin(context.Background(), user.ID) + go h.UpdateLastLogin(user.ID) budgets, err := h.Service.GetBudgetsForUser(c.Request.Context(), user.ID) if err != nil { @@ -144,3 +149,10 @@ func (h *Handler) registerPost(c *gin.Context) { c.JSON(http.StatusOK, LoginResponse{t, user, budgets}) } + +func (h *Handler) UpdateLastLogin(userID uuid.UUID) { + _, err := h.Service.UpdateLastLogin(context.Background(), userID) + if err != nil { + fmt.Printf("Error updating last login: %s", err) + } +} diff --git a/http/transaction.go b/http/transaction.go index a565275..306fe01 100644 --- a/http/transaction.go +++ b/http/transaction.go @@ -31,14 +31,17 @@ func (h *Handler) newTransaction(c *gin.Context) { var payload NewTransactionPayload err := c.BindJSON(&payload) if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) + c.AbortWithError(http.StatusBadRequest, err) return } fmt.Printf("%v\n", payload) amount := postgres.Numeric{} - amount.Set(payload.Amount) + err = amount.Set(payload.Amount) + if err != nil { + c.AbortWithError(http.StatusBadRequest, fmt.Errorf("amount: %w", err)) + } /*transactionUUID, err := getNullUUIDFromParam(c, "transactionid") if err != nil { @@ -60,8 +63,6 @@ func (h *Handler) newTransaction(c *gin.Context) { if err != nil { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err)) } - - return // } /* _, delete := c.GetPostForm("delete") diff --git a/http/util.go b/http/util.go deleted file mode 100644 index 7c2031e..0000000 --- a/http/util.go +++ /dev/null @@ -1,56 +0,0 @@ -package http - -import ( - "fmt" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" -) - -func getUUID(c *gin.Context, name string) (uuid.UUID, error) { - value, succ := c.GetPostForm(name) - if !succ { - return uuid.UUID{}, fmt.Errorf("not set") - } - - id, err := uuid.Parse(value) - if err != nil { - return uuid.UUID{}, fmt.Errorf("not a valid uuid: %w", err) - } - - return id, nil -} - -func getNullUUIDFromParam(c *gin.Context, name string) (uuid.NullUUID, error) { - value := c.Param(name) - if value == "" { - return uuid.NullUUID{}, nil - } - - id, err := uuid.Parse(value) - if err != nil { - return uuid.NullUUID{}, fmt.Errorf("not a valid uuid: %w", err) - } - - return uuid.NullUUID{ - UUID: id, - Valid: true, - }, nil -} - -func getNullUUIDFromForm(c *gin.Context, name string) (uuid.NullUUID, error) { - value, succ := c.GetPostForm(name) - if !succ || value == "" { - return uuid.NullUUID{}, nil - } - - id, err := uuid.Parse(value) - if err != nil { - return uuid.NullUUID{}, fmt.Errorf("not a valid uuid: %w", err) - } - - return uuid.NullUUID{ - UUID: id, - Valid: true, - }, nil -} diff --git a/postgres/budgetservice.go b/postgres/budgetservice.go index ab78aa4..4af5c36 100644 --- a/postgres/budgetservice.go +++ b/postgres/budgetservice.go @@ -11,6 +11,10 @@ import ( // NewBudget creates a budget and adds it to the current user func (s *Database) NewBudget(context context.Context, name string, userID uuid.UUID) (*Budget, error) { tx, err := s.BeginTx(context, &sql.TxOptions{}) + if err != nil { + return nil, fmt.Errorf("begin transaction: %w", err) + } + q := s.WithTx(tx) budget, err := q.CreateBudget(context, CreateBudgetParams{ Name: name, @@ -50,7 +54,10 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U return nil, fmt.Errorf("set inflow category: %w", err) } - tx.Commit() + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("commit: %w", err) + } return &budget, nil } -- 2.47.2 From d8e0f5a160b01a0630da7b5537e9518c32a5c51c Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:26:48 +0000 Subject: [PATCH 05/57] Add explicit CI task to Taskfile --- .drone.yml | 2 +- Taskfile.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 7f6e6f0..60faecb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ steps: image: hub.javil.eu/budgeteer:dev pull: true commands: - - task + - task ci - name: docker image: plugins/docker diff --git a/Taskfile.yml b/Taskfile.yml index 3768d3c..b73145b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -63,6 +63,11 @@ tasks: - golang-ci run - task: build + ci: + desc: Run CI build + cmds: + - task: build-prod + frontend: desc: Build vue frontend dir: web -- 2.47.2 From aaf16dbe9228d43e2acb47c832149e87748709d4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:26:55 +0000 Subject: [PATCH 06/57] Add deps for dev-docker target --- Taskfile.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index b73145b..fe6b9e5 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -93,6 +93,8 @@ tasks: desc: Build budgeeter:dev sources: - ./docker/Dockerfile + - ./docker/build.sh + - ./web/package.json cmds: - docker build -t {{.IMAGE_NAME}}:dev . -f docker/Dockerfile - docker push {{.IMAGE_NAME}}:dev -- 2.47.2 From 584e7ef39394f17a762f4fe05bd872f22c8bf8b4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:27:15 +0000 Subject: [PATCH 07/57] Add explicit CI task to Taskfile --- .woodpecker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 03a5388..2476945 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -4,7 +4,7 @@ pipeline: image: hub.javil.eu/budgeteer:dev pull: true commands: - - task + - task ci docker: image: plugins/docker -- 2.47.2 From 71c54c93735ddeb1a1ccd3860b0dc303f9c50299 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:36:19 +0000 Subject: [PATCH 08/57] Enable all linters --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 6fb6306..b6e7031 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,5 @@ +linters: + enable-all: true linters-settings: errcheck: exclude-functions: -- 2.47.2 From 7a0c4a17a29f78c84fd750f025ffd287aa302d45 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:36:33 +0000 Subject: [PATCH 09/57] Ignore gin.Context for varnamelen --- .golangci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index b6e7031..55cd4a5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,4 +7,7 @@ linters-settings: - io.Copy(*bytes.Buffer) - (*github.com/gin-gonic/gin.Context).AbortWithError - (*github.com/gin-gonic/gin.Context).AbortWithError - - io.Copy(os.Stdout) \ No newline at end of file + - io.Copy(os.Stdout) + varnamelen: + ignore-decls: + - c *gin.Context -- 2.47.2 From 74a53954de209f2334e8edce523e77517bc7e3a6 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:37:04 +0000 Subject: [PATCH 10/57] Wrap more errors --- bcrypt/verifier.go | 8 ++++++-- http/json-date.go | 10 ++++++++-- jwt/login.go | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bcrypt/verifier.go b/bcrypt/verifier.go index 60c7a11..a7d18a6 100644 --- a/bcrypt/verifier.go +++ b/bcrypt/verifier.go @@ -1,6 +1,10 @@ package bcrypt -import "golang.org/x/crypto/bcrypt" +import ( + "fmt" + + "golang.org/x/crypto/bcrypt" +) // Verifier verifys passwords using Bcrypt type Verifier struct { @@ -16,7 +20,7 @@ func (bv *Verifier) Verify(password string, hashOnDb string) error { func (bv *Verifier) Hash(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bv.cost) if err != nil { - return "", err + return "", fmt.Errorf("hash password: %w", err) } return string(hash[:]), nil diff --git a/http/json-date.go b/http/json-date.go index d9e0180..36dd85a 100644 --- a/http/json-date.go +++ b/http/json-date.go @@ -2,6 +2,7 @@ package http import ( "encoding/json" + "fmt" "strings" "time" ) @@ -13,14 +14,19 @@ func (j *JSONDate) UnmarshalJSON(b []byte) error { s := strings.Trim(string(b), "\"") t, err := time.Parse("2006-01-02", s) if err != nil { - return err + return fmt.Errorf("parse date: %w", err) } *j = JSONDate(t) return nil } func (j JSONDate) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Time(j)) + result, err := json.Marshal(time.Time(j)) + if err != nil { + return nil, fmt.Errorf("marshal date: %w", err) + } + + return result, nil } // Maybe a Format function for printing your date diff --git a/jwt/login.go b/jwt/login.go index e15d961..4bc5f04 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -39,7 +39,7 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) { // Generate encoded token and send it as response. t, err := token.SignedString([]byte(secret)) if err != nil { - return "", err + return "", fmt.Errorf("create token: %w", err) } return t, nil -- 2.47.2 From 2f45c415e0a2a674742447e55f9b46484bcbd62a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:37:23 +0000 Subject: [PATCH 11/57] Move init of StaticFS and rename some vars --- cmd/budgeteer/main.go | 10 ++++++++++ http/account_test.go | 16 ++++++++-------- http/http.go | 7 ------- http/session.go | 4 ++-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index cb57d20..ea6a673 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -1,13 +1,17 @@ package main import ( + "io/fs" "log" + netHttp "net/http" + "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/config" "git.javil.eu/jacob1123/budgeteer/http" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" + "git.javil.eu/jacob1123/budgeteer/web" ) func main() { @@ -21,10 +25,16 @@ func main() { log.Fatalf("Failed connecting to DB: %v", err) } + static, err := fs.Sub(web.Static, "dist") + if err != nil { + panic("couldn't open static files") + } + h := &http.Handler{ Service: q, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, + StaticFS: netHttp.FS(static), } h.Serve() diff --git a/http/account_test.go b/http/account_test.go index df8a430..adbb97b 100644 --- a/http/account_test.go +++ b/http/account_test.go @@ -32,8 +32,8 @@ func TestListTimezonesHandler(t *testing.T) { CredentialsVerifier: &bcrypt.Verifier{}, } - rr := httptest.NewRecorder() - c, engine := gin.CreateTestContext(rr) + recorder := httptest.NewRecorder() + c, engine := gin.CreateTestContext(recorder) h.LoadRoutes(engine) t.Run("RegisterUser", func(t *testing.T) { @@ -45,12 +45,12 @@ func TestListTimezonesHandler(t *testing.T) { h.registerPost(c) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK) + if recorder.Code != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } var response LoginResponse - err = json.NewDecoder(rr.Body).Decode(&response) + err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) t.Error("Error registering") @@ -62,12 +62,12 @@ func TestListTimezonesHandler(t *testing.T) { t.Run("GetTransactions", func(t *testing.T) { c.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK) + if recorder.Code != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } var response TransactionsResponse - err = json.NewDecoder(rr.Body).Decode(&response) + err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) t.Error("Error retreiving list of transactions.") diff --git a/http/http.go b/http/http.go index ccb53a3..2881030 100644 --- a/http/http.go +++ b/http/http.go @@ -11,7 +11,6 @@ import ( "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/postgres" - "git.javil.eu/jacob1123/budgeteer/web" "github.com/gin-gonic/gin" ) @@ -36,12 +35,6 @@ func (h *Handler) Serve() { // LoadRoutes initializes all the routes func (h *Handler) LoadRoutes(router *gin.Engine) { - static, err := fs.Sub(web.Static, "dist") - if err != nil { - panic("couldn't open static files") - } - h.StaticFS = http.FS(static) - router.Use(enableCachingForStaticFiles()) router.NoRoute(h.ServeStatic) diff --git a/http/session.go b/http/session.go index 9bd0f75..421ea4c 100644 --- a/http/session.go +++ b/http/session.go @@ -135,7 +135,7 @@ func (h *Handler) registerPost(c *gin.Context) { c.AbortWithError(http.StatusInternalServerError, err) } - t, err := h.TokenVerifier.CreateToken(&user) + token, err := h.TokenVerifier.CreateToken(&user) if err != nil { c.AbortWithError(http.StatusUnauthorized, err) } @@ -147,7 +147,7 @@ func (h *Handler) registerPost(c *gin.Context) { return } - c.JSON(http.StatusOK, LoginResponse{t, user, budgets}) + c.JSON(http.StatusOK, LoginResponse{token, user, budgets}) } func (h *Handler) UpdateLastLogin(userID uuid.UUID) { -- 2.47.2 From 7b20bc9822d72207dcdab036b74f8e341704cb00 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:38:30 +0000 Subject: [PATCH 12/57] Disable deprecated linters --- .golangci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 55cd4a5..1f344aa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,10 @@ linters: enable-all: true + disable: + - golint + - scopelint + - maligned + - interfacer linters-settings: errcheck: exclude-functions: -- 2.47.2 From bb4548c50d962c86e64f0c0550a31a7aa7a23883 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:41:12 +0000 Subject: [PATCH 13/57] Improve error messages --- postgres/ynab-import.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index a9b8891..6d28488 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -67,7 +67,7 @@ func (ynab *YNABImport) ImportAssignments(r io.Reader) error { csvData, err := csv.ReadAll() if err != nil { - return fmt.Errorf("could not read from tsv: %w", err) + return fmt.Errorf("read from tsv: %w", err) } count := 0 @@ -76,19 +76,19 @@ func (ynab *YNABImport) ImportAssignments(r io.Reader) error { dateString := record[0] date, err := time.Parse("Jan 2006", dateString) if err != nil { - return fmt.Errorf("could not parse date %s: %w", dateString, err) + return fmt.Errorf("parse date %s: %w", dateString, err) } categoryGroup, categoryName := record[2], record[3] //also in 1 joined by : category, err := ynab.GetCategory(categoryGroup, categoryName) if err != nil { - return fmt.Errorf("could not get category %s/%s: %w", categoryGroup, categoryName, err) + return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) } amountString := record[4] amount, err := GetAmount(amountString, "0,00€") if err != nil { - return fmt.Errorf("could not parse amount %s: %w", amountString, err) + return fmt.Errorf("parse amount %s: %w", amountString, err) } if amount.Int.Int64() == 0 { @@ -102,7 +102,7 @@ func (ynab *YNABImport) ImportAssignments(r io.Reader) error { } _, err = ynab.queries.CreateAssignment(ynab.Context, assignment) if err != nil { - return fmt.Errorf("could not save assignment %v: %w", assignment, err) + return fmt.Errorf("save assignment %v: %w", assignment, err) } count++ @@ -129,7 +129,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { csvData, err := csv.ReadAll() if err != nil { - return fmt.Errorf("could not read from tsv: %w", err) + return fmt.Errorf("read from tsv: %w", err) } var openTransfers []Transfer @@ -139,7 +139,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { accountName := record[0] account, err := ynab.GetAccount(accountName) if err != nil { - return fmt.Errorf("could not get account %s: %w", accountName, err) + return fmt.Errorf("get account %s: %w", accountName, err) } //flag := record[1] @@ -147,13 +147,13 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { dateString := record[2] date, err := time.Parse("02.01.2006", dateString) if err != nil { - return fmt.Errorf("could not parse date %s: %w", dateString, err) + return fmt.Errorf("parse date %s: %w", dateString, err) } categoryGroup, categoryName := record[5], record[6] //also in 4 joined by : category, err := ynab.GetCategory(categoryGroup, categoryName) if err != nil { - return fmt.Errorf("could not get category %s/%s: %w", categoryGroup, categoryName, err) + return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) } memo := record[7] @@ -162,7 +162,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { inflow := record[9] amount, err := GetAmount(inflow, outflow) if err != nil { - return fmt.Errorf("could not parse amount from (%s/%s): %w", inflow, outflow, err) + return fmt.Errorf("parse amount from (%s/%s): %w", inflow, outflow, err) } statusEnum := TransactionStatusUncleared @@ -190,7 +190,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { transferToAccountName := payeeName[11:] transferToAccount, err := ynab.GetAccount(transferToAccountName) if err != nil { - return fmt.Errorf("Could not get transfer account %s: %w", transferToAccountName, err) + return fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) } transfer := Transfer{ @@ -223,11 +223,11 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { _, err = ynab.queries.CreateTransaction(ynab.Context, transfer.CreateTransactionParams) if err != nil { - return fmt.Errorf("could not save transaction %v: %w", transfer.CreateTransactionParams, err) + return fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) } _, err = ynab.queries.CreateTransaction(ynab.Context, openTransfer.CreateTransactionParams) if err != nil { - return fmt.Errorf("could not save transaction %v: %w", openTransfer.CreateTransactionParams, err) + return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } break } @@ -238,13 +238,13 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { } else { payeeID, err := ynab.GetPayee(payeeName) if err != nil { - return fmt.Errorf("could not get payee %s: %w", payeeName, err) + return fmt.Errorf("get payee %s: %w", payeeName, err) } transaction.PayeeID = payeeID _, err = ynab.queries.CreateTransaction(ynab.Context, transaction) if err != nil { - return fmt.Errorf("could not save transaction %v: %w", transaction, err) + return fmt.Errorf("save transaction %v: %w", transaction, err) } } @@ -255,7 +255,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { fmt.Printf("Saving unmatched transfer from %s to %s on %s over %f as regular transaction\n", openTransfer.FromAccount, openTransfer.ToAccount, openTransfer.Date, openTransfer.Amount.GetFloat64()) _, err = ynab.queries.CreateTransaction(ynab.Context, openTransfer.CreateTransactionParams) if err != nil { - return fmt.Errorf("could not save transaction %v: %w", openTransfer.CreateTransactionParams, err) + return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } } @@ -280,7 +280,7 @@ func GetAmount(inflow string, outflow string) (Numeric, error) { num := Numeric{} err := num.Set(inflow) if err != nil { - return num, fmt.Errorf("Could not parse inflow %s: %w", inflow, err) + return num, fmt.Errorf("parse inflow %s: %w", inflow, err) } // if inflow is zero, use outflow @@ -290,7 +290,7 @@ func GetAmount(inflow string, outflow string) (Numeric, error) { err = num.Set("-" + outflow) if err != nil { - return num, fmt.Errorf("Could not parse outflow %s: %w", inflow, err) + return num, fmt.Errorf("parse outflow %s: %w", inflow, err) } return num, nil } -- 2.47.2 From 737d5fb101ed262b053f4129e41ebb0ffa8bbd1a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:59:41 +0000 Subject: [PATCH 14/57] Fix wrong executable name --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index fe6b9e5..aad931e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -60,7 +60,7 @@ tasks: cmds: - go vet - go fmt - - golang-ci run + - golangci-lint run - task: build ci: -- 2.47.2 From 72b5bdde4f6f5f2d81e671e07e6523df42b808ef Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 15 Feb 2022 12:59:53 +0000 Subject: [PATCH 15/57] Minor fixes --- cmd/budgeteer/main.go | 4 ++-- http/session.go | 4 ++-- postgres/numeric.go | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index ea6a673..eb40d98 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -30,12 +30,12 @@ func main() { panic("couldn't open static files") } - h := &http.Handler{ + handler := &http.Handler{ Service: q, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, StaticFS: netHttp.FS(static), } - h.Serve() + handler.Serve() } diff --git a/http/session.go b/http/session.go index 421ea4c..0805ab8 100644 --- a/http/session.go +++ b/http/session.go @@ -73,7 +73,7 @@ func (h *Handler) loginPost(c *gin.Context) { return } - t, err := h.TokenVerifier.CreateToken(&user) + token, err := h.TokenVerifier.CreateToken(&user) if err != nil { c.AbortWithError(http.StatusUnauthorized, err) } @@ -85,7 +85,7 @@ func (h *Handler) loginPost(c *gin.Context) { return } - c.JSON(http.StatusOK, LoginResponse{t, user, budgets}) + c.JSON(http.StatusOK, LoginResponse{token, user, budgets}) } type LoginResponse struct { diff --git a/postgres/numeric.go b/postgres/numeric.go index 3d477b6..6f89f81 100644 --- a/postgres/numeric.go +++ b/postgres/numeric.go @@ -72,6 +72,7 @@ func (n Numeric) Sub(o Numeric) Numeric { panic("Cannot subtract with different exponents") } + func (n Numeric) Add(o Numeric) Numeric { left := n right := o -- 2.47.2 From daadfd45bc38f3ec038fff774e06a0213e2d21f5 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:28:04 +0000 Subject: [PATCH 16/57] Rename http package to server --- cmd/budgeteer/main.go | 4 ++-- {http => server}/account.go | 2 +- {http => server}/account_test.go | 21 +++++++++++---------- {http => server}/admin.go | 2 +- {http => server}/autocomplete.go | 2 +- {http => server}/budget.go | 2 +- {http => server}/budgeting.go | 2 +- {http => server}/dashboard.go | 2 +- {http => server}/http.go | 2 +- {http => server}/json-date.go | 2 +- {http => server}/session.go | 2 +- {http => server}/transaction.go | 2 +- {http => server}/ynab-import.go | 6 +++--- 13 files changed, 26 insertions(+), 25 deletions(-) rename {http => server}/account.go (98%) rename {http => server}/account_test.go (72%) rename {http => server}/admin.go (99%) rename {http => server}/autocomplete.go (98%) rename {http => server}/budget.go (97%) rename {http => server}/budgeting.go (99%) rename {http => server}/dashboard.go (96%) rename {http => server}/http.go (99%) rename {http => server}/json-date.go (97%) rename {http => server}/session.go (99%) rename {http => server}/transaction.go (99%) rename {http => server}/ynab-import.go (90%) diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index eb40d98..18ac498 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -8,9 +8,9 @@ import ( "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/config" - "git.javil.eu/jacob1123/budgeteer/http" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" + "git.javil.eu/jacob1123/budgeteer/server" "git.javil.eu/jacob1123/budgeteer/web" ) @@ -30,7 +30,7 @@ func main() { panic("couldn't open static files") } - handler := &http.Handler{ + handler := &server.Handler{ Service: q, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, diff --git a/http/account.go b/server/account.go similarity index 98% rename from http/account.go rename to server/account.go index 03296c2..18cfe41 100644 --- a/http/account.go +++ b/server/account.go @@ -1,4 +1,4 @@ -package http +package server import ( "net/http" diff --git a/http/account_test.go b/server/account_test.go similarity index 72% rename from http/account_test.go rename to server/account_test.go index adbb97b..af1869e 100644 --- a/http/account_test.go +++ b/server/account_test.go @@ -1,4 +1,4 @@ -package http +package server_test import ( "encoding/json" @@ -10,6 +10,7 @@ import ( "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" + "git.javil.eu/jacob1123/budgeteer/server" "github.com/gin-gonic/gin" txdb "github.com/DATA-DOG/go-txdb" @@ -20,36 +21,36 @@ func init() { } func TestListTimezonesHandler(t *testing.T) { - db, err := postgres.Connect("pgtx", "example") + database, err := postgres.Connect("pgtx", "example") if err != nil { t.Errorf("could not connect to db: %s", err) return } - h := Handler{ - Service: db, + h := server.Handler{ + Service: database, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, } recorder := httptest.NewRecorder() - c, engine := gin.CreateTestContext(recorder) + context, engine := gin.CreateTestContext(recorder) h.LoadRoutes(engine) t.Run("RegisterUser", func(t *testing.T) { - c.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) + context.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) if err != nil { t.Errorf("error creating request: %s", err) return } - h.registerPost(c) + h.registerPost(context) if recorder.Code != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } - var response LoginResponse + var response server.LoginResponse err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) @@ -61,12 +62,12 @@ func TestListTimezonesHandler(t *testing.T) { }) t.Run("GetTransactions", func(t *testing.T) { - c.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) + context.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) if recorder.Code != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } - var response TransactionsResponse + var response server.TransactionsResponse err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) diff --git a/http/admin.go b/server/admin.go similarity index 99% rename from http/admin.go rename to server/admin.go index c14248a..7d19da3 100644 --- a/http/admin.go +++ b/server/admin.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" diff --git a/http/autocomplete.go b/server/autocomplete.go similarity index 98% rename from http/autocomplete.go rename to server/autocomplete.go index 2a510c1..626b9ee 100644 --- a/http/autocomplete.go +++ b/server/autocomplete.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" diff --git a/http/budget.go b/server/budget.go similarity index 97% rename from http/budget.go rename to server/budget.go index 5b93c40..bd6eb53 100644 --- a/http/budget.go +++ b/server/budget.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" diff --git a/http/budgeting.go b/server/budgeting.go similarity index 99% rename from http/budgeting.go rename to server/budgeting.go index 431c3b7..ec07791 100644 --- a/http/budgeting.go +++ b/server/budgeting.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" diff --git a/http/dashboard.go b/server/dashboard.go similarity index 96% rename from http/dashboard.go rename to server/dashboard.go index f752b10..ec8f08c 100644 --- a/http/dashboard.go +++ b/server/dashboard.go @@ -1,4 +1,4 @@ -package http +package server import ( "net/http" diff --git a/http/http.go b/server/http.go similarity index 99% rename from http/http.go rename to server/http.go index 2881030..4437946 100644 --- a/http/http.go +++ b/server/http.go @@ -1,4 +1,4 @@ -package http +package server import ( "errors" diff --git a/http/json-date.go b/server/json-date.go similarity index 97% rename from http/json-date.go rename to server/json-date.go index 36dd85a..17ba6a2 100644 --- a/http/json-date.go +++ b/server/json-date.go @@ -1,4 +1,4 @@ -package http +package server import ( "encoding/json" diff --git a/http/session.go b/server/session.go similarity index 99% rename from http/session.go rename to server/session.go index 0805ab8..7f56281 100644 --- a/http/session.go +++ b/server/session.go @@ -1,4 +1,4 @@ -package http +package server import ( "context" diff --git a/http/transaction.go b/server/transaction.go similarity index 99% rename from http/transaction.go rename to server/transaction.go index 306fe01..432d5ad 100644 --- a/http/transaction.go +++ b/server/transaction.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" diff --git a/http/ynab-import.go b/server/ynab-import.go similarity index 90% rename from http/ynab-import.go rename to server/ynab-import.go index 948b442..e6cf0f3 100644 --- a/http/ynab-import.go +++ b/server/ynab-import.go @@ -1,4 +1,4 @@ -package http +package server import ( "fmt" @@ -40,7 +40,7 @@ func (h *Handler) importYNAB(c *gin.Context) { return } - err = ynab.ImportTransactions(transactions) + err = ynab.ImportTransactions(c.Request.Context(), transactions) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return @@ -58,7 +58,7 @@ func (h *Handler) importYNAB(c *gin.Context) { return } - err = ynab.ImportAssignments(assignments) + err = ynab.ImportAssignments(c.Request.Context(), assignments) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return -- 2.47.2 From 0f2501dcbd52f729a7fe583ff6817eff8d8fbfd8 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:28:18 +0000 Subject: [PATCH 17/57] Rename parameter to other --- postgres/numeric.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/postgres/numeric.go b/postgres/numeric.go index 6f89f81..bcec717 100644 --- a/postgres/numeric.go +++ b/postgres/numeric.go @@ -54,13 +54,13 @@ func (n Numeric) MatchExp(exp int32) Numeric { }} } -func (n Numeric) Sub(o Numeric) Numeric { +func (n Numeric) Sub(other Numeric) Numeric { left := n - right := o - if n.Exp < o.Exp { - right = o.MatchExp(n.Exp) - } else if n.Exp > o.Exp { - left = n.MatchExp(o.Exp) + right := other + if n.Exp < other.Exp { + right = other.MatchExp(n.Exp) + } else if n.Exp > other.Exp { + left = n.MatchExp(other.Exp) } if left.Exp == right.Exp { @@ -73,13 +73,13 @@ func (n Numeric) Sub(o Numeric) Numeric { panic("Cannot subtract with different exponents") } -func (n Numeric) Add(o Numeric) Numeric { +func (n Numeric) Add(other Numeric) Numeric { left := n - right := o - if n.Exp < o.Exp { - right = o.MatchExp(n.Exp) - } else if n.Exp > o.Exp { - left = n.MatchExp(o.Exp) + right := other + if n.Exp < other.Exp { + right = other.MatchExp(n.Exp) + } else if n.Exp > other.Exp { + left = n.MatchExp(other.Exp) } if left.Exp == right.Exp { -- 2.47.2 From 649f937254f2a9c589f1e609112e9c4ee6d29a7a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:28:28 +0000 Subject: [PATCH 18/57] Remove context from YNABImport struct --- postgres/ynab-import.go | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 6d28488..1438659 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -13,7 +13,6 @@ import ( ) type YNABImport struct { - Context context.Context accounts []Account payees []Payee categories []GetCategoriesRow @@ -44,7 +43,6 @@ func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YN } return &YNABImport{ - Context: context, accounts: accounts, payees: payees, categories: categories, @@ -60,7 +58,7 @@ func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YN //"Apr 2019" "Income: Next Month" "Income" "Next Month" 0,00€ 0,00€ 0,00€ // // Activity and Available are not imported, since they are determined by the transactions and historic assignments -func (ynab *YNABImport) ImportAssignments(r io.Reader) error { +func (ynab *YNABImport) ImportAssignments(context context.Context, r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' csv.LazyQuotes = true @@ -80,7 +78,7 @@ func (ynab *YNABImport) ImportAssignments(r io.Reader) error { } categoryGroup, categoryName := record[2], record[3] //also in 1 joined by : - category, err := ynab.GetCategory(categoryGroup, categoryName) + category, err := ynab.GetCategory(context, categoryGroup, categoryName) if err != nil { return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) } @@ -100,7 +98,7 @@ func (ynab *YNABImport) ImportAssignments(r io.Reader) error { CategoryID: category.UUID, Amount: amount, } - _, err = ynab.queries.CreateAssignment(ynab.Context, assignment) + _, err = ynab.queries.CreateAssignment(context, assignment) if err != nil { return fmt.Errorf("save assignment %v: %w", assignment, err) } @@ -122,7 +120,7 @@ type Transfer struct { // ImportTransactions expects a TSV-file as exported by YNAB in the following format: -func (ynab *YNABImport) ImportTransactions(r io.Reader) error { +func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' csv.LazyQuotes = true @@ -137,7 +135,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { count := 0 for _, record := range csvData[1:] { accountName := record[0] - account, err := ynab.GetAccount(accountName) + account, err := ynab.GetAccount(context, accountName) if err != nil { return fmt.Errorf("get account %s: %w", accountName, err) } @@ -151,7 +149,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { } categoryGroup, categoryName := record[5], record[6] //also in 4 joined by : - category, err := ynab.GetCategory(categoryGroup, categoryName) + category, err := ynab.GetCategory(context, categoryGroup, categoryName) if err != nil { return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) } @@ -188,7 +186,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { if strings.HasPrefix(payeeName, "Transfer : ") { // Transaction is a transfer to transferToAccountName := payeeName[11:] - transferToAccount, err := ynab.GetAccount(transferToAccountName) + transferToAccount, err := ynab.GetAccount(context, transferToAccountName) if err != nil { return fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) } @@ -221,11 +219,11 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { transfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} openTransfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} - _, err = ynab.queries.CreateTransaction(ynab.Context, transfer.CreateTransactionParams) + _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) if err != nil { return fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) } - _, err = ynab.queries.CreateTransaction(ynab.Context, openTransfer.CreateTransactionParams) + _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) if err != nil { return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } @@ -236,13 +234,13 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { openTransfers = append(openTransfers, transfer) } } else { - payeeID, err := ynab.GetPayee(payeeName) + payeeID, err := ynab.GetPayee(context, payeeName) if err != nil { return fmt.Errorf("get payee %s: %w", payeeName, err) } transaction.PayeeID = payeeID - _, err = ynab.queries.CreateTransaction(ynab.Context, transaction) + _, err = ynab.queries.CreateTransaction(context, transaction) if err != nil { return fmt.Errorf("save transaction %v: %w", transaction, err) } @@ -253,7 +251,7 @@ func (ynab *YNABImport) ImportTransactions(r io.Reader) error { for _, openTransfer := range openTransfers { fmt.Printf("Saving unmatched transfer from %s to %s on %s over %f as regular transaction\n", openTransfer.FromAccount, openTransfer.ToAccount, openTransfer.Date, openTransfer.Amount.GetFloat64()) - _, err = ynab.queries.CreateTransaction(ynab.Context, openTransfer.CreateTransactionParams) + _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) if err != nil { return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } @@ -295,14 +293,14 @@ func GetAmount(inflow string, outflow string) (Numeric, error) { return num, nil } -func (ynab *YNABImport) GetAccount(name string) (*Account, error) { +func (ynab *YNABImport) GetAccount(context context.Context, name string) (*Account, error) { for _, acc := range ynab.accounts { if acc.Name == name { return &acc, nil } } - account, err := ynab.queries.CreateAccount(ynab.Context, CreateAccountParams{Name: name, BudgetID: ynab.budgetID}) + account, err := ynab.queries.CreateAccount(context, CreateAccountParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { return nil, err } @@ -311,7 +309,7 @@ func (ynab *YNABImport) GetAccount(name string) (*Account, error) { return &account, nil } -func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { +func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.NullUUID, error) { if name == "" { return uuid.NullUUID{}, nil } @@ -322,7 +320,7 @@ func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { } } - payee, err := ynab.queries.CreatePayee(ynab.Context, CreatePayeeParams{Name: name, BudgetID: ynab.budgetID}) + payee, err := ynab.queries.CreatePayee(context, CreatePayeeParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { return uuid.NullUUID{}, err } @@ -331,7 +329,7 @@ func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { return uuid.NullUUID{UUID: payee.ID, Valid: true}, nil } -func (ynab *YNABImport) GetCategory(group string, name string) (uuid.NullUUID, error) { +func (ynab *YNABImport) GetCategory(context context.Context, group string, name string) (uuid.NullUUID, error) { if group == "" || name == "" { return uuid.NullUUID{}, nil } @@ -345,7 +343,7 @@ func (ynab *YNABImport) GetCategory(group string, name string) (uuid.NullUUID, e for _, categoryGroup := range ynab.categoryGroups { if categoryGroup.Name == group { createCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} - category, err := ynab.queries.CreateCategory(ynab.Context, createCategory) + category, err := ynab.queries.CreateCategory(context, createCategory) if err != nil { return uuid.NullUUID{}, err } @@ -361,13 +359,13 @@ func (ynab *YNABImport) GetCategory(group string, name string) (uuid.NullUUID, e } } - categoryGroup, err := ynab.queries.CreateCategoryGroup(ynab.Context, CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID}) + categoryGroup, err := ynab.queries.CreateCategoryGroup(context, CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID}) if err != nil { return uuid.NullUUID{}, err } ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) - category, err := ynab.queries.CreateCategory(ynab.Context, CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) + category, err := ynab.queries.CreateCategory(context, CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) if err != nil { return uuid.NullUUID{}, err } -- 2.47.2 From c5a0f49719fda430ae2bf4bae01839c60c735474 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:38:38 +0000 Subject: [PATCH 19/57] Disable wsl linter Seems to be excessive --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 1f344aa..795806f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,6 +5,7 @@ linters: - scopelint - maligned - interfacer + - wsl linters-settings: errcheck: exclude-functions: -- 2.47.2 From 46b9b82f304f71ebdf9a838da24e06aa6300ca2f Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:38:57 +0000 Subject: [PATCH 20/57] Small refactorings --- postgres/conn.go | 2 +- postgres/ynab-import.go | 146 +++++++++++++++++++++------------------- 2 files changed, 79 insertions(+), 69 deletions(-) diff --git a/postgres/conn.go b/postgres/conn.go index 046e127..6a5c814 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -5,7 +5,7 @@ import ( "embed" "fmt" - _ "github.com/jackc/pgx/v4/stdlib" + _ "github.com/jackc/pgx/v4/stdlib" // needed for pg connection "github.com/pressly/goose/v3" ) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 1438659..0ca9ba7 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -54,8 +54,8 @@ func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YN } // ImportAssignments expects a TSV-file as exported by YNAB in the following format: -//"Month" "Category Group/Category" "Category Group" "Category" "Budgeted" "Activity" "Available" -//"Apr 2019" "Income: Next Month" "Income" "Next Month" 0,00€ 0,00€ 0,00€ +// "Month" "Category Group/Category" "Category Group" "Category" "Budgeted" "Activity" "Available" +// "Apr 2019" "Income: Next Month" "Income" "Next Month" 0,00€ 0,00€ 0,00€ // // Activity and Available are not imported, since they are determined by the transactions and historic assignments func (ynab *YNABImport) ImportAssignments(context context.Context, r io.Reader) error { @@ -70,14 +70,13 @@ func (ynab *YNABImport) ImportAssignments(context context.Context, r io.Reader) count := 0 for _, record := range csvData[1:] { - dateString := record[0] date, err := time.Parse("Jan 2006", dateString) if err != nil { return fmt.Errorf("parse date %s: %w", dateString, err) } - categoryGroup, categoryName := record[2], record[3] //also in 1 joined by : + categoryGroup, categoryName := record[2], record[3] // also in 1 joined by : category, err := ynab.GetCategory(context, categoryGroup, categoryName) if err != nil { return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) @@ -140,7 +139,7 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) return fmt.Errorf("get account %s: %w", accountName, err) } - //flag := record[1] + // flag := record[1] dateString := record[2] date, err := time.Parse("02.01.2006", dateString) @@ -183,85 +182,96 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) } payeeName := record[3] - if strings.HasPrefix(payeeName, "Transfer : ") { - // Transaction is a transfer to - transferToAccountName := payeeName[11:] - transferToAccount, err := ynab.GetAccount(context, transferToAccountName) - if err != nil { - return fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) - } - - transfer := Transfer{ - transaction, - transferToAccount, - accountName, - transferToAccountName, - } - - found := false - for i, openTransfer := range openTransfers { - if openTransfer.TransferToAccount.ID != transfer.AccountID { - continue - } - if openTransfer.AccountID != transfer.TransferToAccount.ID { - continue - } - if openTransfer.Amount.GetFloat64() != -1*transfer.Amount.GetFloat64() { - continue - } - - fmt.Printf("Matched transfers from %s to %s over %f\n", account.Name, transferToAccount.Name, amount.GetFloat64()) - openTransfers[i] = openTransfers[len(openTransfers)-1] - openTransfers = openTransfers[:len(openTransfers)-1] - found = true - - groupID := uuid.New() - transfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} - openTransfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} - - _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) - if err != nil { - return fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) - } - _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) - if err != nil { - return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) - } - break - } - - if !found { - openTransfers = append(openTransfers, transfer) - } - } else { - payeeID, err := ynab.GetPayee(context, payeeName) - if err != nil { - return fmt.Errorf("get payee %s: %w", payeeName, err) - } - transaction.PayeeID = payeeID - - _, err = ynab.queries.CreateTransaction(context, transaction) - if err != nil { - return fmt.Errorf("save transaction %v: %w", transaction, err) - } + // Transaction is a transfer to + var shouldReturn bool + var returnValue error + openTransfers, shouldReturn, returnValue = ynab.ImportTransaction(payeeName, context, transaction, accountName, openTransfers, account, amount) + if shouldReturn { + return returnValue } count++ } for _, openTransfer := range openTransfers { - fmt.Printf("Saving unmatched transfer from %s to %s on %s over %f as regular transaction\n", openTransfer.FromAccount, openTransfer.ToAccount, openTransfer.Date, openTransfer.Amount.GetFloat64()) + fmt.Printf("Saving unmatched transfer from %s to %s on %s over %f as regular transaction\n", + openTransfer.FromAccount, openTransfer.ToAccount, openTransfer.Date, openTransfer.Amount.GetFloat64()) _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) if err != nil { return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } - } + fmt.Printf("Imported %d transactions\n", count) return nil } +func (ynab *YNABImport) ImportTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers []Transfer, account *Account, amount Numeric) ([]Transfer, bool, error) { + if strings.HasPrefix(payeeName, "Transfer : ") { + transferToAccountName := payeeName[11:] + transferToAccount, err := ynab.GetAccount(context, transferToAccountName) + if err != nil { + return nil, true, fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) + } + + transfer := Transfer{ + transaction, + transferToAccount, + accountName, + transferToAccountName, + } + + found := false + for i, openTransfer := range openTransfers { + if openTransfer.TransferToAccount.ID != transfer.AccountID { + continue + } + if openTransfer.AccountID != transfer.TransferToAccount.ID { + continue + } + if openTransfer.Amount.GetFloat64() != -1*transfer.Amount.GetFloat64() { + continue + } + + fmt.Printf("Matched transfers from %s to %s over %f\n", account.Name, transferToAccount.Name, amount.GetFloat64()) + openTransfers[i] = openTransfers[len(openTransfers)-1] + openTransfers = openTransfers[:len(openTransfers)-1] + found = true + + groupID := uuid.New() + transfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} + openTransfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} + + _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) + if err != nil { + return nil, true, fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) + } + _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) + if err != nil { + return nil, true, fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) + } + break + } + + if !found { + openTransfers = append(openTransfers, transfer) + } + } else { + payeeID, err := ynab.GetPayee(context, payeeName) + if err != nil { + return nil, true, fmt.Errorf("get payee %s: %w", payeeName, err) + } + transaction.PayeeID = payeeID + + _, err = ynab.queries.CreateTransaction(context, transaction) + if err != nil { + return nil, true, fmt.Errorf("save transaction %v: %w", transaction, err) + } + } + return openTransfers, false, nil +} + func trimLastChar(s string) string { r, size := utf8.DecodeLastRuneInString(s) if r == utf8.RuneError && (size == 0 || size == 1) { -- 2.47.2 From 052a2628ab2a736835683bab9cd1ce454c0f37ef Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:42:19 +0000 Subject: [PATCH 21/57] Disable forbidigo and nlreturn linters --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 795806f..8708b12 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,6 +6,8 @@ linters: - maligned - interfacer - wsl + - forbidigo + - nlreturn linters-settings: errcheck: exclude-functions: -- 2.47.2 From 1a19d3a197e7f1cd36d52e5e943558964e82a8e7 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:53:13 +0000 Subject: [PATCH 22/57] Enable formatting via gofumpt --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 153b65f..b61973e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "files.exclude": { "**/node_modules": true, "**/vendor": true + }, + "gopls": { + "formatting.gofumpt": true, } } \ No newline at end of file -- 2.47.2 From 02ba80a5553a366a4c18091c3f84a3c12957b679 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 21:53:30 +0000 Subject: [PATCH 23/57] Some linting fixes --- bcrypt/verifier.go | 10 +++++----- cmd/budgeteer/main.go | 5 ++--- jwt/login.go | 3 +-- postgres/conn.go | 3 ++- postgres/ynab-import.go | 3 +-- server/account_test.go | 12 ++++++------ 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/bcrypt/verifier.go b/bcrypt/verifier.go index a7d18a6..a719190 100644 --- a/bcrypt/verifier.go +++ b/bcrypt/verifier.go @@ -6,17 +6,17 @@ import ( "golang.org/x/crypto/bcrypt" ) -// Verifier verifys passwords using Bcrypt +// Verifier verifys passwords using Bcrypt. type Verifier struct { cost int } -// Verify verifys a Password -func (bv *Verifier) Verify(password string, hashOnDb string) error { - return bcrypt.CompareHashAndPassword([]byte(hashOnDb), []byte(password)) +// Verify verifys a Password. +func (bv *Verifier) Verify(password string, hashOnDB string) error { + return bcrypt.CompareHashAndPassword([]byte(hashOnDB), []byte(password)) } -// Hash calculates a hash to be stored on the database +// Hash calculates a hash to be stored on the database. func (bv *Verifier) Hash(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bv.cost) if err != nil { diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index 18ac498..9564855 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -3,8 +3,7 @@ package main import ( "io/fs" "log" - - netHttp "net/http" + "net/http" "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/config" @@ -34,7 +33,7 @@ func main() { Service: q, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, - StaticFS: netHttp.FS(static), + StaticFS: http.FS(static), } handler.Serve() diff --git a/jwt/login.go b/jwt/login.go index 4bc5f04..dedf8ef 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -11,8 +11,7 @@ import ( ) // TokenVerifier verifies Tokens -type TokenVerifier struct { -} +type TokenVerifier struct{} // Token contains everything to authenticate a user type Token struct { diff --git a/postgres/conn.go b/postgres/conn.go index 6a5c814..86f6de2 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -5,8 +5,9 @@ import ( "embed" "fmt" - _ "github.com/jackc/pgx/v4/stdlib" // needed for pg connection "github.com/pressly/goose/v3" + + _ "github.com/jackc/pgx/v4/stdlib" // needed for pg connection ) //go:embed schema/*.sql diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 0ca9ba7..c2b0ba4 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -50,7 +50,6 @@ func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YN queries: q, budgetID: budgetID, }, nil - } // ImportAssignments expects a TSV-file as exported by YNAB in the following format: @@ -147,7 +146,7 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) return fmt.Errorf("parse date %s: %w", dateString, err) } - categoryGroup, categoryName := record[5], record[6] //also in 4 joined by : + categoryGroup, categoryName := record[5], record[6] // also in 4 joined by : category, err := ynab.GetCategory(context, categoryGroup, categoryName) if err != nil { return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) diff --git a/server/account_test.go b/server/account_test.go index af1869e..da68522 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -1,4 +1,4 @@ -package server_test +package server import ( "encoding/json" @@ -7,11 +7,11 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin" + "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" - "git.javil.eu/jacob1123/budgeteer/server" - "github.com/gin-gonic/gin" txdb "github.com/DATA-DOG/go-txdb" ) @@ -27,7 +27,7 @@ func TestListTimezonesHandler(t *testing.T) { return } - h := server.Handler{ + h := Handler{ Service: database, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, @@ -50,7 +50,7 @@ func TestListTimezonesHandler(t *testing.T) { t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } - var response server.LoginResponse + var response LoginResponse err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) @@ -67,7 +67,7 @@ func TestListTimezonesHandler(t *testing.T) { t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) } - var response server.TransactionsResponse + var response TransactionsResponse err = json.NewDecoder(recorder.Body).Decode(&response) if err != nil { t.Error(err.Error()) -- 2.47.2 From 558fddc139a768593ebafdc68c03d1bb56cecfe4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 22:04:27 +0000 Subject: [PATCH 24/57] Disable linter testpackage --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 8708b12..f867e9e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,6 +8,7 @@ linters: - wsl - forbidigo - nlreturn + - testpackage linters-settings: errcheck: exclude-functions: -- 2.47.2 From b0175542f148fa457394ba26c3582cea391b0904 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 22:04:35 +0000 Subject: [PATCH 25/57] Return own error --- bcrypt/verifier.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bcrypt/verifier.go b/bcrypt/verifier.go index a719190..f52a3b8 100644 --- a/bcrypt/verifier.go +++ b/bcrypt/verifier.go @@ -13,7 +13,12 @@ type Verifier struct { // Verify verifys a Password. func (bv *Verifier) Verify(password string, hashOnDB string) error { - return bcrypt.CompareHashAndPassword([]byte(hashOnDB), []byte(password)) + err := bcrypt.CompareHashAndPassword([]byte(hashOnDB), []byte(password)) + if err != nil { + return fmt.Errorf("verify password: %w", err) + } + + return nil } // Hash calculates a hash to be stored on the database. -- 2.47.2 From 1d2ae0e394c5e0611b75c11e7d78b2ec2ff5b7ce Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 22:04:51 +0000 Subject: [PATCH 26/57] Renames and dots in comments --- cmd/budgeteer/main.go | 4 ++-- server/http.go | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index 9564855..b78e28f 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -19,7 +19,7 @@ func main() { log.Fatalf("Could not load config: %v", err) } - q, err := postgres.Connect("pgx", cfg.DatabaseConnection) + queries, err := postgres.Connect("pgx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) } @@ -30,7 +30,7 @@ func main() { } handler := &server.Handler{ - Service: q, + Service: queries, TokenVerifier: &jwt.TokenVerifier{}, CredentialsVerifier: &bcrypt.Verifier{}, StaticFS: http.FS(static), diff --git a/server/http.go b/server/http.go index 4437946..b28a7da 100644 --- a/server/http.go +++ b/server/http.go @@ -15,7 +15,7 @@ import ( "github.com/gin-gonic/gin" ) -// Handler handles incoming requests +// Handler handles incoming requests. type Handler struct { Service *postgres.Database TokenVerifier budgeteer.TokenVerifier @@ -23,7 +23,7 @@ type Handler struct { StaticFS http.FileSystem } -// Serve starts the http server +// Serve starts the http server. func (h *Handler) Serve() { router := gin.Default() h.LoadRoutes(router) @@ -33,7 +33,7 @@ func (h *Handler) Serve() { } } -// LoadRoutes initializes all the routes +// LoadRoutes initializes all the routes. func (h *Handler) LoadRoutes(router *gin.Engine) { router.Use(enableCachingForStaticFiles()) router.NoRoute(h.ServeStatic) @@ -73,6 +73,7 @@ func (h *Handler) LoadRoutes(router *gin.Engine) { transaction.POST("/new", h.newTransaction) transaction.POST("/:transactionid", h.newTransaction) } + func (h *Handler) ServeStatic(c *gin.Context) { h.ServeStaticFile(c, c.Request.URL.Path) } -- 2.47.2 From f0961ccc3cc1ef5db9a10331998e1d6f37746750 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 22:06:11 +0000 Subject: [PATCH 27/57] Disable linting for prod build while open errors exist but enable for dev instead --- Taskfile.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index aad931e..81f8355 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -52,15 +52,18 @@ tasks: desc: Build budgeteer in dev mode deps: [gomod, sqlc] cmds: + - go vet + - go fmt + - golangci-lint run - task: build build-prod: desc: Build budgeteer in prod mode deps: [gomod, sqlc, frontend] cmds: - - go vet - - go fmt - - golangci-lint run + # TODO - go vet + # TODO - go fmt + # TODO - golangci-lint run - task: build ci: -- 2.47.2 From 77afe700ae7359c95339094cac3585bbfac51bed Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sat, 19 Feb 2022 22:13:55 +0000 Subject: [PATCH 28/57] Fix missing value usage --- web/src/pages/Login.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Login.vue b/web/src/pages/Login.vue index be401cc..000c7a9 100644 --- a/web/src/pages/Login.vue +++ b/web/src/pages/Login.vue @@ -12,7 +12,7 @@ onMounted(() => { function formSubmit(e: MouseEvent) { e.preventDefault(); - useSessionStore().login(login) + useSessionStore().login(login.value) .then(x => { error.value = ""; useRouter().replace("/dashboard"); -- 2.47.2 From c5be03ab6b91c62895bd1fad46e3ca7c1f9f26ae Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 16:51:01 +0000 Subject: [PATCH 29/57] Extract error consts --- jwt/login.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jwt/login.go b/jwt/login.go index dedf8ef..7db743e 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -44,11 +44,17 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) { return t, nil } +var ( + ErrUnexpectedSigningMethod = fmt.Errorf("Unexpected signing method") + ErrInvalidToken = fmt.Errorf("Token is invalid") + ErrTokenExpired = fmt.Errorf("Token has expired") +) + // VerifyToken verifys a given string-token func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + return nil, fmt.Errorf("method '%v': %w", token.Header["alg"], ErrUnexpectedSigningMethod) } return []byte(secret), nil }) @@ -72,16 +78,16 @@ func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error func verifyToken(token *jwt.Token) (jwt.MapClaims, error) { if !token.Valid { - return nil, fmt.Errorf("Token is not valid") + return nil, ErrInvalidToken } claims, ok := token.Claims.(jwt.MapClaims) if !ok { - return nil, fmt.Errorf("Claims are not of Type MapClaims") + return nil, ErrInvalidToken } if !claims.VerifyExpiresAt(time.Now().Unix(), true) { - return nil, fmt.Errorf("Claims have expired") + return nil, ErrTokenExpired } return claims, nil -- 2.47.2 From 9b92e2b5511ea9aad43094bef9998635adbcc8c4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 16:51:17 +0000 Subject: [PATCH 30/57] Run tests in parallel --- server/account_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/account_test.go b/server/account_test.go index da68522..87a96b9 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -21,6 +21,7 @@ func init() { } func TestListTimezonesHandler(t *testing.T) { + t.Parallel() database, err := postgres.Connect("pgtx", "example") if err != nil { t.Errorf("could not connect to db: %s", err) @@ -38,6 +39,7 @@ func TestListTimezonesHandler(t *testing.T) { h.LoadRoutes(engine) t.Run("RegisterUser", func(t *testing.T) { + t.Parallel() context.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) if err != nil { t.Errorf("error creating request: %s", err) @@ -62,6 +64,7 @@ func TestListTimezonesHandler(t *testing.T) { }) t.Run("GetTransactions", func(t *testing.T) { + t.Parallel() context.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) if recorder.Code != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) -- 2.47.2 From 1a1971246df287459c9ac8120e714b97c9aa03ba Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 16:51:28 +0000 Subject: [PATCH 31/57] Use short syntax for errcheck where possible --- server/budget.go | 3 +-- server/http.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/budget.go b/server/budget.go index bd6eb53..e330659 100644 --- a/server/budget.go +++ b/server/budget.go @@ -14,8 +14,7 @@ type newBudgetInformation struct { func (h *Handler) newBudget(c *gin.Context) { var newBudget newBudgetInformation - err := c.BindJSON(&newBudget) - if err != nil { + if err := c.BindJSON(&newBudget); err != nil { c.AbortWithError(http.StatusNotAcceptable, err) return } diff --git a/server/http.go b/server/http.go index b28a7da..6495d56 100644 --- a/server/http.go +++ b/server/http.go @@ -27,8 +27,8 @@ type Handler struct { func (h *Handler) Serve() { router := gin.Default() h.LoadRoutes(router) - err := router.Run(":1323") - if err != nil { + + if err := router.Run(":1323"); err != nil { panic(err) } } -- 2.47.2 From 8035403416f98115bf743bcd537d77c657917e71 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 16:51:48 +0000 Subject: [PATCH 32/57] Use camelCase for json identifiers --- server/transaction.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/transaction.go b/server/transaction.go index 432d5ad..c11afdb 100644 --- a/server/transaction.go +++ b/server/transaction.go @@ -22,8 +22,8 @@ type NewTransactionPayload struct { } `json:"category"` Memo string `json:"memo"` Amount string `json:"amount"` - BudgetID uuid.UUID `json:"budget_id"` - AccountID uuid.UUID `json:"account_id"` + BudgetID uuid.UUID `json:"budgetId"` + AccountID uuid.UUID `json:"accountId"` State string `json:"state"` } @@ -49,14 +49,14 @@ func (h *Handler) newTransaction(c *gin.Context) { return }*/ - //if !transactionUUID.Valid { + // if !transactionUUID.Valid { new := postgres.CreateTransactionParams{ Memo: payload.Memo, Date: time.Time(payload.Date), Amount: amount, AccountID: payload.AccountID, - PayeeID: payload.Payee.ID, //TODO handle new payee - CategoryID: payload.Category.ID, //TODO handle new category + PayeeID: payload.Payee.ID, // TODO handle new payee + CategoryID: payload.Category.ID, // TODO handle new category Status: postgres.TransactionStatus(payload.State), } _, err = h.Service.CreateTransaction(c.Request.Context(), new) -- 2.47.2 From 787165b7f11efd65bd3f49994e10e6348cf79daa Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:41:58 +0000 Subject: [PATCH 33/57] Disable ifshort --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index f867e9e..12b7594 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,6 +9,7 @@ linters: - forbidigo - nlreturn - testpackage + - ifshort linters-settings: errcheck: exclude-functions: -- 2.47.2 From b52ed21d1d51214f81465250554ecf3c1c1a2c1a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:42:07 +0000 Subject: [PATCH 34/57] Remove cost and just use defaultCost --- bcrypt/verifier.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bcrypt/verifier.go b/bcrypt/verifier.go index f52a3b8..22dc5bc 100644 --- a/bcrypt/verifier.go +++ b/bcrypt/verifier.go @@ -7,9 +7,7 @@ import ( ) // Verifier verifys passwords using Bcrypt. -type Verifier struct { - cost int -} +type Verifier struct{} // Verify verifys a Password. func (bv *Verifier) Verify(password string, hashOnDB string) error { @@ -23,7 +21,7 @@ func (bv *Verifier) Verify(password string, hashOnDB string) error { // Hash calculates a hash to be stored on the database. func (bv *Verifier) Hash(password string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bv.cost) + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("hash password: %w", err) } -- 2.47.2 From 96b514ccf8034ed3155fd5f3f0fe691444e90179 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:42:16 +0000 Subject: [PATCH 35/57] Fix capitalization of errors --- jwt/login.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jwt/login.go b/jwt/login.go index 7db743e..b0d5144 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -45,9 +45,9 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) { } var ( - ErrUnexpectedSigningMethod = fmt.Errorf("Unexpected signing method") - ErrInvalidToken = fmt.Errorf("Token is invalid") - ErrTokenExpired = fmt.Errorf("Token has expired") + ErrUnexpectedSigningMethod = fmt.Errorf("unexpected signing method") + ErrInvalidToken = fmt.Errorf("token is invalid") + ErrTokenExpired = fmt.Errorf("token has expired") ) // VerifyToken verifys a given string-token -- 2.47.2 From 36b2f121832a64ab79207a56a6dfd79d32b3df2f Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:42:27 +0000 Subject: [PATCH 36/57] Renames --- postgres/budgetservice.go | 12 ++++++------ postgres/ynab-import.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/postgres/budgetservice.go b/postgres/budgetservice.go index 4af5c36..bcb4512 100644 --- a/postgres/budgetservice.go +++ b/postgres/budgetservice.go @@ -15,8 +15,8 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U return nil, fmt.Errorf("begin transaction: %w", err) } - q := s.WithTx(tx) - budget, err := q.CreateBudget(context, CreateBudgetParams{ + transaction := s.WithTx(tx) + budget, err := transaction.CreateBudget(context, CreateBudgetParams{ Name: name, IncomeCategoryID: uuid.New(), }) @@ -25,12 +25,12 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U } ub := LinkBudgetToUserParams{UserID: userID, BudgetID: budget.ID} - _, err = q.LinkBudgetToUser(context, ub) + _, err = transaction.LinkBudgetToUser(context, ub) if err != nil { return nil, fmt.Errorf("link budget to user: %w", err) } - group, err := q.CreateCategoryGroup(context, CreateCategoryGroupParams{ + group, err := transaction.CreateCategoryGroup(context, CreateCategoryGroupParams{ Name: "Inflow", BudgetID: budget.ID, }) @@ -38,7 +38,7 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U return nil, fmt.Errorf("create inflow category_group: %w", err) } - cat, err := q.CreateCategory(context, CreateCategoryParams{ + cat, err := transaction.CreateCategory(context, CreateCategoryParams{ Name: "Ready to Assign", CategoryGroupID: group.ID, }) @@ -46,7 +46,7 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U return nil, fmt.Errorf("create ready to assign category: %w", err) } - err = q.SetInflowCategory(context, SetInflowCategoryParams{ + err = transaction.SetInflowCategory(context, SetInflowCategoryParams{ IncomeCategoryID: cat.ID, ID: budget.ID, }) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index c2b0ba4..8892d8f 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -21,23 +21,23 @@ type YNABImport struct { budgetID uuid.UUID } -func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YNABImport, error) { - accounts, err := q.GetAccounts(context, budgetID) +func NewYNABImport(context context.Context, queries *Queries, budgetID uuid.UUID) (*YNABImport, error) { + accounts, err := queries.GetAccounts(context, budgetID) if err != nil { return nil, err } - payees, err := q.GetPayees(context, budgetID) + payees, err := queries.GetPayees(context, budgetID) if err != nil { return nil, err } - categories, err := q.GetCategories(context, budgetID) + categories, err := queries.GetCategories(context, budgetID) if err != nil { return nil, err } - categoryGroups, err := q.GetCategoryGroups(context, budgetID) + categoryGroups, err := queries.GetCategoryGroups(context, budgetID) if err != nil { return nil, err } @@ -47,7 +47,7 @@ func NewYNABImport(context context.Context, q *Queries, budgetID uuid.UUID) (*YN payees: payees, categories: categories, categoryGroups: categoryGroups, - queries: q, + queries: queries, budgetID: budgetID, }, nil } -- 2.47.2 From ca51ac5e27b2c6219d68743abe019029a8da4748 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:42:57 +0000 Subject: [PATCH 37/57] Use AbortWithStatusJSON instead of AbortWithError --- server/budgeting.go | 7 +++---- server/http.go | 4 ++++ server/session.go | 25 +++++++++++++++---------- server/ynab-import.go | 3 +-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index ec07791..48ff479 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -55,7 +55,7 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { budgetID := c.Param("budgetid") budgetUUID, err := uuid.Parse(budgetID) if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("budgetid missing from URL")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"budgetid missing from URL"}) return } @@ -80,7 +80,7 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { firstOfNextMonth := firstOfMonth.AddDate(0, 1, 0) cumultativeBalances, err := h.Service.GetCumultativeBalances(c.Request.Context(), budgetUUID) if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("load balances: %w", err)) + c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorResponse{fmt.Sprintf("error loading balances: %s", err)}) return } @@ -115,14 +115,13 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { AvailableBalance postgres.Numeric }{categoriesWithBalance, availableBalance} c.JSON(http.StatusOK, data) - } func (h *Handler) budgeting(c *gin.Context) { budgetID := c.Param("budgetid") budgetUUID, err := uuid.Parse(budgetID) if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("budgetid missing from URL")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"budgetid missing from URL"}) return } diff --git a/server/http.go b/server/http.go index 6495d56..09e1f09 100644 --- a/server/http.go +++ b/server/http.go @@ -33,6 +33,10 @@ func (h *Handler) Serve() { } } +type ErrorResponse struct { + Message string +} + // LoadRoutes initializes all the routes. func (h *Handler) LoadRoutes(router *gin.Engine) { router.Use(enableCachingForStaticFiles()) diff --git a/server/session.go b/server/session.go index 7f56281..49139c8 100644 --- a/server/session.go +++ b/server/session.go @@ -11,16 +11,21 @@ import ( "github.com/google/uuid" ) -func (h *Handler) verifyLogin(c *gin.Context) (budgeteer.Token, error) { - tokenString := c.GetHeader("Authorization") - if len(tokenString) < 8 { - return nil, fmt.Errorf("no authorization header supplied") +const ( + HeaderName = "Authorization" + Bearer = "Bearer " +) + +func (h *Handler) verifyLogin(c *gin.Context) (budgeteer.Token, *ErrorResponse) { //nolint:ireturn + tokenString := c.GetHeader(HeaderName) + if len(tokenString) <= len(Bearer) { + return nil, &ErrorResponse{"no authorization header supplied"} } tokenString = tokenString[7:] token, err := h.TokenVerifier.VerifyToken(tokenString) if err != nil { - return nil, fmt.Errorf("verify token '%s': %w", tokenString, err) + return nil, &ErrorResponse{fmt.Sprintf("verify token '%s': %s", tokenString, err)} } return token, nil @@ -29,8 +34,8 @@ func (h *Handler) verifyLogin(c *gin.Context) (budgeteer.Token, error) { func (h *Handler) verifyLoginWithForbidden(c *gin.Context) { token, err := h.verifyLogin(c) if err != nil { - //c.Header("WWW-Authenticate", "Bearer") - c.AbortWithError(http.StatusForbidden, err) + // c.Header("WWW-Authenticate", "Bearer") + c.AbortWithStatusJSON(http.StatusForbidden, err) return } @@ -104,18 +109,18 @@ func (h *Handler) registerPost(c *gin.Context) { var register registerInformation err := c.BindJSON(®ister) if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse body: %w", err)) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"error parsing body"}) return } if register.Email == "" || register.Password == "" || register.Name == "" { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("e-mail, password and name are required")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"e-mail, password and name are required"}) return } _, err = h.Service.GetUserByUsername(c.Request.Context(), register.Email) if err == nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("email is already taken")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"email is already taken"}) return } diff --git a/server/ynab-import.go b/server/ynab-import.go index e6cf0f3..349449e 100644 --- a/server/ynab-import.go +++ b/server/ynab-import.go @@ -1,7 +1,6 @@ package server import ( - "fmt" "net/http" "git.javil.eu/jacob1123/budgeteer/postgres" @@ -12,7 +11,7 @@ import ( func (h *Handler) importYNAB(c *gin.Context) { budgetID, succ := c.Params.Get("budgetid") if !succ { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("no budget_id specified")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"no budget_id specified"}) return } -- 2.47.2 From 62085cb69439d36db301dc9097ae1f259fdd0d9e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:46:20 +0000 Subject: [PATCH 38/57] UNSURE explicitly init NullUUIDs --- postgres/ynab-import.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 8892d8f..8e63cce 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -320,7 +320,7 @@ func (ynab *YNABImport) GetAccount(context context.Context, name string) (*Accou func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.NullUUID, error) { if name == "" { - return uuid.NullUUID{}, nil + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, nil } for _, pay := range ynab.payees { @@ -331,7 +331,7 @@ func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.Nul payee, err := ynab.queries.CreatePayee(context, CreatePayeeParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { - return uuid.NullUUID{}, err + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err } ynab.payees = append(ynab.payees, payee) @@ -340,7 +340,7 @@ func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.Nul func (ynab *YNABImport) GetCategory(context context.Context, group string, name string) (uuid.NullUUID, error) { if group == "" || name == "" { - return uuid.NullUUID{}, nil + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, nil } for _, category := range ynab.categories { @@ -354,7 +354,7 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name createCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} category, err := ynab.queries.CreateCategory(context, createCategory) if err != nil { - return uuid.NullUUID{}, err + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err } getCategory := GetCategoriesRow{ @@ -370,13 +370,13 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name categoryGroup, err := ynab.queries.CreateCategoryGroup(context, CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID}) if err != nil { - return uuid.NullUUID{}, err + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err } ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) category, err := ynab.queries.CreateCategory(context, CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) if err != nil { - return uuid.NullUUID{}, err + return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err } getCategory := GetCategoriesRow{ -- 2.47.2 From 1dcd0d2f6d5b4eb84d621b03843c77822ceb8cbd Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:51:10 +0000 Subject: [PATCH 39/57] Reformat config.go --- config/config.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/config/config.go b/config/config.go index 0f17ee5..8c0ede6 100644 --- a/config/config.go +++ b/config/config.go @@ -1,19 +1,19 @@ -package config - -import ( - "os" -) - -// Config contains all needed configurations -type Config struct { - DatabaseConnection string -} - -// LoadConfig from path -func LoadConfig() (*Config, error) { - configuration := Config{ - DatabaseConnection: os.Getenv("BUDGETEER_DB"), - } - - return &configuration, nil -} +package config + +import ( + "os" +) + +// Config contains all needed configurations +type Config struct { + DatabaseConnection string +} + +// LoadConfig from path +func LoadConfig() (*Config, error) { + configuration := Config{ + DatabaseConnection: os.Getenv("BUDGETEER_DB"), + } + + return &configuration, nil +} -- 2.47.2 From e39d1dc6e3612670849fbf92a698f0d69ea09c3b Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:52:01 +0000 Subject: [PATCH 40/57] Disable exhaustivstruct --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 12b7594..bfac16f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,6 +10,7 @@ linters: - nlreturn - testpackage - ifshort + - exhaustivestruct linters-settings: errcheck: exclude-functions: -- 2.47.2 From a0d89ee93a9233fb1ae4f1df4c5237005f60da88 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:52:15 +0000 Subject: [PATCH 41/57] Revert "UNSURE explicitly init NullUUIDs" This reverts commit 62085cb69439d36db301dc9097ae1f259fdd0d9e. --- postgres/ynab-import.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 8e63cce..8892d8f 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -320,7 +320,7 @@ func (ynab *YNABImport) GetAccount(context context.Context, name string) (*Accou func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.NullUUID, error) { if name == "" { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, nil + return uuid.NullUUID{}, nil } for _, pay := range ynab.payees { @@ -331,7 +331,7 @@ func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.Nul payee, err := ynab.queries.CreatePayee(context, CreatePayeeParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err + return uuid.NullUUID{}, err } ynab.payees = append(ynab.payees, payee) @@ -340,7 +340,7 @@ func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.Nul func (ynab *YNABImport) GetCategory(context context.Context, group string, name string) (uuid.NullUUID, error) { if group == "" || name == "" { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, nil + return uuid.NullUUID{}, nil } for _, category := range ynab.categories { @@ -354,7 +354,7 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name createCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} category, err := ynab.queries.CreateCategory(context, createCategory) if err != nil { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err + return uuid.NullUUID{}, err } getCategory := GetCategoriesRow{ @@ -370,13 +370,13 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name categoryGroup, err := ynab.queries.CreateCategoryGroup(context, CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID}) if err != nil { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err + return uuid.NullUUID{}, err } ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) category, err := ynab.queries.CreateCategory(context, CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) if err != nil { - return uuid.NullUUID{UUID: uuid.UUID{}, Valid: false}, err + return uuid.NullUUID{}, err } getCategory := GetCategoriesRow{ -- 2.47.2 From 75a6ce157739fe71ae3c27768bb536eaa358244a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:53:18 +0000 Subject: [PATCH 42/57] Use all gofiles as sources for go build --- Taskfile.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 81f8355..1405f8f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -33,12 +33,7 @@ tasks: sources: - ./go.mod - ./go.sum - - ./cmd/budgeteer/*.go - - ./*.go - - ./config/*.go - - ./http/*.go - - ./jwt/*.go - - ./postgres/*.go + - ./**/*.go - ./web/dist/**/* - ./postgres/schema/* generates: -- 2.47.2 From 91b8cc06b244be0ed82ba7a41d46836d4b62d9c3 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 20:58:48 +0000 Subject: [PATCH 43/57] imports, comments and formatting --- bcrypt/verifier.go | 60 +++++++++++++++++++++++----------------------- config/config.go | 4 ++-- jwt/login.go | 8 +++---- postgres/conn.go | 3 +-- token.go | 4 ++-- 5 files changed, 39 insertions(+), 40 deletions(-) diff --git a/bcrypt/verifier.go b/bcrypt/verifier.go index 22dc5bc..8df284f 100644 --- a/bcrypt/verifier.go +++ b/bcrypt/verifier.go @@ -1,30 +1,30 @@ -package bcrypt - -import ( - "fmt" - - "golang.org/x/crypto/bcrypt" -) - -// Verifier verifys passwords using Bcrypt. -type Verifier struct{} - -// Verify verifys a Password. -func (bv *Verifier) Verify(password string, hashOnDB string) error { - err := bcrypt.CompareHashAndPassword([]byte(hashOnDB), []byte(password)) - if err != nil { - return fmt.Errorf("verify password: %w", err) - } - - return nil -} - -// Hash calculates a hash to be stored on the database. -func (bv *Verifier) Hash(password string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return "", fmt.Errorf("hash password: %w", err) - } - - return string(hash[:]), nil -} +package bcrypt + +import ( + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +// Verifier verifys passwords using Bcrypt. +type Verifier struct{} + +// Verify verifys a Password. +func (bv *Verifier) Verify(password string, hashOnDB string) error { + err := bcrypt.CompareHashAndPassword([]byte(hashOnDB), []byte(password)) + if err != nil { + return fmt.Errorf("verify password: %w", err) + } + + return nil +} + +// Hash calculates a hash to be stored on the database. +func (bv *Verifier) Hash(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", fmt.Errorf("hash password: %w", err) + } + + return string(hash), nil +} diff --git a/config/config.go b/config/config.go index 8c0ede6..7c3e324 100644 --- a/config/config.go +++ b/config/config.go @@ -4,12 +4,12 @@ import ( "os" ) -// Config contains all needed configurations +// Config contains all needed configurations. type Config struct { DatabaseConnection string } -// LoadConfig from path +// LoadConfig from path. func LoadConfig() (*Config, error) { configuration := Config{ DatabaseConnection: os.Getenv("BUDGETEER_DB"), diff --git a/jwt/login.go b/jwt/login.go index b0d5144..34fbf98 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -10,10 +10,10 @@ import ( "github.com/google/uuid" ) -// TokenVerifier verifies Tokens +// TokenVerifier verifies Tokens. type TokenVerifier struct{} -// Token contains everything to authenticate a user +// Token contains everything to authenticate a user. type Token struct { username string name string @@ -26,7 +26,7 @@ const ( secret = "uditapbzuditagscwxuqdflgzpbu´ßiaefnlmzeßtrubiadern" ) -// CreateToken creates a new token from username and name +// CreateToken creates a new token from username and name. func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "usr": user.Email, @@ -50,7 +50,7 @@ var ( ErrTokenExpired = fmt.Errorf("token has expired") ) -// VerifyToken verifys a given string-token +// VerifyToken verifys a given string-token. func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { diff --git a/postgres/conn.go b/postgres/conn.go index 86f6de2..6a5c814 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -5,9 +5,8 @@ import ( "embed" "fmt" - "github.com/pressly/goose/v3" - _ "github.com/jackc/pgx/v4/stdlib" // needed for pg connection + "github.com/pressly/goose/v3" ) //go:embed schema/*.sql diff --git a/token.go b/token.go index 1a9cff3..944a25a 100644 --- a/token.go +++ b/token.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" ) -// Token contains data that authenticates a user +// Token contains data that authenticates a user. type Token interface { GetUsername() string GetName() string @@ -13,7 +13,7 @@ type Token interface { GetID() uuid.UUID } -// TokenVerifier verifies a Token +// TokenVerifier verifies a Token. type TokenVerifier interface { VerifyToken(string) (Token, error) CreateToken(*postgres.User) (string, error) -- 2.47.2 From d815e8c3cd64843ddbcbfe3ac6d4589804b9f934 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:00:30 +0000 Subject: [PATCH 44/57] Ignore forcetypeassert for token map --- jwt/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwt/login.go b/jwt/login.go index 34fbf98..1090cd3 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -67,7 +67,7 @@ func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error return nil, fmt.Errorf("verify jwt: %w", err) } - tkn := &Token{ + tkn := &Token{ //nolint:forcetypeassert username: claims["usr"].(string), name: claims["name"].(string), expiry: claims["exp"].(float64), -- 2.47.2 From 260ac2d4ad0444670e53b363518a8b8d75533ef6 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:03:55 +0000 Subject: [PATCH 45/57] Run gci --- server/account_test.go | 4 +--- server/http.go | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index 87a96b9..d7ee98e 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -7,13 +7,11 @@ import ( "strings" "testing" - "github.com/gin-gonic/gin" - "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" - txdb "github.com/DATA-DOG/go-txdb" + "github.com/gin-gonic/gin" ) func init() { diff --git a/server/http.go b/server/http.go index 09e1f09..bdc8d66 100644 --- a/server/http.go +++ b/server/http.go @@ -11,7 +11,6 @@ import ( "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/postgres" - "github.com/gin-gonic/gin" ) -- 2.47.2 From 545f223a97ca82309a43bec446c84de25cf90d0e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:06:17 +0000 Subject: [PATCH 46/57] Comments --- server/json-date.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/json-date.go b/server/json-date.go index 17ba6a2..869069d 100644 --- a/server/json-date.go +++ b/server/json-date.go @@ -9,7 +9,7 @@ import ( type JSONDate time.Time -// Implement Marshaler and Unmarshaler interface +// UnmarshalJSON parses the JSONDate from a JSON input. func (j *JSONDate) UnmarshalJSON(b []byte) error { s := strings.Trim(string(b), "\"") t, err := time.Parse("2006-01-02", s) @@ -20,6 +20,7 @@ func (j *JSONDate) UnmarshalJSON(b []byte) error { return nil } +// MarshalJSON converts the JSONDate to a JSON in ISO format. func (j JSONDate) MarshalJSON() ([]byte, error) { result, err := json.Marshal(time.Time(j)) if err != nil { @@ -29,7 +30,7 @@ func (j JSONDate) MarshalJSON() ([]byte, error) { return result, nil } -// Maybe a Format function for printing your date +// Format formats the time using the regular time.Time mechanics.. func (j JSONDate) Format(s string) string { t := time.Time(j) return t.Format(s) -- 2.47.2 From c03d16878a21d34215b53a417b26c37f73f519f6 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:19:28 +0000 Subject: [PATCH 47/57] Minimal linting improvements --- jwt/login.go | 2 +- postgres/budgetservice.go | 2 +- postgres/conn.go | 2 +- postgres/ynab-import.go | 11 +++++++---- server/account_test.go | 2 +- server/autocomplete.go | 5 ++--- server/budget.go | 3 +-- server/budgeting.go | 3 ++- server/http.go | 6 +++++- server/transaction.go | 4 ++-- 10 files changed, 23 insertions(+), 17 deletions(-) diff --git a/jwt/login.go b/jwt/login.go index 1090cd3..0494e44 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -51,7 +51,7 @@ var ( ) // VerifyToken verifys a given string-token. -func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { +func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error) { //nolint:ireturn token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("method '%v': %w", token.Header["alg"], ErrUnexpectedSigningMethod) diff --git a/postgres/budgetservice.go b/postgres/budgetservice.go index bcb4512..82372dd 100644 --- a/postgres/budgetservice.go +++ b/postgres/budgetservice.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" ) -// NewBudget creates a budget and adds it to the current user +// NewBudget creates a budget and adds it to the current user. func (s *Database) NewBudget(context context.Context, name string, userID uuid.UUID) (*Budget, error) { tx, err := s.BeginTx(context, &sql.TxOptions{}) if err != nil { diff --git a/postgres/conn.go b/postgres/conn.go index 6a5c814..6c04480 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -17,7 +17,7 @@ type Database struct { *sql.DB } -// Connect to a database +// Connect connects to a database. func Connect(typ string, connString string) (*Database, error) { conn, err := sql.Open(typ, connString) if err != nil { diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 8892d8f..3c80bad 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -56,7 +56,7 @@ func NewYNABImport(context context.Context, queries *Queries, budgetID uuid.UUID // "Month" "Category Group/Category" "Category Group" "Category" "Budgeted" "Activity" "Available" // "Apr 2019" "Income: Next Month" "Income" "Next Month" 0,00€ 0,00€ 0,00€ // -// Activity and Available are not imported, since they are determined by the transactions and historic assignments +// Activity and Available are not imported, since they are determined by the transactions and historic assignments. func (ynab *YNABImport) ImportAssignments(context context.Context, r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' @@ -184,7 +184,8 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) // Transaction is a transfer to var shouldReturn bool var returnValue error - openTransfers, shouldReturn, returnValue = ynab.ImportTransaction(payeeName, context, transaction, accountName, openTransfers, account, amount) + openTransfers, shouldReturn, returnValue = ynab.ImportTransaction( + payeeName, context, transaction, accountName, openTransfers, account, amount) if shouldReturn { return returnValue } @@ -368,13 +369,15 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name } } - categoryGroup, err := ynab.queries.CreateCategoryGroup(context, CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID}) + newGroup := CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID} + categoryGroup, err := ynab.queries.CreateCategoryGroup(context, newGroup) if err != nil { return uuid.NullUUID{}, err } ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) - category, err := ynab.queries.CreateCategory(context, CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) + newCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} + category, err := ynab.queries.CreateCategory(context, newCategory) if err != nil { return uuid.NullUUID{}, err } diff --git a/server/account_test.go b/server/account_test.go index d7ee98e..8ea523a 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin" ) -func init() { +func init() { //nolint:gochecknoinits txdb.Register("pgtx", "pgx", "postgres://budgeteer_test:budgeteer_test@localhost:5432/budgeteer_test") } diff --git a/server/autocomplete.go b/server/autocomplete.go index 626b9ee..46dfb3c 100644 --- a/server/autocomplete.go +++ b/server/autocomplete.go @@ -1,7 +1,6 @@ package server import ( - "fmt" "net/http" "git.javil.eu/jacob1123/budgeteer/postgres" @@ -13,7 +12,7 @@ func (h *Handler) autocompleteCategories(c *gin.Context) { budgetID := c.Param("budgetid") budgetUUID, err := uuid.Parse(budgetID) if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("budgetid missing from URL")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"budgetid missing from URL"}) return } @@ -35,7 +34,7 @@ func (h *Handler) autocompletePayee(c *gin.Context) { budgetID := c.Param("budgetid") budgetUUID, err := uuid.Parse(budgetID) if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("budgetid missing from URL")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"budgetid missing from URL"}) return } diff --git a/server/budget.go b/server/budget.go index e330659..dd66e43 100644 --- a/server/budget.go +++ b/server/budget.go @@ -1,7 +1,6 @@ package server import ( - "fmt" "net/http" "git.javil.eu/jacob1123/budgeteer" @@ -20,7 +19,7 @@ func (h *Handler) newBudget(c *gin.Context) { } if newBudget.Name == "" { - c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("Budget name is needed")) + c.AbortWithStatusJSON(http.StatusBadRequest, ErrorResponse{"budget name is required"}) return } diff --git a/server/budgeting.go b/server/budgeting.go index 48ff479..6addb2e 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -85,7 +85,8 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { } // skip everything in the future - categoriesWithBalance, moneyUsed, err := h.calculateBalances(c, budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) + categoriesWithBalance, moneyUsed, err := h.calculateBalances( + c, budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) if err != nil { return } diff --git a/server/http.go b/server/http.go index bdc8d66..569464b 100644 --- a/server/http.go +++ b/server/http.go @@ -104,7 +104,11 @@ func (h *Handler) ServeStaticFile(c *gin.Context, fullPath string) { return } - http.ServeContent(c.Writer, c.Request, stat.Name(), stat.ModTime(), file.(io.ReadSeeker)) + if file, ok := file.(io.ReadSeeker); ok { + http.ServeContent(c.Writer, c.Request, stat.Name(), stat.ModTime(), file) + } else { + panic("File does not implement ReadSeeker") + } } func enableCachingForStaticFiles() gin.HandlerFunc { diff --git a/server/transaction.go b/server/transaction.go index c11afdb..36e6380 100644 --- a/server/transaction.go +++ b/server/transaction.go @@ -50,7 +50,7 @@ func (h *Handler) newTransaction(c *gin.Context) { }*/ // if !transactionUUID.Valid { - new := postgres.CreateTransactionParams{ + newTransaction := postgres.CreateTransactionParams{ Memo: payload.Memo, Date: time.Time(payload.Date), Amount: amount, @@ -59,7 +59,7 @@ func (h *Handler) newTransaction(c *gin.Context) { CategoryID: payload.Category.ID, // TODO handle new category Status: postgres.TransactionStatus(payload.State), } - _, err = h.Service.CreateTransaction(c.Request.Context(), new) + _, err = h.Service.CreateTransaction(c.Request.Context(), newTransaction) if err != nil { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err)) } -- 2.47.2 From 22ec0433bf559b891886952cae45383a7eb55324 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:33:05 +0000 Subject: [PATCH 48/57] Disable gci --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index bfac16f..f499d00 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,6 +11,7 @@ linters: - testpackage - ifshort - exhaustivestruct + - gci # not working linters-settings: errcheck: exclude-functions: -- 2.47.2 From 2423bdd3eefa5fea3553d76bcff9e649ca5270c2 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:33:18 +0000 Subject: [PATCH 49/57] Improvements --- postgres/ynab-import.go | 128 ++++++++++++++++++++++------------------ server/budget.go | 3 +- server/dashboard.go | 3 +- server/session.go | 14 ++++- 4 files changed, 86 insertions(+), 62 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 3c80bad..82a5025 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -209,69 +209,85 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) func (ynab *YNABImport) ImportTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers []Transfer, account *Account, amount Numeric) ([]Transfer, bool, error) { if strings.HasPrefix(payeeName, "Transfer : ") { - transferToAccountName := payeeName[11:] - transferToAccount, err := ynab.GetAccount(context, transferToAccountName) - if err != nil { - return nil, true, fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) - } - - transfer := Transfer{ - transaction, - transferToAccount, - accountName, - transferToAccountName, - } - - found := false - for i, openTransfer := range openTransfers { - if openTransfer.TransferToAccount.ID != transfer.AccountID { - continue - } - if openTransfer.AccountID != transfer.TransferToAccount.ID { - continue - } - if openTransfer.Amount.GetFloat64() != -1*transfer.Amount.GetFloat64() { - continue - } - - fmt.Printf("Matched transfers from %s to %s over %f\n", account.Name, transferToAccount.Name, amount.GetFloat64()) - openTransfers[i] = openTransfers[len(openTransfers)-1] - openTransfers = openTransfers[:len(openTransfers)-1] - found = true - - groupID := uuid.New() - transfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} - openTransfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} - - _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) - if err != nil { - return nil, true, fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) - } - _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) - if err != nil { - return nil, true, fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) - } - break - } - - if !found { - openTransfers = append(openTransfers, transfer) + openTransfers, shouldReturn, returnValue, returnValue1, returnValue2 := ynab.ImportTransferTransaction(payeeName, context, transaction, accountName, openTransfers, account, amount) + if shouldReturn { + return returnValue, returnValue1, returnValue2 } } else { - payeeID, err := ynab.GetPayee(context, payeeName) - if err != nil { - return nil, true, fmt.Errorf("get payee %s: %w", payeeName, err) - } - transaction.PayeeID = payeeID - - _, err = ynab.queries.CreateTransaction(context, transaction) - if err != nil { - return nil, true, fmt.Errorf("save transaction %v: %w", transaction, err) + shouldReturn, returnValue, returnValue1, returnValue2 := ynab.ImportRegularTransaction(context, payeeName, transaction) + if shouldReturn { + return returnValue, returnValue1, returnValue2 } } return openTransfers, false, nil } +func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, transaction CreateTransactionParams) (bool, []Transfer, bool, error) { + payeeID, err := ynab.GetPayee(context, payeeName) + if err != nil { + return true, nil, true, fmt.Errorf("get payee %s: %w", payeeName, err) + } + transaction.PayeeID = payeeID + + _, err = ynab.queries.CreateTransaction(context, transaction) + if err != nil { + return true, nil, true, fmt.Errorf("save transaction %v: %w", transaction, err) + } + return false, nil, false, nil +} + +func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers []Transfer, account *Account, amount Numeric) ([]Transfer, bool, []Transfer, bool, error) { + transferToAccountName := payeeName[11:] + transferToAccount, err := ynab.GetAccount(context, transferToAccountName) + if err != nil { + return nil, true, nil, true, fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) + } + + transfer := Transfer{ + transaction, + transferToAccount, + accountName, + transferToAccountName, + } + + found := false + for i, openTransfer := range openTransfers { + if openTransfer.TransferToAccount.ID != transfer.AccountID { + continue + } + if openTransfer.AccountID != transfer.TransferToAccount.ID { + continue + } + if openTransfer.Amount.GetFloat64() != -1*transfer.Amount.GetFloat64() { + continue + } + + fmt.Printf("Matched transfers from %s to %s over %f\n", account.Name, transferToAccount.Name, amount.GetFloat64()) + openTransfers[i] = openTransfers[len(openTransfers)-1] + openTransfers = openTransfers[:len(openTransfers)-1] + found = true + + groupID := uuid.New() + transfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} + openTransfer.GroupID = uuid.NullUUID{UUID: groupID, Valid: true} + + _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) + if err != nil { + return nil, true, nil, true, fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) + } + _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) + if err != nil { + return nil, true, nil, true, fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) + } + break + } + + if !found { + openTransfers = append(openTransfers, transfer) + } + return openTransfers, false, nil, false, nil +} + func trimLastChar(s string) string { r, size := utf8.DecodeLastRuneInString(s) if r == utf8.RuneError && (size == 0 || size == 1) { diff --git a/server/budget.go b/server/budget.go index dd66e43..682ac75 100644 --- a/server/budget.go +++ b/server/budget.go @@ -3,7 +3,6 @@ package server import ( "net/http" - "git.javil.eu/jacob1123/budgeteer" "github.com/gin-gonic/gin" ) @@ -23,7 +22,7 @@ func (h *Handler) newBudget(c *gin.Context) { return } - userID := c.MustGet("token").(budgeteer.Token).GetID() + userID := MustGetToken(c).GetID() budget, err := h.Service.NewBudget(c.Request.Context(), newBudget.Name, userID) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) diff --git a/server/dashboard.go b/server/dashboard.go index ec8f08c..64667d8 100644 --- a/server/dashboard.go +++ b/server/dashboard.go @@ -3,13 +3,12 @@ package server import ( "net/http" - "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/postgres" "github.com/gin-gonic/gin" ) func (h *Handler) dashboard(c *gin.Context) { - userID := c.MustGet("token").(budgeteer.Token).GetID() + userID := MustGetToken(c).GetID() budgets, err := h.Service.GetBudgetsForUser(c.Request.Context(), userID) if err != nil { return diff --git a/server/session.go b/server/session.go index 49139c8..67590f5 100644 --- a/server/session.go +++ b/server/session.go @@ -14,8 +14,18 @@ import ( const ( HeaderName = "Authorization" Bearer = "Bearer " + ParamName = "token" ) +func MustGetToken(c *gin.Context) budgeteer.Token { //nolint:ireturn + token := c.MustGet(ParamName) + if token, ok := token.(budgeteer.Token); !ok { + return token + } + + panic("Token is not a valid Token") +} + func (h *Handler) verifyLogin(c *gin.Context) (budgeteer.Token, *ErrorResponse) { //nolint:ireturn tokenString := c.GetHeader(HeaderName) if len(tokenString) <= len(Bearer) { @@ -39,7 +49,7 @@ func (h *Handler) verifyLoginWithForbidden(c *gin.Context) { return } - c.Set("token", token) + c.Set(ParamName, token) c.Next() } @@ -51,7 +61,7 @@ func (h *Handler) verifyLoginWithRedirect(c *gin.Context) { return } - c.Set("token", token) + c.Set(ParamName, token) c.Next() } -- 2.47.2 From 6929c940c470213e07c074456ea4c539a62e9e28 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 21:51:07 +0000 Subject: [PATCH 50/57] Improve ynab import by extracting methods --- postgres/ynab-import.go | 90 ++++++++++++++++------------------------- server/account_test.go | 5 ++- 2 files changed, 38 insertions(+), 57 deletions(-) diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 82a5025..4879026 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -181,13 +181,14 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) } payeeName := record[3] - // Transaction is a transfer to - var shouldReturn bool - var returnValue error - openTransfers, shouldReturn, returnValue = ynab.ImportTransaction( - payeeName, context, transaction, accountName, openTransfers, account, amount) - if shouldReturn { - return returnValue + // Transaction is a transfer + if strings.HasPrefix(payeeName, "Transfer : ") { + err = ynab.ImportTransferTransaction(payeeName, context, transaction, accountName, &openTransfers, account, amount) + } else { + err = ynab.ImportRegularTransaction(context, payeeName, transaction) + } + if err != nil { + return err } count++ @@ -207,40 +208,25 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) return nil } -func (ynab *YNABImport) ImportTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers []Transfer, account *Account, amount Numeric) ([]Transfer, bool, error) { - if strings.HasPrefix(payeeName, "Transfer : ") { - openTransfers, shouldReturn, returnValue, returnValue1, returnValue2 := ynab.ImportTransferTransaction(payeeName, context, transaction, accountName, openTransfers, account, amount) - if shouldReturn { - return returnValue, returnValue1, returnValue2 - } - } else { - shouldReturn, returnValue, returnValue1, returnValue2 := ynab.ImportRegularTransaction(context, payeeName, transaction) - if shouldReturn { - return returnValue, returnValue1, returnValue2 - } - } - return openTransfers, false, nil -} - -func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, transaction CreateTransactionParams) (bool, []Transfer, bool, error) { +func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, transaction CreateTransactionParams) error { payeeID, err := ynab.GetPayee(context, payeeName) if err != nil { - return true, nil, true, fmt.Errorf("get payee %s: %w", payeeName, err) + return fmt.Errorf("get payee %s: %w", payeeName, err) } transaction.PayeeID = payeeID _, err = ynab.queries.CreateTransaction(context, transaction) if err != nil { - return true, nil, true, fmt.Errorf("save transaction %v: %w", transaction, err) + return fmt.Errorf("save transaction %v: %w", transaction, err) } - return false, nil, false, nil + return nil } -func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers []Transfer, account *Account, amount Numeric) ([]Transfer, bool, []Transfer, bool, error) { +func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers *[]Transfer, account *Account, amount Numeric) error { transferToAccountName := payeeName[11:] transferToAccount, err := ynab.GetAccount(context, transferToAccountName) if err != nil { - return nil, true, nil, true, fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) + return fmt.Errorf("get transfer account %s: %w", transferToAccountName, err) } transfer := Transfer{ @@ -251,7 +237,7 @@ func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context cont } found := false - for i, openTransfer := range openTransfers { + for i, openTransfer := range *openTransfers { if openTransfer.TransferToAccount.ID != transfer.AccountID { continue } @@ -263,8 +249,9 @@ func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context cont } fmt.Printf("Matched transfers from %s to %s over %f\n", account.Name, transferToAccount.Name, amount.GetFloat64()) - openTransfers[i] = openTransfers[len(openTransfers)-1] - openTransfers = openTransfers[:len(openTransfers)-1] + transfers := *openTransfers + transfers[i] = transfers[len(transfers)-1] + *openTransfers = transfers[:len(transfers)-1] found = true groupID := uuid.New() @@ -273,19 +260,19 @@ func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context cont _, err = ynab.queries.CreateTransaction(context, transfer.CreateTransactionParams) if err != nil { - return nil, true, nil, true, fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) + return fmt.Errorf("save transaction %v: %w", transfer.CreateTransactionParams, err) } _, err = ynab.queries.CreateTransaction(context, openTransfer.CreateTransactionParams) if err != nil { - return nil, true, nil, true, fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) + return fmt.Errorf("save transaction %v: %w", openTransfer.CreateTransactionParams, err) } break } if !found { - openTransfers = append(openTransfers, transfer) + *openTransfers = append(*openTransfers, transfer) } - return openTransfers, false, nil, false, nil + return nil } func trimLastChar(s string) string { @@ -366,31 +353,22 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name } } - for _, categoryGroup := range ynab.categoryGroups { - if categoryGroup.Name == group { - createCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} - category, err := ynab.queries.CreateCategory(context, createCategory) - if err != nil { - return uuid.NullUUID{}, err - } - - getCategory := GetCategoriesRow{ - ID: category.ID, - CategoryGroupID: category.CategoryGroupID, - Name: category.Name, - Group: categoryGroup.Name, - } - ynab.categories = append(ynab.categories, getCategory) - return uuid.NullUUID{UUID: category.ID, Valid: true}, nil + var categoryGroup *CategoryGroup + for _, existingGroup := range ynab.categoryGroups { + if existingGroup.Name == group { + categoryGroup = &existingGroup } } - newGroup := CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID} - categoryGroup, err := ynab.queries.CreateCategoryGroup(context, newGroup) - if err != nil { - return uuid.NullUUID{}, err + if categoryGroup == nil { + newGroup := CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID} + newCategoryGroup, err := ynab.queries.CreateCategoryGroup(context, newGroup) + if err != nil { + return uuid.NullUUID{}, err + } + ynab.categoryGroups = append(ynab.categoryGroups, newCategoryGroup) + categoryGroup = &newCategoryGroup } - ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) newCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} category, err := ynab.queries.CreateCategory(context, newCategory) diff --git a/server/account_test.go b/server/account_test.go index 8ea523a..cd4efcb 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -38,7 +38,10 @@ func TestListTimezonesHandler(t *testing.T) { t.Run("RegisterUser", func(t *testing.T) { t.Parallel() - context.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) + context.Request, err = http.NewRequest( + http.MethodPost, + "/api/v1/user/register", + strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) if err != nil { t.Errorf("error creating request: %s", err) return -- 2.47.2 From e08a21b7508f2281da5b278242db3d40658379a4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:33:15 +0000 Subject: [PATCH 51/57] Disable linter varnamelen --- .golangci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index f499d00..c8a3043 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,8 @@ linters: - testpackage - ifshort - exhaustivestruct - - gci # not working + - gci # not working, shows errors on freshly formatted file + - varnamelen linters-settings: errcheck: exclude-functions: -- 2.47.2 From 3cb39d978a17e9d2e19d5ea8bbddd8ffe4c2b371 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:33:29 +0000 Subject: [PATCH 52/57] Fix multiple linter errors --- postgres/numeric.go | 2 +- postgres/ynab-import.go | 136 +++++++++++++++++++++++----------------- server/budgeting.go | 93 +++++++++++++++------------ 3 files changed, 132 insertions(+), 99 deletions(-) diff --git a/postgres/numeric.go b/postgres/numeric.go index bcec717..b4e5631 100644 --- a/postgres/numeric.go +++ b/postgres/numeric.go @@ -45,7 +45,7 @@ func (n Numeric) IsZero() bool { func (n Numeric) MatchExp(exp int32) Numeric { diffExp := n.Exp - exp - factor := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(diffExp)), nil) + factor := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(diffExp)), nil) //nolint:gomnd return Numeric{pgtype.Numeric{ Exp: exp, Int: big.NewInt(0).Mul(n.Int, factor), diff --git a/postgres/ynab-import.go b/postgres/ynab-import.go index 4879026..8aa512b 100644 --- a/postgres/ynab-import.go +++ b/postgres/ynab-import.go @@ -116,8 +116,7 @@ type Transfer struct { ToAccount string } -// ImportTransactions expects a TSV-file as exported by YNAB in the following format: - +// ImportTransactions expects a TSV-file as exported by YNAB. func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' @@ -132,60 +131,18 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) count := 0 for _, record := range csvData[1:] { - accountName := record[0] - account, err := ynab.GetAccount(context, accountName) + transaction, err := ynab.GetTransaction(context, record) if err != nil { - return fmt.Errorf("get account %s: %w", accountName, err) - } - - // flag := record[1] - - dateString := record[2] - date, err := time.Parse("02.01.2006", dateString) - if err != nil { - return fmt.Errorf("parse date %s: %w", dateString, err) - } - - categoryGroup, categoryName := record[5], record[6] // also in 4 joined by : - category, err := ynab.GetCategory(context, categoryGroup, categoryName) - if err != nil { - return fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) - } - - memo := record[7] - - outflow := record[8] - inflow := record[9] - amount, err := GetAmount(inflow, outflow) - if err != nil { - return fmt.Errorf("parse amount from (%s/%s): %w", inflow, outflow, err) - } - - statusEnum := TransactionStatusUncleared - status := record[10] - switch status { - case "Cleared": - statusEnum = TransactionStatusCleared - case "Reconciled": - statusEnum = TransactionStatusReconciled - case "Uncleared": - } - - transaction := CreateTransactionParams{ - Date: date, - Memo: memo, - AccountID: account.ID, - CategoryID: category, - Amount: amount, - Status: statusEnum, + return err } payeeName := record[3] // Transaction is a transfer if strings.HasPrefix(payeeName, "Transfer : ") { - err = ynab.ImportTransferTransaction(payeeName, context, transaction, accountName, &openTransfers, account, amount) + err = ynab.ImportTransferTransaction(context, payeeName, transaction.CreateTransactionParams, + &openTransfers, transaction.Account, transaction.Amount) } else { - err = ynab.ImportRegularTransaction(context, payeeName, transaction) + err = ynab.ImportRegularTransaction(context, payeeName, transaction.CreateTransactionParams) } if err != nil { return err @@ -208,7 +165,66 @@ func (ynab *YNABImport) ImportTransactions(context context.Context, r io.Reader) return nil } -func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, transaction CreateTransactionParams) error { +type NewTransaction struct { + CreateTransactionParams + Account *Account +} + +func (ynab *YNABImport) GetTransaction(context context.Context, record []string) (NewTransaction, error) { + accountName := record[0] + account, err := ynab.GetAccount(context, accountName) + if err != nil { + return NewTransaction{}, fmt.Errorf("get account %s: %w", accountName, err) + } + + // flag := record[1] + + dateString := record[2] + date, err := time.Parse("02.01.2006", dateString) + if err != nil { + return NewTransaction{}, fmt.Errorf("parse date %s: %w", dateString, err) + } + + categoryGroup, categoryName := record[5], record[6] // also in 4 joined by : + category, err := ynab.GetCategory(context, categoryGroup, categoryName) + if err != nil { + return NewTransaction{}, fmt.Errorf("get category %s/%s: %w", categoryGroup, categoryName, err) + } + + memo := record[7] + + outflow := record[8] + inflow := record[9] + amount, err := GetAmount(inflow, outflow) + if err != nil { + return NewTransaction{}, fmt.Errorf("parse amount from (%s/%s): %w", inflow, outflow, err) + } + + statusEnum := TransactionStatusUncleared + status := record[10] + switch status { + case "Cleared": + statusEnum = TransactionStatusCleared + case "Reconciled": + statusEnum = TransactionStatusReconciled + case "Uncleared": + } + + return NewTransaction{ + CreateTransactionParams: CreateTransactionParams{ + Date: date, + Memo: memo, + AccountID: account.ID, + CategoryID: category, + Amount: amount, + Status: statusEnum, + }, + Account: account, + }, nil +} + +func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeName string, + transaction CreateTransactionParams) error { payeeID, err := ynab.GetPayee(context, payeeName) if err != nil { return fmt.Errorf("get payee %s: %w", payeeName, err) @@ -222,7 +238,9 @@ func (ynab *YNABImport) ImportRegularTransaction(context context.Context, payeeN return nil } -func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context context.Context, transaction CreateTransactionParams, accountName string, openTransfers *[]Transfer, account *Account, amount Numeric) error { +func (ynab *YNABImport) ImportTransferTransaction(context context.Context, payeeName string, + transaction CreateTransactionParams, openTransfers *[]Transfer, + account *Account, amount Numeric) error { transferToAccountName := payeeName[11:] transferToAccount, err := ynab.GetAccount(context, transferToAccountName) if err != nil { @@ -232,7 +250,7 @@ func (ynab *YNABImport) ImportTransferTransaction(payeeName string, context cont transfer := Transfer{ transaction, transferToAccount, - accountName, + account.Name, transferToAccountName, } @@ -342,7 +360,7 @@ func (ynab *YNABImport) GetPayee(context context.Context, name string) (uuid.Nul return uuid.NullUUID{UUID: payee.ID, Valid: true}, nil } -func (ynab *YNABImport) GetCategory(context context.Context, group string, name string) (uuid.NullUUID, error) { +func (ynab *YNABImport) GetCategory(context context.Context, group string, name string) (uuid.NullUUID, error) { //nolint if group == "" || name == "" { return uuid.NullUUID{}, nil } @@ -353,21 +371,21 @@ func (ynab *YNABImport) GetCategory(context context.Context, group string, name } } - var categoryGroup *CategoryGroup + var categoryGroup CategoryGroup for _, existingGroup := range ynab.categoryGroups { if existingGroup.Name == group { - categoryGroup = &existingGroup + categoryGroup = existingGroup } } - if categoryGroup == nil { + if categoryGroup.Name == "" { newGroup := CreateCategoryGroupParams{Name: group, BudgetID: ynab.budgetID} - newCategoryGroup, err := ynab.queries.CreateCategoryGroup(context, newGroup) + var err error + categoryGroup, err = ynab.queries.CreateCategoryGroup(context, newGroup) if err != nil { return uuid.NullUUID{}, err } - ynab.categoryGroups = append(ynab.categoryGroups, newCategoryGroup) - categoryGroup = &newCategoryGroup + ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) } newCategory := CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} diff --git a/server/budgeting.go b/server/budgeting.go index 6addb2e..da7204a 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -91,6 +91,18 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { return } + availableBalance := h.getAvailableBalance(categories, budget, moneyUsed, cumultativeBalances, firstOfNextMonth) + + data := struct { + Categories []CategoryWithBalance + AvailableBalance postgres.Numeric + }{categoriesWithBalance, availableBalance} + c.JSON(http.StatusOK, data) +} + +func (*Handler) getAvailableBalance(categories []postgres.GetCategoriesRow, budget postgres.Budget, + moneyUsed postgres.Numeric, cumultativeBalances []postgres.GetCumultativeBalancesRow, + firstOfNextMonth time.Time) postgres.Numeric { availableBalance := postgres.NewZeroNumeric() for _, cat := range categories { if cat.ID != budget.IncomeCategoryID { @@ -110,12 +122,7 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { availableBalance = availableBalance.Add(bal.Transactions) } } - - data := struct { - Categories []CategoryWithBalance - AvailableBalance postgres.Numeric - }{categoriesWithBalance, availableBalance} - c.JSON(http.StatusOK, data) + return availableBalance } func (h *Handler) budgeting(c *gin.Context) { @@ -146,7 +153,9 @@ func (h *Handler) budgeting(c *gin.Context) { c.JSON(http.StatusOK, data) } -func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow, cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric, error) { +func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, + firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow, + cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric, error) { categoriesWithBalance := []CategoryWithBalance{} hiddenCategory := CategoryWithBalance{ GetCategoriesRow: &postgres.GetCategoriesRow{ @@ -162,39 +171,9 @@ func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, firs moneyUsed := postgres.NewZeroNumeric() for i := range categories { cat := &categories[i] - categoryWithBalance := CategoryWithBalance{ - GetCategoriesRow: cat, - Available: postgres.NewZeroNumeric(), - AvailableLastMonth: postgres.NewZeroNumeric(), - Activity: postgres.NewZeroNumeric(), - Assigned: postgres.NewZeroNumeric(), - } - for _, bal := range cumultativeBalances { - if bal.CategoryID != cat.ID { - continue - } - - if !bal.Date.Before(firstOfNextMonth) { - continue - } - - moneyUsed = moneyUsed.Sub(bal.Assignments) - categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Assignments) - categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Transactions) - if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) { - moneyUsed = moneyUsed.Add(categoryWithBalance.Available) - categoryWithBalance.Available = postgres.NewZeroNumeric() - } - - if bal.Date.Before(firstOfMonth) { - categoryWithBalance.AvailableLastMonth = categoryWithBalance.Available - } else if bal.Date.Before(firstOfNextMonth) { - categoryWithBalance.Activity = bal.Transactions - categoryWithBalance.Assigned = bal.Assignments - } - } - // do not show hidden categories + categoryWithBalance := h.CalculateCategoryBalances(cat, cumultativeBalances, + firstOfNextMonth, &moneyUsed, firstOfMonth, hiddenCategory, budget) if cat.Group == "Hidden Categories" { hiddenCategory.Available = hiddenCategory.Available.Add(categoryWithBalance.Available) hiddenCategory.AvailableLastMonth = hiddenCategory.AvailableLastMonth.Add(categoryWithBalance.AvailableLastMonth) @@ -214,3 +193,39 @@ func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, firs return categoriesWithBalance, moneyUsed, nil } + +func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time, moneyUsed *postgres.Numeric, firstOfMonth time.Time, hiddenCategory CategoryWithBalance, budget postgres.Budget) CategoryWithBalance { + categoryWithBalance := CategoryWithBalance{ + GetCategoriesRow: cat, + Available: postgres.NewZeroNumeric(), + AvailableLastMonth: postgres.NewZeroNumeric(), + Activity: postgres.NewZeroNumeric(), + Assigned: postgres.NewZeroNumeric(), + } + for _, bal := range cumultativeBalances { + if bal.CategoryID != cat.ID { + continue + } + + if !bal.Date.Before(firstOfNextMonth) { + continue + } + + *moneyUsed = moneyUsed.Sub(bal.Assignments) + categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Assignments) + categoryWithBalance.Available = categoryWithBalance.Available.Add(bal.Transactions) + if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) { + *moneyUsed = moneyUsed.Add(categoryWithBalance.Available) + categoryWithBalance.Available = postgres.NewZeroNumeric() + } + + if bal.Date.Before(firstOfMonth) { + categoryWithBalance.AvailableLastMonth = categoryWithBalance.Available + } else if bal.Date.Before(firstOfNextMonth) { + categoryWithBalance.Activity = bal.Transactions + categoryWithBalance.Assigned = bal.Assignments + } + } + + return categoryWithBalance +} -- 2.47.2 From 19d2ddbd65d7dfa0b380aca360da2938df40e0a0 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:37:34 +0000 Subject: [PATCH 53/57] Minor improvements --- server/account_test.go | 2 +- server/budgeting.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index cd4efcb..742b314 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -18,7 +18,7 @@ func init() { //nolint:gochecknoinits txdb.Register("pgtx", "pgx", "postgres://budgeteer_test:budgeteer_test@localhost:5432/budgeteer_test") } -func TestListTimezonesHandler(t *testing.T) { +func TestRegisterUser(t *testing.T) { //nolint:funlen t.Parallel() database, err := postgres.Connect("pgtx", "example") if err != nil { diff --git a/server/budgeting.go b/server/budgeting.go index da7204a..6f57302 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -85,11 +85,8 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { } // skip everything in the future - categoriesWithBalance, moneyUsed, err := h.calculateBalances( - c, budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) - if err != nil { - return - } + categoriesWithBalance, moneyUsed := h.calculateBalances( + budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) availableBalance := h.getAvailableBalance(categories, budget, moneyUsed, cumultativeBalances, firstOfNextMonth) @@ -153,9 +150,9 @@ func (h *Handler) budgeting(c *gin.Context) { c.JSON(http.StatusOK, data) } -func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, +func (h *Handler) calculateBalances(budget postgres.Budget, firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow, - cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric, error) { + cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, postgres.Numeric) { categoriesWithBalance := []CategoryWithBalance{} hiddenCategory := CategoryWithBalance{ GetCategoriesRow: &postgres.GetCategoriesRow{ @@ -191,10 +188,13 @@ func (h *Handler) calculateBalances(c *gin.Context, budget postgres.Budget, categoriesWithBalance = append(categoriesWithBalance, hiddenCategory) - return categoriesWithBalance, moneyUsed, nil + return categoriesWithBalance, moneyUsed } -func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time, moneyUsed *postgres.Numeric, firstOfMonth time.Time, hiddenCategory CategoryWithBalance, budget postgres.Budget) CategoryWithBalance { +func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, + cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time, + moneyUsed *postgres.Numeric, firstOfMonth time.Time, hiddenCategory CategoryWithBalance, + budget postgres.Budget) CategoryWithBalance { categoryWithBalance := CategoryWithBalance{ GetCategoriesRow: cat, Available: postgres.NewZeroNumeric(), -- 2.47.2 From 1bd38bb367010496946abbb26865be67f90af707 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:40:58 +0000 Subject: [PATCH 54/57] Move comment --- server/budgeting.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/budgeting.go b/server/budgeting.go index 6f57302..0645b2b 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -84,7 +84,6 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { return } - // skip everything in the future categoriesWithBalance, moneyUsed := h.calculateBalances( budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) @@ -207,6 +206,7 @@ func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, continue } + // skip everything in the future if !bal.Date.Before(firstOfNextMonth) { continue } -- 2.47.2 From cfda327a5d290060c9b7cba361af040c963938ad Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:41:56 +0000 Subject: [PATCH 55/57] Enable linting nfor CI --- Taskfile.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 1405f8f..3ddda78 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -56,9 +56,9 @@ tasks: desc: Build budgeteer in prod mode deps: [gomod, sqlc, frontend] cmds: - # TODO - go vet - # TODO - go fmt - # TODO - golangci-lint run + - go vet + - go fmt + - golangci-lint run - task: build ci: -- 2.47.2 From 4688d2d94dbc77203a00c83abf96e2a7a5c29b45 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:46:51 +0000 Subject: [PATCH 56/57] Remove commented code and TODOs --- server/transaction.go | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/server/transaction.go b/server/transaction.go index 36e6380..072f8ad 100644 --- a/server/transaction.go +++ b/server/transaction.go @@ -41,50 +41,20 @@ func (h *Handler) newTransaction(c *gin.Context) { err = amount.Set(payload.Amount) if err != nil { c.AbortWithError(http.StatusBadRequest, fmt.Errorf("amount: %w", err)) + return } - /*transactionUUID, err := getNullUUIDFromParam(c, "transactionid") - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("parse transaction id: %w", err)) - return - }*/ - - // if !transactionUUID.Valid { newTransaction := postgres.CreateTransactionParams{ Memo: payload.Memo, Date: time.Time(payload.Date), Amount: amount, AccountID: payload.AccountID, - PayeeID: payload.Payee.ID, // TODO handle new payee - CategoryID: payload.Category.ID, // TODO handle new category + PayeeID: payload.Payee.ID, + CategoryID: payload.Category.ID, Status: postgres.TransactionStatus(payload.State), } _, err = h.Service.CreateTransaction(c.Request.Context(), newTransaction) if err != nil { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err)) } - // } - /* - _, delete := c.GetPostForm("delete") - if delete { - err = h.Service.DeleteTransaction(c.Request.Context(), transactionUUID.UUID) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("delete transaction: %w", err)) - } - return - } - - update := postgres.UpdateTransactionParams{ - ID: transactionUUID.UUID, - Memo: payload.Memo, - Date: time.Time(payload.Date), - Amount: amount, - AccountID: transactionAccountID, - PayeeID: payload.Payee.ID, //TODO handle new payee - CategoryID: payload.Category.ID, //TODO handle new category - } - err = h.Service.UpdateTransaction(c.Request.Context(), update) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("update transaction: %w", err)) - }*/ } -- 2.47.2 From 578e7d071c115e8ab893fda5af86071edeacd71b Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 20 Feb 2022 22:51:54 +0000 Subject: [PATCH 57/57] Get session secret from env instead of hardcoding --- cmd/budgeteer/main.go | 6 ++++-- config/config.go | 2 ++ docker-compose.dev.yml | 1 + jwt/login.go | 9 +++++---- server/account_test.go | 6 ++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index b78e28f..af132b3 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -30,8 +30,10 @@ func main() { } handler := &server.Handler{ - Service: queries, - TokenVerifier: &jwt.TokenVerifier{}, + Service: queries, + TokenVerifier: &jwt.TokenVerifier{ + Secret: cfg.SessionSecret, + }, CredentialsVerifier: &bcrypt.Verifier{}, StaticFS: http.FS(static), } diff --git a/config/config.go b/config/config.go index 7c3e324..5e98f3e 100644 --- a/config/config.go +++ b/config/config.go @@ -7,12 +7,14 @@ import ( // Config contains all needed configurations. type Config struct { DatabaseConnection string + SessionSecret string } // LoadConfig from path. func LoadConfig() (*Config, error) { configuration := Config{ DatabaseConnection: os.Getenv("BUDGETEER_DB"), + SessionSecret: os.Getenv("BUDGETEER_SESSION_SECRET"), } return &configuration, nil diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f747b77..2d907be 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -17,6 +17,7 @@ services: - ~/.cache:/.cache environment: BUDGETEER_DB: postgres://budgeteer:budgeteer@db:5432/budgeteer + BUDGETEER_SESSION_SECRET: random string for JWT authorization depends_on: - db diff --git a/jwt/login.go b/jwt/login.go index 0494e44..bbb44c8 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -11,7 +11,9 @@ import ( ) // TokenVerifier verifies Tokens. -type TokenVerifier struct{} +type TokenVerifier struct { + Secret string +} // Token contains everything to authenticate a user. type Token struct { @@ -23,7 +25,6 @@ type Token struct { const ( expiration = 72 - secret = "uditapbzuditagscwxuqdflgzpbu´ßiaefnlmzeßtrubiadern" ) // CreateToken creates a new token from username and name. @@ -36,7 +37,7 @@ func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) { }) // Generate encoded token and send it as response. - t, err := token.SignedString([]byte(secret)) + t, err := token.SignedString([]byte(tv.Secret)) if err != nil { return "", fmt.Errorf("create token: %w", err) } @@ -56,7 +57,7 @@ func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("method '%v': %w", token.Header["alg"], ErrUnexpectedSigningMethod) } - return []byte(secret), nil + return []byte(tv.Secret), nil }) if err != nil { return nil, fmt.Errorf("parse jwt: %w", err) diff --git a/server/account_test.go b/server/account_test.go index 742b314..fe79cba 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -27,8 +27,10 @@ func TestRegisterUser(t *testing.T) { //nolint:funlen } h := Handler{ - Service: database, - TokenVerifier: &jwt.TokenVerifier{}, + Service: database, + TokenVerifier: &jwt.TokenVerifier{ + Secret: "this_is_my_demo_secret_for_unit_tests", + }, CredentialsVerifier: &bcrypt.Verifier{}, } -- 2.47.2