mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-30 09:33:25 -04:00 
			
		
		
		
	Add per-user default currency
This commit is contained in:
		| @@ -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) { | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
| 	search: search | ||||
| 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, | ||||
| 	fetchCurrencies: fetchCurrencies | ||||
| }; | ||||
|   | ||||
| @@ -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"/> | ||||
| 						</Col> | ||||
| 					</FormGroup> | ||||
| 					<FormGroup> | ||||
| 						<Col componentClass={ControlLabel} xs={2}>Default Currency</Col> | ||||
| 						<Col xs={10}> | ||||
| 						<Combobox | ||||
| 							data={this.props.currencies} | ||||
| 							valueField='SecurityId' | ||||
| 							textField={item => item == undefined || typeof item === 'string' ? item : item.Name + " - " + item.Description} | ||||
| 							defaultValue={this.state.defaultCurrency} | ||||
| 							onChange={this.onSelectCurrency} | ||||
| 							suggest | ||||
| 							filter='contains' | ||||
| 							ref="security" /> | ||||
| 						</Col> | ||||
| 					</FormGroup> | ||||
| 					<FormGroup validationState={this.passwordValidationState()}> | ||||
| 						<Col componentClass={ControlLabel} xs={2}>Password</Col> | ||||
| 						<Col xs={10}> | ||||
|   | ||||
| @@ -27,6 +27,7 @@ class MoneyGoApp extends React.Component { | ||||
| 	} | ||||
| 	componentDidMount() { | ||||
| 		this.props.tryResumingSession(); | ||||
| 		this.props.fetchCurrencies(); | ||||
| 	} | ||||
| 	handleShowSettings() { | ||||
| 		this.setState({showAccountSettingsModal: true}); | ||||
|   | ||||
| @@ -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"/> | ||||
| 						</Col> | ||||
| 					</FormGroup> | ||||
| 					<FormGroup> | ||||
| 						<Col componentClass={ControlLabel} xs={2}>Default Currency</Col> | ||||
| 						<Col xs={10}> | ||||
| 						<Combobox | ||||
| 							data={this.props.currencies} | ||||
| 							valueField='AlternateId' | ||||
| 							textField={item => typeof item === 'string' ? item : item.Name + " - " + item.Description} | ||||
| 							defaultValue={this.state.defaultCurrency} | ||||
| 							onChange={this.onSelectCurrency} | ||||
| 							suggest | ||||
| 							filter='contains' | ||||
| 							ref="security" /> | ||||
| 						</Col> | ||||
| 					</FormGroup> | ||||
| 					<FormGroup validationState={this.passwordValidationState()}> | ||||
| 						<Col componentClass={ControlLabel} xs={2}>Password</Col> | ||||
| 						<Col xs={10}> | ||||
|   | ||||
| @@ -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 | ||||
| }); | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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())}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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")) | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
							
								
								
									
										81
									
								
								users.go
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								users.go
									
									
									
									
									
								
							| @@ -3,7 +3,9 @@ package main | ||||
| import ( | ||||
| 	"crypto/sha256" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"gopkg.in/gorp.v1" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| @@ -12,6 +14,7 @@ import ( | ||||
|  | ||||
| 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:"-"` | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user