Files
old-psihologija/services/doorLock/doorLock.js
2019-06-25 12:10:54 +02:00

240 lines
9.2 KiB
JavaScript

'use strict';
const db = require('../../models');
const fs = require('fs');
const csv = require('csv-parser');
const moment = require('moment-timezone');
const Op = require('sequelize').Op;
const {
UI_TIMEZONE,
USER_ENTRY_EVENT,
ENABLE_PASSAGE_MODE,
DISABLE_PASSAGE_MODE,
VALID_CSV_HEADERS,
doorLockEvents,
csvParserErrors,
} = require('../../constants/constants');
const { fetchAllMembers } = require('../officeRnD/members');
const { getMappingsFromDatabase } = require('../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) => {
const results = [];
const errors = [];
const unknownMembersToReport = [];
let isValidFile = true;
const prefetchDataJobs = [getMappingsFromDatabase(), fetchAllMembers()];
Promise.all(prefetchDataJobs)
.then(result => {
const mappings = result[0];
const allMembers = result[1];
const membersMap = {};
const unknownMembersMap = {};
allMembers.forEach((member) => membersMap[member.name] = member);
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 = membersMap[firstEntry.name];
if (!memberObject){
//Check if member is already labeled as unknown
const unknownMember = unknownMembersMap[firstEntry.name];
if (!unknownMember){
unknownMembersMap[firstEntry.name] = firstEntry.name;
unknownMembersToReport.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.tz(dateTimeString, 'MM/DD/YY HH:mm:ss A', UI_TIMEZONE).tz('UTC').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: unknownMembersToReport,
errors
});
});
})
.catch(error => {
reject(error);
});
});
};
const writeDoorLockEvent = (entry) => {
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
};
const getUnlockEntryForReservation = (reservation) => {
const { start, end, memberId, resourceId } = reservation;
const attributes = ['memberName', 'event', 'timestamp'];
const earliestUnlock = parseInt(process.env.EARLIEST_UNLOCK) || 0;
const fromTimestamp = moment(start).subtract(earliestUnlock).toISOString();
const toTimestamp = end;
const filters = {
memberId,
timestamp: {
[Op.and]: [
{[Op.gt]: fromTimestamp},
{[Op.lte]: toTimestamp}
]
},
event: doorLockEvents.USER_UNLOCKED,
resourceId,
};
return db.doorLockEvent.findOne({
attributes,
where: filters,
})
};
const getRelatedDoorLockEntries = (fromTimestamp, toTimestamp, memberId, resourceId) => {
const attributes = ['memberName', 'event', 'timestamp'];
const filters = {
memberId,
timestamp: {
[Op.and]: [
{[Op.gt]: fromTimestamp},
{[Op.lte]: toTimestamp}
]
},
event: doorLockEvents.USER_LOCKED,
resourceId,
};
return db.doorLockEvent.findOne({
attributes,
where: filters
})
};
module.exports = {
parseDoorLockDataFile,
writeDoorLockEvent,
getRelatedDoorLockEntries,
getUnlockEntryForReservation,
};