mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-31 09:53:27 -04:00 
			
		
		
		
	Merge pull request #32 from aclindsa/models_split
Split models from handlers into own internal package
This commit is contained in:
		| @@ -5,7 +5,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/aclindsa/gorp" | 	"github.com/aclindsa/gorp" | ||||||
| 	"github.com/aclindsa/moneygo/internal/config" | 	"github.com/aclindsa/moneygo/internal/config" | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	_ "github.com/go-sql-driver/mysql" | 	_ "github.com/go-sql-driver/mysql" | ||||||
| 	_ "github.com/lib/pq" | 	_ "github.com/lib/pq" | ||||||
| 	_ "github.com/mattn/go-sqlite3" | 	_ "github.com/mattn/go-sqlite3" | ||||||
| @@ -13,6 +13,9 @@ import ( | |||||||
| 	"strings" | 	"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 | const luaMaxLengthBuffer int = 4096 | ||||||
|  |  | ||||||
| func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { | func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { | ||||||
| @@ -33,15 +36,15 @@ func GetDbMap(db *sql.DB, dbtype config.DbType) (*gorp.DbMap, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dbmap := &gorp.DbMap{Db: db, Dialect: dialect} | 	dbmap := &gorp.DbMap{Db: db, Dialect: dialect} | ||||||
| 	dbmap.AddTableWithName(handlers.User{}, "users").SetKeys(true, "UserId") | 	dbmap.AddTableWithName(models.User{}, "users").SetKeys(true, "UserId") | ||||||
| 	dbmap.AddTableWithName(handlers.Session{}, "sessions").SetKeys(true, "SessionId") | 	dbmap.AddTableWithName(models.Session{}, "sessions").SetKeys(true, "SessionId") | ||||||
| 	dbmap.AddTableWithName(handlers.Account{}, "accounts").SetKeys(true, "AccountId") | 	dbmap.AddTableWithName(models.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(models.Transaction{}, "transactions").SetKeys(true, "TransactionId") | ||||||
| 	dbmap.AddTableWithName(handlers.Split{}, "splits").SetKeys(true, "SplitId") | 	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 := dbmap.AddTableWithName(models.Report{}, "reports").SetKeys(true, "ReportId") | ||||||
| 	rtable.ColMap("Lua").SetMaxSize(handlers.LuaMaxLength + luaMaxLengthBuffer) | 	rtable.ColMap("Lua").SetMaxSize(models.LuaMaxLength + luaMaxLengthBuffer) | ||||||
|  |  | ||||||
| 	err := dbmap.CreateTablesIfNotExists() | 	err := dbmap.CreateTablesIfNotExists() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -1,126 +1,14 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AccountType int64 | func GetAccount(tx *Tx, accountid int64, userid int64) (*models.Account, error) { | ||||||
|  | 	var a models.Account | ||||||
| const ( |  | ||||||
| 	Bank       AccountType = 1 // start at 1 so that the default (0) is invalid |  | ||||||
| 	Cash                   = 2 |  | ||||||
| 	Asset                  = 3 |  | ||||||
| 	Liability              = 4 |  | ||||||
| 	Investment             = 5 |  | ||||||
| 	Income                 = 6 |  | ||||||
| 	Expense                = 7 |  | ||||||
| 	Trading                = 8 |  | ||||||
| 	Equity                 = 9 |  | ||||||
| 	Receivable             = 10 |  | ||||||
| 	Payable                = 11 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var AccountTypes = []AccountType{ |  | ||||||
| 	Bank, |  | ||||||
| 	Cash, |  | ||||||
| 	Asset, |  | ||||||
| 	Liability, |  | ||||||
| 	Investment, |  | ||||||
| 	Income, |  | ||||||
| 	Expense, |  | ||||||
| 	Trading, |  | ||||||
| 	Equity, |  | ||||||
| 	Receivable, |  | ||||||
| 	Payable, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t AccountType) String() string { |  | ||||||
| 	switch t { |  | ||||||
| 	case Bank: |  | ||||||
| 		return "Bank" |  | ||||||
| 	case Cash: |  | ||||||
| 		return "Cash" |  | ||||||
| 	case Asset: |  | ||||||
| 		return "Asset" |  | ||||||
| 	case Liability: |  | ||||||
| 		return "Liability" |  | ||||||
| 	case Investment: |  | ||||||
| 		return "Investment" |  | ||||||
| 	case Income: |  | ||||||
| 		return "Income" |  | ||||||
| 	case Expense: |  | ||||||
| 		return "Expense" |  | ||||||
| 	case Trading: |  | ||||||
| 		return "Trading" |  | ||||||
| 	case Equity: |  | ||||||
| 		return "Equity" |  | ||||||
| 	case Receivable: |  | ||||||
| 		return "Receivable" |  | ||||||
| 	case Payable: |  | ||||||
| 		return "Payable" |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Account struct { |  | ||||||
| 	AccountId         int64 |  | ||||||
| 	ExternalAccountId string |  | ||||||
| 	UserId            int64 |  | ||||||
| 	SecurityId        int64 |  | ||||||
| 	ParentAccountId   int64 // -1 if this account is at the root |  | ||||||
| 	Type              AccountType |  | ||||||
| 	Name              string |  | ||||||
|  |  | ||||||
| 	// monotonically-increasing account transaction version number. Used for |  | ||||||
| 	// allowing a client to ensure they have a consistent version when paging |  | ||||||
| 	// through transactions. |  | ||||||
| 	AccountVersion int64 `json:"Version"` |  | ||||||
|  |  | ||||||
| 	// Optional fields specifying how to fetch transactions from a bank via OFX |  | ||||||
| 	OFXURL       string |  | ||||||
| 	OFXORG       string |  | ||||||
| 	OFXFID       string |  | ||||||
| 	OFXUser      string |  | ||||||
| 	OFXBankID    string // OFX BankID (BrokerID if AcctType == Investment) |  | ||||||
| 	OFXAcctID    string |  | ||||||
| 	OFXAcctType  string // ofxgo.acctType |  | ||||||
| 	OFXClientUID string |  | ||||||
| 	OFXAppID     string |  | ||||||
| 	OFXAppVer    string |  | ||||||
| 	OFXVersion   string |  | ||||||
| 	OFXNoIndent  bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type AccountList struct { |  | ||||||
| 	Accounts *[]Account `json:"accounts"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *Account) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(a) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *Account) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(a) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (al *AccountList) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(al) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (al *AccountList) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(al) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetAccount(tx *Tx, accountid int64, userid int64) (*Account, error) { |  | ||||||
| 	var a Account |  | ||||||
|  |  | ||||||
| 	err := tx.SelectOne(&a, "SELECT * from accounts where UserId=? AND AccountId=?", userid, accountid) | 	err := tx.SelectOne(&a, "SELECT * from accounts where UserId=? AND AccountId=?", userid, accountid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -129,8 +17,8 @@ func GetAccount(tx *Tx, accountid int64, userid int64) (*Account, error) { | |||||||
| 	return &a, nil | 	return &a, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetAccounts(tx *Tx, userid int64) (*[]Account, error) { | func GetAccounts(tx *Tx, userid int64) (*[]models.Account, error) { | ||||||
| 	var accounts []Account | 	var accounts []models.Account | ||||||
|  |  | ||||||
| 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=?", userid) | 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=?", userid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -141,9 +29,9 @@ func GetAccounts(tx *Tx, userid int64) (*[]Account, error) { | |||||||
|  |  | ||||||
| // Get (and attempt to create if it doesn't exist). Matches on UserId, | // Get (and attempt to create if it doesn't exist). Matches on UserId, | ||||||
| // SecurityId, Type, Name, and ParentAccountId | // SecurityId, Type, Name, and ParentAccountId | ||||||
| func GetCreateAccount(tx *Tx, a Account) (*Account, error) { | func GetCreateAccount(tx *Tx, a models.Account) (*models.Account, error) { | ||||||
| 	var accounts []Account | 	var accounts []models.Account | ||||||
| 	var account Account | 	var account models.Account | ||||||
|  |  | ||||||
| 	// Try to find the top-level trading account | 	// Try to find the top-level trading account | ||||||
| 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=? AND SecurityId=? AND Type=? AND Name=? AND ParentAccountId=? ORDER BY AccountId ASC LIMIT 1", a.UserId, a.SecurityId, a.Type, a.Name, a.ParentAccountId) | 	_, err := tx.Select(&accounts, "SELECT * from accounts where UserId=? AND SecurityId=? AND Type=? AND Name=? AND ParentAccountId=? ORDER BY AccountId ASC LIMIT 1", a.UserId, a.SecurityId, a.Type, a.Name, a.ParentAccountId) | ||||||
| @@ -169,9 +57,9 @@ func GetCreateAccount(tx *Tx, a Account) (*Account, error) { | |||||||
|  |  | ||||||
| // Get (and attempt to create if it doesn't exist) the security/currency | // Get (and attempt to create if it doesn't exist) the security/currency | ||||||
| // trading account for the supplied security/currency | // trading account for the supplied security/currency | ||||||
| func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) { | func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*models.Account, error) { | ||||||
| 	var tradingAccount Account | 	var tradingAccount models.Account | ||||||
| 	var account Account | 	var account models.Account | ||||||
|  |  | ||||||
| 	user, err := GetUser(tx, userid) | 	user, err := GetUser(tx, userid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -179,7 +67,7 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tradingAccount.UserId = userid | 	tradingAccount.UserId = userid | ||||||
| 	tradingAccount.Type = Trading | 	tradingAccount.Type = models.Trading | ||||||
| 	tradingAccount.Name = "Trading" | 	tradingAccount.Name = "Trading" | ||||||
| 	tradingAccount.SecurityId = user.DefaultCurrency | 	tradingAccount.SecurityId = user.DefaultCurrency | ||||||
| 	tradingAccount.ParentAccountId = -1 | 	tradingAccount.ParentAccountId = -1 | ||||||
| @@ -199,7 +87,7 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | |||||||
| 	account.Name = security.Name | 	account.Name = security.Name | ||||||
| 	account.ParentAccountId = ta.AccountId | 	account.ParentAccountId = ta.AccountId | ||||||
| 	account.SecurityId = securityid | 	account.SecurityId = securityid | ||||||
| 	account.Type = Trading | 	account.Type = models.Trading | ||||||
|  |  | ||||||
| 	a, err := GetCreateAccount(tx, account) | 	a, err := GetCreateAccount(tx, account) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -211,10 +99,10 @@ func GetTradingAccount(tx *Tx, userid int64, securityid int64) (*Account, error) | |||||||
|  |  | ||||||
| // Get (and attempt to create if it doesn't exist) the security/currency | // Get (and attempt to create if it doesn't exist) the security/currency | ||||||
| // imbalance account for the supplied security/currency | // imbalance account for the supplied security/currency | ||||||
| func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, error) { | func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*models.Account, error) { | ||||||
| 	var imbalanceAccount Account | 	var imbalanceAccount models.Account | ||||||
| 	var account Account | 	var account models.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") | ||||||
| 	} | 	} | ||||||
| @@ -227,7 +115,7 @@ func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, erro | |||||||
| 	imbalanceAccount.Name = "Imbalances" | 	imbalanceAccount.Name = "Imbalances" | ||||||
| 	imbalanceAccount.ParentAccountId = -1 | 	imbalanceAccount.ParentAccountId = -1 | ||||||
| 	imbalanceAccount.SecurityId = xxxsecurity.SecurityId | 	imbalanceAccount.SecurityId = xxxsecurity.SecurityId | ||||||
| 	imbalanceAccount.Type = Bank | 	imbalanceAccount.Type = models.Bank | ||||||
|  |  | ||||||
| 	// Find/create the top-level trading account | 	// Find/create the top-level trading account | ||||||
| 	ia, err := GetCreateAccount(tx, imbalanceAccount) | 	ia, err := GetCreateAccount(tx, imbalanceAccount) | ||||||
| @@ -244,7 +132,7 @@ func GetImbalanceAccount(tx *Tx, userid int64, securityid int64) (*Account, erro | |||||||
| 	account.Name = security.Name | 	account.Name = security.Name | ||||||
| 	account.ParentAccountId = ia.AccountId | 	account.ParentAccountId = ia.AccountId | ||||||
| 	account.SecurityId = securityid | 	account.SecurityId = securityid | ||||||
| 	account.Type = Bank | 	account.Type = models.Bank | ||||||
|  |  | ||||||
| 	a, err := GetCreateAccount(tx, account) | 	a, err := GetCreateAccount(tx, account) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -272,7 +160,7 @@ func (cae CircularAccountsError) Error() string { | |||||||
| 	return "Would result in circular account relationship" | 	return "Would result in circular account relationship" | ||||||
| } | } | ||||||
|  |  | ||||||
| func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | func insertUpdateAccount(tx *Tx, a *models.Account, insert bool) error { | ||||||
| 	found := make(map[int64]bool) | 	found := make(map[int64]bool) | ||||||
| 	if !insert { | 	if !insert { | ||||||
| 		found[a.AccountId] = true | 		found[a.AccountId] = true | ||||||
| @@ -285,7 +173,7 @@ func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | |||||||
| 			return TooMuchNestingError{} | 			return TooMuchNestingError{} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var a Account | 		var a models.Account | ||||||
| 		err := tx.SelectOne(&a, "SELECT * from accounts where AccountId=?", parentid) | 		err := tx.SelectOne(&a, "SELECT * from accounts where AccountId=?", parentid) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return ParentAccountMissingError{} | 			return ParentAccountMissingError{} | ||||||
| @@ -328,15 +216,15 @@ func insertUpdateAccount(tx *Tx, a *Account, insert bool) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func InsertAccount(tx *Tx, a *Account) error { | func InsertAccount(tx *Tx, a *models.Account) error { | ||||||
| 	return insertUpdateAccount(tx, a, true) | 	return insertUpdateAccount(tx, a, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| func UpdateAccount(tx *Tx, a *Account) error { | func UpdateAccount(tx *Tx, a *models.Account) error { | ||||||
| 	return insertUpdateAccount(tx, a, false) | 	return insertUpdateAccount(tx, a, false) | ||||||
| } | } | ||||||
|  |  | ||||||
| func DeleteAccount(tx *Tx, a *Account) error { | func DeleteAccount(tx *Tx, a *models.Account) error { | ||||||
| 	if a.ParentAccountId != -1 { | 	if a.ParentAccountId != -1 { | ||||||
| 		// Re-parent splits to this account's parent account if this account isn't a root account | 		// Re-parent splits to this account's parent account if this account isn't a root account | ||||||
| 		_, err := tx.Exec("UPDATE splits SET AccountId=? WHERE AccountId=?", a.ParentAccountId, a.AccountId) | 		_, err := tx.Exec("UPDATE splits SET AccountId=? WHERE AccountId=?", a.ParentAccountId, a.AccountId) | ||||||
| @@ -383,7 +271,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 			return AccountImportHandler(context, r, user, accountid) | 			return AccountImportHandler(context, r, user, accountid) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var account Account | 		var account models.Account | ||||||
| 		if err := ReadJSON(r, &account); err != nil { | 		if err := ReadJSON(r, &account); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| @@ -414,7 +302,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 	} else if r.Method == "GET" { | 	} else if r.Method == "GET" { | ||||||
| 		if context.LastLevel() { | 		if context.LastLevel() { | ||||||
| 			//Return all Accounts | 			//Return all Accounts | ||||||
| 			var al AccountList | 			var al models.AccountList | ||||||
| 			accounts, err := GetAccounts(context.Tx, user.UserId) | 			accounts, err := GetAccounts(context.Tx, user.UserId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print(err) | 				log.Print(err) | ||||||
| @@ -446,7 +334,7 @@ func AccountHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| 		if r.Method == "PUT" { | 		if r.Method == "PUT" { | ||||||
| 			var account Account | 			var account models.Account | ||||||
| 			if err := ReadJSON(r, &account); err != nil || account.AccountId != accountid { | 			if err := ReadJSON(r, &account); err != nil || account.AccountId != accountid { | ||||||
| 				return NewError(3 /*Invalid Request*/) | 				return NewError(3 /*Invalid Request*/) | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package handlers | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -10,8 +11,8 @@ import ( | |||||||
|  |  | ||||||
| const luaAccountTypeName = "account" | const luaAccountTypeName = "account" | ||||||
|  |  | ||||||
| func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, error) { | func luaContextGetAccounts(L *lua.LState) (map[int64]*models.Account, error) { | ||||||
| 	var account_map map[int64]*Account | 	var account_map map[int64]*models.Account | ||||||
|  |  | ||||||
| 	ctx := L.Context() | 	ctx := L.Context() | ||||||
|  |  | ||||||
| @@ -20,9 +21,9 @@ func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, 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") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	account_map, ok = ctx.Value(accountsContextKey).(map[int64]*Account) | 	account_map, ok = ctx.Value(accountsContextKey).(map[int64]*models.Account) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		user, ok := ctx.Value(userContextKey).(*User) | 		user, ok := ctx.Value(userContextKey).(*models.User) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("Couldn't find User in lua's Context") | 			return nil, errors.New("Couldn't find User in lua's Context") | ||||||
| 		} | 		} | ||||||
| @@ -32,7 +33,7 @@ func luaContextGetAccounts(L *lua.LState) (map[int64]*Account, error) { | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		account_map = make(map[int64]*Account) | 		account_map = make(map[int64]*models.Account) | ||||||
| 		for i := range *accounts { | 		for i := range *accounts { | ||||||
| 			account_map[(*accounts)[i].AccountId] = &(*accounts)[i] | 			account_map[(*accounts)[i].AccountId] = &(*accounts)[i] | ||||||
| 		} | 		} | ||||||
| @@ -68,7 +69,7 @@ func luaRegisterAccounts(L *lua.LState) { | |||||||
| 	L.SetField(mt, "__eq", L.NewFunction(luaAccount__eq)) | 	L.SetField(mt, "__eq", L.NewFunction(luaAccount__eq)) | ||||||
| 	L.SetField(mt, "__metatable", lua.LString("protected")) | 	L.SetField(mt, "__metatable", lua.LString("protected")) | ||||||
|  |  | ||||||
| 	for _, accttype := range AccountTypes { | 	for _, accttype := range models.AccountTypes { | ||||||
| 		L.SetField(mt, accttype.String(), lua.LNumber(float64(accttype))) | 		L.SetField(mt, accttype.String(), lua.LNumber(float64(accttype))) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -78,7 +79,7 @@ func luaRegisterAccounts(L *lua.LState) { | |||||||
| 	L.SetGlobal("get_accounts", getAccountsFn) | 	L.SetGlobal("get_accounts", getAccountsFn) | ||||||
| } | } | ||||||
|  |  | ||||||
| func AccountToLua(L *lua.LState, account *Account) *lua.LUserData { | func AccountToLua(L *lua.LState, account *models.Account) *lua.LUserData { | ||||||
| 	ud := L.NewUserData() | 	ud := L.NewUserData() | ||||||
| 	ud.Value = account | 	ud.Value = account | ||||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaAccountTypeName)) | 	L.SetMetatable(ud, L.GetTypeMetatable(luaAccountTypeName)) | ||||||
| @@ -86,9 +87,9 @@ func AccountToLua(L *lua.LState, account *Account) *lua.LUserData { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Checks whether the first lua argument is a *LUserData with *Account and returns this *Account. | // Checks whether the first lua argument is a *LUserData with *Account and returns this *Account. | ||||||
| func luaCheckAccount(L *lua.LState, n int) *Account { | func luaCheckAccount(L *lua.LState, n int) *models.Account { | ||||||
| 	ud := L.CheckUserData(n) | 	ud := L.CheckUserData(n) | ||||||
| 	if account, ok := ud.Value.(*Account); ok { | 	if account, ok := ud.Value.(*models.Account); ok { | ||||||
| 		return account | 		return account | ||||||
| 	} | 	} | ||||||
| 	L.ArgError(n, "account expected") | 	L.ArgError(n, "account expected") | ||||||
| @@ -153,7 +154,7 @@ func luaAccountBalance(L *lua.LState) int { | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		panic("Couldn't find tx in lua's Context") | 		panic("Couldn't find tx in lua's Context") | ||||||
| 	} | 	} | ||||||
| 	user, ok := ctx.Value(userContextKey).(*User) | 	user, ok := ctx.Value(userContextKey).(*models.User) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		panic("Couldn't find User in lua's Context") | 		panic("Couldn't find User in lua's Context") | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -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 createAccount(client *http.Client, account *handlers.Account) (*handlers.Account, error) { | func createAccount(client *http.Client, account *models.Account) (*models.Account, error) { | ||||||
| 	var a handlers.Account | 	var a models.Account | ||||||
| 	err := create(client, account, &a, "/v1/accounts/") | 	err := create(client, account, &a, "/v1/accounts/") | ||||||
| 	return &a, err | 	return &a, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAccount(client *http.Client, accountid int64) (*handlers.Account, error) { | func getAccount(client *http.Client, accountid int64) (*models.Account, error) { | ||||||
| 	var a handlers.Account | 	var a models.Account | ||||||
| 	err := read(client, &a, "/v1/accounts/"+strconv.FormatInt(accountid, 10)) | 	err := read(client, &a, "/v1/accounts/"+strconv.FormatInt(accountid, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -22,8 +23,8 @@ func getAccount(client *http.Client, accountid int64) (*handlers.Account, error) | |||||||
| 	return &a, nil | 	return &a, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAccounts(client *http.Client) (*handlers.AccountList, error) { | func getAccounts(client *http.Client) (*models.AccountList, error) { | ||||||
| 	var al handlers.AccountList | 	var al models.AccountList | ||||||
| 	err := read(client, &al, "/v1/accounts/") | 	err := read(client, &al, "/v1/accounts/") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -31,8 +32,8 @@ func getAccounts(client *http.Client) (*handlers.AccountList, error) { | |||||||
| 	return &al, nil | 	return &al, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateAccount(client *http.Client, account *handlers.Account) (*handlers.Account, error) { | func updateAccount(client *http.Client, account *models.Account) (*models.Account, error) { | ||||||
| 	var a handlers.Account | 	var a models.Account | ||||||
| 	err := update(client, account, &a, "/v1/accounts/"+strconv.FormatInt(account.AccountId, 10)) | 	err := update(client, account, &a, "/v1/accounts/"+strconv.FormatInt(account.AccountId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -40,7 +41,7 @@ func updateAccount(client *http.Client, account *handlers.Account) (*handlers.Ac | |||||||
| 	return &a, nil | 	return &a, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteAccount(client *http.Client, a *handlers.Account) error { | func deleteAccount(client *http.Client, a *models.Account) error { | ||||||
| 	err := remove(client, "/v1/accounts/"+strconv.FormatInt(a.AccountId, 10)) | 	err := remove(client, "/v1/accounts/"+strconv.FormatInt(a.AccountId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -137,7 +138,7 @@ func TestUpdateAccount(t *testing.T) { | |||||||
| 			curr := d.accounts[i] | 			curr := d.accounts[i] | ||||||
|  |  | ||||||
| 			curr.Name = "blah" | 			curr.Name = "blah" | ||||||
| 			curr.Type = handlers.Payable | 			curr.Type = models.Payable | ||||||
| 			for _, s := range d.securities { | 			for _, s := range d.securities { | ||||||
| 				if s.UserId == curr.UserId { | 				if s.UserId == curr.UserId { | ||||||
| 					curr.SecurityId = s.SecurityId | 					curr.SecurityId = s.SecurityId | ||||||
|   | |||||||
| @@ -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 | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"github.com/aclindsa/moneygo/internal/config" | 	"github.com/aclindsa/moneygo/internal/config" | ||||||
| 	"github.com/aclindsa/moneygo/internal/db" | 	"github.com/aclindsa/moneygo/internal/db" | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/handlers" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"log" | 	"log" | ||||||
| @@ -202,7 +203,7 @@ func uploadFile(client *http.Client, filename, urlsuffix string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func accountBalanceHelper(t *testing.T, client *http.Client, account *handlers.Account, balance string) { | func accountBalanceHelper(t *testing.T, client *http.Client, account *models.Account, balance string) { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 	transactions, err := getAccountTransactions(client, account.AccountId, 0, 0, "") | 	transactions, err := getAccountTransactions(client, account.AccountId, 0, 0, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -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,10 +126,10 @@ type GnucashXMLImport struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type GnucashImport struct { | type GnucashImport struct { | ||||||
| 	Securities   []Security | 	Securities   []models.Security | ||||||
| 	Accounts     []Account | 	Accounts     []models.Account | ||||||
| 	Transactions []Transaction | 	Transactions []models.Transaction | ||||||
| 	Prices       []Price | 	Prices       []models.Price | ||||||
| } | } | ||||||
|  |  | ||||||
| func ImportGnucash(r io.Reader) (*GnucashImport, error) { | func ImportGnucash(r io.Reader) (*GnucashImport, error) { | ||||||
| @@ -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) | ||||||
| @@ -160,7 +161,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | |||||||
| 	// Create prices, setting security and currency IDs from securityMap | 	// Create prices, setting security and currency IDs from securityMap | ||||||
| 	for i := range gncxml.PriceDB.Prices { | 	for i := range gncxml.PriceDB.Prices { | ||||||
| 		price := gncxml.PriceDB.Prices[i] | 		price := gncxml.PriceDB.Prices[i] | ||||||
| 		var p Price | 		var p models.Price | ||||||
| 		security, ok := securityMap[price.Commodity.Name] | 		security, ok := securityMap[price.Commodity.Name] | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, fmt.Errorf("Unable to find commodity '%s' for price '%s'", price.Commodity.Name, price.Id) | 			return nil, fmt.Errorf("Unable to find commodity '%s' for price '%s'", price.Commodity.Name, price.Id) | ||||||
| @@ -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) | ||||||
| @@ -205,7 +206,7 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | |||||||
| 	//Translate to our account format, figuring out parent relationships | 	//Translate to our account format, figuring out parent relationships | ||||||
| 	for guid := range accountMap { | 	for guid := range accountMap { | ||||||
| 		ga := accountMap[guid] | 		ga := accountMap[guid] | ||||||
| 		var a Account | 		var a models.Account | ||||||
|  |  | ||||||
| 		a.AccountId = ga.accountid | 		a.AccountId = ga.accountid | ||||||
| 		if ga.ParentAccountId == rootAccount.AccountId { | 		if ga.ParentAccountId == rootAccount.AccountId { | ||||||
| @@ -228,29 +229,29 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | |||||||
| 		//TODO find account types | 		//TODO find account types | ||||||
| 		switch ga.Type { | 		switch ga.Type { | ||||||
| 		default: | 		default: | ||||||
| 			a.Type = Bank | 			a.Type = models.Bank | ||||||
| 		case "ASSET": | 		case "ASSET": | ||||||
| 			a.Type = Asset | 			a.Type = models.Asset | ||||||
| 		case "BANK": | 		case "BANK": | ||||||
| 			a.Type = Bank | 			a.Type = models.Bank | ||||||
| 		case "CASH": | 		case "CASH": | ||||||
| 			a.Type = Cash | 			a.Type = models.Cash | ||||||
| 		case "CREDIT", "LIABILITY": | 		case "CREDIT", "LIABILITY": | ||||||
| 			a.Type = Liability | 			a.Type = models.Liability | ||||||
| 		case "EQUITY": | 		case "EQUITY": | ||||||
| 			a.Type = Equity | 			a.Type = models.Equity | ||||||
| 		case "EXPENSE": | 		case "EXPENSE": | ||||||
| 			a.Type = Expense | 			a.Type = models.Expense | ||||||
| 		case "INCOME": | 		case "INCOME": | ||||||
| 			a.Type = Income | 			a.Type = models.Income | ||||||
| 		case "PAYABLE": | 		case "PAYABLE": | ||||||
| 			a.Type = Payable | 			a.Type = models.Payable | ||||||
| 		case "RECEIVABLE": | 		case "RECEIVABLE": | ||||||
| 			a.Type = Receivable | 			a.Type = models.Receivable | ||||||
| 		case "MUTUAL", "STOCK": | 		case "MUTUAL", "STOCK": | ||||||
| 			a.Type = Investment | 			a.Type = models.Investment | ||||||
| 		case "TRADING": | 		case "TRADING": | ||||||
| 			a.Type = Trading | 			a.Type = models.Trading | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		gncimport.Accounts = append(gncimport.Accounts, a) | 		gncimport.Accounts = append(gncimport.Accounts, a) | ||||||
| @@ -260,20 +261,20 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) { | |||||||
| 	for i := range gncxml.Transactions { | 	for i := range gncxml.Transactions { | ||||||
| 		gt := gncxml.Transactions[i] | 		gt := gncxml.Transactions[i] | ||||||
|  |  | ||||||
| 		t := new(Transaction) | 		t := new(models.Transaction) | ||||||
| 		t.Description = gt.Description | 		t.Description = gt.Description | ||||||
| 		t.Date = gt.DatePosted.Date.Time | 		t.Date = gt.DatePosted.Date.Time | ||||||
| 		for j := range gt.Splits { | 		for j := range gt.Splits { | ||||||
| 			gs := gt.Splits[j] | 			gs := gt.Splits[j] | ||||||
| 			s := new(Split) | 			s := new(models.Split) | ||||||
|  |  | ||||||
| 			switch gs.Status { | 			switch gs.Status { | ||||||
| 			default: // 'n', or not present | 			default: // 'n', or not present | ||||||
| 				s.Status = Imported | 				s.Status = models.Imported | ||||||
| 			case "c": | 			case "c": | ||||||
| 				s.Status = Cleared | 				s.Status = models.Cleared | ||||||
| 			case "y": | 			case "y": | ||||||
| 				s.Status = Reconciled | 				s.Status = models.Reconciled | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			account, ok := accountMap[gs.AccountId] | 			account, ok := accountMap[gs.AccountId] | ||||||
| @@ -436,7 +437,7 @@ func GnucashImportHandler(r *http.Request, context *Context) ResponseWriterWrite | |||||||
| 			} | 			} | ||||||
| 			split.AccountId = acctId | 			split.AccountId = acctId | ||||||
|  |  | ||||||
| 			exists, err := split.AlreadyImported(context.Tx) | 			exists, err := SplitAlreadyImported(context.Tx, split) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print("Error checking if split was already imported:", err) | 				log.Print("Error checking if split was already imported:", err) | ||||||
| 				return NewError(999 /*Internal Error*/) | 				return NewError(999 /*Internal Error*/) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package handlers_test | package handlers_test | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| @@ -31,19 +31,19 @@ func TestImportGnucash(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Next, find the Expenses/Groceries account and verify it's balance | 		// Next, find the Expenses/Groceries account and verify it's balance | ||||||
| 		var income, equity, liabilities, expenses, salary, creditcard, groceries, cable, openingbalances *handlers.Account | 		var income, equity, liabilities, expenses, salary, creditcard, groceries, cable, openingbalances *models.Account | ||||||
| 		accounts, err := getAccounts(d.clients[0]) | 		accounts, err := getAccounts(d.clients[0]) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("Error fetching accounts: %s\n", err) | 			t.Fatalf("Error fetching accounts: %s\n", err) | ||||||
| 		} | 		} | ||||||
| 		for i, account := range *accounts.Accounts { | 		for i, account := range *accounts.Accounts { | ||||||
| 			if account.Name == "Income" && account.Type == handlers.Income && account.ParentAccountId == -1 { | 			if account.Name == "Income" && account.Type == models.Income && account.ParentAccountId == -1 { | ||||||
| 				income = &(*accounts.Accounts)[i] | 				income = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Equity" && account.Type == handlers.Equity && account.ParentAccountId == -1 { | 			} else if account.Name == "Equity" && account.Type == models.Equity && account.ParentAccountId == -1 { | ||||||
| 				equity = &(*accounts.Accounts)[i] | 				equity = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Liabilities" && account.Type == handlers.Liability && account.ParentAccountId == -1 { | 			} else if account.Name == "Liabilities" && account.Type == models.Liability && account.ParentAccountId == -1 { | ||||||
| 				liabilities = &(*accounts.Accounts)[i] | 				liabilities = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Expenses" && account.Type == handlers.Expense && account.ParentAccountId == -1 { | 			} else if account.Name == "Expenses" && account.Type == models.Expense && account.ParentAccountId == -1 { | ||||||
| 				expenses = &(*accounts.Accounts)[i] | 				expenses = &(*accounts.Accounts)[i] | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -60,15 +60,15 @@ func TestImportGnucash(t *testing.T) { | |||||||
| 			t.Fatalf("Couldn't find 'Expenses' account") | 			t.Fatalf("Couldn't find 'Expenses' account") | ||||||
| 		} | 		} | ||||||
| 		for i, account := range *accounts.Accounts { | 		for i, account := range *accounts.Accounts { | ||||||
| 			if account.Name == "Salary" && account.Type == handlers.Income && account.ParentAccountId == income.AccountId { | 			if account.Name == "Salary" && account.Type == models.Income && account.ParentAccountId == income.AccountId { | ||||||
| 				salary = &(*accounts.Accounts)[i] | 				salary = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Opening Balances" && account.Type == handlers.Equity && account.ParentAccountId == equity.AccountId { | 			} else if account.Name == "Opening Balances" && account.Type == models.Equity && account.ParentAccountId == equity.AccountId { | ||||||
| 				openingbalances = &(*accounts.Accounts)[i] | 				openingbalances = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Credit Card" && account.Type == handlers.Liability && account.ParentAccountId == liabilities.AccountId { | 			} else if account.Name == "Credit Card" && account.Type == models.Liability && account.ParentAccountId == liabilities.AccountId { | ||||||
| 				creditcard = &(*accounts.Accounts)[i] | 				creditcard = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Groceries" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { | 			} else if account.Name == "Groceries" && account.Type == models.Expense && account.ParentAccountId == expenses.AccountId { | ||||||
| 				groceries = &(*accounts.Accounts)[i] | 				groceries = &(*accounts.Accounts)[i] | ||||||
| 			} else if account.Name == "Cable" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { | 			} else if account.Name == "Cable" && account.Type == models.Expense && account.ParentAccountId == expenses.AccountId { | ||||||
| 				cable = &(*accounts.Accounts)[i] | 				cable = &(*accounts.Accounts)[i] | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -94,7 +94,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) | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package handlers | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/aclindsa/gorp" | 	"github.com/aclindsa/gorp" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"path" | 	"path" | ||||||
| @@ -16,7 +17,7 @@ type ResponseWriterWriter interface { | |||||||
|  |  | ||||||
| type Context struct { | type Context struct { | ||||||
| 	Tx           *Tx | 	Tx           *Tx | ||||||
| 	User         *User | 	User         *models.User | ||||||
| 	remainingURL string // portion of URL path not yet reached in the hierarchy | 	remainingURL string // portion of URL path not yet reached in the hierarchy | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package handlers | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/aclindsa/ofxgo" | 	"github.com/aclindsa/ofxgo" | ||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| @@ -22,7 +23,7 @@ func (od *OFXDownload) Read(json_str string) error { | |||||||
| 	return dec.Decode(od) | 	return dec.Decode(od) | ||||||
| } | } | ||||||
|  |  | ||||||
| func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseWriterWriter { | func ofxImportHelper(tx *Tx, r io.Reader, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
| 	itl, err := ImportOFX(r) | 	itl, err := ImportOFX(r) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -56,7 +57,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 	// 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 | ||||||
| @@ -77,7 +78,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 	// TODO Ensure all transactions have at least one split in the account | 	// TODO Ensure all transactions have at least one split in the account | ||||||
| 	// we're importing to? | 	// we're importing to? | ||||||
|  |  | ||||||
| 	var transactions []Transaction | 	var transactions []models.Transaction | ||||||
| 	for _, transaction := range itl.Transactions { | 	for _, transaction := range itl.Transactions { | ||||||
| 		transaction.UserId = user.UserId | 		transaction.UserId = user.UserId | ||||||
|  |  | ||||||
| @@ -90,7 +91,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 		// and fixup the SecurityId to be a valid one for this user's actual | 		// and fixup the SecurityId to be a valid one for this user's actual | ||||||
| 		// securities instead of a placeholder from the import | 		// securities instead of a placeholder from the import | ||||||
| 		for _, split := range transaction.Splits { | 		for _, split := range transaction.Splits { | ||||||
| 			split.Status = Imported | 			split.Status = models.Imported | ||||||
| 			if split.AccountId != -1 { | 			if split.AccountId != -1 { | ||||||
| 				if split.AccountId != importedAccount.AccountId { | 				if split.AccountId != importedAccount.AccountId { | ||||||
| 					log.Print("Imported split's AccountId wasn't -1 but also didn't match the account") | 					log.Print("Imported split's AccountId wasn't -1 but also didn't match the account") | ||||||
| @@ -100,7 +101,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 			} else if split.SecurityId != -1 { | 			} else if split.SecurityId != -1 { | ||||||
| 				if sec, ok := securitymap[split.SecurityId]; ok { | 				if sec, ok := securitymap[split.SecurityId]; ok { | ||||||
| 					// TODO try to auto-match splits to existing accounts based on past transactions that look like this one | 					// TODO try to auto-match splits to existing accounts based on past transactions that look like this one | ||||||
| 					if split.ImportSplitType == TradingAccount { | 					if split.ImportSplitType == models.TradingAccount { | ||||||
| 						// Find/make trading account if we're that type of split | 						// Find/make trading account if we're that type of split | ||||||
| 						trading_account, err := GetTradingAccount(tx, user.UserId, sec.SecurityId) | 						trading_account, err := GetTradingAccount(tx, user.UserId, sec.SecurityId) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| @@ -109,8 +110,8 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 						} | 						} | ||||||
| 						split.AccountId = trading_account.AccountId | 						split.AccountId = trading_account.AccountId | ||||||
| 						split.SecurityId = -1 | 						split.SecurityId = -1 | ||||||
| 					} else if split.ImportSplitType == SubAccount { | 					} else if split.ImportSplitType == models.SubAccount { | ||||||
| 						subaccount := &Account{ | 						subaccount := &models.Account{ | ||||||
| 							UserId:          user.UserId, | 							UserId:          user.UserId, | ||||||
| 							Name:            sec.Name, | 							Name:            sec.Name, | ||||||
| 							ParentAccountId: account.AccountId, | 							ParentAccountId: account.AccountId, | ||||||
| @@ -137,7 +138,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		imbalances, err := transaction.GetImbalances(tx) | 		imbalances, err := GetTransactionImbalances(tx, &transaction) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Print(err) | 			log.Print(err) | ||||||
| 			return NewError(999 /*Internal Error*/) | 			return NewError(999 /*Internal Error*/) | ||||||
| @@ -154,7 +155,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Add new split to fixup imbalance | 				// Add new split to fixup imbalance | ||||||
| 				split := new(Split) | 				split := new(models.Split) | ||||||
| 				r := new(big.Rat) | 				r := new(big.Rat) | ||||||
| 				r.Neg(&imbalance) | 				r.Neg(&imbalance) | ||||||
| 				security, err := GetSecurity(tx, imbalanced_security, user.UserId) | 				security, err := GetSecurity(tx, imbalanced_security, user.UserId) | ||||||
| @@ -185,7 +186,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 				split.SecurityId = -1 | 				split.SecurityId = -1 | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			exists, err := split.AlreadyImported(tx) | 			exists, err := SplitAlreadyImported(tx, split) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print("Error checking if split was already imported:", err) | 				log.Print("Error checking if split was already imported:", err) | ||||||
| 				return NewError(999 /*Internal Error*/) | 				return NewError(999 /*Internal Error*/) | ||||||
| @@ -210,7 +211,7 @@ func ofxImportHelper(tx *Tx, r io.Reader, user *User, accountid int64) ResponseW | |||||||
| 	return SuccessWriter{} | 	return SuccessWriter{} | ||||||
| } | } | ||||||
|  |  | ||||||
| func OFXImportHandler(context *Context, r *http.Request, user *User, accountid int64) ResponseWriterWriter { | func OFXImportHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
| 	var ofxdownload OFXDownload | 	var ofxdownload OFXDownload | ||||||
| 	if err := ReadJSON(r, &ofxdownload); err != nil { | 	if err := ReadJSON(r, &ofxdownload); err != nil { | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| @@ -250,7 +251,7 @@ func OFXImportHandler(context *Context, r *http.Request, user *User, accountid i | |||||||
| 		return NewError(999 /*Internal Error*/) | 		return NewError(999 /*Internal Error*/) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if account.Type == Investment { | 	if account.Type == models.Investment { | ||||||
| 		// Investment account | 		// Investment account | ||||||
| 		statementRequest := ofxgo.InvStatementRequest{ | 		statementRequest := ofxgo.InvStatementRequest{ | ||||||
| 			TrnUID: *transactionuid, | 			TrnUID: *transactionuid, | ||||||
| @@ -305,7 +306,7 @@ func OFXImportHandler(context *Context, r *http.Request, user *User, accountid i | |||||||
| 	return ofxImportHelper(context.Tx, response.Body, user, accountid) | 	return ofxImportHelper(context.Tx, response.Body, user, accountid) | ||||||
| } | } | ||||||
|  |  | ||||||
| func OFXFileImportHandler(context *Context, r *http.Request, user *User, accountid int64) ResponseWriterWriter { | func OFXFileImportHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
| 	multipartReader, err := r.MultipartReader() | 	multipartReader, err := r.MultipartReader() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| @@ -329,7 +330,7 @@ func OFXFileImportHandler(context *Context, r *http.Request, user *User, account | |||||||
| /* | /* | ||||||
|  * Assumes the User is a valid, signed-in user, but accountid has not yet been validated |  * Assumes the User is a valid, signed-in user, but accountid has not yet been validated | ||||||
|  */ |  */ | ||||||
| func AccountImportHandler(context *Context, r *http.Request, user *User, accountid int64) ResponseWriterWriter { | func AccountImportHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
|  |  | ||||||
| 	importType := context.NextLevel() | 	importType := context.NextLevel() | ||||||
| 	switch importType { | 	switch importType { | ||||||
|   | |||||||
| @@ -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     []models.Account | ||||||
| 	Transactions []Transaction | 	Transactions []models.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,26 +33,26 @@ 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) | ||||||
|  |  | ||||||
| 	return &security, nil | 	return &security, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) error { | func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *models.Account) error { | ||||||
| 	var t Transaction | 	var t models.Transaction | ||||||
|  |  | ||||||
| 	t.Date = tran.DtPosted.UTC() | 	t.Date = tran.DtPosted.UTC() | ||||||
|  |  | ||||||
| @@ -69,7 +70,7 @@ func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) er | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var s1, s2 Split | 	var s1, s2 models.Split | ||||||
| 	if len(tran.ExtdName) > 0 { | 	if len(tran.ExtdName) > 0 { | ||||||
| 		s1.Memo = tran.ExtdName.String() | 		s1.Memo = tran.ExtdName.String() | ||||||
| 	} | 	} | ||||||
| @@ -93,15 +94,15 @@ func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) er | |||||||
| 	s1.RemoteId = "ofx:" + tran.FiTID.String() | 	s1.RemoteId = "ofx:" + tran.FiTID.String() | ||||||
| 	// TODO CorrectFiTID/CorrectAction? | 	// TODO CorrectFiTID/CorrectAction? | ||||||
|  |  | ||||||
| 	s1.ImportSplitType = ImportAccount | 	s1.ImportSplitType = models.ImportAccount | ||||||
| 	s2.ImportSplitType = ExternalAccount | 	s2.ImportSplitType = models.ExternalAccount | ||||||
|  |  | ||||||
| 	security := i.Securities[account.SecurityId-1] | 	security := i.Securities[account.SecurityId-1] | ||||||
| 	s1.Amount = amt.FloatString(security.Precision) | 	s1.Amount = amt.FloatString(security.Precision) | ||||||
| 	s2.Amount = amt.Neg(amt).FloatString(security.Precision) | 	s2.Amount = amt.Neg(amt).FloatString(security.Precision) | ||||||
|  |  | ||||||
| 	s1.Status = Imported | 	s1.Status = models.Imported | ||||||
| 	s2.Status = Imported | 	s2.Status = models.Imported | ||||||
|  |  | ||||||
| 	s1.AccountId = account.AccountId | 	s1.AccountId = account.AccountId | ||||||
| 	s2.AccountId = -1 | 	s2.AccountId = -1 | ||||||
| @@ -121,12 +122,12 @@ func (i *OFXImport) importOFXBank(stmt *ofxgo.StatementResponse) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	account := Account{ | 	account := models.Account{ | ||||||
| 		AccountId:         int64(len(i.Accounts) + 1), | 		AccountId:         int64(len(i.Accounts) + 1), | ||||||
| 		ExternalAccountId: stmt.BankAcctFrom.AcctID.String(), | 		ExternalAccountId: stmt.BankAcctFrom.AcctID.String(), | ||||||
| 		SecurityId:        security.SecurityId, | 		SecurityId:        security.SecurityId, | ||||||
| 		ParentAccountId:   -1, | 		ParentAccountId:   -1, | ||||||
| 		Type:              Bank, | 		Type:              models.Bank, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if stmt.BankTranList != nil { | 	if stmt.BankTranList != nil { | ||||||
| @@ -148,12 +149,12 @@ func (i *OFXImport) importOFXCC(stmt *ofxgo.CCStatementResponse) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	account := Account{ | 	account := models.Account{ | ||||||
| 		AccountId:         int64(len(i.Accounts) + 1), | 		AccountId:         int64(len(i.Accounts) + 1), | ||||||
| 		ExternalAccountId: stmt.CCAcctFrom.AcctID.String(), | 		ExternalAccountId: stmt.CCAcctFrom.AcctID.String(), | ||||||
| 		SecurityId:        security.SecurityId, | 		SecurityId:        security.SecurityId, | ||||||
| 		ParentAccountId:   -1, | 		ParentAccountId:   -1, | ||||||
| 		Type:              Liability, | 		Type:              models.Liability, | ||||||
| 	} | 	} | ||||||
| 	i.Accounts = append(i.Accounts, account) | 	i.Accounts = append(i.Accounts, account) | ||||||
|  |  | ||||||
| @@ -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 { | ||||||
| @@ -207,17 +208,17 @@ func (i *OFXImport) importSecurities(seclist *ofxgo.SecurityList) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) Transaction { | func (i *OFXImport) GetInvTran(invtran *ofxgo.InvTran) models.Transaction { | ||||||
| 	var t Transaction | 	var t models.Transaction | ||||||
| 	t.Description = string(invtran.Memo) | 	t.Description = string(invtran.Memo) | ||||||
| 	t.Date = invtran.DtTrade.UTC() | 	t.Date = invtran.DtTrade.UTC() | ||||||
| 	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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -253,10 +254,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Commission, | 			ImportSplitType: models.Commission, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -265,10 +266,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Taxes, | 			ImportSplitType: models.Taxes, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -277,10 +278,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Fees, | 			ImportSplitType: models.Fees, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -289,10 +290,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Load, | 			ImportSplitType: models.Load, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -300,20 +301,20 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 			Amount:          load.FloatString(curdef.Precision), | 			Amount:          load.FloatString(curdef.Precision), | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| 		Memo:            memo, | 		Memo:            memo, | ||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -323,10 +324,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
|  |  | ||||||
| 	var units big.Rat | 	var units big.Rat | ||||||
| 	units.Abs(&buy.Units.Rat) | 	units.Abs(&buy.Units.Rat) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: SubAccount, | 		ImportSplitType: models.SubAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -334,10 +335,10 @@ func (i *OFXImport) GetInvBuyTran(buy *ofxgo.InvBuy, curdef *Security, account * | |||||||
| 		Amount:          units.FloatString(security.Precision), | 		Amount:          units.FloatString(security.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	units.Neg(&units) | 	units.Neg(&units) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + buy.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -369,10 +370,10 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *Security, accoun | |||||||
| 		total.Mul(&total, &income.Currency.CurRate.Rat) | 		total.Mul(&total, &income.Currency.CurRate.Rat) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||||
| @@ -380,10 +381,10 @@ func (i *OFXImport) GetIncomeTran(income *ofxgo.Income, curdef *Security, accoun | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	total.Neg(&total) | 	total.Neg(&total) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: IncomeAccount, | 		ImportSplitType: models.IncomeAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + income.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -414,10 +415,10 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *Securit | |||||||
| 		total.Mul(&total, &expense.Currency.CurRate.Rat) | 		total.Mul(&total, &expense.Currency.CurRate.Rat) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | ||||||
| @@ -425,10 +426,10 @@ func (i *OFXImport) GetInvExpenseTran(expense *ofxgo.InvExpense, curdef *Securit | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	total.Neg(&total) | 	total.Neg(&total) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ExpenseAccount, | 		ImportSplitType: models.ExpenseAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + expense.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.Transaction, error) { | ||||||
| 	t := i.GetInvTran(&marginint.InvTran) | 	t := i.GetInvTran(&marginint.InvTran) | ||||||
|  |  | ||||||
| 	memo := string(marginint.InvTran.Memo) | 	memo := string(marginint.InvTran.Memo) | ||||||
| @@ -453,10 +454,10 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde | |||||||
| 		total.Mul(&total, &marginint.Currency.CurRate.Rat) | 		total.Mul(&total, &marginint.Currency.CurRate.Rat) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | ||||||
| @@ -464,10 +465,10 @@ func (i *OFXImport) GetMarginInterestTran(marginint *ofxgo.MarginInterest, curde | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	total.Neg(&total) | 	total.Neg(&total) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: IncomeAccount, | 		ImportSplitType: models.IncomeAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + marginint.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -517,10 +518,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Commission, | 			ImportSplitType: models.Commission, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -529,10 +530,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Taxes, | 			ImportSplitType: models.Taxes, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -541,10 +542,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Fees, | 			ImportSplitType: models.Fees, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -553,10 +554,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Load, | 			ImportSplitType: models.Load, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -564,10 +565,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 			Amount:          load.FloatString(curdef.Precision), | 			Amount:          load.FloatString(curdef.Precision), | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -575,10 +576,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: IncomeAccount, | 		ImportSplitType: models.IncomeAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -586,20 +587,20 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	total.Neg(&total) | 	total.Neg(&total) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| 		Memo:            memo, | 		Memo:            memo, | ||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -609,10 +610,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
|  |  | ||||||
| 	var units big.Rat | 	var units big.Rat | ||||||
| 	units.Abs(&reinvest.Units.Rat) | 	units.Abs(&reinvest.Units.Rat) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: SubAccount, | 		ImportSplitType: models.SubAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -620,10 +621,10 @@ func (i *OFXImport) GetReinvestTran(reinvest *ofxgo.Reinvest, curdef *Security, | |||||||
| 		Amount:          units.FloatString(security.Precision), | 		Amount:          units.FloatString(security.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	units.Neg(&units) | 	units.Neg(&units) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + reinvest.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -654,10 +655,10 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *Security, | |||||||
| 		total.Mul(&total, &retofcap.Currency.CurRate.Rat) | 		total.Mul(&total, &retofcap.Currency.CurRate.Rat) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | ||||||
| @@ -665,10 +666,10 @@ func (i *OFXImport) GetRetOfCapTran(retofcap *ofxgo.RetOfCap, curdef *Security, | |||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	total.Neg(&total) | 	total.Neg(&total) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: IncomeAccount, | 		ImportSplitType: models.IncomeAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + retofcap.InvTran.FiTID.String(), | ||||||
| @@ -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 *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -721,10 +722,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := commission.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Commission, | 			ImportSplitType: models.Commission, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -733,10 +734,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := taxes.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Taxes, | 			ImportSplitType: models.Taxes, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -745,10 +746,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := fees.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Fees, | 			ImportSplitType: models.Fees, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -757,10 +758,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | 	if num := load.Num(); !num.IsInt64() || num.Int64() != 0 { | ||||||
| 		t.Splits = append(t.Splits, &Split{ | 		t.Splits = append(t.Splits, &models.Split{ | ||||||
| 			// TODO ReversalFiTID? | 			// TODO ReversalFiTID? | ||||||
| 			Status:          Imported, | 			Status:          models.Imported, | ||||||
| 			ImportSplitType: Load, | 			ImportSplitType: models.Load, | ||||||
| 			AccountId:       -1, | 			AccountId:       -1, | ||||||
| 			SecurityId:      curdef.SecurityId, | 			SecurityId:      curdef.SecurityId, | ||||||
| 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 			RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -768,20 +769,20 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 			Amount:          load.FloatString(curdef.Precision), | 			Amount:          load.FloatString(curdef.Precision), | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ImportAccount, | 		ImportSplitType: models.ImportAccount, | ||||||
| 		AccountId:       account.AccountId, | 		AccountId:       account.AccountId, | ||||||
| 		SecurityId:      -1, | 		SecurityId:      -1, | ||||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| 		Memo:            memo, | 		Memo:            memo, | ||||||
| 		Amount:          total.FloatString(curdef.Precision), | 		Amount:          total.FloatString(curdef.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      curdef.SecurityId, | 		SecurityId:      curdef.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -791,10 +792,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
|  |  | ||||||
| 	var units big.Rat | 	var units big.Rat | ||||||
| 	units.Abs(&sell.Units.Rat) | 	units.Abs(&sell.Units.Rat) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: TradingAccount, | 		ImportSplitType: models.TradingAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -802,10 +803,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 		Amount:          units.FloatString(security.Precision), | 		Amount:          units.FloatString(security.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	units.Neg(&units) | 	units.Neg(&units) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: SubAccount, | 		ImportSplitType: models.SubAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + sell.InvTran.FiTID.String(), | ||||||
| @@ -816,10 +817,10 @@ func (i *OFXImport) GetInvSellTran(sell *ofxgo.InvSell, curdef *Security, accoun | |||||||
| 	return &t, nil | 	return &t, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) (*Transaction, error) { | func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *models.Account) (*models.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 | ||||||
| 	} | 	} | ||||||
| @@ -833,10 +834,10 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) | |||||||
| 		units.Neg(&transfer.Units.Rat) | 		units.Neg(&transfer.Units.Rat) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: SubAccount, | 		ImportSplitType: models.SubAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||||
| @@ -844,10 +845,10 @@ func (i *OFXImport) GetTransferTran(transfer *ofxgo.Transfer, account *Account) | |||||||
| 		Amount:          units.FloatString(security.Precision), | 		Amount:          units.FloatString(security.Precision), | ||||||
| 	}) | 	}) | ||||||
| 	units.Neg(&units) | 	units.Neg(&units) | ||||||
| 	t.Splits = append(t.Splits, &Split{ | 	t.Splits = append(t.Splits, &models.Split{ | ||||||
| 		// TODO ReversalFiTID? | 		// TODO ReversalFiTID? | ||||||
| 		Status:          Imported, | 		Status:          models.Imported, | ||||||
| 		ImportSplitType: ExternalAccount, | 		ImportSplitType: models.ExternalAccount, | ||||||
| 		AccountId:       -1, | 		AccountId:       -1, | ||||||
| 		SecurityId:      security.SecurityId, | 		SecurityId:      security.SecurityId, | ||||||
| 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | 		RemoteId:        "ofx:" + transfer.InvTran.FiTID.String(), | ||||||
| @@ -858,12 +859,12 @@ 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 *models.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") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var t *Transaction | 	var t *models.Transaction | ||||||
| 	var err error | 	var err error | ||||||
| 	if tran, ok := (*invtran).(ofxgo.BuyDebt); ok { | 	if tran, ok := (*invtran).(ofxgo.BuyDebt); ok { | ||||||
| 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | 		t, err = i.GetInvBuyTran(&tran.InvBuy, curdef, account) | ||||||
| @@ -925,12 +926,12 @@ func (i *OFXImport) importOFXInv(stmt *ofxgo.InvStatementResponse) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	account := Account{ | 	account := models.Account{ | ||||||
| 		AccountId:         int64(len(i.Accounts) + 1), | 		AccountId:         int64(len(i.Accounts) + 1), | ||||||
| 		ExternalAccountId: stmt.InvAcctFrom.AcctID.String(), | 		ExternalAccountId: stmt.InvAcctFrom.AcctID.String(), | ||||||
| 		SecurityId:        security.SecurityId, | 		SecurityId:        security.SecurityId, | ||||||
| 		ParentAccountId:   -1, | 		ParentAccountId:   -1, | ||||||
| 		Type:              Investment, | 		Type:              models.Investment, | ||||||
| 	} | 	} | ||||||
| 	i.Accounts = append(i.Accounts, account) | 	i.Accounts = append(i.Accounts, account) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ package handlers_test | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -63,7 +63,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 | ||||||
| @@ -76,7 +76,7 @@ func findSecurity(client *http.Client, symbol string, tipe handlers.SecurityType | |||||||
| 	return nil, fmt.Errorf("Unable to find security: \"%s\"", symbol) | 	return nil, fmt.Errorf("Unable to find security: \"%s\"", symbol) | ||||||
| } | } | ||||||
|  |  | ||||||
| func findAccount(client *http.Client, name string, tipe handlers.AccountType, securityid int64) (*handlers.Account, error) { | func findAccount(client *http.Client, name string, tipe models.AccountType, securityid int64) (*models.Account, error) { | ||||||
| 	accounts, err := getAccounts(client) | 	accounts, err := getAccounts(client) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -104,11 +104,11 @@ func TestImportOFX401kMutualFunds(t *testing.T) { | |||||||
| 			t.Fatalf("Error removing default security: %s\n", err) | 			t.Fatalf("Error removing default security: %s\n", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		account := &handlers.Account{ | 		account := &models.Account{ | ||||||
| 			SecurityId:      d.securities[0].SecurityId, | 			SecurityId:      d.securities[0].SecurityId, | ||||||
| 			UserId:          d.users[0].UserId, | 			UserId:          d.users[0].UserId, | ||||||
| 			ParentAccountId: -1, | 			ParentAccountId: -1, | ||||||
| 			Type:            handlers.Investment, | 			Type:            models.Investment, | ||||||
| 			Name:            "401k", | 			Name:            "401k", | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -125,18 +125,18 @@ 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) | ||||||
| 		} | 		} | ||||||
| 		tradingaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", handlers.Trading, security.SecurityId) | 		tradingaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", models.Trading, security.SecurityId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("Error finding VANGUARD TARGET 2045 trading account: %s\n", err) | 			t.Fatalf("Error finding VANGUARD TARGET 2045 trading account: %s\n", err) | ||||||
| 		} | 		} | ||||||
| 		accountBalanceHelper(t, d.clients[0], tradingaccount, "-3.35400") | 		accountBalanceHelper(t, d.clients[0], tradingaccount, "-3.35400") | ||||||
|  |  | ||||||
| 		// Ensure actual holding account was created and in the correct place | 		// Ensure actual holding account was created and in the correct place | ||||||
| 		investmentaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", handlers.Investment, security.SecurityId) | 		investmentaccount, err := findAccount(d.clients[0], "VANGUARD TARGET 2045", models.Investment, security.SecurityId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("Error finding VANGUARD TARGET 2045 investment account: %s\n", err) | 			t.Fatalf("Error finding VANGUARD TARGET 2045 investment account: %s\n", err) | ||||||
| 		} | 		} | ||||||
| @@ -163,11 +163,11 @@ func TestImportOFXBrokerage(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Create the brokerage account | 		// Create the brokerage account | ||||||
| 		account := &handlers.Account{ | 		account := &models.Account{ | ||||||
| 			SecurityId:      d.securities[0].SecurityId, | 			SecurityId:      d.securities[0].SecurityId, | ||||||
| 			UserId:          d.users[0].UserId, | 			UserId:          d.users[0].UserId, | ||||||
| 			ParentAccountId: -1, | 			ParentAccountId: -1, | ||||||
| 			Type:            handlers.Investment, | 			Type:            models.Investment, | ||||||
| 			Name:            "Personal Brokerage", | 			Name:            "Personal Brokerage", | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -184,7 +184,7 @@ func TestImportOFXBrokerage(t *testing.T) { | |||||||
|  |  | ||||||
| 		// Make sure the USD trading account was created and has  the right | 		// Make sure the USD trading account was created and has  the right | ||||||
| 		// value | 		// value | ||||||
| 		usdtrading, err := findAccount(d.clients[0], "USD", handlers.Trading, d.users[0].DefaultCurrency) | 		usdtrading, err := findAccount(d.clients[0], "USD", models.Trading, d.users[0].DefaultCurrency) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("Error finding USD trading account: %s\n", err) | 			t.Fatalf("Error finding USD trading account: %s\n", err) | ||||||
| 		} | 		} | ||||||
| @@ -204,19 +204,19 @@ 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) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			account, err := findAccount(d.clients[0], check.Name, handlers.Investment, security.SecurityId) | 			account, err := findAccount(d.clients[0], check.Name, models.Investment, security.SecurityId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("Error finding trading account: %s\n", err) | 				t.Fatalf("Error finding trading account: %s\n", err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			accountBalanceHelper(t, d.clients[0], account, check.Balance) | 			accountBalanceHelper(t, d.clients[0], account, check.Balance) | ||||||
|  |  | ||||||
| 			tradingaccount, err := findAccount(d.clients[0], check.Name, handlers.Trading, security.SecurityId) | 			tradingaccount, err := findAccount(d.clients[0], check.Name, models.Trading, security.SecurityId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("Error finding trading account: %s\n", err) | 				t.Fatalf("Error finding trading account: %s\n", err) | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -1,47 +1,13 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Price struct { | func CreatePriceIfNotExist(tx *Tx, price *models.Price) error { | ||||||
| 	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 { |  | ||||||
| 	if len(price.RemoteId) == 0 { | 	if len(price.RemoteId) == 0 { | ||||||
| 		// Always create a new price if we can't match on the RemoteId | 		// Always create a new price if we can't match on the RemoteId | ||||||
| 		err := tx.Insert(price) | 		err := tx.Insert(price) | ||||||
| @@ -51,7 +17,7 @@ func CreatePriceIfNotExist(tx *Tx, price *Price) error { | |||||||
| 		return nil | 		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) | 	_, 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 { | 	if err != nil { | ||||||
| @@ -69,8 +35,8 @@ func CreatePriceIfNotExist(tx *Tx, price *Price) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetPrice(tx *Tx, priceid, securityid int64) (*Price, error) { | func GetPrice(tx *Tx, priceid, securityid int64) (*models.Price, error) { | ||||||
| 	var p Price | 	var p models.Price | ||||||
| 	err := tx.SelectOne(&p, "SELECT * from prices where PriceId=? AND SecurityId=?", priceid, securityid) | 	err := tx.SelectOne(&p, "SELECT * from prices where PriceId=? AND SecurityId=?", priceid, securityid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -78,8 +44,8 @@ func GetPrice(tx *Tx, priceid, securityid int64) (*Price, error) { | |||||||
| 	return &p, nil | 	return &p, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetPrices(tx *Tx, securityid int64) (*[]*Price, error) { | func GetPrices(tx *Tx, securityid int64) (*[]*models.Price, error) { | ||||||
| 	var prices []*Price | 	var prices []*models.Price | ||||||
|  |  | ||||||
| 	_, err := tx.Select(&prices, "SELECT * from prices where SecurityId=?", securityid) | 	_, err := tx.Select(&prices, "SELECT * from prices where SecurityId=?", securityid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -89,8 +55,8 @@ 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) (*models.Price, error) { | ||||||
| 	var p Price | 	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) | 	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 { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -99,8 +65,8 @@ 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) (*models.Price, error) { | ||||||
| 	var p Price | 	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) | 	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 { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -109,7 +75,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) (*models.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) | ||||||
|  |  | ||||||
| @@ -129,14 +95,14 @@ func GetClosestPrice(tx *Tx, security, currency *Security, date *time.Time) (*Pr | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func PriceHandler(r *http.Request, context *Context, user *User, securityid int64) ResponseWriterWriter { | func PriceHandler(r *http.Request, context *Context, user *models.User, securityid int64) ResponseWriterWriter { | ||||||
| 	security, err := GetSecurity(context.Tx, securityid, user.UserId) | 	security, err := GetSecurity(context.Tx, securityid, user.UserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if r.Method == "POST" { | 	if r.Method == "POST" { | ||||||
| 		var price Price | 		var price models.Price | ||||||
| 		if err := ReadJSON(r, &price); err != nil { | 		if err := ReadJSON(r, &price); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| @@ -160,7 +126,7 @@ func PriceHandler(r *http.Request, context *Context, user *User, securityid int6 | |||||||
| 	} else if r.Method == "GET" { | 	} else if r.Method == "GET" { | ||||||
| 		if context.LastLevel() { | 		if context.LastLevel() { | ||||||
| 			//Return all this security's prices | 			//Return all this security's prices | ||||||
| 			var pl PriceList | 			var pl models.PriceList | ||||||
|  |  | ||||||
| 			prices, err := GetPrices(context.Tx, security.SecurityId) | 			prices, err := GetPrices(context.Tx, security.SecurityId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -189,7 +155,7 @@ func PriceHandler(r *http.Request, context *Context, user *User, securityid int6 | |||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| 		if r.Method == "PUT" { | 		if r.Method == "PUT" { | ||||||
| 			var price Price | 			var price models.Price | ||||||
| 			if err := ReadJSON(r, &price); err != nil || price.PriceId != priceid { | 			if err := ReadJSON(r, &price); err != nil || price.PriceId != priceid { | ||||||
| 				return NewError(3 /*Invalid Request*/) | 				return NewError(3 /*Invalid Request*/) | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -14,7 +15,7 @@ func luaRegisterPrices(L *lua.LState) { | |||||||
| 	L.SetField(mt, "__metatable", lua.LString("protected")) | 	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 := L.NewUserData() | ||||||
| 	ud.Value = price | 	ud.Value = price | ||||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaPriceTypeName)) | 	L.SetMetatable(ud, L.GetTypeMetatable(luaPriceTypeName)) | ||||||
| @@ -22,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. | // 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) | 	ud := L.CheckUserData(n) | ||||||
| 	if price, ok := ud.Value.(*Price); ok { | 	if price, ok := ud.Value.(*models.Price); ok { | ||||||
| 		return price | 		return price | ||||||
| 	} | 	} | ||||||
| 	L.ArgError(n, "price expected") | 	L.ArgError(n, "price expected") | ||||||
| @@ -59,7 +60,7 @@ func luaPrice__index(L *lua.LState) int { | |||||||
| 		} | 		} | ||||||
| 		L.Push(SecurityToLua(L, c)) | 		L.Push(SecurityToLua(L, c)) | ||||||
| 	case "Value", "value": | 	case "Value", "value": | ||||||
| 		amt, err := GetBigAmount(p.Value) | 		amt, err := models.GetBigAmount(p.Value) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			panic(err) | 			panic(err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -2,20 +2,21 @@ 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" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func createPrice(client *http.Client, price *handlers.Price) (*handlers.Price, error) { | func createPrice(client *http.Client, price *models.Price) (*models.Price, error) { | ||||||
| 	var p handlers.Price | 	var p models.Price | ||||||
| 	err := create(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/") | 	err := create(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/") | ||||||
| 	return &p, err | 	return &p, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func getPrice(client *http.Client, priceid, securityid int64) (*handlers.Price, error) { | func getPrice(client *http.Client, priceid, securityid int64) (*models.Price, error) { | ||||||
| 	var p handlers.Price | 	var p models.Price | ||||||
| 	err := read(client, &p, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/"+strconv.FormatInt(priceid, 10)) | 	err := read(client, &p, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/"+strconv.FormatInt(priceid, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -23,8 +24,8 @@ func getPrice(client *http.Client, priceid, securityid int64) (*handlers.Price, | |||||||
| 	return &p, nil | 	return &p, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getPrices(client *http.Client, securityid int64) (*handlers.PriceList, error) { | func getPrices(client *http.Client, securityid int64) (*models.PriceList, error) { | ||||||
| 	var pl handlers.PriceList | 	var pl models.PriceList | ||||||
| 	err := read(client, &pl, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/") | 	err := read(client, &pl, "/v1/securities/"+strconv.FormatInt(securityid, 10)+"/prices/") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -32,8 +33,8 @@ func getPrices(client *http.Client, securityid int64) (*handlers.PriceList, erro | |||||||
| 	return &pl, nil | 	return &pl, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updatePrice(client *http.Client, price *handlers.Price) (*handlers.Price, error) { | func updatePrice(client *http.Client, price *models.Price) (*models.Price, error) { | ||||||
| 	var p handlers.Price | 	var p models.Price | ||||||
| 	err := update(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/"+strconv.FormatInt(price.PriceId, 10)) | 	err := update(client, price, &p, "/v1/securities/"+strconv.FormatInt(price.SecurityId, 10)+"/prices/"+strconv.FormatInt(price.PriceId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -41,7 +42,7 @@ func updatePrice(client *http.Client, price *handlers.Price) (*handlers.Price, e | |||||||
| 	return &p, nil | 	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)) | 	err := remove(client, "/v1/securities/"+strconv.FormatInt(p.SecurityId, 10)+"/prices/"+strconv.FormatInt(p.PriceId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|   | |||||||
| @@ -2,13 +2,12 @@ package handlers | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -25,67 +24,8 @@ const ( | |||||||
|  |  | ||||||
| const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | ||||||
|  |  | ||||||
| type Report struct { | func GetReport(tx *Tx, reportid int64, userid int64) (*models.Report, error) { | ||||||
| 	ReportId int64 | 	var r models.Report | ||||||
| 	UserId   int64 |  | ||||||
| 	Name     string |  | ||||||
| 	Lua      string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // The maximum length (in bytes) the Lua code may be. This is used to set the |  | ||||||
| // max size of the database columns (with an added fudge factor) |  | ||||||
| const LuaMaxLength int = 65536 |  | ||||||
|  |  | ||||||
| func (r *Report) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(r) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (r *Report) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(r) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type ReportList struct { |  | ||||||
| 	Reports *[]Report `json:"reports"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rl *ReportList) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(rl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rl *ReportList) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(rl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Series struct { |  | ||||||
| 	Values []float64 |  | ||||||
| 	Series map[string]*Series |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Tabulation struct { |  | ||||||
| 	ReportId int64 |  | ||||||
| 	Title    string |  | ||||||
| 	Subtitle string |  | ||||||
| 	Units    string |  | ||||||
| 	Labels   []string |  | ||||||
| 	Series   map[string]*Series |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tabulation) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(t) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tabulation) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(t) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetReport(tx *Tx, reportid int64, userid int64) (*Report, error) { |  | ||||||
| 	var r Report |  | ||||||
|  |  | ||||||
| 	err := tx.SelectOne(&r, "SELECT * from reports where UserId=? AND ReportId=?", userid, reportid) | 	err := tx.SelectOne(&r, "SELECT * from reports where UserId=? AND ReportId=?", userid, reportid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -94,8 +34,8 @@ func GetReport(tx *Tx, reportid int64, userid int64) (*Report, error) { | |||||||
| 	return &r, nil | 	return &r, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetReports(tx *Tx, userid int64) (*[]Report, error) { | func GetReports(tx *Tx, userid int64) (*[]models.Report, error) { | ||||||
| 	var reports []Report | 	var reports []models.Report | ||||||
|  |  | ||||||
| 	_, err := tx.Select(&reports, "SELECT * from reports where UserId=?", userid) | 	_, err := tx.Select(&reports, "SELECT * from reports where UserId=?", userid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -104,7 +44,7 @@ func GetReports(tx *Tx, userid int64) (*[]Report, error) { | |||||||
| 	return &reports, nil | 	return &reports, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func InsertReport(tx *Tx, r *Report) error { | func InsertReport(tx *Tx, r *models.Report) error { | ||||||
| 	err := tx.Insert(r) | 	err := tx.Insert(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -112,7 +52,7 @@ func InsertReport(tx *Tx, r *Report) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func UpdateReport(tx *Tx, r *Report) error { | func UpdateReport(tx *Tx, r *models.Report) error { | ||||||
| 	count, err := tx.Update(r) | 	count, err := tx.Update(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -123,7 +63,7 @@ func UpdateReport(tx *Tx, r *Report) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func DeleteReport(tx *Tx, r *Report) error { | func DeleteReport(tx *Tx, r *models.Report) error { | ||||||
| 	count, err := tx.Delete(r) | 	count, err := tx.Delete(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -134,7 +74,7 @@ func DeleteReport(tx *Tx, r *Report) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func runReport(tx *Tx, user *User, report *Report) (*Tabulation, error) { | func runReport(tx *Tx, user *models.User, report *models.Report) (*models.Tabulation, error) { | ||||||
| 	// Create a new LState without opening the default libs for security | 	// Create a new LState without opening the default libs for security | ||||||
| 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | ||||||
| 	defer L.Close() | 	defer L.Close() | ||||||
| @@ -188,7 +128,7 @@ func runReport(tx *Tx, user *User, report *Report) (*Tabulation, error) { | |||||||
|  |  | ||||||
| 	value := L.Get(-1) | 	value := L.Get(-1) | ||||||
| 	if ud, ok := value.(*lua.LUserData); ok { | 	if ud, ok := value.(*lua.LUserData); ok { | ||||||
| 		if tabulation, ok := ud.Value.(*Tabulation); ok { | 		if tabulation, ok := ud.Value.(*models.Tabulation); ok { | ||||||
| 			return tabulation, nil | 			return tabulation, nil | ||||||
| 		} else { | 		} else { | ||||||
| 			return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) | 			return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) | ||||||
| @@ -198,7 +138,7 @@ func runReport(tx *Tx, user *User, report *Report) (*Tabulation, error) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func ReportTabulationHandler(tx *Tx, r *http.Request, user *User, reportid int64) ResponseWriterWriter { | func ReportTabulationHandler(tx *Tx, r *http.Request, user *models.User, reportid int64) ResponseWriterWriter { | ||||||
| 	report, err := GetReport(tx, reportid, user.UserId) | 	report, err := GetReport(tx, reportid, user.UserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| @@ -223,14 +163,14 @@ func ReportHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if r.Method == "POST" { | 	if r.Method == "POST" { | ||||||
| 		var report Report | 		var report models.Report | ||||||
| 		if err := ReadJSON(r, &report); err != nil { | 		if err := ReadJSON(r, &report); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| 		report.ReportId = -1 | 		report.ReportId = -1 | ||||||
| 		report.UserId = user.UserId | 		report.UserId = user.UserId | ||||||
|  |  | ||||||
| 		if len(report.Lua) >= LuaMaxLength { | 		if len(report.Lua) >= models.LuaMaxLength { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -244,7 +184,7 @@ func ReportHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 	} else if r.Method == "GET" { | 	} else if r.Method == "GET" { | ||||||
| 		if context.LastLevel() { | 		if context.LastLevel() { | ||||||
| 			//Return all Reports | 			//Return all Reports | ||||||
| 			var rl ReportList | 			var rl models.ReportList | ||||||
| 			reports, err := GetReports(context.Tx, user.UserId) | 			reports, err := GetReports(context.Tx, user.UserId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print(err) | 				log.Print(err) | ||||||
| @@ -277,13 +217,13 @@ func ReportHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if r.Method == "PUT" { | 		if r.Method == "PUT" { | ||||||
| 			var report Report | 			var report models.Report | ||||||
| 			if err := ReadJSON(r, &report); err != nil || report.ReportId != reportid { | 			if err := ReadJSON(r, &report); err != nil || report.ReportId != reportid { | ||||||
| 				return NewError(3 /*Invalid Request*/) | 				return NewError(3 /*Invalid Request*/) | ||||||
| 			} | 			} | ||||||
| 			report.UserId = user.UserId | 			report.UserId = user.UserId | ||||||
|  |  | ||||||
| 			if len(report.Lua) >= LuaMaxLength { | 			if len(report.Lua) >= models.LuaMaxLength { | ||||||
| 				return NewError(3 /*Invalid Request*/) | 				return NewError(3 /*Invalid Request*/) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -21,9 +22,9 @@ func luaRegisterTabulations(L *lua.LState) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Checks whether the first lua argument is a *LUserData with *Tabulation and returns *Tabulation | // Checks whether the first lua argument is a *LUserData with *Tabulation and returns *Tabulation | ||||||
| func luaCheckTabulation(L *lua.LState, n int) *Tabulation { | func luaCheckTabulation(L *lua.LState, n int) *models.Tabulation { | ||||||
| 	ud := L.CheckUserData(n) | 	ud := L.CheckUserData(n) | ||||||
| 	if tabulation, ok := ud.Value.(*Tabulation); ok { | 	if tabulation, ok := ud.Value.(*models.Tabulation); ok { | ||||||
| 		return tabulation | 		return tabulation | ||||||
| 	} | 	} | ||||||
| 	L.ArgError(n, "tabulation expected") | 	L.ArgError(n, "tabulation expected") | ||||||
| @@ -31,9 +32,9 @@ func luaCheckTabulation(L *lua.LState, n int) *Tabulation { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Checks whether the first lua argument is a *LUserData with *Series and returns *Series | // Checks whether the first lua argument is a *LUserData with *Series and returns *Series | ||||||
| func luaCheckSeries(L *lua.LState, n int) *Series { | func luaCheckSeries(L *lua.LState, n int) *models.Series { | ||||||
| 	ud := L.CheckUserData(n) | 	ud := L.CheckUserData(n) | ||||||
| 	if series, ok := ud.Value.(*Series); ok { | 	if series, ok := ud.Value.(*models.Series); ok { | ||||||
| 		return series | 		return series | ||||||
| 	} | 	} | ||||||
| 	L.ArgError(n, "series expected") | 	L.ArgError(n, "series expected") | ||||||
| @@ -43,9 +44,9 @@ func luaCheckSeries(L *lua.LState, n int) *Series { | |||||||
| func luaTabulationNew(L *lua.LState) int { | func luaTabulationNew(L *lua.LState) int { | ||||||
| 	numvalues := L.CheckInt(1) | 	numvalues := L.CheckInt(1) | ||||||
| 	ud := L.NewUserData() | 	ud := L.NewUserData() | ||||||
| 	ud.Value = &Tabulation{ | 	ud.Value = &models.Tabulation{ | ||||||
| 		Labels: make([]string, numvalues), | 		Labels: make([]string, numvalues), | ||||||
| 		Series: make(map[string]*Series), | 		Series: make(map[string]*models.Series), | ||||||
| 	} | 	} | ||||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaTabulationTypeName)) | 	L.SetMetatable(ud, L.GetTypeMetatable(luaTabulationTypeName)) | ||||||
| 	L.Push(ud) | 	L.Push(ud) | ||||||
| @@ -94,8 +95,8 @@ func luaTabulationSeries(L *lua.LState) int { | |||||||
| 	if ok { | 	if ok { | ||||||
| 		ud.Value = s | 		ud.Value = s | ||||||
| 	} else { | 	} else { | ||||||
| 		tabulation.Series[name] = &Series{ | 		tabulation.Series[name] = &models.Series{ | ||||||
| 			Series: make(map[string]*Series), | 			Series: make(map[string]*models.Series), | ||||||
| 			Values: make([]float64, cap(tabulation.Labels)), | 			Values: make([]float64, cap(tabulation.Labels)), | ||||||
| 		} | 		} | ||||||
| 		ud.Value = tabulation.Series[name] | 		ud.Value = tabulation.Series[name] | ||||||
| @@ -175,8 +176,8 @@ func luaSeriesSeries(L *lua.LState) int { | |||||||
| 	if ok { | 	if ok { | ||||||
| 		ud.Value = s | 		ud.Value = s | ||||||
| 	} else { | 	} else { | ||||||
| 		parent.Series[name] = &Series{ | 		parent.Series[name] = &models.Series{ | ||||||
| 			Series: make(map[string]*Series), | 			Series: make(map[string]*models.Series), | ||||||
| 			Values: make([]float64, cap(parent.Values)), | 			Values: make([]float64, cap(parent.Values)), | ||||||
| 		} | 		} | ||||||
| 		ud.Value = parent.Series[name] | 		ud.Value = parent.Series[name] | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ package handlers_test | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| @@ -25,7 +25,7 @@ function generate() | |||||||
|     t:title(tostring(test())) |     t:title(tostring(test())) | ||||||
| 	return t | 	return t | ||||||
| end`, lt.Lua) | end`, lt.Lua) | ||||||
| 		r := handlers.Report{ | 		r := models.Report{ | ||||||
| 			Name: lt.Name, | 			Name: lt.Name, | ||||||
| 			Lua:  lua, | 			Lua:  lua, | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -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 createReport(client *http.Client, report *handlers.Report) (*handlers.Report, error) { | func createReport(client *http.Client, report *models.Report) (*models.Report, error) { | ||||||
| 	var r handlers.Report | 	var r models.Report | ||||||
| 	err := create(client, report, &r, "/v1/reports/") | 	err := create(client, report, &r, "/v1/reports/") | ||||||
| 	return &r, err | 	return &r, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func getReport(client *http.Client, reportid int64) (*handlers.Report, error) { | func getReport(client *http.Client, reportid int64) (*models.Report, error) { | ||||||
| 	var r handlers.Report | 	var r models.Report | ||||||
| 	err := read(client, &r, "/v1/reports/"+strconv.FormatInt(reportid, 10)) | 	err := read(client, &r, "/v1/reports/"+strconv.FormatInt(reportid, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -22,8 +23,8 @@ func getReport(client *http.Client, reportid int64) (*handlers.Report, error) { | |||||||
| 	return &r, nil | 	return &r, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getReports(client *http.Client) (*handlers.ReportList, error) { | func getReports(client *http.Client) (*models.ReportList, error) { | ||||||
| 	var rl handlers.ReportList | 	var rl models.ReportList | ||||||
| 	err := read(client, &rl, "/v1/reports/") | 	err := read(client, &rl, "/v1/reports/") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -31,8 +32,8 @@ func getReports(client *http.Client) (*handlers.ReportList, error) { | |||||||
| 	return &rl, nil | 	return &rl, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateReport(client *http.Client, report *handlers.Report) (*handlers.Report, error) { | func updateReport(client *http.Client, report *models.Report) (*models.Report, error) { | ||||||
| 	var r handlers.Report | 	var r models.Report | ||||||
| 	err := update(client, report, &r, "/v1/reports/"+strconv.FormatInt(report.ReportId, 10)) | 	err := update(client, report, &r, "/v1/reports/"+strconv.FormatInt(report.ReportId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -40,7 +41,7 @@ func updateReport(client *http.Client, report *handlers.Report) (*handlers.Repor | |||||||
| 	return &r, nil | 	return &r, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteReport(client *http.Client, r *handlers.Report) error { | func deleteReport(client *http.Client, r *models.Report) error { | ||||||
| 	err := remove(client, "/v1/reports/"+strconv.FormatInt(r.ReportId, 10)) | 	err := remove(client, "/v1/reports/"+strconv.FormatInt(r.ReportId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -48,8 +49,8 @@ func deleteReport(client *http.Client, r *handlers.Report) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func tabulateReport(client *http.Client, reportid int64) (*handlers.Tabulation, error) { | func tabulateReport(client *http.Client, reportid int64) (*models.Tabulation, error) { | ||||||
| 	var t handlers.Tabulation | 	var t models.Tabulation | ||||||
| 	err := read(client, &t, "/v1/reports/"+strconv.FormatInt(reportid, 10)+"/tabulations") | 	err := read(client, &t, "/v1/reports/"+strconv.FormatInt(reportid, 10)+"/tabulations") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -73,7 +74,7 @@ func TestCreateReport(t *testing.T) { | |||||||
| 				t.Errorf("Lua doesn't match") | 				t.Errorf("Lua doesn't match") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.Lua = string(make([]byte, handlers.LuaMaxLength+1)) | 			r.Lua = string(make([]byte, models.LuaMaxLength+1)) | ||||||
| 			_, err := createReport(d.clients[orig.UserId], &r) | 			_, err := createReport(d.clients[orig.UserId], &r) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				t.Fatalf("Expected error creating report with too-long Lua") | 				t.Fatalf("Expected error creating report with too-long Lua") | ||||||
| @@ -173,7 +174,7 @@ func TestUpdateReport(t *testing.T) { | |||||||
| 				t.Errorf("Lua doesn't match") | 				t.Errorf("Lua doesn't match") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.Lua = string(make([]byte, handlers.LuaMaxLength+1)) | 			r.Lua = string(make([]byte, models.LuaMaxLength+1)) | ||||||
| 			_, err = updateReport(d.clients[orig.UserId], r) | 			_, err = updateReport(d.clients[orig.UserId], r) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				t.Fatalf("Expected error updating report with too-long Lua") | 				t.Fatalf("Expected error updating report with too-long Lua") | ||||||
| @@ -214,7 +215,7 @@ func TestDeleteReport(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| func seriesEqualityHelper(t *testing.T, orig, curr map[string]*handlers.Series, name string) { | func seriesEqualityHelper(t *testing.T, orig, curr map[string]*models.Series, name string) { | ||||||
| 	if orig == nil || curr == nil { | 	if orig == nil || curr == nil { | ||||||
| 		if orig != nil { | 		if orig != nil { | ||||||
| 			t.Fatalf("`%s` series unexpectedly nil", name) | 			t.Fatalf("`%s` series unexpectedly nil", name) | ||||||
| @@ -242,7 +243,7 @@ func seriesEqualityHelper(t *testing.T, orig, curr map[string]*handlers.Series, | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func tabulationEqualityHelper(t *testing.T, orig, curr *handlers.Tabulation) { | func tabulationEqualityHelper(t *testing.T, orig, curr *models.Tabulation) { | ||||||
| 	if orig.Title != curr.Title { | 	if orig.Title != curr.Title { | ||||||
| 		t.Errorf("Tabulation Title doesn't match") | 		t.Errorf("Tabulation Title doesn't match") | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -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("}") | ||||||
|   | |||||||
| @@ -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*/) | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -3,13 +3,14 @@ package handlers | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| 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() | ||||||
|  |  | ||||||
| @@ -18,9 +19,9 @@ 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).(*User) | 		user, ok := ctx.Value(userContextKey).(*models.User) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("Couldn't find User in lua's Context") | 			return nil, errors.New("Couldn't find User in lua's Context") | ||||||
| 		} | 		} | ||||||
| @@ -30,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] | ||||||
| 		} | 		} | ||||||
| @@ -42,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 | ||||||
| @@ -50,7 +51,7 @@ func luaContextGetDefaultCurrency(L *lua.LState) (*Security, error) { | |||||||
|  |  | ||||||
| 	ctx := L.Context() | 	ctx := L.Context() | ||||||
|  |  | ||||||
| 	user, ok := ctx.Value(userContextKey).(*User) | 	user, ok := ctx.Value(userContextKey).(*models.User) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, errors.New("Couldn't find User in lua's Context") | 		return nil, errors.New("Couldn't find User in lua's Context") | ||||||
| 	} | 	} | ||||||
| @@ -106,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)) | ||||||
| @@ -114,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") | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -1,37 +1,15 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/rand" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Session struct { | func GetSession(tx *Tx, r *http.Request) (*models.Session, error) { | ||||||
| 	SessionId     int64 | 	var s models.Session | ||||||
| 	SessionSecret string `json:"-"` |  | ||||||
| 	UserId        int64 |  | ||||||
| 	Created       time.Time |  | ||||||
| 	Expires       time.Time |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Session) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(s) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Session) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(s) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetSession(tx *Tx, r *http.Request) (*Session, error) { |  | ||||||
| 	var s Session |  | ||||||
|  |  | ||||||
| 	cookie, err := r.Cookie("moneygo-session") | 	cookie, err := r.Cookie("moneygo-session") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -62,16 +40,8 @@ func DeleteSessionIfExists(tx *Tx, r *http.Request) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewSessionCookie() (string, error) { |  | ||||||
| 	bits := make([]byte, 128) |  | ||||||
| 	if _, err := io.ReadFull(rand.Reader, bits); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return base64.StdEncoding.EncodeToString(bits), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type NewSessionWriter struct { | type NewSessionWriter struct { | ||||||
| 	session *Session | 	session *models.Session | ||||||
| 	cookie  *http.Cookie | 	cookie  *http.Cookie | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -81,14 +51,12 @@ func (n *NewSessionWriter) Write(w http.ResponseWriter) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func NewSession(tx *Tx, r *http.Request, userid int64) (*NewSessionWriter, error) { | func NewSession(tx *Tx, r *http.Request, userid int64) (*NewSessionWriter, error) { | ||||||
| 	s := Session{} | 	s, err := models.NewSession(userid) | ||||||
|  |  | ||||||
| 	session_secret, err := NewSessionCookie() |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	existing, err := tx.SelectInt("SELECT count(*) from sessions where SessionSecret=?", session_secret) | 	existing, err := tx.SelectInt("SELECT count(*) from sessions where SessionSecret=?", s.SessionSecret) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -96,31 +64,17 @@ func NewSession(tx *Tx, r *http.Request, userid int64) (*NewSessionWriter, error | |||||||
| 		return nil, fmt.Errorf("%d session(s) exist with the generated session_secret", existing) | 		return nil, fmt.Errorf("%d session(s) exist with the generated session_secret", existing) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cookie := http.Cookie{ | 	err = tx.Insert(s) | ||||||
| 		Name:     "moneygo-session", |  | ||||||
| 		Value:    session_secret, |  | ||||||
| 		Path:     "/", |  | ||||||
| 		Domain:   r.URL.Host, |  | ||||||
| 		Expires:  time.Now().AddDate(0, 1, 0), // a month from now |  | ||||||
| 		Secure:   true, |  | ||||||
| 		HttpOnly: true, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.SessionSecret = session_secret |  | ||||||
| 	s.UserId = userid |  | ||||||
| 	s.Created = time.Now() |  | ||||||
| 	s.Expires = cookie.Expires |  | ||||||
|  |  | ||||||
| 	err = tx.Insert(&s) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return &NewSessionWriter{&s, &cookie}, nil |  | ||||||
|  | 	return &NewSessionWriter{s, s.Cookie(r.URL.Host)}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func SessionHandler(r *http.Request, context *Context) ResponseWriterWriter { | func SessionHandler(r *http.Request, context *Context) ResponseWriterWriter { | ||||||
| 	if r.Method == "POST" || r.Method == "PUT" { | 	if r.Method == "POST" || r.Method == "PUT" { | ||||||
| 		var user User | 		var user models.User | ||||||
| 		if err := ReadJSON(r, &user); err != nil { | 		if err := ReadJSON(r, &user); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -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" | ||||||
| 	"net/http/cookiejar" | 	"net/http/cookiejar" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| @@ -26,8 +27,8 @@ func newSession(user *User) (*http.Client, error) { | |||||||
| 	return &client, nil | 	return &client, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getSession(client *http.Client) (*handlers.Session, error) { | func getSession(client *http.Client) (*models.Session, error) { | ||||||
| 	var s handlers.Session | 	var s models.Session | ||||||
| 	err := read(client, &s, "/v1/sessions/") | 	err := read(client, &s, "/v1/sessions/") | ||||||
| 	return &s, err | 	return &s, err | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package handlers_test | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/aclindsa/moneygo/internal/handlers" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -36,12 +36,12 @@ 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       []models.Price | ||||||
| 	accounts     []handlers.Account // accounts must appear after their parents in this slice | 	accounts     []models.Account // accounts must appear after their parents in this slice | ||||||
| 	transactions []handlers.Transaction | 	transactions []models.Transaction | ||||||
| 	reports      []handlers.Report | 	reports      []models.Report | ||||||
| 	tabulations  []handlers.Tabulation | 	tabulations  []models.Tabulation | ||||||
| } | } | ||||||
|  |  | ||||||
| type TestDataFunc func(*testing.T, *TestData) | type TestDataFunc func(*testing.T, *TestData) | ||||||
| @@ -112,7 +112,7 @@ func (t *TestData) Initialize() (*TestData, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for i, transaction := range t.transactions { | 	for i, transaction := range t.transactions { | ||||||
| 		transaction.Splits = []*handlers.Split{} | 		transaction.Splits = []*models.Split{} | ||||||
| 		for _, s := range t.transactions[i].Splits { | 		for _, s := range t.transactions[i].Splits { | ||||||
| 			// Make a copy of the split since Splits is a slice of pointers so | 			// Make a copy of the split since Splits is a slice of pointers so | ||||||
| 			// copying the transaction doesn't | 			// copying the transaction doesn't | ||||||
| @@ -170,14 +170,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 +186,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 +195,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,11 +204,11 @@ var data = []TestData{ | |||||||
| 				Description: "Euro", | 				Description: "Euro", | ||||||
| 				Symbol:      "€", | 				Symbol:      "€", | ||||||
| 				Precision:   2, | 				Precision:   2, | ||||||
| 				Type:        handlers.Currency, | 				Type:        models.Currency, | ||||||
| 				AlternateId: "978", | 				AlternateId: "978", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		prices: []handlers.Price{ | 		prices: []models.Price{ | ||||||
| 			{ | 			{ | ||||||
| 				SecurityId: 1, | 				SecurityId: 1, | ||||||
| 				CurrencyId: 0, | 				CurrencyId: 0, | ||||||
| @@ -245,78 +245,78 @@ var data = []TestData{ | |||||||
| 				RemoteId:   "USDEUR819298714", | 				RemoteId:   "USDEUR819298714", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		accounts: []handlers.Account{ | 		accounts: []models.Account{ | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: -1, | 				ParentAccountId: -1, | ||||||
| 				Type:            handlers.Asset, | 				Type:            models.Asset, | ||||||
| 				Name:            "Assets", | 				Name:            "Assets", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: 0, | 				ParentAccountId: 0, | ||||||
| 				Type:            handlers.Bank, | 				Type:            models.Bank, | ||||||
| 				Name:            "Credit Union Checking", | 				Name:            "Credit Union Checking", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: -1, | 				ParentAccountId: -1, | ||||||
| 				Type:            handlers.Expense, | 				Type:            models.Expense, | ||||||
| 				Name:            "Expenses", | 				Name:            "Expenses", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: 2, | 				ParentAccountId: 2, | ||||||
| 				Type:            handlers.Expense, | 				Type:            models.Expense, | ||||||
| 				Name:            "Groceries", | 				Name:            "Groceries", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: 2, | 				ParentAccountId: 2, | ||||||
| 				Type:            handlers.Expense, | 				Type:            models.Expense, | ||||||
| 				Name:            "Cable", | 				Name:            "Cable", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          1, | 				UserId:          1, | ||||||
| 				SecurityId:      2, | 				SecurityId:      2, | ||||||
| 				ParentAccountId: -1, | 				ParentAccountId: -1, | ||||||
| 				Type:            handlers.Asset, | 				Type:            models.Asset, | ||||||
| 				Name:            "Assets", | 				Name:            "Assets", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          1, | 				UserId:          1, | ||||||
| 				SecurityId:      2, | 				SecurityId:      2, | ||||||
| 				ParentAccountId: -1, | 				ParentAccountId: -1, | ||||||
| 				Type:            handlers.Expense, | 				Type:            models.Expense, | ||||||
| 				Name:            "Expenses", | 				Name:            "Expenses", | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				UserId:          0, | 				UserId:          0, | ||||||
| 				SecurityId:      0, | 				SecurityId:      0, | ||||||
| 				ParentAccountId: -1, | 				ParentAccountId: -1, | ||||||
| 				Type:            handlers.Liability, | 				Type:            models.Liability, | ||||||
| 				Name:            "Credit Card", | 				Name:            "Credit Card", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		transactions: []handlers.Transaction{ | 		transactions: []models.Transaction{ | ||||||
| 			{ | 			{ | ||||||
| 				UserId:      0, | 				UserId:      0, | ||||||
| 				Description: "weekly groceries", | 				Description: "weekly groceries", | ||||||
| 				Date:        time.Date(2017, time.October, 15, 1, 16, 59, 0, time.UTC), | 				Date:        time.Date(2017, time.October, 15, 1, 16, 59, 0, time.UTC), | ||||||
| 				Splits: []*handlers.Split{ | 				Splits: []*models.Split{ | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  1, | 						AccountId:  1, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "-5.6", | 						Amount:     "-5.6", | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  3, | 						AccountId:  3, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "5.6", | 						Amount:     "5.6", | ||||||
| @@ -327,15 +327,15 @@ var data = []TestData{ | |||||||
| 				UserId:      0, | 				UserId:      0, | ||||||
| 				Description: "weekly groceries", | 				Description: "weekly groceries", | ||||||
| 				Date:        time.Date(2017, time.October, 31, 19, 10, 14, 0, time.UTC), | 				Date:        time.Date(2017, time.October, 31, 19, 10, 14, 0, time.UTC), | ||||||
| 				Splits: []*handlers.Split{ | 				Splits: []*models.Split{ | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  1, | 						AccountId:  1, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "-81.59", | 						Amount:     "-81.59", | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  3, | 						AccountId:  3, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "81.59", | 						Amount:     "81.59", | ||||||
| @@ -346,15 +346,15 @@ var data = []TestData{ | |||||||
| 				UserId:      0, | 				UserId:      0, | ||||||
| 				Description: "Cable", | 				Description: "Cable", | ||||||
| 				Date:        time.Date(2017, time.September, 2, 0, 00, 00, 0, time.UTC), | 				Date:        time.Date(2017, time.September, 2, 0, 00, 00, 0, time.UTC), | ||||||
| 				Splits: []*handlers.Split{ | 				Splits: []*models.Split{ | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  1, | 						AccountId:  1, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "-39.99", | 						Amount:     "-39.99", | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Entered, | 						Status:     models.Entered, | ||||||
| 						AccountId:  4, | 						AccountId:  4, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "39.99", | 						Amount:     "39.99", | ||||||
| @@ -365,15 +365,15 @@ var data = []TestData{ | |||||||
| 				UserId:      1, | 				UserId:      1, | ||||||
| 				Description: "Gas", | 				Description: "Gas", | ||||||
| 				Date:        time.Date(2017, time.November, 1, 13, 19, 50, 0, time.UTC), | 				Date:        time.Date(2017, time.November, 1, 13, 19, 50, 0, time.UTC), | ||||||
| 				Splits: []*handlers.Split{ | 				Splits: []*models.Split{ | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Reconciled, | 						Status:     models.Reconciled, | ||||||
| 						AccountId:  5, | 						AccountId:  5, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "-24.56", | 						Amount:     "-24.56", | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						Status:     handlers.Entered, | 						Status:     models.Entered, | ||||||
| 						AccountId:  6, | 						AccountId:  6, | ||||||
| 						SecurityId: -1, | 						SecurityId: -1, | ||||||
| 						Amount:     "24.56", | 						Amount:     "24.56", | ||||||
| @@ -381,7 +381,7 @@ var data = []TestData{ | |||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		reports: []handlers.Report{ | 		reports: []models.Report{ | ||||||
| 			{ | 			{ | ||||||
| 				UserId: 0, | 				UserId: 0, | ||||||
| 				Name:   "This Year's Monthly Expenses", | 				Name:   "This Year's Monthly Expenses", | ||||||
| @@ -439,39 +439,39 @@ function generate() | |||||||
| end`, | end`, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		tabulations: []handlers.Tabulation{ | 		tabulations: []models.Tabulation{ | ||||||
| 			{ | 			{ | ||||||
| 				ReportId: 0, | 				ReportId: 0, | ||||||
| 				Title:    "2017 Monthly Expenses", | 				Title:    "2017 Monthly Expenses", | ||||||
| 				Subtitle: "This is my subtitle", | 				Subtitle: "This is my subtitle", | ||||||
| 				Units:    "USD", | 				Units:    "USD", | ||||||
| 				Labels:   []string{"2017-01-01", "2017-02-01", "2017-03-01", "2017-04-01", "2017-05-01", "2017-06-01", "2017-07-01", "2017-08-01", "2017-09-01", "2017-10-01", "2017-11-01", "2017-12-01"}, | 				Labels:   []string{"2017-01-01", "2017-02-01", "2017-03-01", "2017-04-01", "2017-05-01", "2017-06-01", "2017-07-01", "2017-08-01", "2017-09-01", "2017-10-01", "2017-11-01", "2017-12-01"}, | ||||||
| 				Series: map[string]*handlers.Series{ | 				Series: map[string]*models.Series{ | ||||||
| 					"Assets": { | 					"Assets": { | ||||||
| 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 						Series: map[string]*handlers.Series{ | 						Series: map[string]*models.Series{ | ||||||
| 							"Credit Union Checking": { | 							"Credit Union Checking": { | ||||||
| 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 								Series: map[string]*handlers.Series{}, | 								Series: map[string]*models.Series{}, | ||||||
| 							}, | 							}, | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 					"Expenses": { | 					"Expenses": { | ||||||
| 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 						Series: map[string]*handlers.Series{ | 						Series: map[string]*models.Series{ | ||||||
| 							"Groceries": { | 							"Groceries": { | ||||||
| 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 87.19, 0, 0}, | 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 87.19, 0, 0}, | ||||||
| 								Series: map[string]*handlers.Series{}, | 								Series: map[string]*models.Series{}, | ||||||
| 							}, | 							}, | ||||||
| 							"Cable": { | 							"Cable": { | ||||||
| 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 39.99, 0, 0, 0}, | 								Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 39.99, 0, 0, 0}, | ||||||
| 								Series: map[string]*handlers.Series{}, | 								Series: map[string]*models.Series{}, | ||||||
| 							}, | 							}, | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 					"Credit Card": { | 					"Credit Card": { | ||||||
| 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 						Values: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 						Series: map[string]*handlers.Series{}, | 						Series: map[string]*models.Series{}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|   | |||||||
| @@ -1,149 +1,25 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Split.Status | func SplitAlreadyImported(tx *Tx, s *models.Split) (bool, error) { | ||||||
| const ( |  | ||||||
| 	Imported   int64 = 1 |  | ||||||
| 	Entered          = 2 |  | ||||||
| 	Cleared          = 3 |  | ||||||
| 	Reconciled       = 4 |  | ||||||
| 	Voided           = 5 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Split.ImportSplitType |  | ||||||
| const ( |  | ||||||
| 	Default         int64 = 0 |  | ||||||
| 	ImportAccount         = 1 // This split belongs to the main account being imported |  | ||||||
| 	SubAccount            = 2 // This split belongs to a sub-account of that being imported |  | ||||||
| 	ExternalAccount       = 3 |  | ||||||
| 	TradingAccount        = 4 |  | ||||||
| 	Commission            = 5 |  | ||||||
| 	Taxes                 = 6 |  | ||||||
| 	Fees                  = 7 |  | ||||||
| 	Load                  = 8 |  | ||||||
| 	IncomeAccount         = 9 |  | ||||||
| 	ExpenseAccount        = 10 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Split struct { |  | ||||||
| 	SplitId         int64 |  | ||||||
| 	TransactionId   int64 |  | ||||||
| 	Status          int64 |  | ||||||
| 	ImportSplitType int64 |  | ||||||
|  |  | ||||||
| 	// One of AccountId and SecurityId must be -1 |  | ||||||
| 	// In normal splits, AccountId will be valid and SecurityId will be -1. The |  | ||||||
| 	// only case where this is reversed is for transactions that have been |  | ||||||
| 	// imported and not yet associated with an account. |  | ||||||
| 	AccountId  int64 |  | ||||||
| 	SecurityId int64 |  | ||||||
|  |  | ||||||
| 	RemoteId string // unique ID from server, for detecting duplicates |  | ||||||
| 	Number   string // Check or reference number |  | ||||||
| 	Memo     string |  | ||||||
| 	Amount   string // String representation of decimal, suitable for passing to big.Rat.SetString() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetBigAmount(amt string) (*big.Rat, error) { |  | ||||||
| 	var r big.Rat |  | ||||||
| 	_, success := r.SetString(amt) |  | ||||||
| 	if !success { |  | ||||||
| 		return nil, errors.New("Couldn't convert string amount to big.Rat via SetString()") |  | ||||||
| 	} |  | ||||||
| 	return &r, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Split) GetAmount() (*big.Rat, error) { |  | ||||||
| 	return GetBigAmount(s.Amount) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Split) Valid() bool { |  | ||||||
| 	if (s.AccountId == -1) == (s.SecurityId == -1) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	_, err := s.GetAmount() |  | ||||||
| 	return err == nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Split) AlreadyImported(tx *Tx) (bool, error) { |  | ||||||
| 	count, err := tx.SelectInt("SELECT COUNT(*) from splits where RemoteId=? and AccountId=?", s.RemoteId, s.AccountId) | 	count, err := tx.SelectInt("SELECT COUNT(*) from splits where RemoteId=? and AccountId=?", s.RemoteId, s.AccountId) | ||||||
| 	return count == 1, err | 	return count == 1, err | ||||||
| } | } | ||||||
|  |  | ||||||
| type Transaction struct { |  | ||||||
| 	TransactionId int64 |  | ||||||
| 	UserId        int64 |  | ||||||
| 	Description   string |  | ||||||
| 	Date          time.Time |  | ||||||
| 	Splits        []*Split `db:"-"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type TransactionList struct { |  | ||||||
| 	Transactions *[]Transaction `json:"transactions"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type AccountTransactionsList struct { |  | ||||||
| 	Account           *Account |  | ||||||
| 	Transactions      *[]Transaction |  | ||||||
| 	TotalTransactions int64 |  | ||||||
| 	BeginningBalance  string |  | ||||||
| 	EndingBalance     string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Transaction) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(t) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Transaction) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(t) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (tl *TransactionList) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(tl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (tl *TransactionList) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(tl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (atl *AccountTransactionsList) Write(w http.ResponseWriter) error { |  | ||||||
| 	enc := json.NewEncoder(w) |  | ||||||
| 	return enc.Encode(atl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (atl *AccountTransactionsList) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(atl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Transaction) Valid() bool { |  | ||||||
| 	for i := range t.Splits { |  | ||||||
| 		if !t.Splits[i].Valid() { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Return a map of security ID's to big.Rat's containing the amount that | // Return a map of security ID's to big.Rat's containing the amount that | ||||||
| // security is imbalanced by | // security is imbalanced by | ||||||
| func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | func GetTransactionImbalances(tx *Tx, t *models.Transaction) (map[int64]big.Rat, error) { | ||||||
| 	sums := make(map[int64]big.Rat) | 	sums := make(map[int64]big.Rat) | ||||||
|  |  | ||||||
| 	if !t.Valid() { | 	if !t.Valid() { | ||||||
| @@ -154,7 +30,7 @@ func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | |||||||
| 		securityid := t.Splits[i].SecurityId | 		securityid := t.Splits[i].SecurityId | ||||||
| 		if t.Splits[i].AccountId != -1 { | 		if t.Splits[i].AccountId != -1 { | ||||||
| 			var err error | 			var err error | ||||||
| 			var account *Account | 			var account *models.Account | ||||||
| 			account, err = GetAccount(tx, t.Splits[i].AccountId, t.UserId) | 			account, err = GetAccount(tx, t.Splits[i].AccountId, t.UserId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| @@ -171,10 +47,10 @@ func (t *Transaction) GetImbalances(tx *Tx) (map[int64]big.Rat, error) { | |||||||
|  |  | ||||||
| // Returns true if all securities contained in this transaction are balanced, | // Returns true if all securities contained in this transaction are balanced, | ||||||
| // false otherwise | // false otherwise | ||||||
| func (t *Transaction) Balanced(tx *Tx) (bool, error) { | func TransactionBalanced(tx *Tx, t *models.Transaction) (bool, error) { | ||||||
| 	var zero big.Rat | 	var zero big.Rat | ||||||
|  |  | ||||||
| 	sums, err := t.GetImbalances(tx) | 	sums, err := GetTransactionImbalances(tx, t) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
| @@ -187,8 +63,8 @@ func (t *Transaction) Balanced(tx *Tx) (bool, error) { | |||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetTransaction(tx *Tx, transactionid int64, userid int64) (*Transaction, error) { | func GetTransaction(tx *Tx, transactionid int64, userid int64) (*models.Transaction, error) { | ||||||
| 	var t Transaction | 	var t models.Transaction | ||||||
|  |  | ||||||
| 	err := tx.SelectOne(&t, "SELECT * from transactions where UserId=? AND TransactionId=?", userid, transactionid) | 	err := tx.SelectOne(&t, "SELECT * from transactions where UserId=? AND TransactionId=?", userid, transactionid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -203,8 +79,8 @@ func GetTransaction(tx *Tx, transactionid int64, userid int64) (*Transaction, er | |||||||
| 	return &t, nil | 	return &t, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetTransactions(tx *Tx, userid int64) (*[]Transaction, error) { | func GetTransactions(tx *Tx, userid int64) (*[]models.Transaction, error) { | ||||||
| 	var transactions []Transaction | 	var transactions []models.Transaction | ||||||
|  |  | ||||||
| 	_, err := tx.Select(&transactions, "SELECT * from transactions where UserId=?", userid) | 	_, err := tx.Select(&transactions, "SELECT * from transactions where UserId=?", userid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -221,7 +97,7 @@ func GetTransactions(tx *Tx, userid int64) (*[]Transaction, error) { | |||||||
| 	return &transactions, nil | 	return &transactions, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func incrementAccountVersions(tx *Tx, user *User, accountids []int64) error { | func incrementAccountVersions(tx *Tx, user *models.User, accountids []int64) error { | ||||||
| 	for i := range accountids { | 	for i := range accountids { | ||||||
| 		account, err := GetAccount(tx, accountids[i], user.UserId) | 		account, err := GetAccount(tx, accountids[i], user.UserId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -245,7 +121,7 @@ func (ame AccountMissingError) Error() string { | |||||||
| 	return "Account missing" | 	return "Account missing" | ||||||
| } | } | ||||||
|  |  | ||||||
| func InsertTransaction(tx *Tx, t *Transaction, user *User) error { | func InsertTransaction(tx *Tx, t *models.Transaction, user *models.User) error { | ||||||
| 	// Map of any accounts with transaction splits being added | 	// Map of any accounts with transaction splits being added | ||||||
| 	a_map := make(map[int64]bool) | 	a_map := make(map[int64]bool) | ||||||
| 	for i := range t.Splits { | 	for i := range t.Splits { | ||||||
| @@ -295,8 +171,8 @@ func InsertTransaction(tx *Tx, t *Transaction, user *User) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func UpdateTransaction(tx *Tx, t *Transaction, user *User) error { | func UpdateTransaction(tx *Tx, t *models.Transaction, user *models.User) error { | ||||||
| 	var existing_splits []*Split | 	var existing_splits []*models.Split | ||||||
|  |  | ||||||
| 	_, err := tx.Select(&existing_splits, "SELECT * from splits where TransactionId=?", t.TransactionId) | 	_, err := tx.Select(&existing_splits, "SELECT * from splits where TransactionId=?", t.TransactionId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -372,7 +248,7 @@ func UpdateTransaction(tx *Tx, t *Transaction, user *User) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func DeleteTransaction(tx *Tx, t *Transaction, user *User) error { | func DeleteTransaction(tx *Tx, t *models.Transaction, user *models.User) error { | ||||||
| 	var accountids []int64 | 	var accountids []int64 | ||||||
| 	_, err := tx.Select(&accountids, "SELECT DISTINCT AccountId FROM splits WHERE TransactionId=? AND AccountId != -1", t.TransactionId) | 	_, err := tx.Select(&accountids, "SELECT DISTINCT AccountId FROM splits WHERE TransactionId=? AND AccountId != -1", t.TransactionId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -407,7 +283,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if r.Method == "POST" { | 	if r.Method == "POST" { | ||||||
| 		var transaction Transaction | 		var transaction models.Transaction | ||||||
| 		if err := ReadJSON(r, &transaction); err != nil { | 		if err := ReadJSON(r, &transaction); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| @@ -426,7 +302,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		balanced, err := transaction.Balanced(context.Tx) | 		balanced, err := TransactionBalanced(context.Tx, &transaction) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return NewError(999 /*Internal Error*/) | 			return NewError(999 /*Internal Error*/) | ||||||
| 		} | 		} | ||||||
| @@ -448,7 +324,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 	} else if r.Method == "GET" { | 	} else if r.Method == "GET" { | ||||||
| 		if context.LastLevel() { | 		if context.LastLevel() { | ||||||
| 			//Return all Transactions | 			//Return all Transactions | ||||||
| 			var al TransactionList | 			var al models.TransactionList | ||||||
| 			transactions, err := GetTransactions(context.Tx, user.UserId) | 			transactions, err := GetTransactions(context.Tx, user.UserId) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print(err) | 				log.Print(err) | ||||||
| @@ -474,13 +350,13 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| 		if r.Method == "PUT" { | 		if r.Method == "PUT" { | ||||||
| 			var transaction Transaction | 			var transaction models.Transaction | ||||||
| 			if err := ReadJSON(r, &transaction); err != nil || transaction.TransactionId != transactionid { | 			if err := ReadJSON(r, &transaction); err != nil || transaction.TransactionId != transactionid { | ||||||
| 				return NewError(3 /*Invalid Request*/) | 				return NewError(3 /*Invalid Request*/) | ||||||
| 			} | 			} | ||||||
| 			transaction.UserId = user.UserId | 			transaction.UserId = user.UserId | ||||||
|  |  | ||||||
| 			balanced, err := transaction.Balanced(context.Tx) | 			balanced, err := TransactionBalanced(context.Tx, &transaction) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Print(err) | 				log.Print(err) | ||||||
| 				return NewError(999 /*Internal Error*/) | 				return NewError(999 /*Internal Error*/) | ||||||
| @@ -525,7 +401,7 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 	return NewError(3 /*Invalid Request*/) | 	return NewError(3 /*Invalid Request*/) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Transaction) (*big.Rat, error) { | func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []models.Transaction) (*big.Rat, error) { | ||||||
| 	var pageDifference, tmp big.Rat | 	var pageDifference, tmp big.Rat | ||||||
| 	for i := range transactions { | 	for i := range transactions { | ||||||
| 		_, err := tx.Select(&transactions[i].Splits, "SELECT * FROM splits where TransactionId=?", transactions[i].TransactionId) | 		_, err := tx.Select(&transactions[i].Splits, "SELECT * FROM splits where TransactionId=?", transactions[i].TransactionId) | ||||||
| @@ -537,7 +413,7 @@ func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Trans | |||||||
| 		// an ending balance | 		// an ending balance | ||||||
| 		for j := range transactions[i].Splits { | 		for j := range transactions[i].Splits { | ||||||
| 			if transactions[i].Splits[j].AccountId == accountid { | 			if transactions[i].Splits[j].AccountId == accountid { | ||||||
| 				rat_amount, err := GetBigAmount(transactions[i].Splits[j].Amount) | 				rat_amount, err := models.GetBigAmount(transactions[i].Splits[j].Amount) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return nil, err | 					return nil, err | ||||||
| 				} | 				} | ||||||
| @@ -549,8 +425,8 @@ func TransactionsBalanceDifference(tx *Tx, accountid int64, transactions []Trans | |||||||
| 	return &pageDifference, nil | 	return &pageDifference, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetAccountBalance(tx *Tx, user *User, accountid int64) (*big.Rat, error) { | func GetAccountBalance(tx *Tx, user *models.User, accountid int64) (*big.Rat, error) { | ||||||
| 	var splits []Split | 	var splits []models.Split | ||||||
|  |  | ||||||
| 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=?" | 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=?" | ||||||
| 	_, err := tx.Select(&splits, sql, accountid, user.UserId) | 	_, err := tx.Select(&splits, sql, accountid, user.UserId) | ||||||
| @@ -560,7 +436,7 @@ func GetAccountBalance(tx *Tx, user *User, accountid int64) (*big.Rat, error) { | |||||||
|  |  | ||||||
| 	var balance, tmp big.Rat | 	var balance, tmp big.Rat | ||||||
| 	for _, s := range splits { | 	for _, s := range splits { | ||||||
| 		rat_amount, err := GetBigAmount(s.Amount) | 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -572,8 +448,8 @@ func GetAccountBalance(tx *Tx, user *User, accountid int64) (*big.Rat, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Assumes accountid is valid and is owned by the current user | // Assumes accountid is valid and is owned by the current user | ||||||
| func GetAccountBalanceDate(tx *Tx, user *User, accountid int64, date *time.Time) (*big.Rat, error) { | func GetAccountBalanceDate(tx *Tx, user *models.User, accountid int64, date *time.Time) (*big.Rat, error) { | ||||||
| 	var splits []Split | 	var splits []models.Split | ||||||
|  |  | ||||||
| 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date < ?" | 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date < ?" | ||||||
| 	_, err := tx.Select(&splits, sql, accountid, user.UserId, date) | 	_, err := tx.Select(&splits, sql, accountid, user.UserId, date) | ||||||
| @@ -583,7 +459,7 @@ func GetAccountBalanceDate(tx *Tx, user *User, accountid int64, date *time.Time) | |||||||
|  |  | ||||||
| 	var balance, tmp big.Rat | 	var balance, tmp big.Rat | ||||||
| 	for _, s := range splits { | 	for _, s := range splits { | ||||||
| 		rat_amount, err := GetBigAmount(s.Amount) | 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -594,8 +470,8 @@ func GetAccountBalanceDate(tx *Tx, user *User, accountid int64, date *time.Time) | |||||||
| 	return &balance, nil | 	return &balance, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetAccountBalanceDateRange(tx *Tx, user *User, accountid int64, begin, end *time.Time) (*big.Rat, error) { | func GetAccountBalanceDateRange(tx *Tx, user *models.User, accountid int64, begin, end *time.Time) (*big.Rat, error) { | ||||||
| 	var splits []Split | 	var splits []models.Split | ||||||
|  |  | ||||||
| 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date >= ? AND transactions.Date < ?" | 	sql := "SELECT DISTINCT splits.* FROM splits INNER JOIN transactions ON transactions.TransactionId = splits.TransactionId WHERE splits.AccountId=? AND transactions.UserId=? AND transactions.Date >= ? AND transactions.Date < ?" | ||||||
| 	_, err := tx.Select(&splits, sql, accountid, user.UserId, begin, end) | 	_, err := tx.Select(&splits, sql, accountid, user.UserId, begin, end) | ||||||
| @@ -605,7 +481,7 @@ func GetAccountBalanceDateRange(tx *Tx, user *User, accountid int64, begin, end | |||||||
|  |  | ||||||
| 	var balance, tmp big.Rat | 	var balance, tmp big.Rat | ||||||
| 	for _, s := range splits { | 	for _, s := range splits { | ||||||
| 		rat_amount, err := GetBigAmount(s.Amount) | 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -616,9 +492,9 @@ func GetAccountBalanceDateRange(tx *Tx, user *User, accountid int64, begin, end | |||||||
| 	return &balance, nil | 	return &balance, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetAccountTransactions(tx *Tx, user *User, accountid int64, sort string, page uint64, limit uint64) (*AccountTransactionsList, error) { | func GetAccountTransactions(tx *Tx, user *models.User, accountid int64, sort string, page uint64, limit uint64) (*models.AccountTransactionsList, error) { | ||||||
| 	var transactions []Transaction | 	var transactions []models.Transaction | ||||||
| 	var atl AccountTransactionsList | 	var atl models.AccountTransactionsList | ||||||
|  |  | ||||||
| 	var sqlsort, balanceLimitOffset string | 	var sqlsort, balanceLimitOffset string | ||||||
| 	var balanceLimitOffsetArg uint64 | 	var balanceLimitOffsetArg uint64 | ||||||
| @@ -684,7 +560,7 @@ func GetAccountTransactions(tx *Tx, user *User, accountid int64, sort string, pa | |||||||
|  |  | ||||||
| 	var tmp, balance big.Rat | 	var tmp, balance big.Rat | ||||||
| 	for _, amount := range amounts { | 	for _, amount := range amounts { | ||||||
| 		rat_amount, err := GetBigAmount(amount) | 		rat_amount, err := models.GetBigAmount(amount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -699,7 +575,7 @@ func GetAccountTransactions(tx *Tx, user *User, accountid int64, sort string, pa | |||||||
|  |  | ||||||
| // Return only those transactions which have at least one split pertaining to | // Return only those transactions which have at least one split pertaining to | ||||||
| // an account | // an account | ||||||
| func AccountTransactionsHandler(context *Context, r *http.Request, user *User, accountid int64) ResponseWriterWriter { | func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
| 	var page uint64 = 0 | 	var page uint64 = 0 | ||||||
| 	var limit uint64 = 50 | 	var limit uint64 = 50 | ||||||
| 	var sort string = "date-desc" | 	var sort string = "date-desc" | ||||||
|   | |||||||
| @@ -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" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @@ -10,14 +11,14 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func createTransaction(client *http.Client, transaction *handlers.Transaction) (*handlers.Transaction, error) { | func createTransaction(client *http.Client, transaction *models.Transaction) (*models.Transaction, error) { | ||||||
| 	var s handlers.Transaction | 	var s models.Transaction | ||||||
| 	err := create(client, transaction, &s, "/v1/transactions/") | 	err := create(client, transaction, &s, "/v1/transactions/") | ||||||
| 	return &s, err | 	return &s, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func getTransaction(client *http.Client, transactionid int64) (*handlers.Transaction, error) { | func getTransaction(client *http.Client, transactionid int64) (*models.Transaction, error) { | ||||||
| 	var s handlers.Transaction | 	var s models.Transaction | ||||||
| 	err := read(client, &s, "/v1/transactions/"+strconv.FormatInt(transactionid, 10)) | 	err := read(client, &s, "/v1/transactions/"+strconv.FormatInt(transactionid, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -25,8 +26,8 @@ func getTransaction(client *http.Client, transactionid int64) (*handlers.Transac | |||||||
| 	return &s, nil | 	return &s, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getTransactions(client *http.Client) (*handlers.TransactionList, error) { | func getTransactions(client *http.Client) (*models.TransactionList, error) { | ||||||
| 	var tl handlers.TransactionList | 	var tl models.TransactionList | ||||||
| 	err := read(client, &tl, "/v1/transactions/") | 	err := read(client, &tl, "/v1/transactions/") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -34,8 +35,8 @@ func getTransactions(client *http.Client) (*handlers.TransactionList, error) { | |||||||
| 	return &tl, nil | 	return &tl, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAccountTransactions(client *http.Client, accountid, page, limit int64, sort string) (*handlers.AccountTransactionsList, error) { | func getAccountTransactions(client *http.Client, accountid, page, limit int64, sort string) (*models.AccountTransactionsList, error) { | ||||||
| 	var atl handlers.AccountTransactionsList | 	var atl models.AccountTransactionsList | ||||||
| 	params := url.Values{} | 	params := url.Values{} | ||||||
|  |  | ||||||
| 	query := fmt.Sprintf("/v1/accounts/%d/transactions/", accountid) | 	query := fmt.Sprintf("/v1/accounts/%d/transactions/", accountid) | ||||||
| @@ -57,8 +58,8 @@ func getAccountTransactions(client *http.Client, accountid, page, limit int64, s | |||||||
| 	return &atl, nil | 	return &atl, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateTransaction(client *http.Client, transaction *handlers.Transaction) (*handlers.Transaction, error) { | func updateTransaction(client *http.Client, transaction *models.Transaction) (*models.Transaction, error) { | ||||||
| 	var s handlers.Transaction | 	var s models.Transaction | ||||||
| 	err := update(client, transaction, &s, "/v1/transactions/"+strconv.FormatInt(transaction.TransactionId, 10)) | 	err := update(client, transaction, &s, "/v1/transactions/"+strconv.FormatInt(transaction.TransactionId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -66,7 +67,7 @@ func updateTransaction(client *http.Client, transaction *handlers.Transaction) ( | |||||||
| 	return &s, nil | 	return &s, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteTransaction(client *http.Client, s *handlers.Transaction) error { | func deleteTransaction(client *http.Client, s *models.Transaction) error { | ||||||
| 	err := remove(client, "/v1/transactions/"+strconv.FormatInt(s.TransactionId, 10)) | 	err := remove(client, "/v1/transactions/"+strconv.FormatInt(s.TransactionId, 10)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -74,7 +75,7 @@ func deleteTransaction(client *http.Client, s *handlers.Transaction) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func ensureTransactionsMatch(t *testing.T, expected, tran *handlers.Transaction, accounts *[]handlers.Account, matchtransactionids, matchsplitids bool) { | func ensureTransactionsMatch(t *testing.T, expected, tran *models.Transaction, accounts *[]models.Account, matchtransactionids, matchsplitids bool) { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
|  |  | ||||||
| 	if tran.TransactionId == 0 { | 	if tran.TransactionId == 0 { | ||||||
| @@ -136,9 +137,9 @@ func ensureTransactionsMatch(t *testing.T, expected, tran *handlers.Transaction, | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAccountVersionMap(t *testing.T, client *http.Client, tran *handlers.Transaction) map[int64]*handlers.Account { | func getAccountVersionMap(t *testing.T, client *http.Client, tran *models.Transaction) map[int64]*models.Account { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 	accountMap := make(map[int64]*handlers.Account) | 	accountMap := make(map[int64]*models.Account) | ||||||
| 	for _, split := range tran.Splits { | 	for _, split := range tran.Splits { | ||||||
| 		account, err := getAccount(client, split.AccountId) | 		account, err := getAccount(client, split.AccountId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -149,7 +150,7 @@ func getAccountVersionMap(t *testing.T, client *http.Client, tran *handlers.Tran | |||||||
| 	return accountMap | 	return accountMap | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkAccountVersionsUpdated(t *testing.T, client *http.Client, accountMap map[int64]*handlers.Account, tran *handlers.Transaction) { | func checkAccountVersionsUpdated(t *testing.T, client *http.Client, accountMap map[int64]*models.Account, tran *models.Transaction) { | ||||||
| 	for _, split := range tran.Splits { | 	for _, split := range tran.Splits { | ||||||
| 		account, err := getAccount(client, split.AccountId) | 		account, err := getAccount(client, split.AccountId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -177,19 +178,19 @@ func TestCreateTransaction(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Don't allow imbalanced transactions | 		// Don't allow imbalanced transactions | ||||||
| 		tran := handlers.Transaction{ | 		tran := models.Transaction{ | ||||||
| 			UserId:      d.users[0].UserId, | 			UserId:      d.users[0].UserId, | ||||||
| 			Description: "Imbalanced", | 			Description: "Imbalanced", | ||||||
| 			Date:        time.Date(2017, time.September, 1, 0, 00, 00, 0, time.UTC), | 			Date:        time.Date(2017, time.September, 1, 0, 00, 00, 0, time.UTC), | ||||||
| 			Splits: []*handlers.Split{ | 			Splits: []*models.Split{ | ||||||
| 				{ | 				{ | ||||||
| 					Status:     handlers.Reconciled, | 					Status:     models.Reconciled, | ||||||
| 					AccountId:  d.accounts[1].AccountId, | 					AccountId:  d.accounts[1].AccountId, | ||||||
| 					SecurityId: -1, | 					SecurityId: -1, | ||||||
| 					Amount:     "-39.98", | 					Amount:     "-39.98", | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					Status:     handlers.Entered, | 					Status:     models.Entered, | ||||||
| 					AccountId:  d.accounts[4].AccountId, | 					AccountId:  d.accounts[4].AccountId, | ||||||
| 					SecurityId: -1, | 					SecurityId: -1, | ||||||
| 					Amount:     "39.99", | 					Amount:     "39.99", | ||||||
| @@ -209,7 +210,7 @@ func TestCreateTransaction(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Don't allow transactions with 0 splits | 		// Don't allow transactions with 0 splits | ||||||
| 		tran.Splits = []*handlers.Split{} | 		tran.Splits = []*models.Split{} | ||||||
| 		_, err = createTransaction(d.clients[0], &tran) | 		_, err = createTransaction(d.clients[0], &tran) | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			t.Fatalf("Expected error creating with zero splits") | 			t.Fatalf("Expected error creating with zero splits") | ||||||
| @@ -316,9 +317,9 @@ func TestUpdateTransaction(t *testing.T) { | |||||||
|  |  | ||||||
| 			ensureTransactionsMatch(t, &curr, tran, nil, true, true) | 			ensureTransactionsMatch(t, &curr, tran, nil, true, true) | ||||||
|  |  | ||||||
| 			tran.Splits = []*handlers.Split{} | 			tran.Splits = []*models.Split{} | ||||||
| 			for _, s := range curr.Splits { | 			for _, s := range curr.Splits { | ||||||
| 				var split handlers.Split | 				var split models.Split | ||||||
| 				split = *s | 				split = *s | ||||||
| 				tran.Splits = append(tran.Splits, &split) | 				tran.Splits = append(tran.Splits, &split) | ||||||
| 			} | 			} | ||||||
| @@ -346,7 +347,7 @@ func TestUpdateTransaction(t *testing.T) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Don't allow transactions with 0 splits | 			// Don't allow transactions with 0 splits | ||||||
| 			tran.Splits = []*handlers.Split{} | 			tran.Splits = []*models.Split{} | ||||||
| 			_, err = updateTransaction(d.clients[orig.UserId], tran) | 			_, err = updateTransaction(d.clients[orig.UserId], tran) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				t.Fatalf("Expected error updating with zero splits") | 				t.Fatalf("Expected error updating with zero splits") | ||||||
| @@ -391,12 +392,12 @@ func TestDeleteTransaction(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func helperTestAccountTransactions(t *testing.T, d *TestData, account *handlers.Account, limit int64, sort string) { | func helperTestAccountTransactions(t *testing.T, d *TestData, account *models.Account, limit int64, sort string) { | ||||||
| 	if account.UserId != d.users[0].UserId { | 	if account.UserId != d.users[0].UserId { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var transactions []handlers.Transaction | 	var transactions []models.Transaction | ||||||
| 	var lastFetchCount int64 | 	var lastFetchCount int64 | ||||||
|  |  | ||||||
| 	for page := int64(0); page == 0 || lastFetchCount > 0; page++ { | 	for page := int64(0); page == 0 || lastFetchCount > 0; page++ { | ||||||
|   | |||||||
| @@ -1,53 +1,21 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type User struct { |  | ||||||
| 	UserId          int64 |  | ||||||
| 	DefaultCurrency int64 // SecurityId of default currency, or ISO4217 code for it if creating new user |  | ||||||
| 	Name            string |  | ||||||
| 	Username        string |  | ||||||
| 	Password        string `db:"-"` |  | ||||||
| 	PasswordHash    string `json:"-"` |  | ||||||
| 	Email           string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const BogusPassword = "password" |  | ||||||
|  |  | ||||||
| type UserExistsError struct{} | type UserExistsError struct{} | ||||||
|  |  | ||||||
| func (ueu UserExistsError) Error() string { | func (ueu UserExistsError) Error() string { | ||||||
| 	return "User exists" | 	return "User exists" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *User) Write(w http.ResponseWriter) error { | func GetUser(tx *Tx, userid int64) (*models.User, error) { | ||||||
| 	enc := json.NewEncoder(w) | 	var u models.User | ||||||
| 	return enc.Encode(u) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *User) Read(json_str string) error { |  | ||||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) |  | ||||||
| 	return dec.Decode(u) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *User) HashPassword() { |  | ||||||
| 	password_hasher := sha256.New() |  | ||||||
| 	io.WriteString(password_hasher, u.Password) |  | ||||||
| 	u.PasswordHash = fmt.Sprintf("%x", password_hasher.Sum(nil)) |  | ||||||
| 	u.Password = "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetUser(tx *Tx, userid int64) (*User, error) { |  | ||||||
| 	var u User |  | ||||||
|  |  | ||||||
| 	err := tx.SelectOne(&u, "SELECT * from users where UserId=?", userid) | 	err := tx.SelectOne(&u, "SELECT * from users where UserId=?", userid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -56,8 +24,8 @@ func GetUser(tx *Tx, userid int64) (*User, error) { | |||||||
| 	return &u, nil | 	return &u, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetUserByUsername(tx *Tx, username string) (*User, error) { | func GetUserByUsername(tx *Tx, username string) (*models.User, error) { | ||||||
| 	var u User | 	var u models.User | ||||||
|  |  | ||||||
| 	err := tx.SelectOne(&u, "SELECT * from users where Username=?", username) | 	err := tx.SelectOne(&u, "SELECT * from users where Username=?", username) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -66,7 +34,7 @@ func GetUserByUsername(tx *Tx, username string) (*User, error) { | |||||||
| 	return &u, nil | 	return &u, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func InsertUser(tx *Tx, u *User) error { | func InsertUser(tx *Tx, u *models.User) error { | ||||||
| 	security_template := FindCurrencyTemplate(u.DefaultCurrency) | 	security_template := FindCurrencyTemplate(u.DefaultCurrency) | ||||||
| 	if security_template == nil { | 	if security_template == nil { | ||||||
| 		return errors.New("Invalid ISO4217 Default Currency") | 		return errors.New("Invalid ISO4217 Default Currency") | ||||||
| @@ -86,7 +54,7 @@ func InsertUser(tx *Tx, u *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 | ||||||
|  |  | ||||||
| @@ -107,7 +75,7 @@ func InsertUser(tx *Tx, u *User) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetUserFromSession(tx *Tx, r *http.Request) (*User, error) { | func GetUserFromSession(tx *Tx, r *http.Request) (*models.User, error) { | ||||||
| 	s, err := GetSession(tx, r) | 	s, err := GetSession(tx, r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -115,13 +83,13 @@ func GetUserFromSession(tx *Tx, r *http.Request) (*User, error) { | |||||||
| 	return GetUser(tx, s.UserId) | 	return GetUser(tx, s.UserId) | ||||||
| } | } | ||||||
|  |  | ||||||
| func UpdateUser(tx *Tx, u *User) error { | func UpdateUser(tx *Tx, u *models.User) error { | ||||||
| 	security, err := GetSecurity(tx, u.DefaultCurrency, u.UserId) | 	security, err := GetSecurity(tx, u.DefaultCurrency, u.UserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		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") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -135,7 +103,7 @@ func UpdateUser(tx *Tx, u *User) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func DeleteUser(tx *Tx, u *User) error { | func DeleteUser(tx *Tx, u *models.User) error { | ||||||
| 	count, err := tx.Delete(u) | 	count, err := tx.Delete(u) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -177,7 +145,7 @@ func DeleteUser(tx *Tx, u *User) error { | |||||||
|  |  | ||||||
| func UserHandler(r *http.Request, context *Context) ResponseWriterWriter { | func UserHandler(r *http.Request, context *Context) ResponseWriterWriter { | ||||||
| 	if r.Method == "POST" { | 	if r.Method == "POST" { | ||||||
| 		var user User | 		var user models.User | ||||||
| 		if err := ReadJSON(r, &user); err != nil { | 		if err := ReadJSON(r, &user); err != nil { | ||||||
| 			return NewError(3 /*Invalid Request*/) | 			return NewError(3 /*Invalid Request*/) | ||||||
| 		} | 		} | ||||||
| @@ -221,7 +189,7 @@ func UserHandler(r *http.Request, context *Context) ResponseWriterWriter { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// If the user didn't create a new password, keep their old one | 			// If the user didn't create a new password, keep their old one | ||||||
| 			if user.Password != BogusPassword { | 			if user.Password != models.BogusPassword { | ||||||
| 				user.HashPassword() | 				user.HashPassword() | ||||||
| 			} else { | 			} else { | ||||||
| 				user.Password = "" | 				user.Password = "" | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								internal/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								internal/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type AccountType int64 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	Bank       AccountType = 1 // start at 1 so that the default (0) is invalid | ||||||
|  | 	Cash                   = 2 | ||||||
|  | 	Asset                  = 3 | ||||||
|  | 	Liability              = 4 | ||||||
|  | 	Investment             = 5 | ||||||
|  | 	Income                 = 6 | ||||||
|  | 	Expense                = 7 | ||||||
|  | 	Trading                = 8 | ||||||
|  | 	Equity                 = 9 | ||||||
|  | 	Receivable             = 10 | ||||||
|  | 	Payable                = 11 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var AccountTypes = []AccountType{ | ||||||
|  | 	Bank, | ||||||
|  | 	Cash, | ||||||
|  | 	Asset, | ||||||
|  | 	Liability, | ||||||
|  | 	Investment, | ||||||
|  | 	Income, | ||||||
|  | 	Expense, | ||||||
|  | 	Trading, | ||||||
|  | 	Equity, | ||||||
|  | 	Receivable, | ||||||
|  | 	Payable, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t AccountType) String() string { | ||||||
|  | 	switch t { | ||||||
|  | 	case Bank: | ||||||
|  | 		return "Bank" | ||||||
|  | 	case Cash: | ||||||
|  | 		return "Cash" | ||||||
|  | 	case Asset: | ||||||
|  | 		return "Asset" | ||||||
|  | 	case Liability: | ||||||
|  | 		return "Liability" | ||||||
|  | 	case Investment: | ||||||
|  | 		return "Investment" | ||||||
|  | 	case Income: | ||||||
|  | 		return "Income" | ||||||
|  | 	case Expense: | ||||||
|  | 		return "Expense" | ||||||
|  | 	case Trading: | ||||||
|  | 		return "Trading" | ||||||
|  | 	case Equity: | ||||||
|  | 		return "Equity" | ||||||
|  | 	case Receivable: | ||||||
|  | 		return "Receivable" | ||||||
|  | 	case Payable: | ||||||
|  | 		return "Payable" | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Account struct { | ||||||
|  | 	AccountId         int64 | ||||||
|  | 	ExternalAccountId string | ||||||
|  | 	UserId            int64 | ||||||
|  | 	SecurityId        int64 | ||||||
|  | 	ParentAccountId   int64 // -1 if this account is at the root | ||||||
|  | 	Type              AccountType | ||||||
|  | 	Name              string | ||||||
|  |  | ||||||
|  | 	// monotonically-increasing account transaction version number. Used for | ||||||
|  | 	// allowing a client to ensure they have a consistent version when paging | ||||||
|  | 	// through transactions. | ||||||
|  | 	AccountVersion int64 `json:"Version"` | ||||||
|  |  | ||||||
|  | 	// Optional fields specifying how to fetch transactions from a bank via OFX | ||||||
|  | 	OFXURL       string | ||||||
|  | 	OFXORG       string | ||||||
|  | 	OFXFID       string | ||||||
|  | 	OFXUser      string | ||||||
|  | 	OFXBankID    string // OFX BankID (BrokerID if AcctType == Investment) | ||||||
|  | 	OFXAcctID    string | ||||||
|  | 	OFXAcctType  string // ofxgo.acctType | ||||||
|  | 	OFXClientUID string | ||||||
|  | 	OFXAppID     string | ||||||
|  | 	OFXAppVer    string | ||||||
|  | 	OFXVersion   string | ||||||
|  | 	OFXNoIndent  bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AccountList struct { | ||||||
|  | 	Accounts *[]Account `json:"accounts"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Account) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(a) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Account) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(a) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (al *AccountList) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(al) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (al *AccountList) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(al) | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								internal/models/prices.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								internal/models/prices.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								internal/models/reports.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								internal/models/reports.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Report struct { | ||||||
|  | 	ReportId int64 | ||||||
|  | 	UserId   int64 | ||||||
|  | 	Name     string | ||||||
|  | 	Lua      string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // The maximum length (in bytes) the Lua code may be. This is used to set the | ||||||
|  | // max size of the database columns (with an added fudge factor) | ||||||
|  | const LuaMaxLength int = 65536 | ||||||
|  |  | ||||||
|  | func (r *Report) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Report) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ReportList struct { | ||||||
|  | 	Reports *[]Report `json:"reports"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rl *ReportList) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(rl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rl *ReportList) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(rl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Series struct { | ||||||
|  | 	Values []float64 | ||||||
|  | 	Series map[string]*Series | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Tabulation struct { | ||||||
|  | 	ReportId int64 | ||||||
|  | 	Title    string | ||||||
|  | 	Subtitle string | ||||||
|  | 	Units    string | ||||||
|  | 	Labels   []string | ||||||
|  | 	Series   map[string]*Series | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Tabulation) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(t) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Tabulation) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(t) | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								internal/models/securities.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								internal/models/securities.go
									
									
									
									
									
										Normal 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) | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								internal/models/sessions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								internal/models/sessions.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Session struct { | ||||||
|  | 	SessionId     int64 | ||||||
|  | 	SessionSecret string `json:"-"` | ||||||
|  | 	UserId        int64 | ||||||
|  | 	Created       time.Time | ||||||
|  | 	Expires       time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Session) Cookie(domain string) *http.Cookie { | ||||||
|  | 	return &http.Cookie{ | ||||||
|  | 		Name:     "moneygo-session", | ||||||
|  | 		Value:    s.SessionSecret, | ||||||
|  | 		Path:     "/", | ||||||
|  | 		Domain:   domain, | ||||||
|  | 		Expires:  s.Expires, | ||||||
|  | 		Secure:   true, | ||||||
|  | 		HttpOnly: true, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Session) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(s) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Session) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(s) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newSessionSecret() (string, error) { | ||||||
|  | 	bits := make([]byte, 128) | ||||||
|  | 	if _, err := io.ReadFull(rand.Reader, bits); err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return base64.StdEncoding.EncodeToString(bits), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSession(userid int64) (*Session, error) { | ||||||
|  | 	session_secret, err := newSessionSecret() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	now := time.Now() | ||||||
|  |  | ||||||
|  | 	s := Session{ | ||||||
|  | 		SessionSecret: session_secret, | ||||||
|  | 		UserId:        userid, | ||||||
|  | 		Created:       now, | ||||||
|  | 		Expires:       now.AddDate(0, 1, 0), // a month from now | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &s, nil | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								internal/models/transactions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								internal/models/transactions.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"math/big" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Split.Status | ||||||
|  | const ( | ||||||
|  | 	Imported   int64 = 1 | ||||||
|  | 	Entered          = 2 | ||||||
|  | 	Cleared          = 3 | ||||||
|  | 	Reconciled       = 4 | ||||||
|  | 	Voided           = 5 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Split.ImportSplitType | ||||||
|  | const ( | ||||||
|  | 	Default         int64 = 0 | ||||||
|  | 	ImportAccount         = 1 // This split belongs to the main account being imported | ||||||
|  | 	SubAccount            = 2 // This split belongs to a sub-account of that being imported | ||||||
|  | 	ExternalAccount       = 3 | ||||||
|  | 	TradingAccount        = 4 | ||||||
|  | 	Commission            = 5 | ||||||
|  | 	Taxes                 = 6 | ||||||
|  | 	Fees                  = 7 | ||||||
|  | 	Load                  = 8 | ||||||
|  | 	IncomeAccount         = 9 | ||||||
|  | 	ExpenseAccount        = 10 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Split struct { | ||||||
|  | 	SplitId         int64 | ||||||
|  | 	TransactionId   int64 | ||||||
|  | 	Status          int64 | ||||||
|  | 	ImportSplitType int64 | ||||||
|  |  | ||||||
|  | 	// One of AccountId and SecurityId must be -1 | ||||||
|  | 	// In normal splits, AccountId will be valid and SecurityId will be -1. The | ||||||
|  | 	// only case where this is reversed is for transactions that have been | ||||||
|  | 	// imported and not yet associated with an account. | ||||||
|  | 	AccountId  int64 | ||||||
|  | 	SecurityId int64 | ||||||
|  |  | ||||||
|  | 	RemoteId string // unique ID from server, for detecting duplicates | ||||||
|  | 	Number   string // Check or reference number | ||||||
|  | 	Memo     string | ||||||
|  | 	Amount   string // String representation of decimal, suitable for passing to big.Rat.SetString() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetBigAmount(amt string) (*big.Rat, error) { | ||||||
|  | 	var r big.Rat | ||||||
|  | 	_, success := r.SetString(amt) | ||||||
|  | 	if !success { | ||||||
|  | 		return nil, errors.New("Couldn't convert string amount to big.Rat via SetString()") | ||||||
|  | 	} | ||||||
|  | 	return &r, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Split) GetAmount() (*big.Rat, error) { | ||||||
|  | 	return GetBigAmount(s.Amount) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Split) Valid() bool { | ||||||
|  | 	if (s.AccountId == -1) == (s.SecurityId == -1) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	_, err := s.GetAmount() | ||||||
|  | 	return err == nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Transaction struct { | ||||||
|  | 	TransactionId int64 | ||||||
|  | 	UserId        int64 | ||||||
|  | 	Description   string | ||||||
|  | 	Date          time.Time | ||||||
|  | 	Splits        []*Split `db:"-"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TransactionList struct { | ||||||
|  | 	Transactions *[]Transaction `json:"transactions"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AccountTransactionsList struct { | ||||||
|  | 	Account           *Account | ||||||
|  | 	Transactions      *[]Transaction | ||||||
|  | 	TotalTransactions int64 | ||||||
|  | 	BeginningBalance  string | ||||||
|  | 	EndingBalance     string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transaction) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(t) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transaction) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(t) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (tl *TransactionList) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(tl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (tl *TransactionList) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(tl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (atl *AccountTransactionsList) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(atl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (atl *AccountTransactionsList) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(atl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transaction) Valid() bool { | ||||||
|  | 	for i := range t.Splits { | ||||||
|  | 		if !t.Splits[i].Valid() { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								internal/models/users.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/models/users.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type User struct { | ||||||
|  | 	UserId          int64 | ||||||
|  | 	DefaultCurrency int64 // SecurityId of default currency, or ISO4217 code for it if creating new user | ||||||
|  | 	Name            string | ||||||
|  | 	Username        string | ||||||
|  | 	Password        string `db:"-"` | ||||||
|  | 	PasswordHash    string `json:"-"` | ||||||
|  | 	Email           string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const BogusPassword = "password" | ||||||
|  |  | ||||||
|  | func (u *User) Write(w http.ResponseWriter) error { | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	return enc.Encode(u) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *User) Read(json_str string) error { | ||||||
|  | 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||||
|  | 	return dec.Decode(u) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *User) HashPassword() { | ||||||
|  | 	password_hasher := sha256.New() | ||||||
|  | 	io.WriteString(password_hasher, u.Password) | ||||||
|  | 	u.PasswordHash = fmt.Sprintf("%x", password_hasher.Sum(nil)) | ||||||
|  | 	u.Password = "" | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user