Skip to content

Commit

Permalink
fix import, net worth
Browse files Browse the repository at this point in the history
  • Loading branch information
quinn committed Feb 7, 2024
1 parent acae97d commit aa92775
Show file tree
Hide file tree
Showing 18 changed files with 327 additions and 210 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cli/internal/cmd/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func LoadCmd(ctx context.Context) *cobra.Command {
return err
}

err = domain.LoadAccountBalances(ctx, queries, importLogId)
if err != nil {
return err
}

return nil
})
if err != nil {
Expand Down
7 changes: 4 additions & 3 deletions cli/internal/db/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions cli/internal/db/query.sql
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,12 @@ INSERT INTO "AccountBalance"("current",
"available",
"isoCurrencyCode",
"accountPlaidId",
"createdAt")
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP);
"date",
"importLogId",
"importedAt")
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT DO UPDATE SET "current"=excluded."current",
"available"=excluded."available",
"isoCurrencyCode"=excluded."isoCurrencyCode",
"accountPlaidId"=excluded."accountPlaidId",
"date"=excluded."date";
15 changes: 13 additions & 2 deletions cli/internal/db/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 15 additions & 11 deletions cli/internal/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -65,26 +65,30 @@ CREATE TABLE IF NOT EXISTS "Account" (
CONSTRAINT "Account_institutionPlaidId_fkey" FOREIGN KEY ("institutionPlaidId") REFERENCES "Institution" ("plaidId") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Account_importLogId_fkey" FOREIGN KEY ("importLogId") REFERENCES "ImportLog" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS "AccountBalance" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"current" REAL NOT NULL,
"available" REAL NOT NULL,
"isoCurrencyCode" TEXT NOT NULL,
"accountPlaidId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AccountBalance_accountPlaidId_fkey" FOREIGN KEY ("accountPlaidId") REFERENCES "Account" ("plaidId") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS "Budget" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"amount" REAL NOT NULL,
"range" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "BudgetRule" (
CREATE TABLE IF NOT EXISTS "BudgetFilter" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"budgetId" INTEGER NOT NULL,
"column" TEXT NOT NULL,
"operator" TEXT NOT NULL DEFAULT 'CONTAINS',
"value" TEXT NOT NULL,
CONSTRAINT "BudgetRule_budgetId_fkey" FOREIGN KEY ("budgetId") REFERENCES "Budget" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
CONSTRAINT "BudgetFilter_budgetId_fkey" FOREIGN KEY ("budgetId") REFERENCES "Budget" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS "AccountBalance" (
"current" REAL NOT NULL,
"available" REAL NOT NULL,
"isoCurrencyCode" TEXT NOT NULL,
"accountPlaidId" TEXT NOT NULL,
"date" TEXT NOT NULL,
"importedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"importLogId" INTEGER,

PRIMARY KEY ("accountPlaidId", "date"),
CONSTRAINT "AccountBalance_accountPlaidId_fkey" FOREIGN KEY ("accountPlaidId") REFERENCES "Account" ("plaidId") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "AccountBalance_importLogId_fkey" FOREIGN KEY ("importLogId") REFERENCES "ImportLog" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
7 changes: 7 additions & 0 deletions cli/internal/domain/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,10 @@ type Account struct {
Type plaid.AccountType `csv:"type"`
PlaidItemID string `csv:"plaidItemId"`
}

type AccountBalance struct {
PlaidAccountID string `csv:"plaidAccountId"`
Available float64 `csv:"available"`
Current float64 `csv:"current"`
Date string `csv:"date"`
}
28 changes: 23 additions & 5 deletions cli/internal/domain/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,36 @@ func LoadAccounts(ctx context.Context, queries *db.Queries, importLogId int64) e
}

log.Println("created account", account.Name)
}

return nil
}

