Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add testing #5

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ The first rectangle on the left is the vehicle data broadcast server, it's alrea
### Data Storage (To be build)
After data is pushed to NATS it will be available for other services to listen. Now comes the part where you will have to start develop. Data that is broadcast-ed to NATS is not persisted, it means that we can not access historical data (such as data from past weeks) your task is to build a data storage server that will store all data in [MongoDB](https://www.mongodb.com/) and then serve it via an HTTP REST API and a WebSocket server for live data. So to summarize it here by the checklist of task you need to do:

- [ ] Create MongoDB database
- [ ] Push data from NATS to MongoDB
- [ ] Create REST API
- [ ] Create WebSocket API
- [ ] Test all APIs
- [x] Create MongoDB database
- [x] Push data from NATS to MongoDB
- [x] Create REST API
- [x] Create WebSocket API
- [x] Test ~~all~~ some APIs
- [ ] Create Docker container for app (Optional)

### Incident Reporting (Optional)
Expand Down
8 changes: 8 additions & 0 deletions app/dbModels/incidentModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const mongoose = require("mongoose");
const incidentEntry = new mongoose.Schema({
data : {}
}
,{timestamps : true});
const IncidentEntry = mongoose.model("Incident", incidentEntry);

module.exports = IncidentEntry;
15 changes: 15 additions & 0 deletions app/dbModels/routeInformationModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const mongoose = require("mongoose");
const routeInformationEntry = new mongoose.Schema({
time: {type : Date, required: true},
energy: {type : mongoose.Decimal128, required : true},
gps: {type : [String], required : true},
odo: {type : mongoose.Decimal128, required : true},
speed: {type : Number, required : true},
soc: {type : mongoose.Decimal128, required : true},
vehicleName : {type : String, required : true},
error : {}
}
,{timestamps : true});
const RouteInformationEntry = mongoose.model("RouteInformation", routeInformationEntry);

module.exports = RouteInformationEntry;
133 changes: 133 additions & 0 deletions app/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Database logic
const mongoose = require('mongoose')
const RouteInformationEntry = require('./dbModels/routeInformationModel.js')
const IncidentEntry = require('./dbModels/incidentModel.js')
const dbLocation = "mongodb://localhost/viricitiTech"
/* Note:
In production app we would hide locations like these and portinformation in enviroment variables stored in seperate files.
The elements of the server: Database logic, socket logic, routing logic and NATS logic would also be seperated.
*/
mongoose.connect(dbLocation, async (err,res)=>{
if(err){
throw(err)
}else{
console.log('Succesfully connected to database')
// Clean up collection if it gets too big
const routeInformationCount = await RouteInformationEntry.count()
if(routeInformationCount > 3000){
RouteInformationEntry.collection.drop()
IncidentEntry.collection.drop()
console.log('Emptied the RouteInformation and Incident Collection')
}
}
})


// REST Logic
const express = require('express')
const app = express()
app.use(express.json());
app.use(express.urlencoded({extended:true}));
app.set("view engine", "ejs");
const http = require('http').createServer(app)
const port = 2021
http.listen(port, ()=>{
console.log(`Listening on *:${port}`);
});
app.get("/", async(req,res)=>{
res.render('../app/views/routeInformation.ejs')
})
app.get("/incidents", async(req,res)=>{
const incidents = await IncidentEntry.find()
res.render('../app/views/incidents.ejs', {incidents : incidents})
})
app.get("/incidentLookup", async (req,res)=>{
res.render('../app/views/incidentLookup.ejs', {incidents : null})
})
app.post("/findIncidents", async (req,res)=>{
const dbFilter = getRouteFilter(req.body, true)
const foundIncidents = await IncidentEntry.find(dbFilter).catch((err)=>{console.log(err)})
if(req.body.type === 'api'){
res.send({incidents : foundIncidents})
}else{
res.render('../app/views/incidents.ejs', {incidents : foundIncidents})
}
})
app.get("/routeInformationLookup", async (req,res)=>{
res.render('../app/views/routeInformationLookup.ejs', {routeEntries : null})
})
app.post("/findRouteEntries", async (req,res)=>{
const dbFilter = getRouteFilter(req.body)
const foundRouteInformation = await RouteInformationEntry.find(dbFilter).catch((err)=>{console.log(err)})
if(req.body.type === 'api'){
res.send({routeEntries : foundRouteInformation})
}else{
res.render('../app/views/viewRouteInformation.ejs', {routeEntries : foundRouteInformation})
}
})
// Websocket Logic
const io = require('socket.io')(http);
io.on('connection', (socket) => {
let date = new Date (socket.handshake.time)
console.log(`Connection to socket made at ${date} with ${socket.handshake.headers["x-real-ip"]}. ${io.engine.clientsCount} open connection(s).`)
socket.emit('connectedToServer', true)
})

// Recieving and dealing with incoming data from NATS
const NATS = require('nats')
const nats = NATS.connect({json : true})

const listenForVehicleData = (vehicleName) => {
nats.subscribe(`vehicle.${vehicleName}`, async (data)=>{
data.vehicleName = vehicleName
try{
let addNewEntry = await RouteInformationEntry.create(data)
io.emit('vehicleMessage', data)
}catch(err){
incidentHandling(err, data)
}
})
}

const incidentHandling = (data, dataEntered) =>{
data.error = true
io.emit('vehicleError', dataEntered)
IncidentEntry.create({data : dataEntered})
}
const getRouteFilter = (routeCriteria, lookForIncident)=>{
let queryKeys = Object.keys(routeCriteria)
let dbFilter = {}
if(routeCriteria.time[0] !== 'ignore'){
let newDate = new Date(routeCriteria.time[1])
routeCriteria.time[1] = newDate.getTime()
}
queryKeys.forEach(key=>{
let queryItem = routeCriteria[key]
let dbKey = key
if(dbKey.type === 'api'){
return
}
if(lookForIncident){
dbKey = `data.${key}`
}
if(queryItem[2]){
dbFilter[`data.${key}`] = {$eq : ''}
}else if(queryItem[0] === 'ignore'){
}else if(queryItem[0]=== 'gt'){
dbFilter[dbKey] = {$gt : parseFloat(queryItem[1])}
}else if(queryItem[0]=== 'lt'){
dbFilter[dbKey] = {$lt : parseFloat(queryItem[1])}
}else if(queryItem[0]=== '=='){
dbFilter[dbKey] = {$eq : parseFloat(queryItem[1])}
}
})
return dbFilter
}

//
// =================================
//

listenForVehicleData('test-bus-1')

module.exports = app.listen(2022)
176 changes: 176 additions & 0 deletions app/views/incidentLookup.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Incident Report - Viriciti Tech Assesment Robbert-Jan Sebregts</title>
</head>
<style>
.hidden {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

<body>
<div id="mainDiv" class="container">
<div class="text-center">
<div class="h1">Incident Report</div>
<div>
<a href="../" class="btn btn-lg btn-primary">See Most Current Data</a>
<a href="../routeInformationLookup" class="btn btn-lg btn-danger">Find Specific Route Information</a>
<a href="../incidents" class="btn btn-lg btn-danger">See All Incidents</a>
</div>
</div>
<div id="incidentSelect" class="my-3">
<div class="display-4">
Find Incidents
</div>
<form action="findIncidents" method="POST" id="incidentLookup">
<div class="input-group">
<label for="timeSelect" class="input-group-text" id="time">🕑 Time</label>
<select name="time" id="timeSelect" class="form-select">
<option value="ignore"></option>
<option value="gt">After</option>
<option value="lt">Before</option>
<option value="==">At</option>
</select>
<input type="datetime-local" name="time" value="2017-11-23T12:20" class="form-control">
<label for="noTime" class="input-group-text">Empty</label>
<div class="input-group-text">
<input type="checkbox" name="time" id="noTime" class="form-check-input">
</div>
</div>
<div class="input-group">
<label for="energySelect" class="input-group-text" id="energy">🔌 Energy Usage</label>
<select name="energy" id="energySelect" class="form-select">
<option value="ignore"></option>
<option value="gt">Greater than</option>
<option value="lt">Less than</option>
<option value="==">Equals</option>
</select>
<input type="number" name="energy" min="0" step="0.001" class="form-control">
<label for="noEnergy" class="input-group-text">Empty</label>
<div class="input-group-text">
<input type="checkbox" name="energy" id="noEnergy" class="form-check-input">
</div>
</div>
<div class="input-group">
<label for="distanceSelect" class="input-group-text" id="distance">📏 Distance</label>
<select name="odo" id="distanceSelect" class="form-select">
<option value="ignore"></option>
<option value="gt">Greater than</option>
<option value="lt">Less than</option>
<option value="==">Equals</option>
</select>
<input type="number" name="odo" min="0" step="0.001" class="form-control">
<label for="noOdo" class="input-group-text">Empty</label>
<div class="input-group-text">
<input type="checkbox" name="odo" id="noOdo" class="form-check-input">
</div>
</div>
<div class="input-group">
<label for="speedSelect" class="input-group-text" id="speed">🏁 Speed</label>
<select name="speed" id="speedSelect" class="form-select">
<option value="ignore"></option>
<option value="gt">Greater than</option>
<option value="lt">Less than</option>
<option value="==">Equals</option>
</select>
<input type="number" name="speed" class="form-control">
<label for="noSpeed" class="input-group-text">Empty</label>
<div class="input-group-text">
<input type="checkbox" name="speed" id="noSpeed" class="form-check-input">
</div>
</div>
<div class="input-group">
<label for="socSelect" class="input-group-text" id="soc">🔋 Soc</label>
<select name="soc" id="socSelect" class="form-select">
<option value="ignore"></option>
<option value="gt">Greater than</option>
<option value="lt">Less than</option>
<option value="==">Equals</option>
</select>
<input type="number" name="soc" min="0" step="0.1" class="form-control">
<label for="noSoc" class="input-group-text">Empty</label>
<div class="input-group-text">
<input type="checkbox" name="soc" id="noSoc" class="form-check-input">
</div>
</div>
<button type="submit" class="btn btn-success" form="incidentLookup">Find incidents</button>
</form>
</div>

<%if(incidents){%>
<table class="table" id="incidentTable">
<thead class="thead-dark">
<tr>
<th scope="col" class="">🕑 Time</th>
<th scope="col" class="">🚌 Vehicle Name</th>
<th scope="col" class="">🔌 Energy Usage</th>
<th scope="col" class="">🌎 GPS</th>
<th scope="col" class="">📏 Distance</th>
<th scope="col" class="">🏁 Speed</th>
<th scope="col" class="">🔋 Soc</th>
</tr>
</thead>
<tbody>
<%if(incidents){
incidents.forEach((incident,index) =>{%>
<tr class="" id="incident_<%=index%>">
<td class="vehicle_time">
<%
const date = new Date(incident.data.time)
const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
%>
<%=dateString%>
</td>
<td class="vehicle_vehicleName">
<%=incident.data.vehicleName%>
</td>
<td class="vehicle_energy">
<%=incident.data.energy%>
</td>
<td class="vehicle_gps">
<%=incident.data.gps%>
</td>
<td class="vehicle_odo">
<%=incident.data.odo%>
</td>
<td class="vehicle_speed">
<%=incident.data.speed%>
</td>
<td class="vehicle_soc">
<%=incident.data.soc%>
</td>
</tr>
<%})}%>
</tbody>
</table>
<%}%>

</div>
</div>

</body>

</html>


<script>
const allSelects = document.querySelectorAll('select')
const selectBehavior = (event) => {
const select = event.target
const inputForSelect = document.querySelector(`input[name=${select.name}]`)
if (select.value === 'ignore') {
inputForSelect.required = false
} else {
inputForSelect.required = true
}
}
allSelects.forEach(select => {
select.addEventListener('change', selectBehavior)
})
</script>
Loading