<repo_name>rahulshanwal/metal-trade<file_sep>/refdataServices/models/Commodity.js
const mongoose = require('mongoose');

const CommoditySchema = new mongoose.Schema({
    value: String,
    primaryText: String
});

module.exports = mongoose.model("Commodity", CommoditySchema);<file_sep>/refdataServices/router.js
const express = require('express');
const router = express.Router();
const CounterParty = require('./models/CounterParty');
const Commodity = require('./models/Commodity');
const Location = require('./models/Location');
const Side = require('./models/Side');

router.get("/", function(req, res) {
    res.json({
        status: "My Ref Data API is alive!"
    });
});

router.get('/getRefData', function(req, res){
    let refData = {};
    //TODO - refactor below callback hell
    Commodity.find({}, (err1, commodities) => {
        if(err1){
            refData.commodity = [];    
        }else{
            refData.commodity = commodities;
        }
        Location.find({}, (err2, locations) => {
            if(err2){
                refData.location = [];    
            }else{
                refData.location = locations;
            }
            CounterParty.find({}, (err3, counterParties)=> {
                if(err3){
                    refData.counterParty = [];
                }else{
                    refData.counterParty = counterParties;
                }
                Side.find({}, (err4, sides) => {
                    if(err4){
                        refData.side = [];
                    }else{
                        refData.side = sides;
                    }
                    res.json({refData: refData});
                });  
            })
        });

    });
});

module.exports = router;<file_sep>/metallica-backend/auth.js
var passport = require("passport");  
var passportJWT = require("passport-jwt");  
var cfg = require("./config.js");  
var ExtractJwt = passportJWT.ExtractJwt;  
var Strategy = passportJWT.Strategy;  
var params = {  
    secretOrKey: cfg.jwtSecret,
    jwtFromRequest: ExtractJwt.fromUrlQueryParameter('auth_token')
};
var User = require('./models/User');

module.exports = function() {  
    var strategy = new Strategy(params, function(payload, done) {
        User.findById(payload.id, (err, user) => {
            if (err) {
              return done(err, false);
            }
            if (user) {
              done(null, user);
            } else {
              done(null, false);
            }
          });
    });
    passport.use(strategy);
    return {
        initialize: function() {
            return passport.initialize();
        },
        authenticate: function() {
            return passport.authenticate("jwt", cfg.jwtSession);
        }
    };
};<file_sep>/UI/src/components/UI/Username/Username.js
import React from 'react';
import classes from './Username.css';
import Aux from '../../../hoc/Auxiliary/Auxiliary';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/social/person';
import {Link} from 'react-router-dom';
import FlatButton from 'material-ui/FlatButton';

const style = {
  height: 56,
  marginLeft: 10,
  marginRight: 20,
  marginTop: 3
};
const logoutButtonStyle = {
  color: 'white',
  margin: 'auto'
};
const UserName = (props) => {
    return (
    <Aux>
        <div className={classes.username}>{props.children}</div>
        <FlatButton label="Logout" style={logoutButtonStyle}>
          <Link to='/logout' className={classes.Logout}></Link>
        </FlatButton>
        <FloatingActionButton style={style}>
          <ContentAdd />
        </FloatingActionButton>
    </Aux>);
}

export default UserName;<file_sep>/UI/src/components/UI/ConfirmationDialog/ConfirmationDialog.js
import React from 'react';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';

const ConfirmationDialog = (props) => {
    const actions = [
        <FlatButton
            label="No"
            primary={true}
            onClick={props.handleCancelClick}
        />,
        <FlatButton
            label="YES"
            primary={true}
            onClick={props.handleSubmitClick}
        />,
        ];
    return (
        <div>
            <Dialog
            title={props.title}
            actions={actions}
            modal={true}
            open={props.open}>
            {props.text}
            </Dialog>
        </div>
    );
}

export default ConfirmationDialog;<file_sep>/UI/src/containers/Trade/TradeHeader/RightElements.js
import React from 'react';
import ActionDelete from 'material-ui/svg-icons/action/delete';
import ActionEdit from 'material-ui/svg-icons/editor/mode-edit';
import Aux from '../../../hoc/Auxiliary/Auxiliary';

const style = {color: 'white', cursor: 'pointer'};

const rightElements = (props) => (<Aux>
    <ActionEdit style={style} onClick={()=> props.changeEditMode(true)}/>
    <ActionDelete style={style} onClick={()=> props.onDelete()}/>
  </Aux>);
rightElements.muiName = 'IconMenu';

