moneygo/internal/handlers/securities.go

341 lines
8.7 KiB
Go

package handlers
//go:generate make
import (
"errors"
"fmt"
"github.com/aclindsa/moneygo/internal/models"
"github.com/aclindsa/moneygo/internal/store/db"
"log"
"net/http"
"net/url"
"strconv"
"strings"
)
func SearchSecurityTemplates(search string, _type models.SecurityType, limit int64) []*models.Security {
upperSearch := strings.ToUpper(search)
var results []*models.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 models.SecurityType) *models.Security {
for _, security := range SecurityTemplates {
if name == security.Name && _type == security.Type {
return &security
}
}
return nil
}
func FindCurrencyTemplate(iso4217 int64) *models.Security {
iso4217string := strconv.FormatInt(iso4217, 10)
for _, security := range SecurityTemplates {
if security.Type == models.Currency && security.AlternateId == iso4217string {
return &security
}
}
return nil
}
func GetSecurity(tx *db.Tx, securityid int64, userid int64) (*models.Security, error) {
var s models.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 *db.Tx, userid int64) (*[]*models.Security, error) {
var securities []*models.Security
_, err := tx.Select(&securities, "SELECT * from securities where UserId=?", userid)
if err != nil {
return nil, err
}
return &securities, nil
}
func InsertSecurity(tx *db.Tx, s *models.Security) error {
err := tx.Insert(s)
if err != nil {
return err
}
return nil
}
func UpdateSecurity(tx *db.Tx, s *models.Security) (err error) {
user, err := GetUser(tx, s.UserId)
if err != nil {
return
} 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")
}
count, err := tx.Update(s)
if err != nil {
return
}
if count > 1 {
return fmt.Errorf("Updated %d securities (expected 1)", count)
}
return nil
}
type SecurityInUseError struct {
message string
}
func (e SecurityInUseError) Error() string {
return e.message
}
func DeleteSecurity(tx *db.Tx, s *models.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 *db.Tx, userid int64, security *models.Security) (*models.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 []*models.Security
_, 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 {
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, context *Context) ResponseWriterWriter {
user, err := GetUserFromSession(context.Tx, r)
if err != nil {
return NewError(1 /*Not Signed In*/)
}
if r.Method == "POST" {
if !context.LastLevel() {
securityid, err := context.NextID()
if err != nil {
return NewError(3 /*Invalid Request*/)
}
if context.NextLevel() != "prices" {
return NewError(3 /*Invalid Request*/)
}
return PriceHandler(r, context, user, securityid)
}
var security models.Security
if err := ReadJSON(r, &security); err != nil {
return NewError(3 /*Invalid Request*/)
}
security.SecurityId = -1
security.UserId = user.UserId
err = InsertSecurity(context.Tx, &security)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
return ResponseWrapper{201, &security}
} else if r.Method == "GET" {
if context.LastLevel() {
//Return all securities
var sl models.SecurityList
securities, err := GetSecurities(context.Tx, user.UserId)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
sl.Securities = securities
return &sl
} else {
securityid, err := context.NextID()
if err != nil {
return NewError(3 /*Invalid Request*/)
}
if !context.LastLevel() {
if context.NextLevel() != "prices" {
return NewError(3 /*Invalid Request*/)
}
return PriceHandler(r, context, user, securityid)
}
security, err := GetSecurity(context.Tx, securityid, user.UserId)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
return security
}
} else {
securityid, err := context.NextID()
if err != nil {
return NewError(3 /*Invalid Request*/)
}
if !context.LastLevel() {
if context.NextLevel() != "prices" {
return NewError(3 /*Invalid Request*/)
}
return PriceHandler(r, context, user, securityid)
}
if r.Method == "PUT" {
var security models.Security
if err := ReadJSON(r, &security); err != nil || security.SecurityId != securityid {
return NewError(3 /*Invalid Request*/)
}
security.UserId = user.UserId
err = UpdateSecurity(context.Tx, &security)
if err != nil {
log.Print(err)
return NewError(999 /*Internal Error*/)
}
return &security
} else if r.Method == "DELETE" {
security, err := GetSecurity(context.Tx, securityid, user.UserId)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
err = DeleteSecurity(context.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(r *http.Request, context *Context) ResponseWriterWriter {
if r.Method == "GET" {
var sl models.SecurityList
query, _ := url.ParseQuery(r.URL.RawQuery)
var limit int64 = -1
search := query.Get("search")
var _type models.SecurityType = 0
typestring := query.Get("type")
if len(typestring) > 0 {
_type = models.GetSecurityType(typestring)
if _type == 0 {
return NewError(3 /*Invalid Request*/)
}
}
limitstring := query.Get("limit")
if limitstring != "" {
limitint, err := strconv.ParseInt(limitstring, 10, 0)
if err != nil {
return NewError(3 /*Invalid Request*/)
}
limit = limitint
}
securities := SearchSecurityTemplates(search, _type, limit)
sl.Securities = &securities
return &sl
} else {
return NewError(3 /*Invalid Request*/)
}
}