func LoadAccountBalances(ctx context.Context, queries *db.Queries, importLogId int64) error {
csvFile := path.Join(os.Getenv("HOME"), ".config", "budgeted", "csv", "account_balances.csv")
data, err := os.Open(csvFile)
if err != nil {
return errors.Wrapf(err, "failed to read file: %s", csvFile)
}

var accountBalances []AccountBalance
if err := gocsv.Unmarshal(data, &accountBalances); err != nil {
return errors.Wrapf(err, "failed to parse CSV file: %s", csvFile)
}

for _, accountBalance := range accountBalances {
err = queries.AccountBalanceCreate(ctx, db.AccountBalanceCreateParams{
Current: account.CurrentBalance,
Available: account.AvailableBalance,
IsoCurrencyCode: account.ISOCurrencyCode,
AccountPlaidId: account.PlaidID,
AccountPlaidId: accountBalance.PlaidAccountID,
Current: accountBalance.Current,
Available: accountBalance.Available,
Date: accountBalance.Date,
ImportLogId: sql.NullInt64{Int64: importLogId, Valid: true},
})
if err != nil {
return err
}

log.Println("created account balance", account.Name)
log.Println("created account balance", accountBalance.Current)
}

return nil
Expand Down
111 changes: 62 additions & 49 deletions cli/internal/domain/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ package domain
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"time"

"github.com/gocarina/gocsv"
"github.com/plaid/plaid-go/v20/plaid"
Expand Down Expand Up @@ -134,72 +131,88 @@ func TransformTransactions(ctx context.Context, jsonStorage string, csvStorage s
// The unfortunate side-effect of that is rebuilding a SQLite database will no longer rebuild
// historical account balances.
func TransformAccounts(ctx context.Context, jsonStorage string, csvStorage string) error {
var accountsCSV []Account

accountsPath := fmt.Sprintf("%s/accounts", jsonStorage)
accountMap := make(map[string]Account)
accountBalanceMap := make(map[string]AccountBalance)
var accounts []Account
var accountBalances []AccountBalance

files, err := os.ReadDir(accountsPath)
if err != nil {
log.Fatal(err)
}

var newestTime time.Time
var newestFile fs.DirEntry
for _, file := range files {
if file.IsDir() {
continue
err := filepath.WalkDir(accountsPath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}

log.Println("path", path)
date := d.Name()[:10]

info, err := file.Info()
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
}

if newestFile == nil || info.ModTime().After(newestTime) {
newestFile = file
newestTime = info.ModTime()
response := plaid.AccountsGetResponse{}
if err = json.Unmarshal(bytes, &response); err != nil {
return err
}

for _, account := range response.GetAccounts() {
balance := account.GetBalances()
item := response.GetItem()

accountMap[account.GetAccountId()] = Account{
PlaidID: account.GetAccountId(),
AvailableBalance: balance.GetAvailable(),
CurrentBalance: balance.GetCurrent(),
ISOCurrencyCode: balance.GetIsoCurrencyCode(),
Limit: balance.GetLimit(),
Mask: account.GetMask(),
Name: account.GetName(),
OfficialName: account.GetOfficialName(),
Subtype: account.GetSubtype(),
Type: account.GetType(),
PlaidItemID: item.GetItemId(),
}

balanceKey := fmt.Sprintf("%s-%s", date, account.GetAccountId())

accountBalanceMap[balanceKey] = AccountBalance{
PlaidAccountID: account.GetAccountId(),
Date: date,
Available: balance.GetAvailable(),
Current: balance.GetCurrent(),
}
}

return nil
})

for _, account := range accountMap {
accounts = append(accounts, account)
}
if newestFile == nil {
return errors.New("no valid account files found")
for _, accountBalance := range accountBalanceMap {
accountBalances = append(accountBalances, accountBalance)
}

jsonFilePath := accountsPath + "/" + newestFile.Name()
bytes, err := os.ReadFile(jsonFilePath)
if err != nil {
log.Fatal(err)
}
var csvContent string
var fp string

response := plaid.AccountsGetResponse{}
if err = json.Unmarshal(bytes, &response); err != nil {
csvContent, err = gocsv.MarshalString(&accounts)
if err != nil {
return err
}

for _, account := range response.GetAccounts() {
balance := account.GetBalances()
item := response.GetItem()

accountsCSV = append(accountsCSV, Account{
PlaidID: account.GetAccountId(),
AvailableBalance: balance.GetAvailable(),
CurrentBalance: balance.GetCurrent(),
ISOCurrencyCode: balance.GetIsoCurrencyCode(),
Limit: balance.GetLimit(),
Mask: account.GetMask(),
Name: account.GetName(),
OfficialName: account.GetOfficialName(),
Subtype: account.GetSubtype(),
Type: account.GetType(),
PlaidItemID: item.GetItemId(),
})
fp = filepath.Join(csvStorage, "accounts.csv")
if err := os.WriteFile(fp, []byte(csvContent), 0644); err != nil {
return err
}

csvContent, err := gocsv.MarshalString(&accountsCSV)
csvContent, err = gocsv.MarshalString(&accountBalances)
if err != nil {
return err
}

fp := filepath.Join(csvStorage, "accounts.csv")
fp = filepath.Join(csvStorage, "account_balances.csv")
if err := os.WriteFile(fp, []byte(csvContent), 0644); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion electron/main/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export const router = t.router({
}),
accountBalances: loggedProcedure.query(async () => {
return await prisma.accountBalance.findMany({
orderBy: { createdAt: 'asc' },
orderBy: { date: 'asc' },
include: { account: true },
})
}),
Expand Down
19 changes: 19 additions & 0 deletions prisma/migrations/20240207161829_add_log_to_balances/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_AccountBalance" (
"current" REAL NOT NULL,
"available" REAL NOT NULL,
"isoCurrencyCode" TEXT NOT NULL,
"accountPlaidId" TEXT NOT NULL,
"date" TEXT NOT NULL,
"importLogId" INTEGER,

PRIMARY KEY ("accountPlaidId", "date"),
CONSTRAINT "AccountBalance_accountPlaidId_fkey" FOREIGN KEY ("accountPlaidId") REFERENCES "Account" ("plaidId") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "AccountBalance_importLogId_fkey" FOREIGN KEY ("importLogId") REFERENCES "ImportLog" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_AccountBalance" ("accountPlaidId", "available", "current", "date", "isoCurrencyCode") SELECT "accountPlaidId", "available", "current", "date", "isoCurrencyCode" FROM "AccountBalance";
DROP TABLE "AccountBalance";
ALTER TABLE "new_AccountBalance" RENAME TO "AccountBalance";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
Loading

0 comments on commit aa92775

Please sign in to comment.