export default rightElements;<file_sep>/README.md
This project is built as a part of the Full Stack Program for the Metallica assignment.
The running app can be checked at the below location :
[Metallica - metal trading](https://shielded-bayou-75551.herokuapp.com/)

## Table of Contents
- [Folder Structure](#folder-structure)
- [High level Architecture](#high-level-architecture)
- [Technology Stack](#technology-stack)

## Folder Structure
**UI**: Contains the UI side project build on React/redux
- 'src' - src folder containes the source for all the js/html/css files
  - 'components' - contains the custom stateless components used in the app.
  - 'conatainers' - contains the custom containers holding some state used in the app.
  - 'hoc' - higher order components
  - 'Store' - the Redux store - holding actions and reducers acting as models for the app


**metallica-backend**: Contains the backend side code build using node. API gateway/proxy and authentication is handled here


**tradeServices**: microservice for the trade actions. To change add/edit/delete trades.

**refdataServices**: microservice to handle the static data for counterparty, location, side and commodity.

**marketDataServices**: microservice to feed the market data for different metals. here I have kept the data in a js file and changing it every 10 seconds to feed to the tickr.

**notificationServices**: microservice to consume all the message from amqp queues and send notification to UI via websockets.


## High Level Architecture
All the requests from the frontend are routed through the API gateway, the authentication happens here and then the calls are routed to the appropriate microservices. These microservices then publishes the event on the message broker which are then picked up by the notification services to be fed to the frontend via the websockets.
Each microservices has its own db


## Technology Stack

**Front End**
- ES6
- React
- Redux
- Material UI

**API Gateway**
- http-proxy-middleware (node module)

**Authentication** 
- passport (node module)
- passport-jwt (node module)

**Microservices**
- Node.js

**Messaging Infrastructure**
- amqplib (node module for rabitmq)

**Streaming**
- socket.io (websockets)

**Database**
- MongoDB
<file_sep>/UI/src/containers/TradeContainer/TradeContainer.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {Link} from 'react-router-dom';
import classes from './TradeContainer.css';
import TradesList from '../TradesList/TradesList';
import Trade from '../Trade/Trade';
import * as TradeActions from '../../Store/action/tradeActions';
import ConfirmationDialog from '../../components/UI/ConfirmationDialog/ConfirmationDialog';
import Loader from '../../components/UI/Loader/Loader';

class TradeContainer extends Component {
    render () {
        let tradeContainer = (<div className={classes.Container}>
            <TradesList/>
            <Trade/>
            <ConfirmationDialog title={'Delete Trade'} 
                text={'Are you sure you want delete this trade ?'}
                open={this.props.isConfirmationDialogOpen}
                handleSubmitClick={()=>{this.props.deleteSelectedTrade(this.props.selectedTrade, this.props.authToken)}}
                handleCancelClick={()=>{this.props.toggleDeleteTradeDialog(false)}}/>
            <Loader show={this.props.isLoading}/>  
        </div>);
        if(!this.props.isAuthenticated) {
            tradeContainer = <div className={classes.UnAuthenticContainer}>
                <span>You need to login to trade in metals. To login or register </span>
                <Link to='/'>Click here</Link>
            </div>
        }
        return (tradeContainer);
    }
}

const mapStateToProps = state => {
    return {
      isConfirmationDialogOpen: state.tradeReducer.showDeleteDialog,
      isLoading: state.tradeReducer.loading,
      isAuthenticated: state.auth.token !== null,
      authToken: state.auth.token,
      selectedTrade: state.tradeReducer.selectedTrade
    }
  };

const mapDispatchToProps = dispatch => {
    return {
        toggleDeleteTradeDialog: (open) => dispatch(TradeActions.toggleDeleteTrade(open)),
        deleteSelectedTrade: (tradeData, authToken) => dispatch(TradeActions.deleteSelectedTrade(tradeData, authToken)),
    };
  };

export default connect(mapStateToProps, mapDispatchToProps)(TradeContainer);<file_sep>/tradeServices/models/Trade.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const autoIncrement = require('mongoose-auto-increment');

let TradeSchema = new Schema({
    tradeDate: String,
    price: String,
    quantity: Number,
    side: String,
    userId: String,
    commodity: String,
    counterParty: String,
    location: String
});

autoIncrement.initialize(mongoose.connection);
TradeSchema.plugin(autoIncrement.plugin, 'Trade')
// Export the model
module.exports = mongoose.model('Trade', TradeSchema);<file_sep>/marketDataServices/prices.js
module.exports = [
    {
        name: "Aluminium",
        price: 100
    },
    {
        name: "Zinc",
        price: 10
    },
    {
        name: "Copper",
        price: 25
    },
    {
        name: "Gold",
        price: 200
    },
    {
        name: "Silver",
        price: 14
    },
    {
        name: "Platinum",
        price: 389
    },
    {
        name: "Titanium",
        price: 257
    },
    {
        name: "Magnesium",
        price: 146
    },
    {
        name: "Tin",
        price: 23
    },
    {
        name: "Bismuth",
        price: 129
    },
    {
        name: "Nickel",
        price: 68
    },
    {
        name: "Cobalt",
        price: 37
    },
];<file_sep>/UI/src/containers/Auth/Auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';

import Input from '../../components/UI/InputFields/InputFields';
import RaisedButton from 'material-ui/RaisedButton';
import Loader from '../../components/UI/Loader/Loader';
import classes from './Auth.css';
import * as AuthActions from '../../Store/action/authActions';

class Auth extends Component {
    state = {
        controls: {
            email: {
                elementType: 'input',
                elementConfig: {
                    type: 'email',
                    hintText: 'Mail Address'
                },
                value: '',
                validation: {
                    required: true,
                    isEmail: true
                },
                valid: false,
                touched: false
            },
            password: {
                elementType: 'input',
                elementConfig: {
                    type: 'password',
                    hintText: '<PASSWORD>'
                },
                value: '',
                validation: {
                    required: true,
                    minLength: 6
                },
                valid: false,
                touched: false
            }
        },
        formIsValid: false,
        isSignup: false
    }

    checkValidity ( value, rules ) {
        let isValid = true;
        if ( !rules ) {
            return true;
        }

        if ( rules.required ) {
            isValid = value.trim() !== '' && isValid;
        }

        if ( rules.minLength ) {
            isValid = value.length >= rules.minLength && isValid
        }

        if ( rules.maxLength ) {
            isValid = value.length <= rules.maxLength && isValid
        }

        if ( rules.isEmail ) {
            const pattern = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
            isValid = pattern.test( value ) && isValid
        }

        if ( rules.isNumeric ) {
            const pattern = /^[0-9]*[.][0-9]+$/;
            isValid = pattern.test( value ) && isValid
        }

        return isValid;
    }

    inputChangedHandler = ( event, controlName ) => {
        const updatedControls = {
            ...this.state.controls,
            [controlName]: {
                ...this.state.controls[controlName],
                value: event.target.value,
                valid: this.checkValidity( event.target.value, this.state.controls[controlName].validation ),
                touched: true
            }
        };
        let formIsValid = true;
        for (let identifier in updatedControls) {
            formIsValid = updatedControls[identifier].valid && formIsValid;
            if(!updatedControls[identifier].valid && updatedControls[identifier].touched) {
                updatedControls[identifier].elementConfig.errorText = 'Invalid value';
            }else{
                updatedControls[identifier].elementConfig.errorText = null;
            }
        }
        this.setState( { controls: updatedControls, formIsValid: formIsValid } );
    }

    submitHandler = ( event ) => {
        event.preventDefault();
        this.props.onAuth( this.state.controls.email.value, this.state.controls.password.value, this.state.isSignup );
    }

    switchAuthModeHandler = () => {
        this.setState(prevState => {
            return {isSignup: !prevState.isSignup};
        });
    }

    render () {
        const formElementsArray = [];
        for ( let key in this.state.controls ) {
            formElementsArray.push( {
                id: key,
                config: this.state.controls[key]
            } );
        }

        let form = formElementsArray.map( formElement => (
            <Input
                key={formElement.id}
                elementType={formElement.config.elementType}
                elementConfig={formElement.config.elementConfig}
                value={formElement.config.value}
                invalid={!formElement.config.valid}
                shouldValidate={formElement.config.validation}
                touched={formElement.config.touched}
                changed={( event ) => this.inputChangedHandler( event, formElement.id )} />
        ) );

        if (this.props.loading) {
            form = <Loader show={true}/>
        }

        let errorMessage = null;

        if (this.props.error) {
            errorMessage = (
                <p className={classes.ErrorMessage}>{this.props.error.message}</p>
            );
        }

        let authRedirect = null;
        if (this.props.isAuthenticated) {
            authRedirect = <Redirect to={this.props.authRedirectPath}/>
        }
        let switchLabel = `SWITCH TO ${this.state.isSignup ? 'SIGNIN' : 'REGISTER'}`;
        let submitLabel = this.state.isSignup ? 'REGISTER' : 'SIGN IN';
        const style = {
            margin: 5,
            width: 200
          };

        return (
            <div className={classes.Auth}>
                {authRedirect}
                {errorMessage}
                <form onSubmit={this.submitHandler}>
                    {form}
                    <RaisedButton type={'submit'} primary={true} label={submitLabel} 
                        style={style} disabled={!this.state.formIsValid}/>
                </form>
                <RaisedButton 
                    onClick={this.switchAuthModeHandler} style={style}
                    label={switchLabel} secondary={true}/>
            </div>
        );
    }
}

const mapStateToProps = state => {
    return {
        loading: state.auth.loading,
        error: state.auth.error,
        isAuthenticated: state.auth.token !== null,
        authRedirectPath: state.auth.authRedirectPath
    };
};

const mapDispatchToProps = dispatch => {
    return {
        onAuth: ( email, password, isSignup ) => dispatch( AuthActions.auth( email, password, isSignup ) ),
        onSetAuthRedirectPath: () => dispatch(AuthActions.setAuthRedirectPath('/trades'))
    };
};

export default connect( mapStateToProps, mapDispatchToProps )( Auth );<file_sep>/tradeServices/amqp-connect.js
const amqp = require('amqplib/callback_api');
let channel, rabbitmqurl = process.env.AMQPURL || 'amqp://localhost';

amqp.connect(rabbitmqurl, function(err, conn) {
    if (err) {
        console.log("[AMQP] error --> "+err.message);
        return;
    }
    conn.on("error", function(err) {
        console.log("[AMQP] error --> " + err.message);
        return;
    });
    conn.createChannel(function(err, ch) {
        if(err) {
            console.log('Not able to create channel');
        }
        ch.assertQueue('tradeupdate', {durable: true}); //create queue
        channel = ch;
    });
    conn.on("close", function() {
        console.log("[AMQP] close");
        return setTimeout(function() { conn.close(); process.exit(0) }, 500);
    });
});
module.exports = {
    sendDataToQueue : (alltrades) => {
        if(channel) {
            channel.sendToQueue('tradeupdate', Buffer.from(JSON.stringify(alltrades))); //send alltrades on the queue
        }
    }
};<file_sep>/UI/src/components/UI/Loader/Loader.js
import React from 'react';
import CircularProgress from 'material-ui/CircularProgress';
import Backdrop from '../Backdrop/Backdrop';

const style = {
    position: 'fixed',
    top: '45vh',
    left: '45vw'
}

const Loader = (props) => {
    let loader = props.show ? <Backdrop show={props.show}><CircularProgress style={style} size={80} thickness={5}/></Backdrop> : null
    return loader;
}

export default Loader;<file_sep>/UI/src/components/UI/AddButton/AddButton.js
import React from 'react';

import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/content/add';

const style = {
    position: 'absolute',
    right: 2,
    top: 2
}

const AddButton = (props) => {
    return (
        <FloatingActionButton style={style} onClick={()=> props.clicked()}>
            <ContentAdd />
        </FloatingActionButton>
    );
}

export default AddButton;<file_sep>/UI/src/__test__/TradeList.spec.js
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

import {Table} from 'material-ui/Table';
import {TradeList} from '../containers/TradesList/TradesList';

configure({adapter: new Adapter()});

describe('TradesList component', () => {
    let wrapper;
    beforeEach(() => {
        wrapper = shallow(<TradeList tabledata={[]} fetchTrades={()=>{}}/>);
    });
    it('should not render the table if there is an error property set', () => {
        wrapper.setProps({error: 'not able to fetch data'});
        expect(wrapper.find(Table)).toHaveLength(0);
    });
    it('should not render the table if no data is present', () => {
        expect(wrapper.find(Table)).toHaveLength(0);
    });
    it('should render the table if data is present', () => {
        let data = {"_id":12,"tradeDate":"2018-1-29","commodity":"Bi","side":"sell","counterParty":"Ipsum","price":"62","quantity":19,"location":"tky"};
        wrapper.setProps({tabledata: [data], refData: {commodity:[],side:[],counterParty:[],location:[]}});
        expect(wrapper.find(Table)).toHaveLength(1);
    });
});<file_sep>/UI/src/containers/Home/Home.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
import Header from '../../components/Header/Header';
import TradesContainer from '../TradeContainer/TradeContainer';
import Aux from '../../hoc/Auxiliary/Auxiliary';
import Ticker from '../../components/UI/Ticker/Ticker';
import * as TradeActions from '../../Store/action/tradeActions';
import socketIOClient from 'socket.io-client';

const config = require('Config');
const webSocketConnectionEndpoint = config.websocketUrl;
let socket;


class Home extends Component {
    state = {
        tickerData: null
    }
    componentDidMount() {
        socket = socketIOClient(webSocketConnectionEndpoint);
        socket.on("metalPrices", data => this.setState({ tickerData: data }));
        socket.on("tradeUpdate", data => this.props.updateTrades(data));
    }
    componentWillUnmount() {
        socket.disconnect();
    }
    render () {
        return (
            <Aux>
                <Ticker data={this.state.tickerData}/>
                <Header/>
                <TradesContainer/>
            </Aux>
        );
    }
}

const mapDispatchToProps = dispatch => {
    return {
        updateTrades: (trades) => dispatch(TradeActions.fetchTradeSuccess(trades))
    };
};

export default connect(null, mapDispatchToProps)(Home)<file_sep>/UI/src/containers/Trade/TradeHeader/TradeHeader.js
import React from 'react';
import AppBar from 'material-ui/AppBar';
import RightElements from './RightElements';

const style = {
    color: 'white',
    height: '35px',
    paddingLeft: '5px'
}

const titleStyle = {
    fontSize: 16,
    lineHeight: '35px'
}

/**
 * A simple example of `AppBar` with an icon on the right.
 * By default, the left icon is a navigation-menu.
 */
const TradeHeader = (props) => (
  <AppBar
    title= {`Trade ID : ${props.tradeId}`} 
    showMenuIconButton={false}
    style= {style}
    titleStyle = {titleStyle}
    iconElementRight={<RightElements onDelete={props.onDelete} changeEditMode={props.changeEditMode}/>}>
  </AppBar>
);

export default TradeHeader;<file_sep>/metallica-backend/routes.js
var express = require('express');
var router = express.Router();
var User = require('./models/User');
var cfg = require("./config"); 
var jwt = require("jwt-simple"); 

// Register new users
router.post('/register', function(req, res) {
  if (!req.body.email || !req.body.password || req.body.password.length < 6) {
    res.status(401).send({message: 'Email or password not valid'});
  } else {
    let newUser = new User({
      email: req.body.email,
      password: req.body.password
    });

    // Attempt to save the user
    newUser.save(function(err, user) {
      if (err) {
        res.status(401).send({message: 'Email address already exists.'});
      }
      let token = jwt.encode({id: user.id}, cfg.jwtSecret);
      res.json({
        token: token,
        expiresIn: 3600,
        userId: user.id
      });
    });
  }
});

router.get('/', function(req, res) {
    res.json('Auth service is up');
});

// Authenticate the user and get a JSON Web Token to include in the header of future requests.
router.post('/login', (req, res) => {
  User.findOne({
    email: req.body.email
  }, function(err, user) {
    if (err) throw err;

    if (!user) {
        res.status(401).send({message: 'User not found.'});
    } else {
      // Check if password matches
      user.comparePassword(req.body.password, function(err, isMatch) {
        if (isMatch && !err) {
            console.log('user id ------------------> '+user.id);
            // Create token if the password matched and no error was thrown
            let token = jwt.encode({id: user.id}, cfg.jwtSecret);
            res.json({
                token: token,
                userId: user.id,
                expiresIn: 3600
            });
        }
        else {
            res.status(401).send({message: 'Authentication failed. Passwords did not match.'});
        }
      });
    }
  });
});

module.exports = router;<file_sep>/UI/src/Store/action/tradeActions.js
import * as actionTypes from './actionTypes';
import axios from '../../axios-instance';

//start updating a trade
export const updateTradeStart = () => {
    return {
        type: actionTypes.UPADTE_TRADE
    };
};
//add or edit trade success
export const updateTradeSuccess = (trades) => {
    return {
        type: actionTypes.UPDATE_TRADE_SUCCESS,
        trades: trades
    };
};
//update trade failure
export const updateTradeFailure = (err) => {
    return {
        type: actionTypes.UPDATE_TRADE_FAILURE,
        error: err.message
    };
};

//update trade when edit/added by clicking on the save button
export const updateTrade = (tradeData, authToken, userId) => {
   return dispatch => {
        dispatch(updateTradeStart());
        tradeData.userId = userId;
        tradeData.tradeDate = `${tradeData.tradeDate.getFullYear()}-${tradeData.tradeDate.getMonth() + 1}-${tradeData.tradeDate.getDate()}`;
        let tradePromise;
        if(typeof tradeData._id !== 'undefined' && tradeData._id !== null) {
        tradePromise =  axios.put('/trade?auth_token='+authToken, {trade: tradeData});
        }
        else {
            tradePromise = axios.post('/trade?auth_token='+authToken, {trade: tradeData});
        }
        tradePromise.then(response => {
            dispatch(updateTradeSuccess(response.data.trades));
        }).catch(err => {
            dispatch(updateTradeFailure(err));
        });
    }  
}
//update selected trade property of the model when a row is selected from the list
export const updateSelectedTrade = (tradeData, auth_token) => {
    return dispatch => {
        dispatch({type: actionTypes.UPADTE_SELECTED_TRADE, tradeData:tradeData});
       }
}
//delete selected trade by clicking the delete butotn from list or trade container
export const deleteSelectedTrade = (tradeData, authToken) => {
    return dispatch => {
        dispatch(updateTradeStart());
        if(tradeData._id !== 'undefined' && tradeData._id !== null){
            axios.delete('/trade?auth_token='+authToken, {headers: { 'Content-Type': 'application/json'}, data:{id: tradeData._id, userId: tradeData.userId}})
            .then(response => {
                dispatch({type: actionTypes.DELETE_SELECTED_TRADE, trades: response.data.trades});
            }).catch(err => {
                dispatch(updateTradeFailure(err));
            });
        }
       }
}
//open or close the delete confirmation dialog
export const toggleDeleteTrade = (open) => {
    return dispatch => {
        dispatch({type: actionTypes.SHOW_DELETE_TRADE, open: open});
       }
}
//start fetching trades
export const fetchTradeStart = () => {
    return {
        type: actionTypes.FETCH_TRADE_START
    };
};
//get All trades success
export const fetchTradeSuccess = (trades) => {
    return {
        type: actionTypes.FETCH_TRADE_SUCCESS,
        trades: trades
    };
};
//get All trades failure
export const fetchTradeFailure = (err) => {
    return {
        type: actionTypes.FETCH_TRADE_FAILURE,
        error: err.message
    };
};
//fetch all trades for the user
export const fetchTrades = (authToken, userId) => {
    return dispatch => {
        dispatch(fetchTradeStart());
        axios.get('/trades?auth_token='+authToken+'&userId='+userId)
        .then(response => {
            dispatch(fetchTradeSuccess());
        }).catch(err => {
            dispatch(fetchTradeFailure(err));
        })
   }
}
//start fetching ref data
export const fetchTradeRefDataStart = () => {
    return {
        type: actionTypes.FETCH_TRADE_REF_DATA_START
    };
};
//get metadata success
export const fetchTradeRefDataSuccess = (refData) => {
    return {
        type: actionTypes.FETCH_TRADE_REF_DATA_SUCCESS,
        refData: refData
    };
};
//get metadata failure
export const fetchTradeRefDataFailure = () => {
    return {
        type: actionTypes.FETCH_TRADE_REF_DATA_FAILURE
    };
};
//fetch metadata for location, counter party and side
export const getTradeRefData = () => {
    return dispatch => {
        dispatch(fetchTradeRefDataStart());
        axios.get('/getRefData')
        .then(response => {
            dispatch(fetchTradeRefDataSuccess(response.data.refData));
        }).catch(err => {
            dispatch(fetchTradeRefDataFailure(err));
        })
   }
}
//update all trades from websocket connection
export const updateAlltrades = (trades) => {
    return dispatch => {
        dispatch(fetchTradeSuccess(trades));
    }
}<file_sep>/tradeServices/routes.js
const express = require('express');
const amqp = require('./amqp-connect');
const router = express.Router();
const Trade = require('./models/Trade');

router.get("/", function(req, res) {  
    res.json({
        status: "My Trade API is alive!"
    });
});

const getAllTrades = (req, res, uid) => {
    Trade.find({userId: uid}, function(err, allTrades){
        if(err) {
            res.status(500).send(new Error('Not able to fetch all trades'));
        }
        amqp.sendDataToQueue(allTrades);
        res.send({message: 'Trade updated successfully'});
    });
};

router.get("/trades", function(req, res) {  
    getAllTrades(req, res, req.query.userId);
});

router.post('/trade', function(req, res){
    Trade.create(req.body.trade, function(err, savedTrade){
        if(err){
            res.status(500).send(new Error('Not able to save the trade'));
        }
        getAllTrades(req, res, req.body.trade.userId);
    });
});

router.put('/trade', function(req, res){
    Trade.findByIdAndUpdate(req.body.trade._id, req.body.trade, function(err, savedTrade){
        if(err){
            res.status(500).send(new Error('Not able to save the trade'));
        }
        getAllTrades(req, res, req.body.trade.userId);
    });
});

router.delete('/trade', function(req, res){
    Trade.findByIdAndRemove(req.body.id, function(err, savedTrade){
        if(err){
            res.status(500).send(new Error('Not able to save the trade'));
        }
        getAllTrades(req, res, req.body.userId);
    });
});

module.exports = router;<file_sep>/UI/src/components/Header/Header.js
import React from 'react';
import AppBar from 'material-ui/AppBar';
import UserName from '../UI/Username/Username'

/**
 * A simple example of `AppBar` with an icon on the right.
 * By default, the left icon is a navigation-menu.
 */
const Header = () => (
  <AppBar
    style={{marginTop: 30}}
    title="TRADES" 
    showMenuIconButton={false}>
    <UserName />
  </AppBar>
);

export default Header;<file_sep>/UI/src/__test__/TradeReducer.spec.js
import TradeReducer from '../Store/reducers/trades';
import * as ActionTypes from '../Store/action/actionTypes';

describe('Trade reducer', () => {
    let initialState = {
        trades : [],
        refData: {
            counterParty: [],
            location: [],
            side: [],
            commodity: []
        },
        error: null,
        loading: false,
        selectedTrade: null,
        showDeleteDialog: false
    };
    it('should return the initial state', () => {
        expect(TradeReducer(undefined, {})).toEqual(initialState);
    });
    it('should return the ref data on the success event type', () => {
        expect(TradeReducer(initialState, {
            type: ActionTypes.FETCH_TRADE_REF_DATA_SUCCESS,
            refData: {counterParty:'lorem'}
        })).toEqual({
            trades : [],
            refData: {counterParty:'lorem'},
            error: null,
            loading: false,
            selectedTrade: null,
            showDeleteDialog: false
        })
    });
});<file_sep>/tradeServices/server.js
var express = require("express"),
bodyParser = require("body-parser"),  
app = express(),
routes = require('./routes'),
mongoose = require("mongoose");

mongoose.connect(process.env.TRADEDATABASEURL || 'mongodb://localhost/tradesdb');
app.use(bodyParser.json());  
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});
app.use('/', routes);

app.listen(process.env.PORT || 3002, function(){
    console.log('Trade service has started');
});

module.exports = app;  <file_sep>/UI/src/__test__/Ticker.spec.js
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

import Ticker from '../components/UI/Ticker/Ticker';

configure({adapter: new Adapter()});

describe('Ticker component', () => {
    it('should have one span element for each metal', () => {
        const metals = [
            {
                name: "Aluminium",
                price: 100
            },
            {
                name: "Zinc",
                price: 10
            },
            {
                name: "Copper",
                price: 25
            },
            {
                name: "Gold",
                price: 200
            },
            {
                name: "Silver",
                price: 14
            }
        ];
        const wrapper = shallow(<Ticker data={metals}/>);
        expect(wrapper.find('span')).toHaveLength(5);
    });
});<file_sep>/UI/src/containers/TradesList/TradesList.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
import IconButton from 'material-ui/IconButton';
import ActionDelete from 'material-ui/svg-icons/action/delete';
import AddButton from '../../components/UI/AddButton/AddButton';
import classes from './TradesList.css';
import * as TradeActions from '../../Store/action/tradeActions';
import {
  Table,
  TableBody,
  TableHeader,
  TableHeaderColumn,
  TableRow,
  TableRowColumn,
} from 'material-ui/Table';

const findNameFromRefData = (arr, val, prop) => {
  const matchedObj = arr.find( obj => obj.value === val);
  return matchedObj && matchedObj[prop] ? matchedObj[prop] : null;
}

export class TradeList extends Component {
  //row selection handler
  handleRowSelection = (selectedRowIndex) => {
    this.props.updateSelectedTrade(this.props.tabledata[selectedRowIndex]);
    this.props.tabledata.forEach((row, i) => { //TODO - anti- pattern - look into it later
      row.selected = selectedRowIndex.indexOf(i) > -1;
    });
  }
  //delete button handler
  handleDeleteButton = event => {
    event.stopPropagation();
    if(this.props.selectedTrade){
      this.props.showDeleteDialog(true);
    }
  }
  //life cycle method
  componentDidMount() {
    this.props.fetchTrades(this.props.authToken, this.props.userId);
  }
  // life cycle method
  render(){
    let table, deleteButtonStyle = {
      opacity : 1
    },
    deleteButtonHidden = {
      opacity : 0.1
    };
    if(this.props.error) {
      table = <div className={classes.TradeListParentError}>{this.props.error}</div>
    }
    else if(this.props.tabledata.length > 0) {
      table = <Table
      fixedHeader={true}
      selectable={true}
      multiSelectable={false}
      onRowSelection={this.handleRowSelection}>
      <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
        <TableRow>
          <TableHeaderColumn>Trade Date</TableHeaderColumn>
          <TableHeaderColumn>Commodity</TableHeaderColumn>
          <TableHeaderColumn>Side</TableHeaderColumn>
          <TableHeaderColumn>QTY(MT)</TableHeaderColumn>
          <TableHeaderColumn>Price(/MT)</TableHeaderColumn>
          <TableHeaderColumn>CounterParty</TableHeaderColumn>
          <TableHeaderColumn>Location</TableHeaderColumn>
          <TableHeaderColumn></TableHeaderColumn>
        </TableRow>
      </TableHeader>
      <TableBody
        displayRowCheckbox={false}
        deselectOnClickaway={false}
        showRowHover={true}
        stripedRows={true}
      >
        {this.props.tabledata.map( (row, index) => (
          <TableRow key={index}>
            <TableRowColumn>{row.tradeDate}</TableRowColumn>
            <TableRowColumn>{findNameFromRefData(this.props.refData['commodity'], row.commodity, 'primaryText')}</TableRowColumn>
            <TableRowColumn>{findNameFromRefData(this.props.refData['side'], row.side, 'label')}</TableRowColumn>
            <TableRowColumn>{row.quantity}</TableRowColumn>
            <TableRowColumn>{row.price}</TableRowColumn>
            <TableRowColumn>{findNameFromRefData(this.props.refData['counterParty'], row.counterParty, 'primaryText')}</TableRowColumn>
            <TableRowColumn>{findNameFromRefData(this.props.refData['location'], row.location, 'primaryText')}</TableRowColumn>
            <TableRowColumn>
              <IconButton style={row.selected ? deleteButtonStyle : deleteButtonHidden}>
                <ActionDelete onClick={this.handleDeleteButton}/>
              </IconButton>
            </TableRowColumn>
          </TableRow>
          ))}
      </TableBody>
    </Table>

    } else if(this.props.tabledata.length === 0){
      table = <div className={classes.TradeListParentError}>No trade data found. Add new trades</div>
    }

    return (<div className={classes.TradeListParent}>
        {table}
        <AddButton className={classes.AddButton} clicked={()=>{this.props.updateSelectedTrade(undefined)}}/>
    </div>);
  }
}

const mapStateToProps = state => {
  return {
    tabledata: state.tradeReducer.trades,
    authToken: state.auth.token,
    error: state.tradeReducer.error,
    refData: state.tradeReducer.refData,
    selectedTrade: state.tradeReducer.selectedTrade,
    userId: state.auth.userId
  }
};

const mapDispatchToProps = dispatch => {
  return {
      updateSelectedTrade: (tradeData) => dispatch(TradeActions.updateSelectedTrade(tradeData)),
      showDeleteDialog: (open) => dispatch(TradeActions.toggleDeleteTrade(open)),
      fetchTrades: (token, userId) => dispatch(TradeActions.fetchTrades(token, userId))
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(TradeList);<file_sep>/UI/src/__test__/InputFields.spec.js
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import {RadioButtonGroup} from 'material-ui/RadioButton';
import DatePicker from 'material-ui/DatePicker';
import Input from '../components/UI/InputFields/InputFields';

configure({adapter: new Adapter()});

describe('InputField component', () => {
    let wrapper;

    beforeEach(() => {
        wrapper = shallow(<Input/>);
    });

    it('should have one text field for the element of type text', () => {
        wrapper.setProps({
            elementType:'input',
            invalid:'false',
            changed: () => {}
        });
        expect(wrapper.find(TextField)).toHaveLength(1);
    });
    it('should have one date picker field for the element of type date', () => {
        wrapper.setProps({
            elementType:'date',
            invalid:'false',
            changed: () => {}
        });
        expect(wrapper.find(DatePicker)).toHaveLength(1);
    });
    it('should have one select field for the element of type select', () => {
        wrapper.setProps({
            elementType:'select',
            invalid:'false',
            changed: () => {}
        });
        expect(wrapper.find(SelectField)).toHaveLength(1);
    });
    it('should have a radio group for the element of type radiogroup', () => {
        wrapper.setProps({
            elementType:'radiogroup',
            invalid:'false',
            elementConfig: {name: 'side'},
            changed: () => {}
        });
        expect(wrapper.find(RadioButtonGroup)).toHaveLength(1);
    });

});<file_sep>/UI/src/components/UI/Ticker/Ticker.js
import React from 'react';
import classes from './Ticker.css';

const Ticker = (props) => {
  let metals = null;
  if(props.data){
    metals = props.data.map((metal, index) => (
      <span className={classes.TickerText} key={metal.name+index}>{metal.name + ' : '+ metal.price}</span>
    ));
  }
  return (
    <div className={classes.TickerParent}>
      <div className={classes.Ticker + ' Ticker'}>
        <div className={classes.TickerAnimation}>
          {metals}
        </div>
      </div>
    </div>
  )
}
export default Ticker;<file_sep>/UI/src/containers/Trade/Trade.js
import React, {Component} from 'react';
import { connect } from 'react-redux';

import TradeHeader from './TradeHeader/TradeHeader';
import classes from './Trade.css';
import Input from '../../components/UI/InputFields/InputFields';
import * as TradeActions from '../../Store/action/tradeActions';
import RaisedButton from 'material-ui/RaisedButton';

class Trade extends Component {
    state = {
        tradeForm: {
            tradeDate: {
                elementType: 'date',
                elementConfig: {
                    hintText: 'Trade date'
                },
                value: '',
                validation: {
                    required: true
                },
                valid: false,
                touched: false
            },
            commodity: {
                elementType: 'select',
                elementConfig: {
                    hintText: 'Commodity'
                },
                value: '',
                validation: {
                    required: true
                },
                childConfig: [],
                valid: false,
                touched: false
            },
            side: {
                elementType: 'radiogroup',
                elementConfig: {
                    name: 'side'
                },
                childConfig: [{
                    value: 'Buy',
                    label: 'BUY'
                },
                {
                    value: 'Sell',
                    label: 'SELL'
                }],
                value: 'Buy',
                valid: true,
                touched: false
            },
            counterParty: {
                elementType: 'select',
                elementConfig: {
                    hintText: 'Counter Party'
                },
                childConfig:[],
                value: '',
                validation: {
                    required: true
                },
                valid: false,
                touched: false
            },
            price: {
                elementType: 'input',
                elementConfig: {
                    hintText: 'Price',
                    id: 'priceInput',
                    errorText : null
                },
                value: '',
                validation: {
                    required: true,
                    isNumeric: true
                },
                valid: false,
                touched: false
            },
            quantity: {
                elementType: 'input',
                elementConfig: {
                    hintText: 'Quantity',
                    id: 'qtyInput',
                    errorText : null
                },
                value: '',
                validation: {
                    required: true,
                    isNumeric: true
                },
                valid: false,
                touched: false
            },
            location: {
                elementType: 'select',
                elementConfig: {
                    hintText: 'Location'
                },
                childConfig:[],
                value: '',
                validation: {
                    required: true
                },
                valid: false,
                touched: false
            }
        },
        formIsValid: false,
        isAddEditMode: false
    }
    //utiity method to check of the trade values entered are valid. Should be moved to a separate utilities file
    checkValidity(value, rules) {
        let isValid = true;
        if (!rules) {
            return true;
        }
        
        if (rules.required) {
            isValid = typeof value !== 'object' ? value.trim() !== '' && isValid : true;
        }

        if (rules.minLength) {
            isValid = value.length >= rules.minLength && isValid
        }

        if (rules.maxLength) {
            isValid = value.length <= rules.maxLength && isValid
        }

        if (rules.isEmail) {
            const pattern = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
            isValid = pattern.test(value) && isValid
        }

        if (rules.isNumeric) {
            const pattern = /^\d*[.]?[0-9]+$/;
            isValid = pattern.test(value) && isValid
        }

        return isValid;
    }
    //submit click handler
    submitHandler = ( event ) => {
        event.preventDefault();
  
        const formData = {};
        for (let formElementIdentifier in this.state.tradeForm) {
            formData[formElementIdentifier] = this.state.tradeForm[formElementIdentifier].value;
        }
        formData._id = this.props.tradeData ? this.props.tradeData._id : null;
        this.props.updateTrade(formData, this.props.authToken, this.props.userId);
    }
    //cancel click handler, switch to old data and make the fields non-editable
    cancelHandler = (event) => {
        this.syncUpdatedStatefromProps();
        this.updateAddEditMode(false);
    }
    //method to handle all the input changes for a trade
    inputChangedHandler = (value, selectValue, inputIdentifier) => {
        const updatedTradeForm = {
            ...this.state.tradeForm
        };
        const updatedFormElement = { 
            ...updatedTradeForm[inputIdentifier]
        };
        updatedFormElement.value = selectValue ? selectValue : value;
        updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);
        updatedFormElement.touched = true;
        updatedTradeForm[inputIdentifier] = updatedFormElement;
        
        let formIsValid = true;
        for (let identifier in updatedTradeForm) {
            formIsValid = updatedTradeForm[identifier].valid && formIsValid;
            if(!updatedTradeForm[identifier].valid && updatedTradeForm[identifier].touched) {
                updatedTradeForm[identifier].elementConfig.errorText = 'This is a required numeric field';
            }else{
                updatedTradeForm[identifier].elementConfig.errorText = null;
            }
        }
        this.setState({tradeForm: updatedTradeForm, formIsValid: formIsValid});
    }
    //utility method to switch the add-edit / non-editable mode
    updateAddEditMode = (val) => {
        this.setState({isAddEditMode: val});
    }
    //dispatch the delete action from here, to be handled by the model/trade reducer
    handleDeleteAction = () => {
        if(this.props.tradeData){
            this.props.showDeleteDialog(true);
        }
    }
    //life cycle method
    componentWillMount() {
        this.syncUpdatedStatefromProps();
    }
    //life cycle method
    componentWillReceiveProps(nextProps) {
        this.syncUpdatedStatefromProps(nextProps);
        this.addRefData(nextProps)
    }
    //life cycle method
    componentDidMount() {
        this.props.getTradeRefData();
    }
    addRefData(nextProps) {
        const updatedTradeFormData = {
            ...this.state.tradeForm
        };
        //checking only for commodity here, ideally should check for all the props with 'childConfig'
        //but this should be fine since all the metadata is coming together
        if(this.state.tradeForm.commodity.childConfig.length === 0){
            for (let key in updatedTradeFormData) {
                if(key === 'commodity' || key === 'counterParty' || key === 'location' || key === 'side'){
                    updatedTradeFormData[key].childConfig = nextProps.refData[key];
                }
            }
            this.setState({tradeForm: updatedTradeFormData}); 
        }
    }
    //sync state to props - called from life cycle methods
    syncUpdatedStatefromProps(nextProps, nextState) {
        const _props = nextProps || this.props;
        const _state = nextState || this.state;
        const updatedTradeFormData = {
            ..._state.tradeForm
        };
        
        for (let key in updatedTradeFormData) {
            if(_props.tradeData) {
                if(key === 'tradeDate'){
                    updatedTradeFormData[key].value = new Date(_props.tradeData[key]);
                }
                else {
                    updatedTradeFormData[key].value = _props.tradeData[key];
                }
                updatedTradeFormData[key].valid = true;
                updatedTradeFormData[key].elementConfig.errorText = null;
            }
            else {
                updatedTradeFormData[key].value = undefined;
                updatedTradeFormData[key].elementConfig.errorText = null;
                updatedTradeFormData[key].valid = false;
            }
        }
        this.setState({tradeForm: updatedTradeFormData, formIsValid: !!_props.tradeData, isAddEditMode: !_props.tradeData});
    }
    // life cycle method
    render() {
        const formElementsArray = [];
        for (let key in this.state.tradeForm) {
            formElementsArray.push({
                id: key,
                config: this.state.tradeForm[key]
            });
        }
        return (<div className={classes.TradeParent}>
            <TradeHeader tradeId={this.props.tradeData ? this.props.tradeData._id : ''} 
            changeEditMode={this.updateAddEditMode}
            onDelete={this.handleDeleteAction}/>
            <form onSubmit={this.submitHandler}>
                {formElementsArray.map(formElement => {
                    formElement.config.elementConfig.disabled = !this.state.isAddEditMode;
                    return (<Input 
                        key={formElement.id}
                        elementType={formElement.config.elementType}
                        elementConfig={formElement.config.elementConfig}
                        childConfig={formElement.config.childConfig}
                        value={formElement.config.value}
                        invalid={!formElement.config.valid}
                        shouldValidate={formElement.config.validation}
                        touched={formElement.config.touched}
                        changed={(event, value, selectValue) => this.inputChangedHandler(value, selectValue, formElement.id)} />
                        
                )
            })}
                <RaisedButton label="Cancel" onClick={this.cancelHandler} disabled={!this.state.isAddEditMode}/>
                <RaisedButton label="Save" primary={true} style={{marginLeft: '10px'}} disabled={!(this.state.formIsValid && this.state.isAddEditMode)} onClick={this.submitHandler}/>
            </form>
        </div>);
    }
}

const mapStateToProps = state => {
    return {
      tradeData: state.tradeReducer.selectedTrade,
      refData: state.tradeReducer.refData,
      authToken: state.auth.token,
      userId: state.auth.userId
    }
};

const mapDispatchToProps = dispatch => {
    return {
        updateTrade: (tradeData, authToken, userId) => dispatch(TradeActions.updateTrade(tradeData, authToken, userId)),
        showDeleteDialog: (open) => dispatch(TradeActions.toggleDeleteTrade(open)),
        getTradeRefData: () => dispatch(TradeActions.getTradeRefData()),
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Trade);