Distributed Systems (2019) : Assignment 2
Distributed Systems (2019) : Assignment 2
Batch: SE - Weekend
3rd Year – 1st Semester
IT16170162 - T. M. Guruge
1|Page
Table of Contents
1. Introduction........................................................................................................................3
2. High Level Architecture………………......................................................................................5
2.1. Component diagram…………….…………………………….……………………………....….…………….5
2.2. Overall system architecture.………………………………………………………………………………….6
3. Rest APIs…………………………………………………………………………………………………………….………….7
3.1. Railway………………………………………………………………………………………………………………….7
3.2. Users……………………………………………………………………………….…………………………………….9
3.3. Payment……………………………………………………………………………….……………………………….9
3.4. Register…………………………………………………………………………………………………………………9
3.5. Login…………………………………………………………………………………………….……………………....9
3.6. Gov (dummy government service) ……………………………………………………………….…….10
4. Workflow………………………………………………………………………………….………………………………….11
4.1. System Workflow.....................................................................................................11
4.2. System Workflow Scenario......................................................................................13
5. Authentication and Security Mechanism……………………………………………………………………….21
6. Appendix………...................................................................................................................24
6.1. Front-end (Client side) ............................................................................................24
6.2. Back-end (Server
side) .............................................................................................55
6.3. WSO2 EI (Enterprise Integration – ESB) ……………………………………………………………….71
7. Known Issues…………………………………………………………………………………………………………………76
8. GitHub Repository…………………………………………………………………………………………………………77
2|Page
1. Introduction
This is the report of the “Train Ticket Reservation System” web application, in which front end
(client side) is developed using React JS and back end (server side) is developed using Node JS
and Express JS. This web application use MongoDB as the database, which is a cross-platform
document-oriented database.
In this web application users can provide the start and the destination locations, train, class,
time, ticket quantity and the date of booking. Users can pay using credit/debit cards or by
mobile phone, in which the amount is added to the mobile bill. If the user chooses card
payment, the confirmed booking details will send to the email of the user by using
“nodemailer” email service. If user chooses mobile payment the booking details will send to the
entered mobile number using “Twilio” mobile service.
In the web application booking page, when user select a start location (which contains all the
stations of all routes), the destination locations are filtered according to the route of the
selected station.
If the user is a government employee, they can have special discounts in this web application.
Once user gave their NIC when registering, that NIC is validated using government web service
to ensure that user is eligible to have discounts. NIC is optional and if user enter a NIC, then
only it will be validated using government web service.
Following are sample text emails and text messages sent using previously mentioned services,
3|Page
Fig 1: Email sent using “nodemailer”
4|Page
2. High Level Architectural Diagrams
Front end of the web application is developed using React.js, backend is developed using
Node.js and Express.js, MongoDB database is connected to the back end and the front end and
the back end communicates with WSO2 EI, which is a comprehensive integration solution that
enables communication among various, disparate applications.
5|Page
2.2. Overall System Architecture
6|Page
3. Rest APIs
3.1. Railway
A. /railway/routes
This is a GET endpoint which returns an array of routes which includes route name and the
array of stations in that route.
B. /railway/route/{id}
This is a GET endpoint which has a path parameter of route id. It returns all the stations for a
given route id.
C. /railway/trains
This is a GET endpoint which returns array of all the trains in the database.
D. /railway/trains/{route}
This is a GET endpoint which has a path parameter of route id. It returns array of all the trains
which are running on the specified route.
E. /railway/classes
This is a GET endpoint which returns array of all the train classes available in the database.
F. /railway/schedules
This is a GET endpoint which returns array of all the train schedules available in the database.
7|Page
G. /railway/reservations
This endpoint support both GET and POST requests. If it is a GET request it returns all the
reservations in the database. If it is a POST request it creates a new reservation according to the
data in request body and save it in the database. After the new reservation saving it send email
or a text message (according to the payment method, card payment - email, mobile payment -
text message). Sample email and text messages are shown in Fig 1 and Fig 2 in introduction
section.
H. /railway/reservations/{user}
This is a GET endpoint which contains path parameter of user id. It returns an array of all the
reservations of the specified user.
I. /railway/reservations/{id}
This is a DELETE endpoint, which delete the specified reservation (reservation id) in the request
path parameter.
J. /railway/contact
This is a POST endpoint, which used to process customer support requests. In this service it
saves the support information given by the customer (email, phone, message, etc.) and send
the confirmation details in email to both railway customer support team and to the customer.
8|Page
3.2. Users
A. /users/{id}
This endpoint supports PUT requests. User id should be specified in path parameter and the
new user data should be send along with request body will be updated in the database.
3.3. Payment
Payment services are dummy web services which are used to represent the payment gateway.
A. /payment/card
This is a POST endpoint, which validate the credit/debit card details and the payment amount
send inside request body and send the validation status in response.
B. /payment/phone
This is a POST endpoint, which validate the mobile phone details and the payment amount send
inside request body and send the validation status in response.
3.4. Register
A. /Register
This is a POST endpoint. It reads the new user details send inside the request body and save
them in the database.
3.5. Login
A. /login
This is a POST endpoint. It reads the username and password sent inside request body and
validate them with values in the database and send the validation status in response.
9|Page
3.6. Gov
This is a dummy government service which used to validate whether the user is a government
employee or not.
A. /gov/employee/{nic}
This is a GET endpoint, which accept the NIC number as a path parameter. It checks whether
there is any employee with that NIC and validate. The validation status will be sent back in the
response.
10 | P a g e
4. Workflow
Following system workflow diagrams and system workflow scenario will help to get a better
understanding on how the system works.
11 | P a g e
Fig 7: user update profile data
12 | P a g e
4.2. System Workflow Scenario
The figure below shows the landing page of the web application. Any user can fill the
reservation details in the form and view the cost of tickets, but if user want to make a
reservation user should login first.
Firstly, user have to fill the “From” station, then the “To” stations are filtered according to the
route of the selected “From” station. Once user filled the form, they can view the cost of
tickets.
13 | P a g e
Fig 10: user enter booking details
If user need to make a reservation, user need to login first. If user is not registered before, they
can register their account first.
Users can click join now link in navigation bar and enter their details in the modal shown after
clicking the link. NIC field is optional and if you enter a NIC it will be validated using government
service to ensure that user is eligible to discounts.
Once user has successfully register user can login to the system and make reservations.
L
14 | P a g e
Fig 12: login modal
Once user login successfully the navigation bar will change and show user’s first name in
dropdown and the “My Reservations” link. The user data will be saved in local storage until they
sign out.
If the user is a government employee, user is eligible for 10% discount and the discount amount
is shown.
15 | P a g e
Fig 14: gov employees can have discounts
Then user can click “Make Reservation” button in the bottom of the form and the user will
directed to “Payment” page. Only logged in users are allowed in payment page, otherwise they
are asked to login.
In the payment page it shows the amount and user can select a payment method (card or
phone).
16 | P a g e
When user select mobile payment, the user’s mobile number (entered when registering) will be
auto filled, but user can change it if they want to make the payment with different mobile
number.
If user select card payment, an email will be sent to their email address. If user select mobile
payment, a text message will be sent to the given mobile number.
17 | P a g e
Fig 18: email sent for card payment (left), text message sent for mobile payment (right)
Users can cancel the reservation by clicking the cancel button in the reservations shown in the
“My Reservations” page. User will be asked confirmation after clicking the cancel button.
18 | P a g e
Fig 20: cancel reservation confirmation
Users can edit their profile through the link in the navigation bar.
19 | P a g e
Fig 22: successfully change profile data
Users can view contact details from “Contact Us” page or they can send a message to support
team regarding any of their issues. Once user send support request an email will send to both
the user and the support team.
20 | P a g e
Fig 24: confirmation of support request
When saving user passwords, it saves the hash value generated by the JavaScript function
rather than saving the plane text password.
Following is the JavaScript function used to generate hash code for a given string.
{
"_id" : ObjectId("5cda20be69664c34d0968526"),
"fname" : "Tenusha",
21 | P a g e
"lname" : "Guruge",
"phone" : "0777296705",
"nic" : "123456789V",
"address" : "506/1, Parackrama Mawatha\nThalahena",
"email" : "tenushamadhushan@gmail.com",
"password" : "-1144286319",
"discount" : true,
"__v" : 0
}
In the front end only one account can be created with one email address.
When users are login entered username and password will send to the back end for the
validation. When sending data to back end the password will be hashed before sending.
22 | P a g e
Fig 26: login POST request
With only valid username (previously registered) and valid password, Users can login to the
system. If a user tries to access a page like “Payment” it will automatically redirect the user to
the landing page of the web application. This redirection is handled using ReactJS lifecycle
methods ( componentWillUpdate(), componentDidMount() ).
23 | P a g e
6. Appendix
Following figure shows the folder structure of the web (front-end) component.
24 | P a g e
Fig 28: front-end folder structure
This is where the configs of web application are stored. The "baseUrl" is where the back-end
services deployed. In this case I have given the URL of WSO2 EI API base URL. If you have the
WSO2 server in a separate location, you have to simply change the URL in the config file and
deploy the application.
{
"baseUrl": "http://localhost:8280"
}
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
25 | P a g e
import React, { Component, Suspense } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'react-toastify/dist/ReactToastify.css'
import 'react-datepicker/dist/react-datepicker.css'
import './App.css'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { ToastContainer } from 'react-toastify'
this.state = {
showLogin: false,
showRegister: false
}
this.config = {
selected: 'home'
}
this.baseState = this.state
}
handleLogout = () => {
this.setState(this.baseState)
localStorage.clear()
}
handleLoginShow = () => {
this.setState({ showLogin: true })
}
handleLoginClose = () => {
this.setState({ showLogin: false })
}
handleRegisterShow = () => {
26 | P a g e
this.setState({ showRegister: true })
}
handleRegisterClose = () => {
this.setState({ showRegister: false })
}
render() {
return (
<>
<div className="main-container">
<NavBar
handleLoginShow={this.handleLoginShow}
handleRegisterShow={this.handleRegisterShow}
logout={this.handleLogout}
{...this.state}
/>
<Login
showLogin={this.state.showLogin}
handleShow={this.handleLoginShow}
handleClose={this.handleLoginClose}
handleRegisterShow={this.handleRegisterShow}
/>
<Register
showRegister={this.state.showRegister}
handleShow={this.handleRegisterShow}
handleClose={this.handleRegisterClose}
handleLoginShow={this.handleLoginShow}
/>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/reservations" component={Reservations} />
<Route path="/payment" component={Payment} />
<Route path="/account" component={AccountSettings} />
</Switch>
</Suspense>
</Router>
</div>
<Footer />
<ToastContainer
autoClose={3000}
position="bottom-right"
/>
</>
);
}
27 | P a g e
}
Styles (/web/src/App.css)
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
.react-datepicker-wrapper, .react-datepicker__input-container {
display: block;
}
body {
height: 100%;
position: relative;
}
* {
box-sizing: border-box;
}
*:before,
*:after {
box-sizing: border-box;
}
.main-container {
min-height: 100vh; /* will cover the 100% of viewport */
overflow: hidden;
display: block;
position: relative;
28 | P a g e
padding-bottom: 80px; /* height of your footer */
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
constructor(props, context) {
super(props, context)
this.state = {
// validated: false,
modalShowErr: false,
modalErrMsg: "Incorrect username or password!!!",
username: "",
password: ""
}
this.baseState = this.state
}
componentWillUnmount() {
this.setState(this.baseState)
}
29 | P a g e
}
joinClick = () => {
this.props.handleClose()
this.props.handleRegisterShow()
}
render() {
return (
<Modal show={this.props.showLogin} onHide={this.props.handleClose}>
<Form onSubmit={e => this.handleSubmit(e)}>
<Modal.Header closeButton>
</Modal.Header>
<Modal.Body>
<Row style={{ alignItems: 'center', justifyContent:
'center' }}>
<Image src={require("../images/login.png")} width='30%'
/>
</Row>
<Form.Group controlId="formBasicEmail">
<Form.Label>Email</Form.Label>
<Form.Control required type="username" placeholder="Enter
email" onChange={this.handleChange('username')} />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control required type="password" placeholder="Enter
Password" onChange={this.handleChange('password')} />
</Form.Group>
{this.state.modalShowErr && <p style={{ color:
'red' }}>{this.state.modalErrMsg}</p>}
<Button variant="primary" type="submit" block>
Sign in
</Button>
30 | P a g e
</Modal.Body>
<Modal.Footer>
<Button variant="light" block onClick={this.joinClick}>
Join Now
</Button>
</Modal.Footer>
</Form>
</Modal>
);
}
}
constructor(props, context) {
super(props, context)
this.state = {
// validated: false,
modalShowErr: false,
modalErrMsg: "Entered email already exist!!!",
}
this.baseState = this.state
}
componentWillUnmount() {
this.setState(this.baseState)
}
31 | P a g e
handleSubmit = event => {
this.setState({ modalShowErr: false })
const form = event.currentTarget
loginClick = () => {
this.props.handleClose()
this.props.handleLoginShow()
}
render() {
return (
<Modal show={this.props.showRegister} onHide={this.props.handleClose}>
<Form onSubmit={e => this.handleSubmit(e)}>
<Modal.Header closeButton>
</Modal.Header>
<Modal.Body>
<Form.Row>
<Form.Group as={Col} controlId="formGridFName">
<Form.Label>First name</Form.Label>
<Form.Control required type="username"
placeholder="Enter first name" onChange={this.handleChange('fname')} />
</Form.Group>
32 | P a g e
<Form.Group controlId="formGridPhone">
<Form.Label>Phone</Form.Label>
<Form.Control required type="username" placeholder="Enter
Phone Number" onChange={this.handleChange('phone')} />
</Form.Group>
<Form.Group controlId="formGridNIC">
<Form.Label>NIC</Form.Label>
<Form.Control type="username" placeholder="Enter NIC
(Optional)" onChange={this.handleChange('nic')} />
</Form.Group>
<Form.Group controlId="controlTextarea1">
<Form.Label>Address</Form.Label>
<Form.Control required as="textarea" rows="3"
onChange={this.handleChange('address')} />
</Form.Group>
<Form.Group controlId="formGridEmail">
<Form.Label>Email</Form.Label>
<Form.Control required type="email" placeholder="Enter
email" onChange={this.handleChange('email')} />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control required type="password" placeholder="Enter
Password" onChange={this.handleChange('password')} />
</Form.Group>
{this.state.modalShowErr && <p style={{ color:
'red' }}>{this.state.modalErrMsg}</p>}
<Button variant="primary" type="submit" block>
Create account
</Button>
</Modal.Body>
<Modal.Footer>
<Button variant="light" block onClick={this.loginClick}>
Sign in
</Button>
</Modal.Footer>
</Form>
</Modal >
)
}
}
render() {
33 | P a g e
var user = localStorage.getItem('user')
if (user) {
user = JSON.parse(user)
}
return (
<>
<Navbar bg="light" expand="sm">
<Navbar.Brand href="/">
Railway e-ticketing
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
{user ?
<>
<Nav.Link href="/reservations" >My
Reservations</Nav.Link>
<NavDropdown title={user.fname} id="nav-dropdown"
alignRight>
<NavDropdown.Item href="/account">Account
Settings</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item
onClick={this.props.logout}>Sign out</NavDropdown.Item>
</NavDropdown>
</>
:
<>
<Nav.Link href=""
onClick={this.props.handleLoginShow}>Sign In</Nav.Link>
<Nav.Link href=""
onClick={this.props.handleRegisterShow}>Join Now</Nav.Link>
</>
}
</Nav>
</Navbar.Collapse>
</Navbar>
34 | P a g e
</Navbar.Collapse>
</Navbar>
</>
);
}
}
All the functions of service calls to the back-end are stored in this component
35 | P a g e
export function schedules() {
return callGet(baseUrl + '/railway/schedules/');
}
36 | P a g e
return fetch(url, {
method: 'DELETE'
}).then(handleres);
}
constructor(props) {
super(props);
this.state = {
fromOptions: [],
toOptions: [],
trains: [],
errMsg: 'Please fill all the fields!!!',
showErr: false,
};
}
componentDidMount() {
var options = []
routes()
.then(res => {
res.map((item, i) => {
return item.route.map((station, i) => {
return options.push({ value: station.name, label:
station.name, route: item._id, id: i, fair: station.fair })
})
})
this.setState({ fromOptions: options })
})
.catch(err => {
console.log(err)
37 | P a g e
})
classes()
.then(res => {
var classes = []
res.map((trainClass, i) => {
return classes.push({ value: trainClass.name, label:
trainClass.name, id: trainClass._id, fairRatio: trainClass.fairRatio })
})
this.setState({ classes: classes })
})
.catch(err => {
console.log(err)
})
schedules()
.then(res => {
var schedules = []
res.map((schedule, i) => {
return schedules.push({ value: schedule.time, label:
schedule.time, id: schedule._id })
})
this.setState({ schedules: schedules })
})
.catch(err => {
console.log(err)
})
38 | P a g e
.catch(err => {
console.log(err)
})
}
}
calculateFair = () => {
var user = localStorage.getItem('user')
if (user) {
user = JSON.parse(user)
}
if (this.state.to && this.state.from && this.state.trainClass &&
this.state.qty) {
var amount = Math.abs(this.state.to.fair - this.state.from.fair) *
this.state.trainClass.fairRatio * this.state.qty
amount = amount.toFixed(2)
var discount = (user && user.discount ? 0.1 * amount : 0).toFixed(2)
var total = (amount - discount).toFixed(2)
this.setState({ amount: amount, discount: discount, total: total })
}
}
handleDateChange = dt => {
const date = moment(dt).format('YYYY-MM-DD')
this.setState({ date: date })
}
render() {
return (
<Form style={{ padding: 20 }} onSubmit={(e) => this.handleSubmit(e)}>
<Row style={{ alignItems: 'center', justifyContent: 'center' }}>
<Form.Row style={{ width: '75%', borderBottom: '1px solid
rgb(200,200,200)', marginBottom: 20 }}>
39 | P a g e
<h4>Book Train Tickets</h4>
</Form.Row>
<Form.Row style={{ width: '75%' }}>
<Form.Group as={Col} controlId="from">
<Form.Label>From</Form.Label>
<Select options={this.state.fromOptions}
onChange={this.handleChange("from")} />
</Form.Group>
<Form.Group as={Col} controlId="to">
<Form.Label>To</Form.Label>
<Select options={this.state.toOptions}
onChange={this.handleChange("to")} value={this.state.to} />
</Form.Group>
</Form.Row>
<Form.Row style={{ width: '75%' }}>
<Form.Group as={Col} controlId="from">
<Form.Label>Train</Form.Label>
<Select options={this.state.trains}
onChange={this.handleChange("train")} value={this.state.train} />
</Form.Group>
<Form.Group as={Col} controlId="to">
<Form.Label>Class</Form.Label>
<Select options={this.state.classes}
onChange={this.handleChange("trainClass")} value={this.state.trainClass} />
</Form.Group>
</Form.Row>
<Form.Row style={{ width: '75%' }}>
<Form.Group as={Col} controlId="from">
<Form.Label>Time</Form.Label>
<Select options={this.state.schedules}
onChange={this.handleChange("time")} value={this.state.time} />
</Form.Group>
<Form.Group as={Col} controlId="formGridEmail">
<Form.Label>No of Tickets</Form.Label>
<Form.Control placeholder="qty"
onChange={this.handleQtyChange()} />
</Form.Group>
</Form.Row>
<Form.Row style={{ width: '75%', paddingBottom: 20 }}>
<Col md={6} lg={6} xl={6}>
<Form.Label>Date</Form.Label>
<DatePicker
className="form-control"
onChange={this.handleDateChange}
minDate={new Date()}
value={this.state.date}
placeholderText="YYYY-MM-DD"
/>
</Col>
</Form.Row>
<Form.Row style={{ width: '75%', paddingLeft: 5, align:
'right' }}>
{this.state.amount &&
<Table striped bordered hover size="sm">
<tbody>
40 | P a g e
<tr>
<td align='right'>Amount</td>
<td align='right'>{this.state.amount}
LKR</td>
</tr>
<tr>
<td align='right'>Discount</td>
<td align='right'>{this.state.discount}
LKR</td>
</tr>
<tr>
<td align='right'>Total</td>
<td align='right'>{this.state.total} LKR</td>
</tr>
</tbody>
</Table>
}
</Form.Row>
<Form.Row style={{ width: '75%' }}>
{this.state.showErr && <p style={{ color:
'red' }}>{this.state.errMsg}</p>}
</Form.Row>
<Form.Row style={{ width: '75%', padding: 5 }}>
<Button variant="primary" type="submit">
Make Reservation
</Button>
</Form.Row>
</Row>
</Form >
);
}
}
constructor(props) {
super(props);
this.state = {
checked: 'card',
errMsg: 'Please fill all the fields!!!',
showPaymentErr: false,
validateErrMsg: 'Entered data not valid!!!',
41 | P a g e
showValidateErr: false,
cardNo: '',
cvc: '',
exp: '',
phoneNo: '',
pin: ''
};
}
componentDidMount() {
if (this.props.location) {
this.setState({ ...this.props.location.state })
}
var user = localStorage.getItem('user')
if (user) {
this.setState({ phoneNo: JSON.parse(user).phone })
}
}
componentWillUpdate() {
var user = localStorage.getItem('user')
if (!user) {
this.props.history.push('/')
}
}
42 | P a g e
} else {
this.setState({ showPaymentErr: true })
}
}
if (state.checked === 'phone') {
if (state.phoneNo && state.pin) {
validatePhone({ phone: state.phoneNo, pin: state.pin, total:
state.total })
.then(res => {
if (res.validated) {
this.createReservation({ phone: state.phoneNo })
} else {
this.setState({ showValidateErr: true })
}
})
.catch(err => {
console.log(err)
})
} else {
this.setState({ showPaymentErr: true })
}
}
}
43 | P a g e
render() {
return (
<Form style={{ padding: 20 }} onSubmit={(e) => this.handleSubmit(e)}>
<Row style={{ alignItems: 'center', justifyContent: 'center' }}>
<Form.Row style={{ width: '75%' }}>
<Table striped bordered hover size="sm">
<tbody>
<tr>
<td align='right'>Amount</td>
<td align='right'>{this.state.amount} LKR</td>
</tr>
<tr>
<td align='right'>Discount</td>
<td align='right'>{this.state.discount} LKR</td>
</tr>
<tr>
<td align='right'>Total</td>
<td align='right'>{this.state.total} LKR</td>
</tr>
</tbody>
</Table>
</Form.Row>
<Form.Row style={{ width: '75%' }}>
<Form.Label as="legend">
Select a payment method
</Form.Label>
</Form.Row>
<Form.Row style={{ width: '75%', paddingBottom: 10 }}>
<Col>
<Form.Check
type="radio"
label="Credit Card"
name="formHorizontalRadios"
id="formHorizontalRadios1"
defaultChecked
onChange={this.handleChange('card')}
/>
<Form.Check
type="radio"
label="Mobile Number"
name="formHorizontalRadios"
id="formHorizontalRadios2"
onChange={this.handleChange('phone')}
/>
</Col>
</Form.Row>
{this.state.checked === 'card' &&
<Form.Row style={{ width: '75%' }}>
<Form.Group as={Col} controlId="cardNo">
<Form.Label>Card Number</Form.Label>
<Form.Control placeholder="card number"
onChange={this.handleChange('cardNo')} value={this.state.cardNo} />
</Form.Group>
<Form.Group as={Col} controlId="cvc">
44 | P a g e
<Form.Label>CVC Number</Form.Label>
<Form.Control placeholder="CVC"
onChange={this.handleChange('cvc')} value={this.state.cvc} />
</Form.Group>
<Form.Group as={Col} controlId="exp">
<Form.Label>Exp date</Form.Label>
<Form.Control placeholder="dd/mm"
onChange={this.handleChange('exp')} value={this.state.exp} />
</Form.Group>
</Form.Row>
}
{this.state.checked === 'phone' &&
<Form.Row style={{ width: '75%' }}>
<Form.Group as={Col} controlId="phoneNo">
<Form.Label>Phone Number</Form.Label>
<Form.Control placeholder="Phone number"
onChange={this.handleChange('phoneNo')} value={this.state.phoneNo} />
</Form.Group>
<Form.Group as={Col} controlId="pin">
<Form.Label>PIN</Form.Label>
<Form.Control placeholder="PIN"
onChange={this.handleChange('pin')} value={this.state.pin} />
</Form.Group>
</Form.Row>
}
<Form.Row style={{ width: '75%' }}>
{this.state.showPaymentErr && <p style={{ color:
'red' }}>{this.state.errMsg}</p>}
{this.state.showValidateErr && <p style={{ color:
'red' }}>{this.state.validateErrMsg}</p>}
</Form.Row>
<Form.Row style={{ width: '75%' }}>
<Button variant="primary" type="submit">
Make Payment
</Button>
</Form.Row>
</Row>
</Form>
)
}
}
45 | P a g e
constructor(props) {
super(props);
this.state = {
reservations: [],
items: [],
offset: 1,
lastPage: 1,
paginateItems: []
};
}
componentDidMount() {
this.updateReservations()
}
componentWillUpdate() {
var user = localStorage.getItem('user')
if (!user) {
this.props.history.push('/')
}
}
updateReservations = () => {
var user = localStorage.getItem('user')
if (!user) {
this.props.history.push('/')
} else {
user = JSON.parse(user)
getReservations(user._id)
.then(res => {
this.setState({ reservations: res }, () =>
this.paginateReservations())
})
.catch(err => {
console.log(err)
})
}
}
cancelReservation = id => {
var c = window.confirm("The reservation " + id + " will be deleted")
if (c) {
deleteReservation(id)
.then(res => {
toast.success("Successfully removed reservation " + id)
this.updateReservations()
})
.catch(err => {
console.log(err)
})
}
}
paginateReservations = () => {
46 | P a g e
let items = [];
const offset = (this.state.offset - 1) * 5
47 | P a g e
<Pagination.Item key={number} active={number === this.state.offset}
onClick={() => this.pageChange(number)}>
{number}
</Pagination.Item>,
);
}
this.setState({ paginateItems: paginateItems, items: items, lastPage:
lastPage })
}
pageChange = n => {
console.log(n)
this.setState({ offset: n }, () => this.paginateReservations())
}
render() {
return (
<Row style={{ alignItems: 'center', justifyContent: 'center', width:
'100%' }}>
{this.state.reservations.length <= 0 &&
<Row style={{ width: '75%', padding: 10 }}>
<Col>
<Card>
<Card.Body>You don't have any reservations yet!!!
</Card.Body>
</Card>
</Col>
</Row>
}
{this.state.reservations.length > 0 &&
<>
<Row style={{ width: '75%', paddingTop: 20, paddingLeft:
15 }}>
<Pagination>
<Pagination.First onClick={() => this.pageChange(1)}
/>
{this.state.paginateItems}
<Pagination.Last onClick={() =>
this.pageChange(this.state.lastPage)} />
</Pagination>
</Row>
{this.state.items.map((reservation, i) => {
return (
reservation
)
})}
<Row style={{ width: '75%', paddingTop: 20, paddingLeft:
15 }}>
<Pagination>
<Pagination.First onClick={() => this.pageChange(1)}
/>
{this.state.paginateItems}
<Pagination.Last onClick={() =>
this.pageChange(this.state.lastPage)} />
</Pagination>
48 | P a g e
</Row>
</>
}
</Row>
);
}
}
constructor(props) {
super(props);
this.state = {
fname: '',
lname: '',
phone: '',
email: '',
message: ''
};
this.baseState = this.state
}
49 | P a g e
.catch(err => {
console.log(err)
})
}
render() {
return (
<Row style={{ alignItems: 'center', justifyContent: 'center' }}>
<Col>
<Card style={{ padding: 20, margin: 10 }}>
<Form onSubmit={e => this.handleSubmit(e)}>
<Form.Row>
<Form.Group as={Col} controlId="formGridFName">
<Form.Label>First name</Form.Label>
<Form.Control required type="username"
placeholder="Enter first name" onChange={this.handleChange('fname')}
value={this.state.fname} />
</Form.Group>
50 | P a g e
<Col>
<div id="page">
<p><strong><span style={{ textDecoration: 'underline'
}}>General Information</span></strong></p>
<p><strong>Telephones : </strong>+94 11 2 421281
<br /><strong>Fax Nos : </strong>+94 11 2 446490<br /><strong>Email : </strong>
<a
href="mailto:gmr@railway.gov.lk">gmr@railway.gov.lk</a>
<span style={{ display: 'none' }}>This e-mail
address is being protected from spambots. You need JavaScript enabled to view it
</span>
</p>
<p><strong>Railway Head Office Exchange
Number</strong> : +94 11 2 421281</p>
<p><strong>Fort Railway Station Inquiries</strong> :
+94 11 2 434215</p>
<p><strong>Deputy Operating Superintendent</strong> :
+94 11 2 687099</p>
<p className="MsoNormal"><strong>Assistant
Transportation Superintendent (Operation)</strong> : +94 11 2 692286</p>
</div>
</Col>
</Row>
</Col>
</Row>
);
}
}
constructor(props, context) {
super(props, context)
this.state = {
fname: '',
lname: '',
phone: '',
nic: '',
email: '',
address: ''
}
51 | P a g e
this.baseState = this.state
}
componentDidMount() {
var user = localStorage.getItem('user')
if (user) {
user = JSON.parse(user)
this.setState({
fname: user.fname,
lname: user.lname,
phone: user.phone,
nic: user.nic || '',
email: user.email,
address: user.address
})
}
}
componentWillUpdate() {
var user = localStorage.getItem('user')
if (!user) {
this.props.history.push('/')
}
}
render() {
52 | P a g e
return (
<Row style={{ alignItems: 'center', justifyContent: 'center' }}>
<Row style={{ width: '60%', padding: 10 }}>
<Col>
<Card style={{ padding: 20 }}>
<Form onSubmit={e => this.handleSubmit(e)}>
<Form.Row>
<Form.Group as={Col} controlId="formGridFName">
<Form.Label>First name</Form.Label>
<Form.Control required type="username"
placeholder="Enter first name" onChange={this.handleChange('fname')}
value={this.state.fname} />
</Form.Group>
53 | P a g e
</Form>
</Card>
</Col>
</Row>
</Row>
)
}
}
render() {
return (
<footer className="page-footer font-small" style={{ backgroundColor:
'#4B4A4A', color: 'white', position: 'absolute', bottom: 0, width: '100%' }}>
<div className="footer-copyright text-center py-3">© 2019 Copyright:
<a href="https://tenusha.wordpress.com">
tenusha.wordpress.com</a>
</div>
</footer>
)
}
}
54 | P a g e
6.2. Back-End (NodeJS, ExpressJS, MongoDB)
Following figure shows the folder structure of the services (back-end) component.
55 | P a g e
Fig 29: back-end folder structure
{
"mongoDB": "mongodb://localhost/railway",
"govAPI": "http://localhost:3001/gov/employee/",
"emailClient": {
"host": "smtp.gmail.com",
"email": "sl.railway.e.ticketing@gmail.com",
"auth": {
"user": "sl.railway.e.ticketing@gmail.com",
"pass": "railway@123"
}
},
"messageClient": {
"accountSid": "AC86b7448c3ed5e78b18f44d5e84fbdcb1",
"authToken": "fee993da9d4cafa918929607a8b37827",
"phoneNo": "+18504040553"
}
}
It contains the URL of government service to validate NIC of users, email client information and
the configs of Twilio text message service. If you want to use a premium Twilio account, you
only have to change the configs in this file.
'use strict'
const express = require('express')
const app = express()
const config = require('./config.json')
const login = require('./routers/login')
const register = require('./routers/register')
56 | P a g e
const railway = require('./routers/railway')
const payment = require('./routers/payment')
const gov = require('./routers/gov')
const user = require('./routers/user')
const contact = require('./routers/contact')
const mongoose = require('mongoose')
app.use(express.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");
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
next();
});
app.use(login)
app.use(register)
app.use(railway)
app.use(payment)
app.use(gov)
app.use(user)
app.use(contact)
This is where all the connections to the outside network are stored. (government service, email
service and Twilio text message service)
module.exports = {
validateNIC: function (nic) {
57 | P a g e
return fetch(config.govAPI + nic)
.then(handleErrors)
.then(res => res.json())
.then(data => {
return data.validated
})
.catch(err => {
console.log(err)
})
},
var mailOptions = {
from: '"Sri Lanka Railways"' + emailConfig.email,
to: body.email,
subject: body.subject,
html: body.html
};
58 | P a g e
}
}
handleErrors = response => {
if (!response.ok) {
throw new Error("Request failed " + response.statusText)
}
return response
}
59 | P a g e
const route = await routeModel.findOne({ '_id': req.params.route })
const result = await trainModel.find({ route: route.name })
res.status(200).json(result)
} catch (err) {
res.status(500).json(err)
}
});
60 | P a g e
}
});
module.exports = router
if (exist) {
res.status(409).json({ exist: true })
} else {
const discount = await client.validateNIC(body.nic)
var user = new UserModel({ ...body, discount: discount })
var result = await user.save()
res.status(200).json(result)
61 | P a g e
}
} catch (err) {
res.status(500).json(err)
}
});
module.exports = router
module.exports = router
try {
UserModel.findOne({ email: username, password: password }, (err, val) => {
if (err) {
console.log(err);
} else {
62 | P a g e
if (val) {
res.status(200).json(val)
} else {
res.status(401).json("unauthorized")
}
}
});
} catch (err) {
res.status(500).json(err)
}
});
module.exports = router
Payment service (/services/routers/payment.js)
try {
CardModel.findOne({ card: body.card, cvc: body.cvc, exp: body.exp }, (err,
val) => {
if (err) {
console.log(err);
res.status(500).json(err)
} else if (!val) {
res.status(200).json({ validated: false })
} else {
console.log(req.body.total + " paid")
res.status(200).json({ validated: true })
}
});
} catch (err) {
res.status(500).json(err)
}
});
try {
PhoneModel.findOne({ phone: body.phone, pin: body.pin }, (err, val) => {
if (err) {
console.log(err);
res.status(500).json(err)
} else if (!val) {
res.status(200).json({ validated: false })
} else {
63 | P a g e
console.log(req.body.total + " paid")
res.status(200).json({ validated: true })
}
});
} catch (err) {
res.status(500).json(err)
}
});
module.exports = router
module.exports = router
64 | P a g e
} else {
if (val) {
res.status(200).json({ validated: true })
} else {
res.status(200).json({ validated: false })
}
}
});
} catch (err) {
res.status(500).json(err)
}
});
module.exports = router
User DB Schema (/services/model/user.js)
65 | P a g e
Train DB Schema (/services/model/train.js)
66 | P a g e
}
}
]
})
67 | P a g e
discount: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
card:{
type: String
},
phone:{
type: String
},
email:{
type: String
}
})
68 | P a g e
nic: {
type: 'String'
},
address: {
type: [
'Mixed'
]
}
})
69 | P a g e
type: Number,
required: true,
}
})
70 | P a g e
6.3. WSO2 EI (Enterprise Integration – ESB)
71 | P a g e
Fig 30: WSO2 -EI project structure
CardPayment Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/CardPaymentEndpoint.xml)
72 | P a g e
MobilePayment Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/MobilePaymentEndpoint.xml)
Login Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/LoginEndpoint.xml)
73 | P a g e
<endpoint key="RailwayEndpoint"/>
</send>
</inSequence>
<outSequence>
<respond/>
</outSequence>
<faultSequence/>
</resource>
</api>
Railway Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/RailwayEndpoint.xml)
Register Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/RegisterEndpoint.xml)
74 | P a g e
Users API (/wso2-ei/RailwayESBConfig/src/main/synapse-config/api/Users.xml)
Users Endpoint
(/wso2-ei/RailwayESBConfig/src/main/synapse-config/endpoints/UsersEndpoint.xml)
75 | P a g e
7. Known Issues
If you are getting an error like below, it’s not a fault of the back-end services. It occur because
some virus guard applications block “nodemailer” email service.
This is a common problem with Avast antivirus, this problem will not occur in ESET and
Kaspersky.
I have also asked the problem in https://stackoverflow.com. They also suggest to disable the
virus guard when running the back-end services.
If you are getting some error like this, please disable the virus guard and try again. Anyway, the
reservation process will not abort even if the error occurred.
“Twilio” free message service will not allow to sent messages to unverified mobile
numbers.
If you are getting an error like below, it occurs because I’m using Twilio free trial and the
entered mobile number should be validated through Twilio dashboard before send messages to
76 | P a g e
that number. If you have paid Twilio account please add account details in back-end
“config.json” file.
{ [Error: The number +94777123456 is unverified. Trial accounts cannot send messages
to unverified numbers; verify +94777123456 at twilio.com/user/account/phone-
numbers/verified, or purchase a Twilio number to send messages to unverified
numbers.]
status: 400,
message: 'The number +94777123456 is unverified. Trial accounts cannot send
messages to unverified numbers; verify +94777123456 at twilio.com/user/account/phone-
numbers/verified, or purchase a Twilio number to send messages to unverified
numbers.',
code: 21608,
moreInfo: 'https://www.twilio.com/docs/errors/21608',
detail: undefined }
8. GitHub Repository
https://github.com/tenusha/ds-assignment-2
77 | P a g e