Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Martin/holiday cleanup #59

Merged
merged 3 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Go

on: [push, pull_request]
on: push

jobs:

Expand Down
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
.vscode
*.csv
*.env

# Ignore config files
nordigen*.json
ynabber*.json
*.json
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ cat <<EOT >> ynabber.env
YNAB_BUDGETID=<budget_id>
YNAB_TOKEN=<account token>
YNAB_ACCOUNTMAP={"<IBAN>": "<YNAB account ID>"}
YNAB_IMPORT_ID_V2=2023-01-01 # Not required but will give better results

# Nordigen
NORDIGEN_BANKID=<nordigen bank ID>
Expand Down
20 changes: 0 additions & 20 deletions cmd/ynabber/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,6 @@ func main() {
log.Fatal("YNAB_CLEARED must be one of cleared, uncleared or reconciled")
}

// Handle movement of config options and warn users if better options are
// available.
if cfg.Nordigen.PayeeStrip == nil {
if cfg.PayeeStrip != nil {
log.Printf("Config YNABBER_PAYEE_STRIP is going to be depreciated, please use NORDIGEN_PAYEE_STRIP instead")
cfg.Nordigen.PayeeStrip = cfg.PayeeStrip
}
}
if cfg.YNAB.AccountMap == nil {
if cfg.Nordigen.AccountMap != nil {
log.Printf("Config NORDIGEN_ACCOUNTMAP is going to be depreciated, please use YNAB_ACCOUNTMAP instead")
cfg.YNAB.AccountMap = cfg.Nordigen.AccountMap
}
}
// Defacto means that ImportID v2 is not used
if cfg.YNAB.ImportID.V2 == ynabber.Date(time.Date(9999, time.January, 1, 00, 00, 00, 00, time.UTC)) {
log.Printf("Consider using v2 import IDs for YNAB. See description " +
"for config option YNAB_IMPORT_ID_V2 for more information")
}

