From ea83759be21054aa90630ae448243a8b615fba03 Mon Sep 17 00:00:00 2001 From: bel Date: Tue, 3 Aug 2021 21:32:09 -0600 Subject: [PATCH] MVP w UI --- ledger.go | 22 +++++++++++++--- main.go | 3 ++- public/index.html | 4 +-- server.go | 38 ++++++++++++++++++++++++++-- testdata/ledger-groceries-custom.dat | 21 +++++++++++++++ transaction.go | 15 ++++++++++- 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 testdata/ledger-groceries-custom.dat diff --git a/ledger.go b/ledger.go index 3b03189..84661a7 100644 --- a/ledger.go +++ b/ledger.go @@ -52,9 +52,17 @@ func (ledger Ledger) SetTransactions(transactions []Transaction) error { return os.Rename(f.Name(), ledger.path) } +func (ledger Ledger) LossyBalances() (Balances, error) { + return ledger.balances(true) +} + func (ledger Ledger) Balances() (Balances, error) { + return ledger.balances(false) +} + +func (ledger Ledger) balances(lossy bool) (Balances, error) { transactions, err := ledger.Transactions() - if err != nil { + if !lossy && err != nil { return nil, err } balances := make(Balances) @@ -65,14 +73,22 @@ func (ledger Ledger) Balances() (Balances, error) { return balances, nil } +func (ledger Ledger) LossyTransactions() ([]Transaction, error) { + return ledger.transactions(true) +} + func (ledger Ledger) Transactions() ([]Transaction, error) { + return ledger.transactions(false) +} + +func (ledger Ledger) transactions(lossy bool) ([]Transaction, error) { f, err := ledger.open() if err != nil { return nil, err } defer f.Close() transactions := make([]Transaction, 0) - for err == nil { + for (!lossy && err == nil) || (lossy && err != io.EOF) { var transaction Transaction transaction, err = readTransaction(f) if err == io.EOF { @@ -82,7 +98,7 @@ func (ledger Ledger) Transactions() ([]Transaction, error) { err = fmt.Errorf("error parsing transaction #%d: %v", len(transactions), err) } } - if err == io.EOF { + if err == io.EOF || lossy { err = nil } return transactions, err diff --git a/main.go b/main.go index 93da03e..95df715 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( func main() { as := args.NewArgSet() as.Append(args.INT, "p", "port to listen on", "8101") + as.Append(args.BOOL, "debug", "debug mode", false) as.Append(args.STRING, "f", "file to abuse", "/tmp/ledger-ui.dat") if err := as.Parse(); err != nil { panic(err) @@ -17,7 +18,7 @@ func main() { if err != nil { panic(err) } - if err := http.ListenAndServe(":"+fmt.Sprint(as.GetInt("p")), Server{ledger: ledger}); err != nil { + if err := http.ListenAndServe(":"+fmt.Sprint(as.GetInt("p")), Server{ledger: ledger, debug: as.GetBool("debug")}); err != nil { panic(err) } } diff --git a/public/index.html b/public/index.html index 18cbfe0..983c59e 100644 --- a/public/index.html +++ b/public/index.html @@ -113,8 +113,8 @@ } zachsPayment = (x) => setRowKeyValue(x, "Payer", "AssetAccount:Zach") belsPayment = (x) => setRowKeyValue(x, "Payer", "AssetAccount:Bel") - zachsCharge = (x) => setRowKeyValue(x, "Payee", "DebtAccount:Zach") - belsCharge = (x) => setRowKeyValue(x, "belsCharge", "DebtAccount:Bel") + zachsCharge = (x) => setRowKeyValue(x, "Payee", "AssetAccount:Zach") + belsCharge = (x) => setRowKeyValue(x, "Payee", "AssetAccount:Bel") function http(method, remote, callback, body) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { diff --git a/server.go b/server.go index 60118a8..0bd2c9b 100644 --- a/server.go +++ b/server.go @@ -2,7 +2,9 @@ package main import ( _ "embed" + "encoding/json" "fmt" + "io/ioutil" "log" "net/http" ) @@ -12,6 +14,7 @@ var index string type Server struct { ledger Ledger + debug bool } func (server Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -24,19 +27,50 @@ func (server Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "GET/api/balances": server.getBalances(w, r) default: - fmt.Fprint(w, index) + if server.debug { + b, _ := ioutil.ReadFile("./public/index.html") + w.Write(b) + } else { + fmt.Fprint(w, index) + } } } +func (server Server) err(w http.ResponseWriter, r *http.Request, err error) { + log.Println(r.Method, r.URL.Path, err) + http.Error(w, fmt.Sprint(err.Error()), http.StatusInternalServerError) +} + func (server Server) getTransactions(w http.ResponseWriter, r *http.Request) { transactions, err := server.ledger.Transactions() if err != nil { - + server.err(w, r, err) + return } + json.NewEncoder(w).Encode(map[string]interface{}{"Transactions": transactions}) } func (server Server) putTransactions(w http.ResponseWriter, r *http.Request) { + var request struct { + IDX int `json:"idx"` + Transaction + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + server.err(w, r, err) + return + } + if err := server.ledger.SetTransaction(request.IDX, request.Transaction); err != nil { + server.err(w, r, err) + return + } + json.NewEncoder(w).Encode(map[string]interface{}{"ok": true}) } func (server Server) getBalances(w http.ResponseWriter, r *http.Request) { + balances, err := server.ledger.Balances() + if err != nil { + server.err(w, r, err) + return + } + json.NewEncoder(w).Encode(map[string]interface{}{"Balances": balances}) } diff --git a/testdata/ledger-groceries-custom.dat b/testdata/ledger-groceries-custom.dat new file mode 100644 index 0000000..58eae64 --- /dev/null +++ b/testdata/ledger-groceries-custom.dat @@ -0,0 +1,21 @@ +2021-01-01 groceries + Withdrawal:Target $10 + AssetAccount:Chase:8824 +2021-01-05 bel vidya + Withdrawal:Target $1 + AssetAccount:Chase:8824 +2021-01-06 qt vidya + Withdrawal:Target $100 + AssetAccount:Chase:8824 +2021-01-07 bel payment + AssetAccount:Chase:8824 $1 + AssetAccount:Payment +2021-01-08 qt payment + AssetAccount:Chase:8824 $100 + AssetAccount:Payment +2021-01-09 bel grocery payment + AssetAccount:Chase:8824 $5 + AssetAccount:Payment +2021-01-10 qt grocery payment + AssetAccount:Chase:8824 $5 + AssetAccount:Payment diff --git a/transaction.go b/transaction.go index b53293c..740315d 100644 --- a/transaction.go +++ b/transaction.go @@ -3,6 +3,7 @@ package main import ( "bufio" "bytes" + "errors" "fmt" "io" "strconv" @@ -75,16 +76,25 @@ func words(b []byte) [][]byte { } func (transaction *Transaction) readDate(lines [][]byte) error { + if len(lines) < 1 || len(words(lines[0])) < 1 { + return errors.New("stub") + } transaction.Date = string(words(lines[0])[0]) return nil } func (transaction *Transaction) readDescription(lines [][]byte) error { + if len(lines) < 1 || len(words(lines[0])) < 1 { + return errors.New("stub") + } transaction.Description = string(bytes.Join(words(lines[0])[1:], []byte(" "))) return nil } func (transaction *Transaction) readAmount(lines [][]byte) error { + if len(lines) < 2 || len(words(lines[1])) < 2 { + return errors.New("stub") + } amount := string(words(lines[1])[1]) f, err := strconv.ParseFloat(strings.Trim(amount, "$"), 32) transaction.Amount = float32(f) @@ -92,6 +102,9 @@ func (transaction *Transaction) readAmount(lines [][]byte) error { } func (transaction *Transaction) readPayerPayee(lines [][]byte) error { + if len(lines) < 3 || len(words(lines[1])) < 1 || len(words(lines[2])) < 1 { + return errors.New("stub") + } payer := string(words(lines[1])[0]) payee := string(words(lines[2])[0]) if transaction.Amount >= 0 { @@ -108,7 +121,7 @@ func (transaction *Transaction) readPayerPayee(lines [][]byte) error { func (transaction Transaction) Marshal() string { return fmt.Sprintf( - "%-25s%s\n%25s%-50s$%.2f\n%25s%s", + "%-25s%s\n%25s%-49s $%.2f\n%25s%s", transaction.Date, transaction.Description, "",