From 5f296e86693a8ce0171aeaab510e1532ce239f42 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Mon, 4 Dec 2017 21:05:17 -0500 Subject: [PATCH] Split prices into models --- internal/db/db.go | 5 ++- internal/handlers/gnucash.go | 4 +- internal/handlers/prices.go | 63 +++++++----------------------- internal/handlers/prices_lua.go | 6 +-- internal/handlers/prices_test.go | 19 ++++----- internal/handlers/testdata_test.go | 4 +- internal/models/prices.go | 41 +++++++++++++++++++ 7 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 internal/models/prices.go diff --git a/internal/db/db.go b/internal/db/db.go index b92e9bd..a23a029 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -14,6 +14,9 @@ import ( "strings" ) +// luaMaxLengthBuffer is intended to be enough bytes such that a given string +// no longer than models.LuaMaxLength is sure to fit within a database +// implementation's string type specified by the same. const luaMaxLengthBuffer int = 4096 func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { @@ -40,7 +43,7 @@ func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { dbmap.AddTableWithName(models.Security{}, "securities").SetKeys(true, "SecurityId") dbmap.AddTableWithName(models.Transaction{}, "transactions").SetKeys(true, "TransactionId") dbmap.AddTableWithName(models.Split{}, "splits").SetKeys(true, "SplitId") - dbmap.AddTableWithName(handlers.Price{}, "prices").SetKeys(true, "PriceId") + dbmap.AddTableWithName(models.Price{}, "prices").SetKeys(true, "PriceId") rtable := dbmap.AddTableWithName(handlers.Report{}, "reports").SetKeys(true, "ReportId") rtable.ColMap("Lua").SetMaxSize(handlers.LuaMaxLength + luaMaxLengthBuffer) diff --git a/internal/handlers/gnucash.go b/internal/handlers/gnucash.go index f99978e..2399a6b 100644 --- a/internal/handlers/gnucash.go +++ b/internal/handlers/gnucash.go @@ -129,7 +129,7 @@ type GnucashImport struct { Securities []models.Security Accounts []models.Account Transactions []models.Transaction - Prices []Price + Prices []models.Price } func ImportGnucash(r io.Reader) (*GnucashImport, error) { @@ -161,7 +161,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { // Create prices, setting security and currency IDs from securityMap for i := range gncxml.PriceDB.Prices { price := gncxml.PriceDB.Prices[i] - var p Price + var p models.Price security, ok := securityMap[price.Commodity.Name] if !ok { return nil, fmt.Errorf("Unable to find commodity '%s' for price '%s'", price.Commodity.Name, price.Id) diff --git a/internal/handlers/prices.go b/internal/handlers/prices.go index 2689378..c92eeb4 100644 --- a/internal/handlers/prices.go +++ b/internal/handlers/prices.go @@ -1,48 +1,13 @@ package handlers import ( - "encoding/json" "github.com/aclindsa/moneygo/internal/models" "log" "net/http" - "strings" "time" ) -type Price struct { - PriceId int64 - SecurityId int64 - CurrencyId int64 - Date time.Time - Value string // String representation of decimal price of Security in Currency units, suitable for passing to big.Rat.SetString() - RemoteId string // unique ID from source, for detecting duplicates -} - -type PriceList struct { - Prices *[]*Price `json:"prices"` -} - -func (p *Price) Read(json_str string) error { - dec := json.NewDecoder(strings.NewReader(json_str)) - return dec.Decode(p) -} - -func (p *Price) Write(w http.ResponseWriter) error { - enc := json.NewEncoder(w) - return enc.Encode(p) -} - -func (pl *PriceList) Read(json_str string) error { - dec := json.NewDecoder(strings.NewReader(json_str)) - return dec.Decode(pl) -} - -func (pl *PriceList) Write(w http.ResponseWriter) error { - enc := json.NewEncoder(w) - return enc.Encode(pl) -} - -func CreatePriceIfNotExist(tx *Tx, price *Price) error { +func CreatePriceIfNotExist(tx *Tx, price *models.Price) error { if len(price.RemoteId) == 0 { // Always create a new price if we can't match on the RemoteId err := tx.Insert(price) @@ -52,7 +17,7 @@ func CreatePriceIfNotExist(tx *Tx, price *Price) error { return nil } - var prices []*Price + var prices []*models.Price _, err := tx.Select(&prices, "SELECT * from prices where SecurityId=? AND CurrencyId=? AND Date=? AND Value=?", price.SecurityId, price.CurrencyId, price.Date, price.Value) if err != nil { @@ -70,8 +35,8 @@ func CreatePriceIfNotExist(tx *Tx, price *Price) error { return nil } -func GetPrice(tx *Tx, priceid, securityid int64) (*Price, error) { - var p Price +func GetPrice(tx *Tx, priceid, securityid int64) (*models.Price, error) { + var p models.Price err := tx.SelectOne(&p, "SELECT * from prices where PriceId=? AND SecurityId=?", priceid, securityid) if err != nil { return nil, err @@ -79,8 +44,8 @@ func GetPrice(tx *Tx, priceid, securityid int64) (*Price, error) { return &p, nil } -func GetPrices(tx *Tx, securityid int64) (*[]*Price, error) { - var prices []*Price +func GetPrices(tx *Tx, securityid int64) (*[]*models.Price, error) { + var prices []*models.Price _, err := tx.Select(&prices, "SELECT * from prices where SecurityId=?", securityid) if err != nil { @@ -90,8 +55,8 @@ func GetPrices(tx *Tx, securityid int64) (*[]*Price, error) { } // Return the latest price for security in currency units before date -func GetLatestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) { - var p Price +func GetLatestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { + var p models.Price err := tx.SelectOne(&p, "SELECT * from prices where SecurityId=? AND CurrencyId=? AND Date <= ? ORDER BY Date DESC LIMIT 1", security.SecurityId, currency.SecurityId, date) if err != nil { return nil, err @@ -100,8 +65,8 @@ func GetLatestPrice(tx *Tx, security, currency *models.Security, date *time.Time } // Return the earliest price for security in currency units after date -func GetEarliestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) { - var p Price +func GetEarliestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { + var p models.Price err := tx.SelectOne(&p, "SELECT * from prices where SecurityId=? AND CurrencyId=? AND Date >= ? ORDER BY Date ASC LIMIT 1", security.SecurityId, currency.SecurityId, date) if err != nil { return nil, err @@ -110,7 +75,7 @@ func GetEarliestPrice(tx *Tx, security, currency *models.Security, date *time.Ti } // Return the price for security in currency closest to date -func GetClosestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) { +func GetClosestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { earliest, _ := GetEarliestPrice(tx, security, currency, date) latest, err := GetLatestPrice(tx, security, currency, date) @@ -137,7 +102,7 @@ func PriceHandler(r *http.Request, context *Context, user *models.User, security } if r.Method == "POST" { - var price Price + var price models.Price if err := ReadJSON(r, &price); err != nil { return NewError(3 /*Invalid Request*/) } @@ -161,7 +126,7 @@ func PriceHandler(r *http.Request, context *Context, user *models.User, security } else if r.Method == "GET" { if context.LastLevel() { //Return all this security's prices - var pl PriceList + var pl models.PriceList prices, err := GetPrices(context.Tx, security.SecurityId) if err != nil { @@ -190,7 +155,7 @@ func PriceHandler(r *http.Request, context *Context, user *models.User, security return NewError(3 /*Invalid Request*/) } if r.Method == "PUT" { - var price Price + var price models.Price if err := ReadJSON(r, &price); err != nil || price.PriceId != priceid { return NewError(3 /*Invalid Request*/) } diff --git a/internal/handlers/prices_lua.go b/internal/handlers/prices_lua.go index 8450319..1ff0da2 100644 --- a/internal/handlers/prices_lua.go +++ b/internal/handlers/prices_lua.go @@ -15,7 +15,7 @@ func luaRegisterPrices(L *lua.LState) { L.SetField(mt, "__metatable", lua.LString("protected")) } -func PriceToLua(L *lua.LState, price *Price) *lua.LUserData { +func PriceToLua(L *lua.LState, price *models.Price) *lua.LUserData { ud := L.NewUserData() ud.Value = price L.SetMetatable(ud, L.GetTypeMetatable(luaPriceTypeName)) @@ -23,9 +23,9 @@ func PriceToLua(L *lua.LState, price *Price) *lua.LUserData { } // Checks whether the first lua argument is a *LUserData with *Price and returns this *Price. -func luaCheckPrice(L *lua.LState, n int) *Price { +func luaCheckPrice(L *lua.LState, n int) *models.Price { ud := L.CheckUserData(n) - if price, ok := ud.Value.(*Price); ok { + if price, ok := ud.Value.(*models.Price); ok { return price } L.ArgError(n, "price expected") diff --git a/internal/handlers/prices_test.go b/internal/handlers/prices_test.go index 1cbca93..8c44379 100644 --- a/internal/handlers/prices_test.go +++ b/internal/handlers/prices_test.go @@ -2,20 +2,21 @@ package handlers_test import ( "github.com/aclindsa/moneygo/internal/handlers" + "github.com/aclindsa/moneygo/internal/models" "net/http" "strconv" "testing" "time" ) -func createPrice(client *http.Client, price *handlers.Price) (*handlers.Price, error) { - var p handlers.Price +func createPrice(client *http.Client, price *models.Price) (*models.Price, error) { + var p models.Price err := create(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/") return &p, err } -func getPrice(client *http.Client, priceid, securityid int64) (*handlers.Price, error) { - var p handlers.Price +func getPrice(client *http.Client, priceid, securityid int64) (*models.Price, error) { + var p models.Price err := read(client, &p, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/"+strconv.FormatInt(priceid, 10)) if err != nil { return nil, err @@ -23,8 +24,8 @@ func getPrice(client *http.Client, priceid, securityid int64) (*handlers.Price, return &p, nil } -func getPrices(client *http.Client, securityid int64) (*handlers.PriceList, error) { - var pl handlers.PriceList +func getPrices(client *http.Client, securityid int64) (*models.PriceList, error) { + var pl models.PriceList err := read(client, &pl, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/") if err != nil { return nil, err @@ -32,8 +33,8 @@ func getPrices(client *http.Client, securityid int64) (*handlers.PriceList, erro return &pl, nil } -func updatePrice(client *http.Client, price *handlers.Price) (*handlers.Price, error) { - var p handlers.Price +func updatePrice(client *http.Client, price *models.Price) (*models.Price, error) { + var p models.Price err := update(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/"+strconv.FormatInt(price.PriceId, 10)) if err != nil { return nil, err @@ -41,7 +42,7 @@ func updatePrice(client *http.Client, price *handlers.Price) (*handlers.Price, e return &p, nil } -func deletePrice(client *http.Client, p *handlers.Price) error { +func deletePrice(client *http.Client, p *models.Price) error { err := remove(client, "/v1/securities/"+strconv.FormatInt(p.SecurityId, 10)+"/prices/"+strconv.FormatInt(p.PriceId, 10)) if err != nil { return err diff --git a/internal/handlers/testdata_test.go b/internal/handlers/testdata_test.go index 2cef0cd..65bfee8 100644 --- a/internal/handlers/testdata_test.go +++ b/internal/handlers/testdata_test.go @@ -38,7 +38,7 @@ type TestData struct { users []User clients []*http.Client securities []models.Security - prices []handlers.Price + prices []models.Price accounts []models.Account // accounts must appear after their parents in this slice transactions []models.Transaction reports []handlers.Report @@ -209,7 +209,7 @@ var data = []TestData{ AlternateId: "978", }, }, - prices: []handlers.Price{ + prices: []models.Price{ { SecurityId: 1, CurrencyId: 0, diff --git a/internal/models/prices.go b/internal/models/prices.go new file mode 100644 index 0000000..7958e52 --- /dev/null +++ b/internal/models/prices.go @@ -0,0 +1,41 @@ +package models + +import ( + "encoding/json" + "net/http" + "strings" + "time" +) + +type Price struct { + PriceId int64 + SecurityId int64 + CurrencyId int64 + Date time.Time + Value string // String representation of decimal price of Security in Currency units, suitable for passing to big.Rat.SetString() + RemoteId string // unique ID from source, for detecting duplicates +} + +type PriceList struct { + Prices *[]*Price `json:"prices"` +} + +func (p *Price) Read(json_str string) error { + dec := json.NewDecoder(strings.NewReader(json_str)) + return dec.Decode(p) +} + +func (p *Price) Write(w http.ResponseWriter) error { + enc := json.NewEncoder(w) + return enc.Encode(p) +} + +func (pl *PriceList) Read(json_str string) error { + dec := json.NewDecoder(strings.NewReader(json_str)) + return dec.Decode(pl) +} + +func (pl *PriceList) Write(w http.ResponseWriter) error { + enc := json.NewEncoder(w) + return enc.Encode(pl) +}