Split securities into models

This commit is contained in:
Aaron Lindsay 2017-12-03 06:38:22 -05:00
parent 3f4d6d15a1
commit f72c86ef58
17 changed files with 170 additions and 151 deletions

View File

@ -37,7 +37,7 @@ func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) {
dbmap.AddTableWithName(models.User{}, "users").SetKeys(true, "UserId") dbmap.AddTableWithName(models.User{}, "users").SetKeys(true, "UserId")
dbmap.AddTableWithName(models.Session{}, "sessions").SetKeys(true, "SessionId") dbmap.AddTableWithName(models.Session{}, "sessions").SetKeys(true, "SessionId")
dbmap.AddTableWithName(handlers.Account{}, "accounts").SetKeys(true, "AccountId") dbmap.AddTableWithName(handlers.Account{}, "accounts").SetKeys(true, "AccountId")
dbmap.AddTableWithName(handlers.Security{}, "securities").SetKeys(true, "SecurityId") dbmap.AddTableWithName(models.Security{}, "securities").SetKeys(true, "SecurityId")
dbmap.AddTableWithName(handlers.Transaction{}, "transactions").SetKeys(true, "TransactionId") dbmap.AddTableWithName(handlers.Transaction{}, "transactions").SetKeys(true, "TransactionId")
dbmap.AddTableWithName(handlers.Split{}, "splits").SetKeys(true, "SplitId") dbmap.AddTableWithName(handlers.Split{}, "splits").SetKeys(true, "SplitId")
dbmap.AddTableWithName(handlers.Price{}, "prices").SetKeys(true, "PriceId") dbmap.AddTableWithName(handlers.Price{}, "prices").SetKeys(true, "PriceId")

View File

@ -3,6 +3,7 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/aclindsa/moneygo/internal/models"
"log" "log"
"net/http" "net/http"
"strings" "strings"
@ -214,7 +215,7 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error)
func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, error) { func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, error) {
var imbalanceAccount Account var imbalanceAccount Account
var account Account var account Account
xxxtemplate := FindSecurityTemplate("XXX", Currency) xxxtemplate := FindSecurityTemplate("XXX", models.Currency)
if xxxtemplate == nil { if xxxtemplate == nil {
return nil, errors.New("Couldn't find XXX security template") return nil, errors.New("Couldn't find XXX security template")
} }

View File

@ -1,12 +1,13 @@
package handlers package handlers
import ( import (
"github.com/aclindsa/moneygo/internal/models"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
"math/big" "math/big"
) )
type Balance struct { type Balance struct {
Security *Security Security *models.Security
Amount *big.Rat Amount *big.Rat
} }

View File

@ -6,6 +6,7 @@ import (
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
"github.com/aclindsa/moneygo/internal/models"
"io" "io"
"log" "log"
"math" "math"
@ -22,7 +23,7 @@ type GnucashXMLCommodity struct {
XCode string `xml:"http://www.gnucash.org/XML/cmdty xcode"` XCode string `xml:"http://www.gnucash.org/XML/cmdty xcode"`
} }
type GnucashCommodity struct{ Security } type GnucashCommodity struct{ models.Security }
func (gc *GnucashCommodity) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (gc *GnucashCommodity) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var gxc GnucashXMLCommodity var gxc GnucashXMLCommodity
@ -35,12 +36,12 @@ func (gc *GnucashCommodity) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
gc.Description = gxc.Description gc.Description = gxc.Description
gc.AlternateId = gxc.XCode gc.AlternateId = gxc.XCode
gc.Security.Type = Stock // assumed default gc.Security.Type = models.Stock // assumed default
if gxc.Type == "ISO4217" { if gxc.Type == "ISO4217" {
gc.Security.Type = Currency gc.Security.Type = models.Currency
// Get the number from our templates for the AlternateId because // Get the number from our templates for the AlternateId because
// Gnucash uses 'id' (our Name) to supply the string ISO4217 code // Gnucash uses 'id' (our Name) to supply the string ISO4217 code
template := FindSecurityTemplate(gxc.Name, Currency) template := FindSecurityTemplate(gxc.Name, models.Currency)
if template == nil { if template == nil {
return errors.New("Unable to find security template for Gnucash ISO4217 commodity") return errors.New("Unable to find security template for Gnucash ISO4217 commodity")
} }
@ -125,7 +126,7 @@ type GnucashXMLImport struct {
} }
type GnucashImport struct { type GnucashImport struct {
Securities []Security Securities []models.Security
Accounts []Account Accounts []Account
Transactions []Transaction Transactions []Transaction
Prices []Price Prices []Price
@ -143,7 +144,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) {
} }
// Fixup securities, making a map of them as we go // Fixup securities, making a map of them as we go
securityMap := make(map[string]Security) securityMap := make(map[string]models.Security)
for i := range gncxml.Commodities { for i := range gncxml.Commodities {
s := gncxml.Commodities[i].Security s := gncxml.Commodities[i].Security
s.SecurityId = int64(i + 1) s.SecurityId = int64(i + 1)
@ -169,7 +170,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) {
if !ok { if !ok {
return nil, fmt.Errorf("Unable to find currency '%s' for price '%s'", price.Currency.Name, price.Id) return nil, fmt.Errorf("Unable to find currency '%s' for price '%s'", price.Currency.Name, price.Id)
} }
if currency.Type != Currency { if currency.Type != models.Currency {
return nil, fmt.Errorf("Currency for imported price isn't actually a currency\n") return nil, fmt.Errorf("Currency for imported price isn't actually a currency\n")
} }
p.PriceId = int64(i + 1) p.PriceId = int64(i + 1)

View File

@ -2,6 +2,7 @@ package handlers_test
import ( import (
"github.com/aclindsa/moneygo/internal/handlers" "github.com/aclindsa/moneygo/internal/handlers"
"github.com/aclindsa/moneygo/internal/models"
"net/http" "net/http"
"testing" "testing"
) )
@ -94,7 +95,7 @@ func TestImportGnucash(t *testing.T) {
accountBalanceHelper(t, d.clients[0], groceries, "287.56") // 87.19 from preexisting transactions and 200.37 from Gnucash accountBalanceHelper(t, d.clients[0], groceries, "287.56") // 87.19 from preexisting transactions and 200.37 from Gnucash
accountBalanceHelper(t, d.clients[0], cable, "89.98") accountBalanceHelper(t, d.clients[0], cable, "89.98")
var ge *handlers.Security var ge *models.Security
securities, err := getSecurities(d.clients[0]) securities, err := getSecurities(d.clients[0])
if err != nil { if err != nil {
t.Fatalf("Error fetching securities: %s\n", err) t.Fatalf("Error fetching securities: %s\n", err)

View File

@ -57,7 +57,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) Re
// Find matching existing securities or create new ones for those // Find matching existing securities or create new ones for those
// referenced by the OFX import. Also create a map from placeholder import // referenced by the OFX import. Also create a map from placeholder import
// SecurityIds to the actual SecurityIDs // SecurityIds to the actual SecurityIDs
var securitymap = make(map[int64]Security) var securitymap = make(map[int64]models.Security)
for _, ofxsecurity := range itl.Securities { for _, ofxsecurity := range itl.Securities {
// save off since ImportGetCreateSecurity overwrites SecurityId on // save off since ImportGetCreateSecurity overwrites SecurityId on
// ofxsecurity // ofxsecurity

View File

@ -3,26 +3,27 @@ package handlers
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/aclindsa/moneygo/internal/models"
"github.com/aclindsa/ofxgo" "github.com/aclindsa/ofxgo"
"io" "io"
"math/big" "math/big"
) )
type OFXImport struct { type OFXImport struct {
Securities []Security Securities []models.Security
Accounts []Account Accounts []Account
Transactions []Transaction Transactions []Transaction
// Balances map[int64]string // map AccountIDs to ending balances // Balances map[int64]string // map AccountIDs to ending balances
} }
func (i *OFXImport) GetSecurity(ofxsecurityid int64) (*Security, error) { func (i *OFXImport) GetSecurity(ofxsecurityid int64) (*models.Security, error) {
if ofxsecurityid < 0 || ofxsecurityid > int64(len(i.Securities)) { if ofxsecurityid < 0 || ofxsecurityid > int64(len(i.Securities)) {
return nil, errors.New("OFXImport.GetSecurity: SecurityID out of range") return nil, errors.New("OFXImport.GetSecurity: SecurityID out of range")
} }
return &i.Securities[ofxsecurityid], nil return &i.Securities[ofxsecurityid], nil
} }
func (i *OFXImport) GetSecurityAlternateId(alternateid string, securityType SecurityType) (*Security, error) { func (i *OFXImport) GetSecurityAlternateId(alternateid string, securityType models.SecurityType) (*models.Security, error) {
for _, security := range i.Securities { for _, security := range i.Securities {
if alternateid == security.AlternateId && securityType == security.Type { if alternateid == security.AlternateId && securityType == security.Type {
return &security, nil return &security, nil
@ -32,18 +33,18 @@ func (i *OFXImport) GetSecurityAlternateId(alternateid string, securityType Secu
return nil, errors.New("OFXImport.FindSecurity: Unable to find security") return nil, errors.New("OFXImport.FindSecurity: Unable to find security")
} }
func (i *OFXImport) GetAddCurrency(isoname string) (*Security, error) { func (i *OFXImport) GetAddCurrency(isoname string) (*models.Security, error) {
for _, security := range i.Securities { for _, security := range i.Securities {
if isoname == security.Name && Currency == security.Type { if isoname == security.Name && models.Currency == security.Type {
return &security, nil return &security, nil
} }
} }
template := FindSecurityTemplate(isoname, Currency) template := FindSecurityTemplate(isoname, models.Currency)
if template == nil { if template == nil {
return nil, fmt.Errorf("Failed to find Security for \"%s\"", isoname) return nil, fmt.Errorf("Failed to find Security for \"%s\"", isoname)
} }
var security Security = *template var security models.Security = *template
security.SecurityId = int64(len(i.Securities) + 1) security.SecurityId = int64(len(i.Securities) + 1)
i.Securities = append(i.Securities, security) i.Securities = append(i.Securities, security)
@ -186,13 +187,13 @@ func (i *OFXImport) importSecurities(seclist *ofxgo.SecurityList) error {
} else { } else {
return errors.New("Can't import unrecognized type satisfying ofxgo.Security interface") return errors.New("Can't import unrecognized type satisfying ofxgo.Security interface")
} }
s := Security{ s := models.Security{
SecurityId: int64(len(i.Securities) + 1), SecurityId: int64(len(i.Securities) + 1),
Name: string(si.SecName), Name: string(si.SecName),
Description: string(si.Memo), Description: string(si.Memo),
Symbol: string(si.Ticker), Symbol: string(si.Ticker),
Precision: 5, // TODO How to actually determine this? Precision: 5, // TODO How to actually determine this?
Type: Stock, Type: models.Stock,
AlternateId: string(si.SecID.UniqueID), AlternateId: string(si.SecID.UniqueID),
} }
if len(s.Description) == 0 { if len(s.Description) == 0 {
@ -214,10 +215,10 @@ func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) Transaction {
return t return t
} }
func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&buy.InvTran) t := i.GetInvTran(&buy.InvTran)
security, err := i.GetSecurityAlternateId(string(buy.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(buy.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -348,10 +349,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account *
return &t, nil return &t, nil
} }
func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&income.InvTran) t := i.GetInvTran(&income.InvTran)
security, err := i.GetSecurityAlternateId(string(income.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(income.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -394,10 +395,10 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *Security, accoun
return &t, nil return &t, nil
} }
func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&expense.InvTran) t := i.GetInvTran(&expense.InvTran)
security, err := i.GetSecurityAlternateId(string(expense.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(expense.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -439,7 +440,7 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *Securit
return &t, nil return &t, nil
} }
func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&marginint.InvTran) t := i.GetInvTran(&marginint.InvTran)
memo := string(marginint.InvTran.Memo) memo := string(marginint.InvTran.Memo)
@ -478,10 +479,10 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde
return &t, nil return &t, nil
} }
func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&reinvest.InvTran) t := i.GetInvTran(&reinvest.InvTran)
security, err := i.GetSecurityAlternateId(string(reinvest.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(reinvest.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -634,10 +635,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security,
return &t, nil return &t, nil
} }
func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&retofcap.InvTran) t := i.GetInvTran(&retofcap.InvTran)
security, err := i.GetSecurityAlternateId(string(retofcap.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(retofcap.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -679,10 +680,10 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *Security,
return &t, nil return &t, nil
} }
func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, account *Account) (*Transaction, error) { func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *models.Security, account *Account) (*Transaction, error) {
t := i.GetInvTran(&sell.InvTran) t := i.GetInvTran(&sell.InvTran)
security, err := i.GetSecurityAlternateId(string(sell.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(sell.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -819,7 +820,7 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun
func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) (*Transaction, error) { func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) (*Transaction, error) {
t := i.GetInvTran(&transfer.InvTran) t := i.GetInvTran(&transfer.InvTran)
security, err := i.GetSecurityAlternateId(string(transfer.SecID.UniqueID), Stock) security, err := i.GetSecurityAlternateId(string(transfer.SecID.UniqueID), models.Stock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -858,7 +859,7 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account)
return &t, nil return &t, nil
} }
func (i *OFXImport) AddInvTransaction(invtran *ofxgo.InvTransaction, account *Account, curdef *Security) error { func (i *OFXImport) AddInvTransaction(invtran *ofxgo.InvTransaction, account *Account, curdef *models.Security) error {
if curdef.SecurityId < 1 || curdef.SecurityId > int64(len(i.Securities)) { if curdef.SecurityId < 1 || curdef.SecurityId > int64(len(i.Securities)) {
return errors.New("Internal error: security index not found in OFX import\n") return errors.New("Internal error: security index not found in OFX import\n")
} }

View File

@ -3,6 +3,7 @@ package handlers_test
import ( import (
"fmt" "fmt"
"github.com/aclindsa/moneygo/internal/handlers" "github.com/aclindsa/moneygo/internal/handlers"
"github.com/aclindsa/moneygo/internal/models"
"net/http" "net/http"
"strconv" "strconv"
"testing" "testing"
@ -63,7 +64,7 @@ func TestImportOFXCreditCard(t *testing.T) {
}) })
} }
func findSecurity(client *http.Client, symbol string, tipe handlers.SecurityType) (*handlers.Security, error) { func findSecurity(client *http.Client, symbol string, tipe models.SecurityType) (*models.Security, error) {
securities, err := getSecurities(client) securities, err := getSecurities(client)
if err != nil { if err != nil {
return nil, err return nil, err
@ -125,7 +126,7 @@ func TestImportOFX401kMutualFunds(t *testing.T) {
// Make sure the security was created and that the trading account has // Make sure the security was created and that the trading account has
// the right value // the right value
security, err := findSecurity(d.clients[0], "VANGUARD TARGET 2045", handlers.Stock) security, err := findSecurity(d.clients[0], "VANGUARD TARGET 2045", models.Stock)
if err != nil { if err != nil {
t.Fatalf("Error finding VANGUARD TARGET 2045 security: %s\n", err) t.Fatalf("Error finding VANGUARD TARGET 2045 security: %s\n", err)
} }
@ -204,7 +205,7 @@ func TestImportOFXBrokerage(t *testing.T) {
} }
for _, check := range checks { for _, check := range checks {
security, err := findSecurity(d.clients[0], check.Ticker, handlers.Stock) security, err := findSecurity(d.clients[0], check.Ticker, models.Stock)
if err != nil { if err != nil {
t.Fatalf("Error finding security: %s\n", err) t.Fatalf("Error finding security: %s\n", err)
} }

View File

@ -90,7 +90,7 @@ func GetPrices(tx *Tx, securityid int64) (*[]*Price, error) {
} }
// Return the latest price for security in currency units before date // Return the latest price for security in currency units before date
func GetLatestPrice(tx *Tx, security, currency *Security, date *time.Time) (*Price, error) { func GetLatestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) {
var p Price var p 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) 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 { if err != nil {
@ -100,7 +100,7 @@ func GetLatestPrice(tx *Tx, security, currency *Security, date *time.Time) (*Pri
} }
// Return the earliest price for security in currency units after date // Return the earliest price for security in currency units after date
func GetEarliestPrice(tx *Tx, security, currency *Security, date *time.Time) (*Price, error) { func GetEarliestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) {
var p Price var p 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) 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 { if err != nil {
@ -110,7 +110,7 @@ func GetEarliestPrice(tx *Tx, security, currency *Security, date *time.Time) (*P
} }
// Return the price for security in currency closest to date // Return the price for security in currency closest to date
func GetClosestPrice(tx *Tx, security, currency *Security, date *time.Time) (*Price, error) { func GetClosestPrice(tx *Tx, security, currency *models.Security, date *time.Time) (*Price, error) {
earliest, _ := GetEarliestPrice(tx, security, currency, date) earliest, _ := GetEarliestPrice(tx, security, currency, date)
latest, err := GetLatestPrice(tx, security, currency, date) latest, err := GetLatestPrice(tx, security, currency, date)

View File

@ -26,7 +26,7 @@ class Security(object):
self.type = _type self.type = _type
self.precision = precision self.precision = precision
def unicode(self): def unicode(self):
s = """\tSecurity{ s = """\t{
\t\tName: \"%s\", \t\tName: \"%s\",
\t\tDescription: \"%s\", \t\tDescription: \"%s\",
\t\tSymbol: \"%s\", \t\tSymbol: \"%s\",
@ -72,7 +72,7 @@ def process_ccyntry(currency_list, node):
else: else:
precision = int(n.firstChild.nodeValue) precision = int(n.firstChild.nodeValue)
if nameSet and numberSet: if nameSet and numberSet:
currency_list.add(Security(name, description, number, "Currency", precision)) currency_list.add(Security(name, description, number, "models.Currency", precision))
def get_currency_list(): def get_currency_list():
currency_list = SecurityList("ISO 4217, from http://www.currency-iso.org/en/home/tables/table-a1.html") currency_list = SecurityList("ISO 4217, from http://www.currency-iso.org/en/home/tables/table-a1.html")
@ -97,7 +97,7 @@ def get_cusip_list(filename):
cusip = row[0] cusip = row[0]
name = row[1] name = row[1]
description = ",".join(row[2:]) description = ",".join(row[2:])
cusip_list.add(Security(name, description, cusip, "Stock", 5)) cusip_list.add(Security(name, description, cusip, "models.Stock", 5))
return cusip_list return cusip_list
def main(): def main():
@ -105,7 +105,10 @@ def main():
cusip_list = get_cusip_list('cusip_list.csv') cusip_list = get_cusip_list('cusip_list.csv')
print("package handlers\n") print("package handlers\n")
print("var SecurityTemplates = []Security{") print("import (")
print("\t\"github.com/aclindsa/moneygo/internal/models\"")
print(")\n")
print("var SecurityTemplates = []models.Security{")
print(currency_list.unicode()) print(currency_list.unicode())
print(cusip_list.unicode()) print(cusip_list.unicode())
print("}") print("}")

View File

@ -3,9 +3,9 @@ package handlers
//go:generate make //go:generate make
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/aclindsa/moneygo/internal/models"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@ -13,64 +13,9 @@ import (
"strings" "strings"
) )
type SecurityType int64 func SearchSecurityTemplates(search string, _type models.SecurityType, limit int64) []*models.Security {
const (
Currency SecurityType = 1
Stock = 2
)
func GetSecurityType(typestring string) SecurityType {
if strings.EqualFold(typestring, "currency") {
return Currency
} else if strings.EqualFold(typestring, "stock") {
return Stock
} else {
return 0
}
}
type Security struct {
SecurityId int64
UserId int64
Name string
Description string
Symbol string
// Number of decimal digits (to the right of the decimal point) this
// security is precise to
Precision int `db:"Preciseness"`
Type SecurityType
// AlternateId is CUSIP for Type=Stock, ISO4217 for Type=Currency
AlternateId string
}
type SecurityList struct {
Securities *[]*Security `json:"securities"`
}
func (s *Security) Read(json_str string) error {
dec := json.NewDecoder(strings.NewReader(json_str))
return dec.Decode(s)
}
func (s *Security) Write(w http.ResponseWriter) error {
enc := json.NewEncoder(w)
return enc.Encode(s)
}
func (sl *SecurityList) Read(json_str string) error {
dec := json.NewDecoder(strings.NewReader(json_str))
return dec.Decode(sl)
}
func (sl *SecurityList) Write(w http.ResponseWriter) error {
enc := json.NewEncoder(w)
return enc.Encode(sl)
}
func SearchSecurityTemplates(search string, _type SecurityType, limit int64) []*Security {
upperSearch := strings.ToUpper(search) upperSearch := strings.ToUpper(search)
var results []*Security var results []*models.Security
for i, security := range SecurityTemplates { for i, security := range SecurityTemplates {
if strings.Contains(strings.ToUpper(security.Name), upperSearch) || if strings.Contains(strings.ToUpper(security.Name), upperSearch) ||
strings.Contains(strings.ToUpper(security.Description), upperSearch) || strings.Contains(strings.ToUpper(security.Description), upperSearch) ||
@ -86,7 +31,7 @@ func SearchSecurityTemplates(search string, _type SecurityType, limit int64) []*
return results return results
} }
func FindSecurityTemplate(name string, _type SecurityType) *Security { func FindSecurityTemplate(name string, _type models.SecurityType) *models.Security {
for _, security := range SecurityTemplates { for _, security := range SecurityTemplates {
if name == security.Name && _type == security.Type { if name == security.Name && _type == security.Type {
return &security return &security
@ -95,18 +40,18 @@ func FindSecurityTemplate(name string, _type SecurityType) *Security {
return nil return nil
} }
func FindCurrencyTemplate(iso4217 int64) *Security { func FindCurrencyTemplate(iso4217 int64) *models.Security {
iso4217string := strconv.FormatInt(iso4217, 10) iso4217string := strconv.FormatInt(iso4217, 10)
for _, security := range SecurityTemplates { for _, security := range SecurityTemplates {
if security.Type == Currency && security.AlternateId == iso4217string { if security.Type == models.Currency && security.AlternateId == iso4217string {
return &security return &security
} }
} }
return nil return nil
} }
func GetSecurity(tx *Tx, securityid int64, userid int64) (*Security, error) { func GetSecurity(tx *Tx, securityid int64, userid int64) (*models.Security, error) {
var s Security var s models.Security
err := tx.SelectOne(&s, "SELECT * from securities where UserId=? AND SecurityId=?", userid, securityid) err := tx.SelectOne(&s, "SELECT * from securities where UserId=? AND SecurityId=?", userid, securityid)
if err != nil { if err != nil {
@ -115,8 +60,8 @@ func GetSecurity(tx *Tx, securityid int64, userid int64) (*Security, error) {
return &s, nil return &s, nil
} }
func GetSecurities(tx *Tx, userid int64) (*[]*Security, error) { func GetSecurities(tx *Tx, userid int64) (*[]*models.Security, error) {
var securities []*Security var securities []*models.Security
_, err := tx.Select(&securities, "SELECT * from securities where UserId=?", userid) _, err := tx.Select(&securities, "SELECT * from securities where UserId=?", userid)
if err != nil { if err != nil {
@ -125,7 +70,7 @@ func GetSecurities(tx *Tx, userid int64) (*[]*Security, error) {
return &securities, nil return &securities, nil
} }
func InsertSecurity(tx *Tx, s *Security) error { func InsertSecurity(tx *Tx, s *models.Security) error {
err := tx.Insert(s) err := tx.Insert(s)
if err != nil { if err != nil {
return err return err
@ -133,11 +78,11 @@ func InsertSecurity(tx *Tx, s *Security) error {
return nil return nil
} }
func UpdateSecurity(tx *Tx, s *Security) (err error) { func UpdateSecurity(tx *Tx, s *models.Security) (err error) {
user, err := GetUser(tx, s.UserId) user, err := GetUser(tx, s.UserId)
if err != nil { if err != nil {
return return
} else if user.DefaultCurrency == s.SecurityId && s.Type != Currency { } else if user.DefaultCurrency == s.SecurityId && s.Type != models.Currency {
return errors.New("Cannot change security which is user's default currency to be non-currency") return errors.New("Cannot change security which is user's default currency to be non-currency")
} }
@ -160,7 +105,7 @@ func (e SecurityInUseError) Error() string {
return e.message return e.message
} }
func DeleteSecurity(tx *Tx, s *Security) error { func DeleteSecurity(tx *Tx, s *models.Security) error {
// First, ensure no accounts are using this security // First, ensure no accounts are using this security
accounts, err := tx.SelectInt("SELECT count(*) from accounts where UserId=? and SecurityId=?", s.UserId, s.SecurityId) accounts, err := tx.SelectInt("SELECT count(*) from accounts where UserId=? and SecurityId=?", s.UserId, s.SecurityId)
@ -193,7 +138,7 @@ func DeleteSecurity(tx *Tx, s *Security) error {
return nil return nil
} }
func ImportGetCreateSecurity(tx *Tx, userid int64, security *Security) (*Security, error) { func ImportGetCreateSecurity(tx *Tx, userid int64, security *models.Security) (*models.Security, error) {
security.UserId = userid security.UserId = userid
if len(security.AlternateId) == 0 { if len(security.AlternateId) == 0 {
// Always create a new local security if we can't match on the AlternateId // Always create a new local security if we can't match on the AlternateId
@ -204,7 +149,7 @@ func ImportGetCreateSecurity(tx *Tx, userid int64, security *Security) (*Securit
return security, nil return security, nil
} }
var securities []*Security var securities []*models.Security
_, err := tx.Select(&securities, "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Preciseness=?", userid, security.Type, security.AlternateId, security.Precision) _, err := tx.Select(&securities, "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Preciseness=?", userid, security.Type, security.AlternateId, security.Precision)
if err != nil { if err != nil {
@ -264,7 +209,7 @@ func SecurityHandler(r *http.Request, context *Context) ResponseWriterWriter {
return PriceHandler(r, context, user, securityid) return PriceHandler(r, context, user, securityid)
} }
var security Security var security models.Security
if err := ReadJSON(r, &security); err != nil { if err := ReadJSON(r, &security); err != nil {
return NewError(3 /*Invalid Request*/) return NewError(3 /*Invalid Request*/)
} }
@ -281,7 +226,7 @@ func SecurityHandler(r *http.Request, context *Context) ResponseWriterWriter {
} else if r.Method == "GET" { } else if r.Method == "GET" {
if context.LastLevel() { if context.LastLevel() {
//Return all securities //Return all securities
var sl SecurityList var sl models.SecurityList
securities, err := GetSecurities(context.Tx, user.UserId) securities, err := GetSecurities(context.Tx, user.UserId)
if err != nil { if err != nil {
@ -324,7 +269,7 @@ func SecurityHandler(r *http.Request, context *Context) ResponseWriterWriter {
} }
if r.Method == "PUT" { if r.Method == "PUT" {
var security Security var security models.Security
if err := ReadJSON(r, &security); err != nil || security.SecurityId != securityid { if err := ReadJSON(r, &security); err != nil || security.SecurityId != securityid {
return NewError(3 /*Invalid Request*/) return NewError(3 /*Invalid Request*/)
} }
@ -359,17 +304,17 @@ func SecurityHandler(r *http.Request, context *Context) ResponseWriterWriter {
func SecurityTemplateHandler(r *http.Request, context *Context) ResponseWriterWriter { func SecurityTemplateHandler(r *http.Request, context *Context) ResponseWriterWriter {
if r.Method == "GET" { if r.Method == "GET" {
var sl SecurityList var sl models.SecurityList
query, _ := url.ParseQuery(r.URL.RawQuery) query, _ := url.ParseQuery(r.URL.RawQuery)
var limit int64 = -1 var limit int64 = -1
search := query.Get("search") search := query.Get("search")
var _type SecurityType = 0 var _type models.SecurityType = 0
typestring := query.Get("type") typestring := query.Get("type")
if len(typestring) > 0 { if len(typestring) > 0 {
_type = GetSecurityType(typestring) _type = models.GetSecurityType(typestring)
if _type == 0 { if _type == 0 {
return NewError(3 /*Invalid Request*/) return NewError(3 /*Invalid Request*/)
} }

View File

@ -9,8 +9,8 @@ import (
const luaSecurityTypeName = "security" const luaSecurityTypeName = "security"
func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) { func luaContextGetSecurities(L *lua.LState) (map[int64]*models.Security, error) {
var security_map map[int64]*Security var security_map map[int64]*models.Security
ctx := L.Context() ctx := L.Context()
@ -19,7 +19,7 @@ func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) {
return nil, errors.New("Couldn't find tx in lua's Context") return nil, errors.New("Couldn't find tx in lua's Context")
} }
security_map, ok = ctx.Value(securitiesContextKey).(map[int64]*Security) security_map, ok = ctx.Value(securitiesContextKey).(map[int64]*models.Security)
if !ok { if !ok {
user, ok := ctx.Value(userContextKey).(*models.User) user, ok := ctx.Value(userContextKey).(*models.User)
if !ok { if !ok {
@ -31,7 +31,7 @@ func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) {
return nil, err return nil, err
} }
security_map = make(map[int64]*Security) security_map = make(map[int64]*models.Security)
for i := range *securities { for i := range *securities {
security_map[(*securities)[i].SecurityId] = (*securities)[i] security_map[(*securities)[i].SecurityId] = (*securities)[i]
} }
@ -43,7 +43,7 @@ func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) {
return security_map, nil return security_map, nil
} }
func luaContextGetDefaultCurrency(L *lua.LState) (*Security, error) { func luaContextGetDefaultCurrency(L *lua.LState) (*models.Security, error) {
security_map, err := luaContextGetSecurities(L) security_map, err := luaContextGetSecurities(L)
if err != nil { if err != nil {
return nil, err return nil, err
@ -107,7 +107,7 @@ func luaRegisterSecurities(L *lua.LState) {
L.SetGlobal("get_default_currency", getDefaultCurrencyFn) L.SetGlobal("get_default_currency", getDefaultCurrencyFn)
} }
func SecurityToLua(L *lua.LState, security *Security) *lua.LUserData { func SecurityToLua(L *lua.LState, security *models.Security) *lua.LUserData {
ud := L.NewUserData() ud := L.NewUserData()
ud.Value = security ud.Value = security
L.SetMetatable(ud, L.GetTypeMetatable(luaSecurityTypeName)) L.SetMetatable(ud, L.GetTypeMetatable(luaSecurityTypeName))
@ -115,9 +115,9 @@ func SecurityToLua(L *lua.LState, security *Security) *lua.LUserData {
} }
// Checks whether the first lua argument is a *LUserData with *Security and returns this *Security. // Checks whether the first lua argument is a *LUserData with *Security and returns this *Security.
func luaCheckSecurity(L *lua.LState, n int) *Security { func luaCheckSecurity(L *lua.LState, n int) *models.Security {
ud := L.CheckUserData(n) ud := L.CheckUserData(n)
if security, ok := ud.Value.(*Security); ok { if security, ok := ud.Value.(*models.Security); ok {
return security return security
} }
L.ArgError(n, "security expected") L.ArgError(n, "security expected")

View File

@ -2,19 +2,20 @@ package handlers_test
import ( import (
"github.com/aclindsa/moneygo/internal/handlers" "github.com/aclindsa/moneygo/internal/handlers"
"github.com/aclindsa/moneygo/internal/models"
"net/http" "net/http"
"strconv" "strconv"
"testing" "testing"
) )
func createSecurity(client *http.Client, security *handlers.Security) (*handlers.Security, error) { func createSecurity(client *http.Client, security *models.Security) (*models.Security, error) {
var s handlers.Security var s models.Security
err := create(client, security, &s, "/v1/securities/") err := create(client, security, &s, "/v1/securities/")
return &s, err return &s, err
} }
func getSecurity(client *http.Client, securityid int64) (*handlers.Security, error) { func getSecurity(client *http.Client, securityid int64) (*models.Security, error) {
var s handlers.Security var s models.Security
err := read(client, &s, "/v1/securities/"+strconv.FormatInt(securityid, 10)) err := read(client, &s, "/v1/securities/"+strconv.FormatInt(securityid, 10))
if err != nil { if err != nil {
return nil, err return nil, err
@ -22,8 +23,8 @@ func getSecurity(client *http.Client, securityid int64) (*handlers.Security, err
return &s, nil return &s, nil
} }
func getSecurities(client *http.Client) (*handlers.SecurityList, error) { func getSecurities(client *http.Client) (*models.SecurityList, error) {
var sl handlers.SecurityList var sl models.SecurityList
err := read(client, &sl, "/v1/securities/") err := read(client, &sl, "/v1/securities/")
if err != nil { if err != nil {
return nil, err return nil, err
@ -31,8 +32,8 @@ func getSecurities(client *http.Client) (*handlers.SecurityList, error) {
return &sl, nil return &sl, nil
} }
func updateSecurity(client *http.Client, security *handlers.Security) (*handlers.Security, error) { func updateSecurity(client *http.Client, security *models.Security) (*models.Security, error) {
var s handlers.Security var s models.Security
err := update(client, security, &s, "/v1/securities/"+strconv.FormatInt(security.SecurityId, 10)) err := update(client, security, &s, "/v1/securities/"+strconv.FormatInt(security.SecurityId, 10))
if err != nil { if err != nil {
return nil, err return nil, err
@ -40,7 +41,7 @@ func updateSecurity(client *http.Client, security *handlers.Security) (*handlers
return &s, nil return &s, nil
} }
func deleteSecurity(client *http.Client, s *handlers.Security) error { func deleteSecurity(client *http.Client, s *models.Security) error {
err := remove(client, "/v1/securities/"+strconv.FormatInt(s.SecurityId, 10)) err := remove(client, "/v1/securities/"+strconv.FormatInt(s.SecurityId, 10))
if err != nil { if err != nil {
return err return err

View File

@ -2,12 +2,13 @@ package handlers_test
import ( import (
"github.com/aclindsa/moneygo/internal/handlers" "github.com/aclindsa/moneygo/internal/handlers"
"github.com/aclindsa/moneygo/internal/models"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
func TestSecurityTemplates(t *testing.T) { func TestSecurityTemplates(t *testing.T) {
var sl handlers.SecurityList var sl models.SecurityList
response, err := server.Client().Get(server.URL + "/v1/securitytemplates/?search=USD&type=currency") response, err := server.Client().Get(server.URL + "/v1/securitytemplates/?search=USD&type=currency")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -30,7 +31,7 @@ func TestSecurityTemplates(t *testing.T) {
num_usd := 0 num_usd := 0
if sl.Securities != nil { if sl.Securities != nil {
for _, s := range *sl.Securities { for _, s := range *sl.Securities {
if s.Type != handlers.Currency { if s.Type != models.Currency {
t.Fatalf("Requested Currency-only security templates, received a non-Currency template for %s", s.Name) t.Fatalf("Requested Currency-only security templates, received a non-Currency template for %s", s.Name)
} }
@ -46,7 +47,7 @@ func TestSecurityTemplates(t *testing.T) {
} }
func TestSecurityTemplateLimit(t *testing.T) { func TestSecurityTemplateLimit(t *testing.T) {
var sl handlers.SecurityList var sl models.SecurityList
response, err := server.Client().Get(server.URL + "/v1/securitytemplates/?search=e&limit=5") response, err := server.Client().Get(server.URL + "/v1/securitytemplates/?search=e&limit=5")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/aclindsa/moneygo/internal/handlers" "github.com/aclindsa/moneygo/internal/handlers"
"github.com/aclindsa/moneygo/internal/models"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@ -36,7 +37,7 @@ type TestData struct {
initialized bool initialized bool
users []User users []User
clients []*http.Client clients []*http.Client
securities []handlers.Security securities []models.Security
prices []handlers.Price prices []handlers.Price
accounts []handlers.Account // accounts must appear after their parents in this slice accounts []handlers.Account // accounts must appear after their parents in this slice
transactions []handlers.Transaction transactions []handlers.Transaction
@ -170,14 +171,14 @@ var data = []TestData{
Email: "bbob+moneygo@my-domain.com", Email: "bbob+moneygo@my-domain.com",
}, },
}, },
securities: []handlers.Security{ securities: []models.Security{
{ {
UserId: 0, UserId: 0,
Name: "USD", Name: "USD",
Description: "US Dollar", Description: "US Dollar",
Symbol: "$", Symbol: "$",
Precision: 2, Precision: 2,
Type: handlers.Currency, Type: models.Currency,
AlternateId: "840", AlternateId: "840",
}, },
{ {
@ -186,7 +187,7 @@ var data = []TestData{
Description: "SPDR S&P 500 ETF Trust", Description: "SPDR S&P 500 ETF Trust",
Symbol: "SPY", Symbol: "SPY",
Precision: 5, Precision: 5,
Type: handlers.Stock, Type: models.Stock,
AlternateId: "78462F103", AlternateId: "78462F103",
}, },
{ {
@ -195,7 +196,7 @@ var data = []TestData{
Description: "Euro", Description: "Euro",
Symbol: "€", Symbol: "€",
Precision: 2, Precision: 2,
Type: handlers.Currency, Type: models.Currency,
AlternateId: "978", AlternateId: "978",
}, },
{ {
@ -204,7 +205,7 @@ var data = []TestData{
Description: "Euro", Description: "Euro",
Symbol: "€", Symbol: "€",
Precision: 2, Precision: 2,
Type: handlers.Currency, Type: models.Currency,
AlternateId: "978", AlternateId: "978",
}, },
}, },

View File

@ -54,7 +54,7 @@ func InsertUser(tx *Tx, u *models.User) error {
} }
// Copy the security template and give it our new UserId // Copy the security template and give it our new UserId
var security Security var security models.Security
security = *security_template security = *security_template
security.UserId = u.UserId security.UserId = u.UserId
@ -89,7 +89,7 @@ func UpdateUser(tx *Tx, u *models.User) error {
return err return err
} else if security.UserId != u.UserId || security.SecurityId != u.DefaultCurrency { } else if security.UserId != u.UserId || security.SecurityId != u.DefaultCurrency {
return errors.New("UserId and DefaultCurrency don't match the fetched security") return errors.New("UserId and DefaultCurrency don't match the fetched security")
} else if security.Type != Currency { } else if security.Type != models.Currency {
return errors.New("New DefaultCurrency security is not a currency") return errors.New("New DefaultCurrency security is not a currency")
} }

View File

@ -0,0 +1,62 @@
package models
import (
"encoding/json"
"net/http"
"strings"
)
type SecurityType int64
const (
Currency SecurityType = 1
Stock = 2
)
func GetSecurityType(typestring string) SecurityType {
if strings.EqualFold(typestring, "currency") {
return Currency
} else if strings.EqualFold(typestring, "stock") {
return Stock
} else {
return 0
}
}
type Security struct {
SecurityId int64
UserId int64
Name string
Description string
Symbol string
// Number of decimal digits (to the right of the decimal point) this
// security is precise to
Precision int `db:"Preciseness"`
Type SecurityType
// AlternateId is CUSIP for Type=Stock, ISO4217 for Type=Currency
AlternateId string
}
type SecurityList struct {
Securities *[]*Security `json:"securities"`
}
func (s *Security) Read(json_str string) error {
dec := json.NewDecoder(strings.NewReader(json_str))
return dec.Decode(s)
}
func (s *Security) Write(w http.ResponseWriter) error {
enc := json.NewEncoder(w)
return enc.Encode(s)
}
func (sl *SecurityList) Read(json_str string) error {
dec := json.NewDecoder(strings.NewReader(json_str))
return dec.Decode(sl)
}
func (sl *SecurityList) Write(w http.ResponseWriter) error {
enc := json.NewEncoder(w)
return enc.Encode(sl)
}