1
0
mirror of https://github.com/aclindsa/moneygo.git synced 2024-10-30 15:50:04 -04:00
moneygo/internal/handlers/securities.go
Aaron Lindsay 2ff1f47432 Remove duplicate *Tx versions of database access methods
Simplify naming to remove "Tx" now that all handlers only have access to
transactions anyway, and always use "tx" as the name of the variable
representing the SQL transactions (to make it less likely to cause
confusion with monetary transactions).
2017-10-14 19:41:13 -04:00

384 lines
9.2 KiB
Go

package handlers
//go:generate make
import (
"encoding/json"
"errors"
"log"
"net/http"
"net/url"
"strconv"
"strings"
)
const (
Currency int64 = 1
Stock = 2
)
func GetSecurityType(typestring string) int64 {
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
Type int64
// 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 int64, limit int64) []*Security {
upperSearch := strings.ToUpper(search)
var results []*Security
for i, security := range SecurityTemplates {
if strings.Contains(strings.ToUpper(security.Name), upperSearch) ||
strings.Contains(strings.ToUpper(security.Description), upperSearch) ||
strings.Contains(strings.ToUpper(security.Symbol), upperSearch) {
if _type == 0 || _type == security.Type {
results = append(results, &SecurityTemplates[i])
if limit != -1 && int64(len(results)) >= limit {
break
}
}
}
}
return results
}
func FindSecurityTemplate(name string, _type int64) *Security {
for _, security := range SecurityTemplates {
if name == security.Name && _type == security.Type {
return &security
}
}
return nil
}
func FindCurrencyTemplate(iso4217 int64) *Security {
iso4217string := strconv.FormatInt(iso4217, 10)
for _, security := range SecurityTemplates {
if security.Type == Currency && security.AlternateId == iso4217string {
return &security
}
}
return nil
}
func GetSecurity(tx *Tx, securityid int64, userid int64) (*Security, error) {
var s Security
err := tx.SelectOne(&s, "SELECT * from securities where UserId=? AND SecurityId=?", userid, securityid)
if err != nil {
return nil, err
}
return &s, nil
}
func GetSecurities(tx *Tx, userid int64) (*[]*Security, error) {
var securities []*Security
_, err := tx.Select(&securities, "SELECT * from securities where UserId=?", userid)
if err != nil {
return nil, err
}
return &securities, nil
}
func InsertSecurity(tx *Tx, s *Security) error {
err := tx.Insert(s)
if err != nil {
return err
}
return nil
}
func UpdateSecurity(tx *Tx, s *Security) (err error) {
user, err := GetUser(tx, s.UserId)
if err != nil {
return
} else if user.DefaultCurrency == s.SecurityId && s.Type != Currency {
return errors.New("Cannot change security which is user's default currency to be non-currency")
}
count, err := tx.Update(s)
if err != nil {
return
}
if count != 1 {
return errors.New("Updated more than one security")
}
return nil
}
type SecurityInUseError struct {
message string
}
func (e SecurityInUseError) Error() string {
return e.message
}
func DeleteSecurity(tx *Tx, s *Security) error {
// First, ensure no accounts are using this security
accounts, err := tx.SelectInt("SELECT count(*) from accounts where UserId=? and SecurityId=?", s.UserId, s.SecurityId)
if accounts != 0 {
return SecurityInUseError{"One or more accounts still use this security"}
}
user, err := GetUser(tx, s.UserId)
if err != nil {
return err
} else if user.DefaultCurrency == s.SecurityId {
return SecurityInUseError{"Cannot delete security which is user's default currency"}
}
// Remove all prices involving this security (either of this security, or
// using it as a currency)
_, err = tx.Exec("DELETE FROM prices WHERE SecurityId=? OR CurrencyId=?", s.SecurityId, s.SecurityId)
if err != nil {
return err
}
count, err := tx.Delete(s)
if err != nil {
return err
}
if count != 1 {
return errors.New("Deleted more than one security")
}
return nil
}
func ImportGetCreateSecurity(tx *Tx, userid int64, security *Security) (*Security, error) {
security.UserId = userid
if len(security.AlternateId) == 0 {
// Always create a new local security if we can't match on the AlternateId
err := InsertSecurity(tx, security)
if err != nil {
return nil, err
}
return security, nil
}
var securities []*Security
_, err := tx.Select(&securities, "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Precision=?", userid, security.Type, security.AlternateId, security.Precision)
if err != nil {
return nil, err
}
// First try to find a case insensitive match on the name or symbol
upperName := strings.ToUpper(security.Name)
upperSymbol := strings.ToUpper(security.Symbol)
for _, s := range securities {
if (len(s.Name) > 0 && strings.ToUpper(s.Name) == upperName) ||
(len(s.Symbol) > 0 && strings.ToUpper(s.Symbol) == upperSymbol) {
return s, nil
}
}
// if strings.Contains(strings.ToUpper(security.Name), upperSearch) ||
// Try to find a partial string match on the name or symbol
for _, s := range securities {
sUpperName := strings.ToUpper(s.Name)
sUpperSymbol := strings.ToUpper(s.Symbol)
if (len(upperName) > 0 && len(s.Name) > 0 && (strings.Contains(upperName, sUpperName) || strings.Contains(sUpperName, upperName))) ||
(len(upperSymbol) > 0 && len(s.Symbol) > 0 && (strings.Contains(upperSymbol, sUpperSymbol) || strings.Contains(sUpperSymbol, upperSymbol))) {
return s, nil
}
}
// Give up and return the first security in the list
if len(securities) > 0 {
return securities[0], nil
}
// If there wasn't even one security in the list, make a new one
err = InsertSecurity(tx, security)
if err != nil {
return nil, err
}
return security, nil
}
func SecurityHandler(r *http.Request, tx *Tx) ResponseWriterWriter {
user, err := GetUserFromSession(tx, r)
if err != nil {
return NewError(1 /*Not Signed In*/)
}
if r.Method == "POST" {
security_json := r.PostFormValue("security")
if security_json == "" {
return NewError(3 /*Invalid Request*/)
}
var security Security
err := security.Read(security_json)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
security.SecurityId = -1
security.UserId = user.UserId
err = InsertSecurity(tx, &security)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
return ResponseWrapper{201, &security}
} else if r.Method == "GET" {
var securityid int64
n, err := GetURLPieces(r.URL.Path, "/security/%d", &securityid)
if err != nil || n != 1 {
//Return all securities
var sl SecurityList
securities, err := GetSecurities(tx, user.UserId)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
sl.Securities = securities
return &sl
} else {
security, err := GetSecurity(tx, securityid, user.UserId)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
return security
}
} else {
securityid, err := GetURLID(r.URL.Path)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
if r.Method == "PUT" {
security_json := r.PostFormValue("security")
if security_json == "" {
return NewError(3 /*Invalid Request*/)
}
var security Security
err := security.Read(security_json)
if err != nil || security.SecurityId != securityid {
return NewError(3 /*Invalid Request*/)
}
security.UserId = user.UserId
err = UpdateSecurity(tx, &security)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
return &security
} else if r.Method == "DELETE" {
security, err := GetSecurity(tx, securityid, user.UserId)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
err = DeleteSecurity(tx, security)
if _, ok := err.(SecurityInUseError); ok {
return NewError(7 /*In Use Error*/)
} else if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
return SuccessWriter{}
}
}
return NewError(3 /*Invalid Request*/)
}
func SecurityTemplateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
var sl SecurityList
query, _ := url.ParseQuery(r.URL.RawQuery)
var limit int64 = -1
search := query.Get("search")
var _type int64 = 0
typestring := query.Get("type")
if len(typestring) > 0 {
_type = GetSecurityType(typestring)
if _type == 0 {
WriteError(w, 3 /*Invalid Request*/)
return
}
}
limitstring := query.Get("limit")
if limitstring != "" {
limitint, err := strconv.ParseInt(limitstring, 10, 0)
if err != nil {
WriteError(w, 3 /*Invalid Request*/)
return
}
limit = limitint
}
securities := SearchSecurityTemplates(search, _type, limit)
sl.Securities = &securities
err := (&sl).Write(w)
if err != nil {
WriteError(w, 999 /*Internal Error*/)
log.Print(err)
return
}
} else {
WriteError(w, 3 /*Invalid Request*/)
}
}