add mapping; store resourceId in doorLockEvents table
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'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 { fetchAllBookings, writeBookingReservation } = require('../services/officeRnD/bookings');
|
||||||
const { officeRnDAPIErrors } = require('../constants/constants');
|
const { officeRnDAPIErrors } = require('../constants/constants');
|
||||||
|
|
||||||
@@ -8,16 +9,16 @@ const IncomingForm = require('formidable').IncomingForm;
|
|||||||
|
|
||||||
const uploadDoorLockData = (req, res) => {
|
const uploadDoorLockData = (req, res) => {
|
||||||
const form = new IncomingForm();
|
const form = new IncomingForm();
|
||||||
const parsingResults = [];
|
const fileParsers = [];
|
||||||
|
|
||||||
form.on('file', (field, file) => {
|
form.on('file', (field, file) => {
|
||||||
if (file && file.type === 'text/csv') {
|
if (file && file.type === 'text/csv') {
|
||||||
parsingResults.push(parseDoorLockDataFile(file));
|
fileParsers.push(parseDoorLockDataFile(file));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
form.on('end', () => {
|
form.on('end', () => {
|
||||||
Promise.all(parsingResults)
|
Promise.all(fileParsers)
|
||||||
.then((parserResults) => {
|
.then((parserResults) => {
|
||||||
const parsedData = [];
|
const parsedData = [];
|
||||||
const parserErrors = [];
|
const parserErrors = [];
|
||||||
@@ -48,7 +49,7 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
writeDoorLockEvent(entry);
|
writeDoorLockEvent(entry);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
res.status(500).send(officeRnDAPIErrors.FAILED_TO_FETCH_MEMBERS);
|
res.status(500).send(officeRnDAPIErrors.FAILED_TO_FETCH_MEMBERS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -56,6 +57,37 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
form.parse(req);
|
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 = {
|
module.exports = {
|
||||||
uploadDoorLockData,
|
uploadDoorLockData,
|
||||||
|
getKnownOfficeResourceMappings,
|
||||||
|
addNewMapping,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
||||||
const { uploadDoorLockData } = require('../controllers/doorLock');
|
const { uploadDoorLockData, getKnownOfficeResourceMappings, addNewMapping } = require('../controllers/doorLock');
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get('/', apiStatusCheck);
|
router.get('/', apiStatusCheck);
|
||||||
|
|
||||||
router.post('/doorLock/upload', uploadDoorLockData);
|
router.post('/doorLock/upload', uploadDoorLockData);
|
||||||
|
router.get('/doorLock/mappings', getKnownOfficeResourceMappings);
|
||||||
|
router.post('/doorLock/mappings', addNewMapping);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const app = express();
|
|||||||
const port = process.env.PORT || 5000;
|
const port = process.env.PORT || 5000;
|
||||||
|
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
app.use('/api', routes);
|
app.use('/api', routes);
|
||||||
|
|
||||||
app.use(basicAuth({
|
app.use(basicAuth({
|
||||||
|
|||||||
@@ -9,14 +9,29 @@ const {
|
|||||||
USER_ENTRY_EVENT,
|
USER_ENTRY_EVENT,
|
||||||
ENABLE_PASSAGE_MODE,
|
ENABLE_PASSAGE_MODE,
|
||||||
DISABLE_PASSAGE_MODE,
|
DISABLE_PASSAGE_MODE,
|
||||||
USER_UNLOCKED_DOOR,
|
|
||||||
USER_LOCKED_DOOR,
|
|
||||||
VALID_CSV_HEADERS,
|
VALID_CSV_HEADERS,
|
||||||
|
doorLockEvents,
|
||||||
csvParserErrors,
|
csvParserErrors,
|
||||||
} = require('../constants/constants');
|
} = require('../constants/constants');
|
||||||
|
|
||||||
const { fetchAllMembers, findMember } = require('../services/officeRnD/members');
|
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) => {
|
const parseDoorLockDataFile = (file) => {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
@@ -25,6 +40,134 @@ const parseDoorLockDataFile = (file) => {
|
|||||||
const unknownMembers = [];
|
const unknownMembers = [];
|
||||||
let isValidFile = true;
|
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()
|
fetchAllMembers()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fs.createReadStream(file.path)
|
fs.createReadStream(file.path)
|
||||||
@@ -135,13 +278,15 @@ const parseDoorLockDataFile = (file) => {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeDoorLockEvent = (entry) => {
|
const writeDoorLockEvent = (entry) => {
|
||||||
db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}})
|
console.log('Write entry : ');
|
||||||
.then()
|
console.log(entry);
|
||||||
.catch();
|
console.log('====');
|
||||||
|
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const fetchAllBookings = () => {
|
|||||||
cleanedBookingReservations.push({
|
cleanedBookingReservations.push({
|
||||||
reservationId: fullBookingEntry['_id'],
|
reservationId: fullBookingEntry['_id'],
|
||||||
memberId: fullBookingEntry.member,
|
memberId: fullBookingEntry.member,
|
||||||
resource: fullBookingEntry.resourceId,
|
resourceId: fullBookingEntry.resourceId,
|
||||||
start: fullBookingEntry.start.dateTime,
|
start: fullBookingEntry.start.dateTime,
|
||||||
end: fullBookingEntry.end.dateTime,
|
end: fullBookingEntry.end.dateTime,
|
||||||
});
|
});
|
||||||
@@ -29,9 +29,7 @@ const fetchAllBookings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const writeBookingReservation = (bookingReservation) => {
|
const writeBookingReservation = (bookingReservation) => {
|
||||||
db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}})
|
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
|
||||||
.then()
|
|
||||||
.catch();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
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