if cfg.Debug {
log.Printf("Config: %+v\n", cfg)
}
Expand Down
27 changes: 2 additions & 25 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (accountMap *AccountMap) Decode(value string) error {

// Config is loaded from the environment during execution with cmd/ynabber
type Config struct {
// DataDir is the path for storing files e.g. Nordigen authorization
// DataDir is the path for storing files
DataDir string `envconfig:"YNABBER_DATADIR" default:"."`

// Debug prints more log statements
Expand All @@ -45,23 +45,16 @@ type Config struct {
// Nordigen is supported.
Readers []string `envconfig:"YNABBER_READERS" default:"nordigen"`

// Writers is a list of destinations to write transactions from. Currently
// only YNAB is supported.
// Writers is a list of destinations to write transactions to.
Writers []string `envconfig:"YNABBER_WRITERS" default:"ynab"`

// PayeeStrip is depreciated please use Nordigen.PayeeStrip instead
PayeeStrip []string `envconfig:"YNABBER_PAYEE_STRIP"`

// Reader and/or writer specific settings
Nordigen Nordigen
YNAB YNAB
}

// Nordigen related settings
type Nordigen struct {
// AccountMap is depreciated please use YNAB.AccountMap instead
AccountMap AccountMap `envconfig:"NORDIGEN_ACCOUNTMAP"`

// BankID is used to create requisition
BankID string `envconfig:"NORDIGEN_BANKID"`

Expand Down Expand Up @@ -128,20 +121,4 @@ type YNAB struct {
//
// Example: "DK9520000123456789,NO8330001234567"
SwapFlow []string `envconfig:"YNAB_SWAPFLOW"`

ImportID ImportID
}

// ImportID can be either v1 or v2. All new users should use v2 because it
// have a lower potability of making duplicate transactions. But v1 remains
// the default to retain backwards compatibility.
//
// To migrate from v1 to v2 simply set the v2 to any date and all transactions
// from and including that date will be using v2 of the import ID generator.
type ImportID struct {
// V1 will be used from this date
V1 Date `envconfig:"YNAB_IMPORT_ID_V1" default:"1970-01-01"`

// V2 will be used from this date, for example: 2022-12-24
V2 Date `envconfig:"YNAB_IMPORT_ID_V2" default:"9999-01-01"`
}
2 changes: 1 addition & 1 deletion reader/nordigen/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestStore(t *testing.T) {
DataDir: ".",
},
}
want := "foo"
want := "foo.json"
got := r.requisitionStore()
if want != got {
t.Fatalf("default: %s != %s", want, got)
Expand Down
44 changes: 10 additions & 34 deletions writer/ynab/ynab.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,43 +52,19 @@ func accountParser(iban string, accountMap map[string]string) (string, error) {
return "", fmt.Errorf("no account for: %s in map: %s", iban, accountMap)
}

// importIDMaker tries to return a unique YNAB import ID to avoid duplicate
// transactions.
func importIDMaker(cfg ynabber.Config, t ynabber.Transaction) string {
// Common between versions
// makeID returns a unique YNAB import ID to avoid duplicate transactions.
func makeID(cfg ynabber.Config, t ynabber.Transaction) string {
date := t.Date.Format("2006-01-02")
amount := t.Amount.String()

// Version 1 uses the memo, amount and date from Ytransaction
v1Cutover := time.Time(cfg.YNAB.ImportID.V1)
v1 := func(t ynabber.Transaction) string {
hash := sha256.Sum256([]byte(t.Memo))
return fmt.Sprintf("YBBR:%s:%s:%x", amount, date, hash[:2])
}

// Version 2 uses, in order, the account IBAN, transaction ID, date, and
// amount to build a hash of the transaction.
v2Cutover := time.Time(cfg.YNAB.ImportID.V2)
v2 := func(t ynabber.Transaction) string {
s := [][]byte{
[]byte(t.Account.IBAN),
[]byte(t.ID),
[]byte(date),
[]byte(amount),
}
hash := sha256.Sum256(bytes.Join(s, []byte("")))
return fmt.Sprintf("YBBR:%x", hash)[:32]
}

// Return the first generator from latest to oldest that have a cutover date
// after or equal to the transaction date.
if t.Date.After(v2Cutover) || t.Date.Equal(v2Cutover) {
return v2(t)
} else if t.Date.After(v1Cutover) || t.Date.Equal(v1Cutover) {
return v1(t)
} else {
return v1(t)
s := [][]byte{
[]byte(t.Account.IBAN),
[]byte(t.ID),
[]byte(date),
[]byte(amount),
}
hash := sha256.Sum256(bytes.Join(s, []byte("")))
return fmt.Sprintf("YBBR:%x", hash)[:32]
}

func ynabberToYNAB(cfg ynabber.Config, t ynabber.Transaction) (Ytransaction, error) {
Expand Down Expand Up @@ -126,7 +102,7 @@ func ynabberToYNAB(cfg ynabber.Config, t ynabber.Transaction) (Ytransaction, err
}

return Ytransaction{
ImportID: importIDMaker(cfg, t),
ImportID: makeID(cfg, t),
AccountID: accountID,
Date: date,
Amount: t.Amount.String(),
Expand Down
25 changes: 3 additions & 22 deletions writer/ynab/ynab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/martinohansen/ynabber"
)

func TestImportIDMaker(t *testing.T) {
func TestMakeID(t *testing.T) {
// The import IDs cant be more then 32 chars
var maxLength = 32

Expand All @@ -29,37 +29,18 @@ func TestImportIDMaker(t *testing.T) {
args args
want string
}{
{
name: "v1",
args: args{
defaultConfig,
ynabber.Transaction{
Amount: ynabber.Milliunits(-100000),
Date: time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC),
Memo: "foo",
},
},
want: "YBBR:-100000:2000-01-01:2c26",
},

{
name: "v2",
args: args{
ynabber.Config{
YNAB: ynabber.YNAB{
ImportID: ynabber.ImportID{
V2: ynabber.Date(time.Date(2022, 12, 24, 0, 0, 0, 0, time.UTC)),
},
},
},
ynabber.Config{},
ynabber.Transaction{Date: time.Date(2022, 12, 24, 0, 0, 0, 0, time.UTC)},
},
want: "YBBR:5ca3430298b7fb93d2f4fe1e302",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := importIDMaker(tt.args.cfg, tt.args.t)
got := makeID(tt.args.cfg, tt.args.t)
// Test max length of all test cases
if len(got) > maxLength {
t.Errorf("importIDMaker() = %v chars long, max length is %v", len(got), maxLength)
Expand Down
Loading