diff --git a/js/actions/SecurityTemplateActions.js b/js/actions/SecurityTemplateActions.js
index a92ae37..15d9513 100644
--- a/js/actions/SecurityTemplateActions.js
+++ b/js/actions/SecurityTemplateActions.js
@@ -5,6 +5,7 @@ var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var Security = models.Security;
var Error = models.Error;
+var SecurityType = models.SecurityType;
function searchSecurityTemplates(searchString, searchType) {
return {
@@ -23,6 +24,19 @@ function securityTemplatesSearched(searchString, searchType, securities) {
}
}
+function fetchCurrencyTemplates() {
+ return {
+ type: SecurityTemplateConstants.FETCH_CURRENCIES
+ }
+}
+
+function currencyTemplatesFetched(currencies) {
+ return {
+ type: SecurityTemplateConstants.CURRENCIES_FETCHED,
+ currencies: currencies
+ }
+}
+
function search(searchString, searchType, limit) {
return function (dispatch) {
dispatch(searchSecurityTemplates(searchString, searchType));
@@ -57,6 +71,38 @@ function search(searchString, searchType, limit) {
};
}
+function fetchCurrencies() {
+ return function (dispatch) {
+ dispatch(fetchCurrencyTemplates());
+
+ $.ajax({
+ type: "GET",
+ dataType: "json",
+ url: "securitytemplate/?search=&type=currency",
+ success: function(data, status, jqXHR) {
+ var e = new Error();
+ e.fromJSON(data);
+ if (e.isError()) {
+ dispatch(ErrorActions.serverError(e));
+ } else if (data.securities == null) {
+ dispatch(currencyTemplatesFetched(new Array()));
+ } else {
+ dispatch(currencyTemplatesFetched(
+ data.securities.map(function(json) {
+ var s = new Security();
+ s.fromJSON(json);
+ return s;
+ })));
+ }
+ },
+ error: function(jqXHR, status, error) {
+ dispatch(ErrorActions.ajaxError(error));
+ }
+ });
+ };
+}
+
module.exports = {
- search: search
+ search: search,
+ fetchCurrencies: fetchCurrencies
};
diff --git a/js/components/AccountSettingsModal.js b/js/components/AccountSettingsModal.js
index 00eb757..ccee8a2 100644
--- a/js/components/AccountSettingsModal.js
+++ b/js/components/AccountSettingsModal.js
@@ -12,6 +12,8 @@ var FormControl = ReactBootstrap.FormControl;
var ControlLabel = ReactBootstrap.ControlLabel;
var Col = ReactBootstrap.Col;
+var Combobox = require('react-widgets').Combobox;
+
var models = require('../models');
var User = models.User;
@@ -22,6 +24,7 @@ class AccountSettingsModal extends React.Component {
name: props ? props.user.Name: "",
username: props ? props.user.Username : "",
email: props ? props.user.Email : "",
+ defaultCurrency: props ? props.user.DefaultCurrency : "",
password: models.BogusPassword,
confirm_password: models.BogusPassword,
passwordChanged: false,
@@ -33,6 +36,7 @@ class AccountSettingsModal extends React.Component {
this.state = this._getInitialState();
this.onCancel = this.handleCancel.bind(this);
this.onChange = this.handleChange.bind(this);
+ this.onSelectCurrency = this.handleSelectCurrency.bind(this);
this.onSubmit = this.handleSubmit.bind(this);
}
componentWillReceiveProps(nextProps) {
@@ -73,6 +77,13 @@ class AccountSettingsModal extends React.Component {
confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value
});
}
+ handleSelectCurrency(security) {
+ if (security.hasOwnProperty('SecurityId')) {
+ this.setState({
+ defaultCurrency: security.SecurityId
+ });
+ }
+ }
handleSubmit(e) {
var u = new User();
e.preventDefault();
@@ -81,6 +92,7 @@ class AccountSettingsModal extends React.Component {
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
+ u.DefaultCurrency = this.state.defaultCurrency;
if (this.state.passwordChanged) {
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
@@ -130,6 +142,20 @@ class AccountSettingsModal extends React.Component {
ref="email"/>
+
+ Default Currency
+
+ item == undefined || typeof item === 'string' ? item : item.Name + " - " + item.Description}
+ defaultValue={this.state.defaultCurrency}
+ onChange={this.onSelectCurrency}
+ suggest
+ filter='contains'
+ ref="security" />
+
+
Password
diff --git a/js/components/MoneyGoApp.js b/js/components/MoneyGoApp.js
index d4b4c4b..b8046b2 100644
--- a/js/components/MoneyGoApp.js
+++ b/js/components/MoneyGoApp.js
@@ -27,6 +27,7 @@ class MoneyGoApp extends React.Component {
}
componentDidMount() {
this.props.tryResumingSession();
+ this.props.fetchCurrencies();
}
handleShowSettings() {
this.setState({showAccountSettingsModal: true});
diff --git a/js/components/NewUserModal.js b/js/components/NewUserModal.js
index dc8587b..b29b6d0 100644
--- a/js/components/NewUserModal.js
+++ b/js/components/NewUserModal.js
@@ -11,6 +11,8 @@ var Col = ReactBootstrap.Col;
var Button = ReactBootstrap.Button;
var ButtonGroup = ReactBootstrap.ButtonGroup;
+var Combobox = require('react-widgets').Combobox;
+
var models = require('../models');
var User = models.User;
@@ -22,6 +24,7 @@ class NewUserModal extends React.Component {
name: "",
username: "",
email: "",
+ defaultCurrency: '840', // ISO4217 code for USD
password: "",
confirm_password: "",
passwordChanged: false,
@@ -29,6 +32,7 @@ class NewUserModal extends React.Component {
};
this.onCancel = this.handleCancel.bind(this);
this.onChange = this.handleChange.bind(this);
+ this.onSelectCurrency = this.handleSelectCurrency.bind(this);
this.onSubmit = this.handleSubmit.bind(this);
}
passwordValidationState() {
@@ -64,6 +68,13 @@ class NewUserModal extends React.Component {
confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value
});
}
+ handleSelectCurrency(security) {
+ if (security.hasOwnProperty('SecurityId')) {
+ this.setState({
+ defaultCurrency: security.AlternateId
+ });
+ }
+ }
handleSubmit(e) {
var u = new User();
var error = "";
@@ -72,6 +83,7 @@ class NewUserModal extends React.Component {
u.Name = this.state.name;
u.Username = this.state.username;
u.Email = this.state.email;
+ u.DefaultCurrency = Number.parseInt(this.state.defaultCurrency);
u.Password = this.state.password;
if (u.Password != this.state.confirm_password) {
this.setState({error: "Error: passwords do not match"});
@@ -118,6 +130,20 @@ class NewUserModal extends React.Component {
ref="email"/>
+
+ Default Currency
+
+ typeof item === 'string' ? item : item.Name + " - " + item.Description}
+ defaultValue={this.state.defaultCurrency}
+ onChange={this.onSelectCurrency}
+ suggest
+ filter='contains'
+ ref="security" />
+
+
Password
diff --git a/js/constants/SecurityTemplateConstants.js b/js/constants/SecurityTemplateConstants.js
index b8fc901..d0dd5b5 100644
--- a/js/constants/SecurityTemplateConstants.js
+++ b/js/constants/SecurityTemplateConstants.js
@@ -1,6 +1,8 @@
var keyMirror = require('keymirror');
module.exports = keyMirror({
+ FETCH_CURRENCIES: null,
+ CURRENCIES_FETCHED: null,
SEARCH_SECURITY_TEMPLATES: null,
SECURITY_TEMPLATES_SEARCHED: null
});
diff --git a/js/containers/AccountSettingsModalContainer.js b/js/containers/AccountSettingsModalContainer.js
index f653083..ade3aba 100644
--- a/js/containers/AccountSettingsModalContainer.js
+++ b/js/containers/AccountSettingsModalContainer.js
@@ -1,11 +1,13 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
+
var AccountSettingsModal = require('../components/AccountSettingsModal');
function mapStateToProps(state) {
return {
- user: state.user
+ user: state.user,
+ currencies: state.securities.currency_list
}
}
diff --git a/js/containers/MoneyGoAppContainer.js b/js/containers/MoneyGoAppContainer.js
index b348c6e..8a9edf8 100644
--- a/js/containers/MoneyGoAppContainer.js
+++ b/js/containers/MoneyGoAppContainer.js
@@ -1,6 +1,7 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
+var SecurityTemplateActions = require('../actions/SecurityTemplateActions');
var MoneyGoApp = require('../components/MoneyGoApp');
@@ -13,6 +14,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
tryResumingSession: function() {dispatch(UserActions.tryResumingSession())},
+ fetchCurrencies: function() {dispatch(SecurityTemplateActions.fetchCurrencies())},
}
}
diff --git a/js/containers/NewUserModalContainer.js b/js/containers/NewUserModalContainer.js
index 4b7f8d9..237843b 100644
--- a/js/containers/NewUserModalContainer.js
+++ b/js/containers/NewUserModalContainer.js
@@ -5,7 +5,9 @@ var UserActions = require('../actions/UserActions');
var NewUserModal = require('../components/NewUserModal');
function mapStateToProps(state) {
- return {}
+ return {
+ currencies: state.securityTemplates.currencies
+ }
}
function mapDispatchToProps(dispatch) {
diff --git a/js/models.js b/js/models.js
index 2890840..b3df031 100644
--- a/js/models.js
+++ b/js/models.js
@@ -13,6 +13,7 @@ function getJSONObj(json_input) {
class User {
constructor() {
this.UserId = -1;
+ this.DefaultCurrency = -1;
this.Name = "";
this.Username = "";
this.Password = "";
@@ -21,6 +22,7 @@ class User {
toJSON() {
var json_obj = {};
json_obj.UserId = this.UserId;
+ json_obj.DefaultCurrency = this.DefaultCurrency;
json_obj.Name = this.Name;
json_obj.Username = this.Username;
json_obj.Password = this.Password;
@@ -32,6 +34,8 @@ class User {
if (json_obj.hasOwnProperty("UserId"))
this.UserId = json_obj.UserId;
+ if (json_obj.hasOwnProperty("DefaultCurrency"))
+ this.DefaultCurrency = json_obj.DefaultCurrency;
if (json_obj.hasOwnProperty("Name"))
this.Name = json_obj.Name;
if (json_obj.hasOwnProperty("Username"))
diff --git a/js/reducers/SecurityTemplateReducer.js b/js/reducers/SecurityTemplateReducer.js
index b1ade5a..9e0b0a7 100644
--- a/js/reducers/SecurityTemplateReducer.js
+++ b/js/reducers/SecurityTemplateReducer.js
@@ -3,30 +3,37 @@ var assign = require('object-assign');
var SecurityTemplateConstants = require('../constants/SecurityTemplateConstants');
var UserConstants = require('../constants/UserConstants');
-var SecurityType = require('../models').SecurityType;
+const initialState = {
+ search: "",
+ type: 0,
+ templates: [],
+ currencies: []
+};
-module.exports = function(state = {search: "", type: 0, templates: [], searchNumber: 0}, action) {
+module.exports = function(state = initialState, action) {
switch (action.type) {
case SecurityTemplateConstants.SEARCH_SECURITY_TEMPLATES:
- return {
+ return assign({}, state, {
search: action.searchString,
type: action.searchType,
templates: []
- };
+ });
case SecurityTemplateConstants.SECURITY_TEMPLATES_SEARCHED:
if ((action.searchString != state.search) || (action.searchType != state.type))
return state;
- return {
+ return assign({}, state, {
search: action.searchString,
type: action.searchType,
templates: action.securities
- };
+ });
+ case SecurityTemplateConstants.CURRENCIES_FETCHED:
+ return assign({}, state, {
+ currencies: action.currencies
+ });
case UserConstants.USER_LOGGEDOUT:
- return {
- search: "",
- type: 0,
- templates: []
- };
+ return assign({}, initialState, {
+ currencies: state.currencies
+ });
default:
return state;
}
diff --git a/securities.go b/securities.go
index 9843825..f670824 100644
--- a/securities.go
+++ b/securities.go
@@ -36,7 +36,7 @@ type Security struct {
// security is precise to
Precision int
Type int64
- // AlternateId is CUSIP for Type=Stock
+ // AlternateId is CUSIP for Type=Stock, ISO4217 for Type=Currency
AlternateId string
}
@@ -86,6 +86,16 @@ func FindSecurityTemplate(name string, _type int64) *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(securityid int64, userid int64) (*Security, error) {
var s Security
@@ -171,6 +181,15 @@ func DeleteSecurity(s *Security) error {
return errors.New("One or more accounts still use this security")
}
+ user, err := GetUserTx(transaction, s.UserId)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ } else if user.DefaultCurrency == s.SecurityId {
+ transaction.Rollback()
+ return errors.New("Cannot delete security which is user's default currency")
+ }
+
count, err := transaction.Delete(s)
if err != nil {
transaction.Rollback()
diff --git a/users.go b/users.go
index 3629c3c..e994188 100644
--- a/users.go
+++ b/users.go
@@ -3,7 +3,9 @@ package main
import (
"crypto/sha256"
"encoding/json"
+ "errors"
"fmt"
+ "gopkg.in/gorp.v1"
"io"
"log"
"net/http"
@@ -11,12 +13,13 @@ import (
)
type User struct {
- UserId int64
- Name string
- Username string
- Password string `db:"-"`
- PasswordHash string `json:"-"`
- Email string
+ 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"
@@ -54,6 +57,16 @@ func GetUser(userid int64) (*User, error) {
return &u, nil
}
+func GetUserTx(transaction *gorp.Transaction, userid int64) (*User, error) {
+ var u User
+
+ err := transaction.SelectOne(&u, "SELECT * from users where UserId=?", userid)
+ if err != nil {
+ return nil, err
+ }
+ return &u, nil
+}
+
func GetUserByUsername(username string) (*User, error) {
var u User
@@ -70,6 +83,12 @@ func InsertUser(u *User) error {
return err
}
+ security_template := FindCurrencyTemplate(u.DefaultCurrency)
+ if security_template == nil {
+ transaction.Rollback()
+ return errors.New("Invalid ISO4217 Default Currency")
+ }
+
existing, err := transaction.SelectInt("SELECT count(*) from users where Username=?", u.Username)
if err != nil {
transaction.Rollback()
@@ -86,6 +105,28 @@ func InsertUser(u *User) error {
return err
}
+ // Copy the security template and give it our new UserId
+ var security Security
+ security = *security_template
+ security.UserId = u.UserId
+
+ err = InsertSecurityTx(transaction, &security)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ }
+
+ // Update the user's DefaultCurrency to our new SecurityId
+ u.DefaultCurrency = security.SecurityId
+ count, err := transaction.Update(u)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ } else if count != 1 {
+ transaction.Rollback()
+ return errors.New("Would have updated more than one user")
+ }
+
err = transaction.Commit()
if err != nil {
transaction.Rollback()
@@ -103,6 +144,42 @@ func GetUserFromSession(r *http.Request) (*User, error) {
return GetUser(s.UserId)
}
+func UpdateUser(u *User) error {
+ transaction, err := DB.Begin()
+ if err != nil {
+ return err
+ }
+
+ security, err := GetSecurityTx(transaction, u.DefaultCurrency, u.UserId)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ } else if security.UserId != u.UserId || security.SecurityId != u.DefaultCurrency {
+ transaction.Rollback()
+ return errors.New("UserId and DefaultCurrency don't match the fetched security")
+ } else if security.Type != Currency {
+ transaction.Rollback()
+ return errors.New("New DefaultCurrency security is not a currency")
+ }
+
+ count, err := transaction.Update(u)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ } else if count != 1 {
+ transaction.Rollback()
+ return errors.New("Would have updated more than one user")
+ }
+
+ err = transaction.Commit()
+ if err != nil {
+ transaction.Rollback()
+ return err
+ }
+
+ return nil
+}
+
func UserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
user_json := r.PostFormValue("user")
@@ -187,8 +264,8 @@ func UserHandler(w http.ResponseWriter, r *http.Request) {
user.PasswordHash = old_pwhash
}
- count, err := DB.Update(user)
- if count != 1 || err != nil {
+ err = UpdateUser(user)
+ if err != nil {
WriteError(w, 999 /*Internal Error*/)
log.Print(err)
return