diff --git a/http/http.go b/http/http.go index 471543f..22177fa 100644 --- a/http/http.go +++ b/http/http.go @@ -80,12 +80,51 @@ func (h *Handler) Serve() { { transaction.Use(h.verifyLoginWithRedirect) transaction.POST("/new", h.newTransaction) + transaction.POST("/import/ynab", h.importYNAB) } } router.Run(":1323") } +func (h *Handler) importYNAB(c *gin.Context) { + transactionsFile, err := c.FormFile("transactions") + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + transactions, err := transactionsFile.Open() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + budgetID, succ := c.GetPostForm("budget_id") + if !succ { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + budgetUUID, err := uuid.Parse(budgetID) + if !succ { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + ynab, err := NewYNABImport(h.Service.DB, budgetUUID) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + err = ynab.Import(transactions) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } +} + func (h *Handler) verifyLoginWithRedirect(c *gin.Context) { _, err := h.verifyLogin(c) if err != nil { diff --git a/http/ynab-import.go b/http/ynab-import.go index c368826..ccc42d1 100644 --- a/http/ynab-import.go +++ b/http/ynab-import.go @@ -3,11 +3,13 @@ package http import ( "context" "encoding/csv" + "fmt" "io" "time" "git.javil.eu/jacob1123/budgeteer/postgres" "github.com/google/uuid" + "github.com/jackc/pgtype" ) type YNABImport struct { @@ -15,9 +17,10 @@ type YNABImport struct { accounts []postgres.Account payees []postgres.Payee queries *postgres.Queries + budgetID uuid.UUID } -func NewYNABImport(q *postgres.Queries) (*YNABImport, error) { +func NewYNABImport(q *postgres.Queries, budgetID uuid.UUID) (*YNABImport, error) { accounts, err := q.GetAccounts(context.Background(), uuid.UUID{}) if err != nil { return nil, err @@ -33,24 +36,26 @@ func NewYNABImport(q *postgres.Queries) (*YNABImport, error) { accounts: accounts, payees: payees, queries: q, + budgetID: budgetID, }, nil } -func (ynab *YNABImport) Import(q *postgres.Queries, r io.Reader) error { +func (ynab *YNABImport) Import(r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' + csv.LazyQuotes = true csvData, err := csv.ReadAll() if err != nil { - return err + return fmt.Errorf("could not read from tsv: %w", err) } for _, record := range csvData { accountName := record[0] account, err := ynab.GetAccount(accountName) if err != nil { - return err + return fmt.Errorf("could not get account %s: %w", accountName, err) } //flag := record[1] @@ -58,22 +63,45 @@ func (ynab *YNABImport) Import(q *postgres.Queries, r io.Reader) error { dateString := record[2] date, err := time.Parse("02.01.2006", dateString) if err != nil { - return err + return fmt.Errorf("could not parse date %s: %w", dateString, err) } - payee := record[3] - category := record[4] //also in 5 + 6 split by group/category + payeeName := record[3] + payeeID, err := ynab.GetPayee(payeeName) + if err != nil { + return fmt.Errorf("could not get payee %s: %w", payeeName, err) + } + + //category := record[4] //also in 5 + 6 split by group/category memo := record[7] + outflow := record[8] inflow := record[9] - cleared := record[10] + amount := GetAmount(inflow, outflow) - transaction := postgres.Transaction{ + //cleared := record[10] + + transaction := postgres.CreateTransactionParams{ Date: date, Memo: memo, AccountID: account.ID, + PayeeID: payeeID, + Amount: amount, } + ynab.queries.CreateTransaction(ynab.Context, transaction) } + + return nil +} + +func GetAmount(inflow string, outflow string) pgtype.Numeric { + // Remove currency + inflow = inflow[:len(inflow)-1] + outflow = outflow[:len(outflow)-1] + + num := pgtype.Numeric{} + num.Set(inflow) + return num } func (ynab *YNABImport) GetAccount(name string) (*postgres.Account, error) { @@ -92,18 +120,22 @@ func (ynab *YNABImport) GetAccount(name string) (*postgres.Account, error) { return &account, nil } -func (ynab *YNABImport) GetPayee(name string) (*postgres.Payee, error) { +func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { + if name == "" { + return uuid.NullUUID{}, nil + } + for _, pay := range ynab.payees { if pay.Name == name { - return &pay, nil + return uuid.NullUUID{UUID: pay.ID, Valid: true}, nil } } payee, err := ynab.queries.CreatePayee(ynab.Context, name) if err != nil { - return nil, err + return uuid.NullUUID{}, err } ynab.payees = append(ynab.payees, payee) - return &payee, nil + return uuid.NullUUID{UUID: payee.ID, Valid: true}, nil } diff --git a/postgres/accounts.sql.go b/postgres/accounts.sql.go index 305ab12..d865584 100644 --- a/postgres/accounts.sql.go +++ b/postgres/accounts.sql.go @@ -11,13 +11,18 @@ import ( const createAccount = `-- name: CreateAccount :one INSERT INTO accounts -(name) -VALUES ($1) +(name, budget_id) +VALUES ($1, $2) RETURNING id, budget_id, name ` -func (q *Queries) CreateAccount(ctx context.Context, name string) (Account, error) { - row := q.db.QueryRow(ctx, createAccount, name) +type CreateAccountParams struct { + Name string + BudgetID uuid.UUID +} + +func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) { + row := q.db.QueryRow(ctx, createAccount, arg.Name, arg.BudgetID) var i Account err := row.Scan(&i.ID, &i.BudgetID, &i.Name) return i, err diff --git a/postgres/payees.sql.go b/postgres/payees.sql.go index 383a15e..7a62bde 100644 --- a/postgres/payees.sql.go +++ b/postgres/payees.sql.go @@ -11,13 +11,18 @@ import ( const createPayee = `-- name: CreatePayee :one INSERT INTO payees -(name) -VALUES ($1) +(name, budget_id) +VALUES ($1, $2) RETURNING id, budget_id, name ` -func (q *Queries) CreatePayee(ctx context.Context, name string) (Payee, error) { - row := q.db.QueryRow(ctx, createPayee, name) +type CreatePayeeParams struct { + Name string + BudgetID uuid.UUID +} + +func (q *Queries) CreatePayee(ctx context.Context, arg CreatePayeeParams) (Payee, error) { + row := q.db.QueryRow(ctx, createPayee, arg.Name, arg.BudgetID) var i Payee err := row.Scan(&i.ID, &i.BudgetID, &i.Name) return i, err diff --git a/postgres/queries/accounts.sql b/postgres/queries/accounts.sql index 22784a5..663fd3e 100644 --- a/postgres/queries/accounts.sql +++ b/postgres/queries/accounts.sql @@ -1,7 +1,7 @@ -- name: CreateAccount :one INSERT INTO accounts -(name) -VALUES ($1) +(name, budget_id) +VALUES ($1, $2) RETURNING *; -- name: GetAccounts :many diff --git a/postgres/queries/payees.sql b/postgres/queries/payees.sql index 9d25d69..811b1c5 100644 --- a/postgres/queries/payees.sql +++ b/postgres/queries/payees.sql @@ -1,7 +1,7 @@ -- name: CreatePayee :one INSERT INTO payees -(name) -VALUES ($1) +(name, budget_id) +VALUES ($1, $2) RETURNING *; -- name: GetPayees :many