add mapping; store resourceId in doorLockEvents table

This commit is contained in:
Bilal Catic
2019-06-10 05:59:05 +02:00
parent 04c9ee3806
commit e05b6f0f56
6 changed files with 256 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
'use strict';
const { parseDoorLockDataFile, writeDoorLockEvent } = require("../services/doorLock");
const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources');
const { parseDoorLockDataFile, writeDoorLockEvent } = require('../services/doorLock');
const { fetchAllBookings, writeBookingReservation } = require('../services/officeRnD/bookings');
const { officeRnDAPIErrors } = require('../constants/constants');
@@ -8,16 +9,16 @@ const IncomingForm = require('formidable').IncomingForm;
const uploadDoorLockData = (req, res) => {
const form = new IncomingForm();
const parsingResults = [];
const fileParsers = [];
form.on('file', (field, file) => {
if (file && file.type === 'text/csv') {
parsingResults.push(parseDoorLockDataFile(file));
fileParsers.push(parseDoorLockDataFile(file));
}
});
form.on('end', () => {
Promise.all(parsingResults)
Promise.all(fileParsers)
.then((parserResults) => {
const parsedData = [];
const parserErrors = [];
@@ -48,7 +49,7 @@ const uploadDoorLockData = (req, res) => {
writeDoorLockEvent(entry);
});
})
.catch(() => {
.catch((error) => {
res.status(500).send(officeRnDAPIErrors.FAILED_TO_FETCH_MEMBERS);
});
});
@@ -56,6 +57,37 @@ const uploadDoorLockData = (req, res) => {
form.parse(req);
};
const getKnownOfficeResourceMappings = (req, res) => {
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
Promise.all(dataToFetch)
.then(result => {
res.send({
existingMappings: result[0],
offices: result[1],
resources: result[2],
});
})
.catch(error => {
res.status(500).send();
});
};
const addNewMapping = (req, res) => {
const newMapping = req.body && req.body.mapping ? req.body.mapping : null;
if (newMapping && newMapping.officeSlug && newMapping.resourceSlug && newMapping.officeId && newMapping.resourceId){
saveNewMappingToDatabase(newMapping)
.then(result => {
res.send(newMapping);
})
.catch(error => {
res.status(500).send(error);
});
}
};
module.exports = {
uploadDoorLockData,
getKnownOfficeResourceMappings,
addNewMapping,
};

View File

@@ -1,12 +1,15 @@
'use strict';
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
const { uploadDoorLockData } = require('../controllers/doorLock');
const { uploadDoorLockData, getKnownOfficeResourceMappings, addNewMapping } = require('../controllers/doorLock');
const express = require('express');
const router = express.Router();
router.get('/', apiStatusCheck);
router.post('/doorLock/upload', uploadDoorLockData);
router.get('/doorLock/mappings', getKnownOfficeResourceMappings);
router.post('/doorLock/mappings', addNewMapping);
module.exports = router;

View File

@@ -13,6 +13,8 @@ const app = express();
const port = process.env.PORT || 5000;
app.use(express.json());
app.use('/api', routes);
app.use(basicAuth({

View File

@@ -9,14 +9,29 @@ const {
USER_ENTRY_EVENT,
ENABLE_PASSAGE_MODE,
DISABLE_PASSAGE_MODE,
USER_UNLOCKED_DOOR,
USER_LOCKED_DOOR,
VALID_CSV_HEADERS,
doorLockEvents,
csvParserErrors,
} = require('../constants/constants');
const { fetchAllMembers, findMember } = require('../services/officeRnD/members');
const { getMappingsFromDatabase } = require('../services/officeRnD/resources');
const extractMappingFromFileName = (fileName) => {
const contentBetweenBracketsRegex = /\[(.*?)\]/;
const rawContent = fileName.match(contentBetweenBracketsRegex)[1];
const mappingContent = rawContent.split('-').map(word => word.trim());
return {
officeSlug: mappingContent[0],
resourceSlug: mappingContent[1],
}
};
const checkIfMappingExsists = (mappingFromFileName, mappings) => {
const { officeSlug, resourceSlug } = mappingFromFileName;
return mappings.find(mapping => (mapping.officeSlug === officeSlug) && (mapping.resourceSlug === resourceSlug));
};
const parseDoorLockDataFile = (file) => {
return new Promise ((resolve, reject) => {
@@ -25,6 +40,134 @@ const parseDoorLockDataFile = (file) => {
const unknownMembers = [];
let isValidFile = true;
const prefetchDataJobs = [getMappingsFromDatabase(), fetchAllMembers()];
Promise.all(prefetchDataJobs)
.then(result => {
const mappings = result[0];
const mappingFromFileName = extractMappingFromFileName(file.name);
const mappingObject = checkIfMappingExsists(mappingFromFileName, mappings);
if (!mappingObject){
reject('Error ! File contains unknown location');
return;
}
fs.createReadStream(file.path)
.pipe(csv({
mapHeaders: ({ header, index }) => header.trim().toLowerCase(),
mapValues: ({ header, index, value }) => value.trim()
}))
.on('headers', (headers) => {
const sortedValidHeadersArray = VALID_CSV_HEADERS.concat().sort();
const sortedParsedHeadersArray = headers.map(header => header.trim()).sort();
let validHeaders = true;
if (sortedParsedHeadersArray.length !== sortedValidHeadersArray.length) {
validHeaders = false;
}else {
for (let i = 0; i < sortedValidHeadersArray.length; i++){
validHeaders = validHeaders
&& (sortedValidHeadersArray[i] === sortedParsedHeadersArray[i]);
}
}
if (!validHeaders){
isValidFile = false;
errors.push({
error: csvParserErrors.INVALID_HEADERS,
details: `Expected headers : ${JSON.stringify(VALID_CSV_HEADERS)}`,
file: file.name,
});
}
})
.on('data', (data) => {
if (!isValidFile) {
return;
}
const eventType = data.event.trim();
if ([USER_ENTRY_EVENT, ENABLE_PASSAGE_MODE, DISABLE_PASSAGE_MODE].includes(eventType)){
results.push(data);
}
})
.on('end', () => {
const parsedData = [];
let i = 0;
while (i < results.length){
//Verify pair
//First entry type should be user entry and second should be enable / disable passage
const firstEntry = results[i];
const secondEntry = results[i+1];
if (firstEntry && (firstEntry.event === USER_ENTRY_EVENT)){
const memberObject = findMember(firstEntry.name);
if (!memberObject){
//Check if member is already labeled as unknown
const unknownMember = unknownMembers.find((member) => member.details === firstEntry.name);
if (!unknownMember){
unknownMembers.push({
error: csvParserErrors.UNKNOWN_MEMBER,
details: firstEntry.name,
file: file.name,
});
}
}
if (secondEntry && (secondEntry.event === ENABLE_PASSAGE_MODE || secondEntry.event === DISABLE_PASSAGE_MODE)){
const event = (secondEntry.event === ENABLE_PASSAGE_MODE) ?
doorLockEvents.USER_UNLOCKED : doorLockEvents.USER_LOCKED;
const dateTimeString = `${firstEntry.date} ${firstEntry.time}`;
const timestamp = moment.utc(dateTimeString, 'MM/DD/YY HH:mm:ss A').toISOString();
//Verify that member is registered in OfficeRnD system
if (memberObject){
const entryData = {
memberName: firstEntry.name,
memberNumber: firstEntry['user no'],
memberId: memberObject.memberId,
timestamp,
event,
resourceId: mappingObject.resourceId,
};
parsedData.push(entryData);
}
i+=2;
} else {
errors.push({
error: csvParserErrors.INVALID_ENTRY_EXPECTED_PASSAGE_MODE,
details: firstEntry,
file: file.name,
});
i+=1;
}
} else {
errors.push({
error: csvParserErrors.INVALID_ENTRY_EXPECTED_USER,
details: firstEntry,
file: file.name,
});
i+=1;
}
}
resolve({
parsedData,
unknownMembers,
errors
});
});
})
.catch(error => {
reject(error);
});
/*
fetchAllMembers()
.then(() => {
fs.createReadStream(file.path)
@@ -135,13 +278,15 @@ const parseDoorLockDataFile = (file) => {
.catch((error) => {
reject(error);
});
*/
});
};
const writeDoorLockEvent = (entry) => {
db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}})
.then()
.catch();
console.log('Write entry : ');
console.log(entry);
console.log('====');
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
};
module.exports = {

View File

@@ -15,7 +15,7 @@ const fetchAllBookings = () => {
cleanedBookingReservations.push({
reservationId: fullBookingEntry['_id'],
memberId: fullBookingEntry.member,
resource: fullBookingEntry.resourceId,
resourceId: fullBookingEntry.resourceId,
start: fullBookingEntry.start.dateTime,
end: fullBookingEntry.end.dateTime,
});
@@ -29,9 +29,7 @@ const fetchAllBookings = () => {
};
const writeBookingReservation = (bookingReservation) => {
db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}})
.then()
.catch();
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
};
module.exports = {

View File

@@ -0,0 +1,61 @@
'use strict';
const db = require('../../models/index');
const { API } = require('../../helpers/api');
const fetchOffices = () => {
return new Promise((resolve, reject) => {
API.get('/offices')
.then((result) => {
const offices = result.data || [];
const cleanedOffices = [];
offices.forEach(office => {
cleanedOffices.push({
officeId: office['_id'],
officeName: office.name,
});
});
resolve(cleanedOffices);
})
.catch((error) => {
reject(error);
});
});
};
const fetchResources = () => {
return new Promise((resolve, reject) => {
API.get('/resources')
.then((result) => {
const resources = result.data || [];
const cleanedResources = [];
resources.forEach(resource => {
cleanedResources.push({
resourceId: resource['_id'],
resourceName: resource.name,
officeId: resource.office,
});
});
resolve(cleanedResources);
})
.catch((error) => {
reject(error);
});
});
};
const getMappingsFromDatabase = () => {
return db.officeResourceMapping.findAll();
};
const saveNewMappingToDatabase = (mapping) => {
return db.officeResourceMapping.findOrCreate({where: {...mapping}, defaults: {...mapping}});
};
module.exports = {
getMappingsFromDatabase,
fetchOffices,
fetchResources,
saveNewMappingToDatabase,
};