add mapping; store resourceId in doorLockEvents table
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,6 +13,8 @@ const app = express();
|
||||
const port = process.env.PORT || 5000;
|
||||
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use('/api', routes);
|
||||
|
||||
app.use(basicAuth({
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
61
services/officeRnD/resources.js
Normal file
61
services/officeRnD/resources.js
Normal 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,
|
||||
};
|
||||
Reference in New Issue
Block a user