2017-10-04 19:35:59 -04:00
package handlers
//go:generate make
2015-06-25 22:36:58 -04:00
2015-06-27 08:31:38 -04:00
import (
"encoding/json"
2016-10-16 08:19:11 -04:00
"errors"
2017-10-24 20:24:12 -04:00
"fmt"
2015-06-27 08:31:38 -04:00
"log"
"net/http"
2016-10-16 08:19:11 -04:00
"net/url"
2016-10-26 06:58:14 -04:00
"strconv"
2016-10-16 08:19:11 -04:00
"strings"
2015-06-27 08:31:38 -04:00
)
2017-11-05 20:43:32 -05:00
type SecurityType int64
2015-06-25 22:36:58 -04:00
const (
2017-11-05 20:43:32 -05:00
Currency SecurityType = 1
Stock = 2
2015-06-25 22:36:58 -04:00
)
2017-11-05 20:43:32 -05:00
func GetSecurityType ( typestring string ) SecurityType {
2016-10-16 08:19:11 -04:00
if strings . EqualFold ( typestring , "currency" ) {
return Currency
} else if strings . EqualFold ( typestring , "stock" ) {
return Stock
} else {
return 0
}
}
2015-06-25 22:36:58 -04:00
type Security struct {
2016-01-13 20:44:58 -05:00
SecurityId int64
2016-10-16 08:19:11 -04:00
UserId int64
2016-01-13 20:44:58 -05:00
Name string
Description string
Symbol string
2015-06-25 22:36:58 -04:00
// Number of decimal digits (to the right of the decimal point) this
// security is precise to
2015-08-21 06:54:17 -04:00
Precision int
2017-11-05 20:43:32 -05:00
Type SecurityType
2017-06-21 21:25:38 -04:00
// AlternateId is CUSIP for Type=Stock, ISO4217 for Type=Currency
2016-02-02 21:46:27 -05:00
AlternateId string
2015-06-25 22:36:58 -04:00
}
2015-06-27 08:31:38 -04:00
type SecurityList struct {
Securities * [ ] * Security ` json:"securities" `
}
2016-10-16 08:19:11 -04:00
func ( s * Security ) Read ( json_str string ) error {
dec := json . NewDecoder ( strings . NewReader ( json_str ) )
return dec . Decode ( s )
}
2016-01-13 20:44:58 -05:00
2016-10-16 08:19:11 -04:00
func ( s * Security ) Write ( w http . ResponseWriter ) error {
enc := json . NewEncoder ( w )
return enc . Encode ( s )
2015-06-27 08:31:38 -04:00
}
2017-10-05 08:06:08 -04:00
func ( sl * SecurityList ) Read ( json_str string ) error {
dec := json . NewDecoder ( strings . NewReader ( json_str ) )
return dec . Decode ( sl )
}
2016-10-16 08:19:11 -04:00
func ( sl * SecurityList ) Write ( w http . ResponseWriter ) error {
enc := json . NewEncoder ( w )
return enc . Encode ( sl )
}
2015-06-27 08:31:38 -04:00
2017-11-05 20:43:32 -05:00
func SearchSecurityTemplates ( search string , _type SecurityType , limit int64 ) [ ] * Security {
2016-10-16 08:19:11 -04:00
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 ] )
2016-10-26 06:58:14 -04:00
if limit != - 1 && int64 ( len ( results ) ) >= limit {
break
}
2016-10-16 08:19:11 -04:00
}
}
2015-06-27 08:31:38 -04:00
}
2016-10-16 08:19:11 -04:00
return results
2015-06-27 08:31:38 -04:00
}
2017-11-05 20:43:32 -05:00
func FindSecurityTemplate ( name string , _type SecurityType ) * Security {
2017-02-19 07:50:36 -05:00
for _ , security := range SecurityTemplates {
if name == security . Name && _type == security . Type {
return & security
}
}
return nil
}
2017-06-21 21:25:38 -04:00
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
}
2017-10-14 14:20:50 -04:00
func GetSecurity ( tx * Tx , securityid int64 , userid int64 ) ( * Security , error ) {
2016-10-16 08:19:11 -04:00
var s Security
2017-10-14 14:20:50 -04:00
err := tx . SelectOne ( & s , "SELECT * from securities where UserId=? AND SecurityId=?" , userid , securityid )
2016-10-16 08:19:11 -04:00
if err != nil {
return nil , err
2015-06-27 08:31:38 -04:00
}
2016-10-16 08:19:11 -04:00
return & s , nil
2015-06-27 08:31:38 -04:00
}
2017-10-14 14:20:50 -04:00
func GetSecurities ( tx * Tx , userid int64 ) ( * [ ] * Security , error ) {
2016-10-16 08:19:11 -04:00
var securities [ ] * Security
2017-10-14 14:20:50 -04:00
_ , err := tx . Select ( & securities , "SELECT * from securities where UserId=?" , userid )
2016-10-16 08:19:11 -04:00
if err != nil {
return nil , err
2016-02-02 21:46:27 -05:00
}
2016-10-16 08:19:11 -04:00
return & securities , nil
2016-02-15 11:28:44 -05:00
}
2017-10-14 14:20:50 -04:00
func InsertSecurity ( tx * Tx , s * Security ) error {
err := tx . Insert ( s )
2016-10-16 08:19:11 -04:00
if err != nil {
return err
}
return nil
}
2017-10-14 14:20:50 -04:00
func UpdateSecurity ( tx * Tx , s * Security ) ( err error ) {
2017-10-14 19:41:13 -04:00
user , err := GetUser ( tx , s . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return
2017-06-21 21:53:01 -04:00
} 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" )
}
2017-10-14 14:20:50 -04:00
count , err := tx . Update ( s )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return
2016-10-16 08:19:11 -04:00
}
2017-10-24 20:24:12 -04:00
if count > 1 {
return fmt . Errorf ( "Updated %d securities (expected 1)" , count )
2016-10-16 08:19:11 -04:00
}
return nil
2016-02-02 21:46:27 -05:00
}
2017-10-12 20:47:44 -04:00
type SecurityInUseError struct {
message string
}
func ( e SecurityInUseError ) Error ( ) string {
return e . message
}
2017-10-14 14:20:50 -04:00
func DeleteSecurity ( tx * Tx , s * Security ) error {
2016-10-16 08:19:11 -04:00
// First, ensure no accounts are using this security
2017-10-14 14:20:50 -04:00
accounts , err := tx . SelectInt ( "SELECT count(*) from accounts where UserId=? and SecurityId=?" , s . UserId , s . SecurityId )
2016-10-16 08:19:11 -04:00
if accounts != 0 {
2017-10-12 20:47:44 -04:00
return SecurityInUseError { "One or more accounts still use this security" }
2016-10-16 08:19:11 -04:00
}
2017-10-14 19:41:13 -04:00
user , err := GetUser ( tx , s . UserId )
2017-06-21 21:25:38 -04:00
if err != nil {
return err
} else if user . DefaultCurrency == s . SecurityId {
2017-10-12 20:47:44 -04:00
return SecurityInUseError { "Cannot delete security which is user's default currency" }
2017-06-21 21:25:38 -04:00
}
2017-07-17 20:24:47 -04:00
// Remove all prices involving this security (either of this security, or
// using it as a currency)
2017-10-14 14:20:50 -04:00
_ , err = tx . Exec ( "DELETE FROM prices WHERE SecurityId=? OR CurrencyId=?" , s . SecurityId , s . SecurityId )
2017-07-17 20:24:47 -04:00
if err != nil {
return err
}
2017-10-14 14:20:50 -04:00
count , err := tx . Delete ( s )
2016-10-16 08:19:11 -04:00
if err != nil {
return err
}
if count != 1 {
return errors . New ( "Deleted more than one security" )
}
return nil
2015-06-27 08:31:38 -04:00
}
2017-10-14 19:41:13 -04:00
func ImportGetCreateSecurity ( tx * Tx , userid int64 , security * Security ) ( * Security , error ) {
2017-06-04 16:01:42 -04:00
security . UserId = userid
2017-02-19 07:50:36 -05:00
if len ( security . AlternateId ) == 0 {
// Always create a new local security if we can't match on the AlternateId
2017-10-14 19:41:13 -04:00
err := InsertSecurity ( tx , security )
2017-02-19 07:50:36 -05:00
if err != nil {
return nil , err
}
return security , nil
}
var securities [ ] * Security
2017-10-14 19:41:13 -04:00
_ , err := tx . Select ( & securities , "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Precision=?" , userid , security . Type , security . AlternateId , security . Precision )
2017-02-19 07:50:36 -05:00
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
2017-10-14 19:41:13 -04:00
err = InsertSecurity ( tx , security )
2017-02-19 07:50:36 -05:00
if err != nil {
return nil , err
}
return security , nil
}
2017-11-12 20:17:27 -05:00
func SecurityHandler ( r * http . Request , context * Context ) ResponseWriterWriter {
user , err := GetUserFromSession ( context . Tx , r )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 1 /*Not Signed In*/ )
2016-10-16 08:19:11 -04:00
}
if r . Method == "POST" {
2017-11-16 19:17:51 -05:00
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 )
}
2016-10-16 08:19:11 -04:00
var security Security
2017-11-13 20:48:19 -05:00
if err := ReadJSON ( r , & security ) ; err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-16 08:19:11 -04:00
}
security . SecurityId = - 1
security . UserId = user . UserId
2017-11-12 20:17:27 -05:00
err = InsertSecurity ( context . Tx , & security )
2016-10-16 08:19:11 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2016-10-16 08:19:11 -04:00
}
2017-10-14 14:20:50 -04:00
return ResponseWrapper { 201 , & security }
2016-10-16 08:19:11 -04:00
} else if r . Method == "GET" {
2017-11-12 21:12:49 -05:00
if context . LastLevel ( ) {
2016-10-16 08:19:11 -04:00
//Return all securities
var sl SecurityList
2017-11-12 20:17:27 -05:00
securities , err := GetSecurities ( context . Tx , user . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2016-10-16 08:19:11 -04:00
}
sl . Securities = securities
2017-10-14 14:20:50 -04:00
return & sl
2016-10-16 08:19:11 -04:00
} else {
2017-11-12 21:12:49 -05:00
securityid , err := context . NextID ( )
if err != nil {
return NewError ( 3 /*Invalid Request*/ )
}
2017-11-16 19:17:51 -05:00
if ! context . LastLevel ( ) {
if context . NextLevel ( ) != "prices" {
return NewError ( 3 /*Invalid Request*/ )
}
return PriceHandler ( r , context , user , securityid )
}
2017-11-12 20:17:27 -05:00
security , err := GetSecurity ( context . Tx , securityid , user . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-16 08:19:11 -04:00
}
2017-10-14 14:20:50 -04:00
return security
2016-10-16 08:19:11 -04:00
}
} else {
2017-11-12 21:12:49 -05:00
securityid , err := context . NextID ( )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-16 08:19:11 -04:00
}
2017-11-16 19:17:51 -05:00
if ! context . LastLevel ( ) {
if context . NextLevel ( ) != "prices" {
return NewError ( 3 /*Invalid Request*/ )
}
return PriceHandler ( r , context , user , securityid )
}
2016-10-16 08:19:11 -04:00
if r . Method == "PUT" {
var security Security
2017-11-13 20:48:19 -05:00
if err := ReadJSON ( r , & security ) ; err != nil || security . SecurityId != securityid {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-27 08:31:38 -04:00
}
2016-10-16 08:19:11 -04:00
security . UserId = user . UserId
2017-11-12 20:17:27 -05:00
err = UpdateSecurity ( context . Tx , & security )
2015-06-27 08:31:38 -04:00
if err != nil {
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2015-06-27 08:31:38 -04:00
}
2016-10-16 08:19:11 -04:00
2017-10-14 14:20:50 -04:00
return & security
2016-10-16 08:19:11 -04:00
} else if r . Method == "DELETE" {
2017-11-12 20:17:27 -05:00
security , err := GetSecurity ( context . Tx , securityid , user . UserId )
2016-10-16 08:19:11 -04:00
if err != nil {
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-16 08:19:11 -04:00
}
2017-11-12 20:17:27 -05:00
err = DeleteSecurity ( context . Tx , security )
2017-10-12 20:47:44 -04:00
if _ , ok := err . ( SecurityInUseError ) ; ok {
2017-10-14 14:20:50 -04:00
return NewError ( 7 /*In Use Error*/ )
2017-10-12 20:47:44 -04:00
} else if err != nil {
2016-10-16 08:19:11 -04:00
log . Print ( err )
2017-10-14 14:20:50 -04:00
return NewError ( 999 /*Internal Error*/ )
2016-10-16 08:19:11 -04:00
}
2017-10-14 14:20:50 -04:00
return SuccessWriter { }
2016-10-16 08:19:11 -04:00
}
}
2017-10-14 14:20:50 -04:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-16 08:19:11 -04:00
}
2017-11-12 20:17:27 -05:00
func SecurityTemplateHandler ( r * http . Request , context * Context ) ResponseWriterWriter {
2016-10-16 08:19:11 -04:00
if r . Method == "GET" {
var sl SecurityList
query , _ := url . ParseQuery ( r . URL . RawQuery )
2016-10-26 06:58:14 -04:00
var limit int64 = - 1
2016-10-16 08:19:11 -04:00
search := query . Get ( "search" )
2017-10-05 08:06:08 -04:00
2017-11-05 20:43:32 -05:00
var _type SecurityType = 0
2017-10-05 08:06:08 -04:00
typestring := query . Get ( "type" )
if len ( typestring ) > 0 {
_type = GetSecurityType ( typestring )
if _type == 0 {
2017-11-12 20:17:27 -05:00
return NewError ( 3 /*Invalid Request*/ )
2017-10-05 08:06:08 -04:00
}
}
2016-10-16 08:19:11 -04:00
2016-10-26 06:58:14 -04:00
limitstring := query . Get ( "limit" )
if limitstring != "" {
limitint , err := strconv . ParseInt ( limitstring , 10 , 0 )
if err != nil {
2017-11-12 20:17:27 -05:00
return NewError ( 3 /*Invalid Request*/ )
2016-10-26 06:58:14 -04:00
}
limit = limitint
}
securities := SearchSecurityTemplates ( search , _type , limit )
2016-10-16 08:19:11 -04:00
sl . Securities = & securities
2017-11-12 20:17:27 -05:00
return & sl
2015-06-27 08:31:38 -04:00
} else {
2017-11-12 20:17:27 -05:00
return NewError ( 3 /*Invalid Request*/ )
2015-06-27 08:31:38 -04:00
}
}