upload, parse and store door lock entries

This commit is contained in:
Bilal Catic
2019-05-30 03:44:11 +02:00
parent d141889c4d
commit 79bcf91dc7
16 changed files with 528 additions and 57 deletions

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.18.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.0.3",

View File

@@ -0,0 +1,54 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {Form} from "semantic-ui-react";
import { uploadDoorLockData } from "../../../store/actions";
class FileUpload extends Component {
constructor(props) {
super(props);
this.state = {
file: null,
};
this.onFileChange = this.onFileChange.bind(this);
this.onUploadClick = this.onUploadClick.bind(this);
}
onFileChange(event) {
const file = event.target.files[0];
this.setState({file});
};
onUploadClick() {
const { uploadDoorLockData } = this.props;
const { file } = this.state;
if (file) {
uploadDoorLockData(file);
}
};
render() {
return (
<div>
<Form.Input
fluid
required
label="Select DLock file"
type="file"
accept=".csv"
onChange={this.onFileChange}
/>
<Form.Button onClick={this.onUploadClick}>Upload</Form.Button>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
uploadDoorLockData: (doorLockDataFile) => uploadDoorLockData(dispatch, doorLockDataFile)
});
export default connect(null, mapDispatchToProps)(FileUpload);

View File

@@ -0,0 +1,61 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Loader, Message } from 'semantic-ui-react';
class UploadResults extends Component {
render(){
const {pending, result, error} = this.props;
const parserErrors = result && result.parserErrors && result.parserErrors.length > 0 ? result.parserErrors : null;
const parsedDataCount = result && result.parsedData && result.parsedData.length > 0 ? result.parsedData.length : null;
return (
<div>
{
pending && <Loader active />
}
<br/>
{
error &&
<Message negative>
<Message.Header>Upload failed</Message.Header>
<p>There was error uploading file</p>
</Message>
}
<br/>
{
!error && parsedDataCount &&
<Message positive>
<Message.Header>Upload complete</Message.Header>
<p>{parsedDataCount} entries successfully inserted</p>
{parserErrors && <p style={{ color: 'red' }}>Some entries could not be parsed. Details are shown below</p>}
</Message>
}
<br/>
{
!error && parserErrors && parserErrors.map((parserError, index) => {
return (
<div key={`message-${index}`}>
<Message negative>
<Message.Header>{parserError.error}</Message.Header>
<p>{JSON.stringify(parserError.details)}</p>
<p>File : {parserError.file}</p>
</Message>
<br/>
</div>
);
})
}
</div>
)
}
}
const mapStateToProps = (state) => ({
pending: state.doorLockData.pending,
result: state.doorLockData.result,
error: state.doorLockData.error,
});
export default connect(mapStateToProps)(UploadResults);

View File

@@ -1,39 +1,22 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import React from 'react';
import { Container, Form } from "semantic-ui-react";
import MainMenu from '../../components/MainMenu';
import { uploadDoorLockData } from "../../store/actions";
import FileUpload from './components/FileUpload';
import UploadResults from './components/UploadResults';
class UploadDLockData extends Component {
render () {
return (
<Container>
<MainMenu/>
<h3>DLock Data</h3>
<hr/>
<Form>
<Form.Input
fluid
required
label="Select DLock file"
type="file"
/>
<Form.Button onClick={this.props.uploadDoorLockData}>Upload</Form.Button>
</Form>
</Container>
);
}
function UploadDLockData() {
return (
<Container>
<MainMenu/>
<h3>DLock Data</h3>
<hr/>
<Form>
<FileUpload/>
</Form>
<UploadResults/>
</Container>
);
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
uploadDoorLockData: () => uploadDoorLockData(dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadDLockData);
export default UploadDLockData;

View File

@@ -4,14 +4,21 @@ import {
UPLOAD_DOOR_LOCK_DATA_FAILED
} from "../constants";
export const uploadDoorLockData = (dispatch) => {
import API from '../../utilities/api';
export const uploadDoorLockData = (dispatch, doorLockDataFile) => {
const formData = new FormData();
formData.append('doorLockDataFile', doorLockDataFile);
const additionalConfig = {
headers: {'content-type': 'multipart/form-data'}
};
dispatch({type: UPLOAD_DOOR_LOCK_DATA_PENDING});
fetch('/api/doorLockData')
.then(response => response.json())
.then(data => {
dispatch({type: UPLOAD_DOOR_LOCK_DATA_SUCCESS, payload: data})
})
.catch(err => {
dispatch({type: UPLOAD_DOOR_LOCK_DATA_FAILED, payload: err})
API.post('doorLock/upload', formData, additionalConfig)
.then(response => {
dispatch({type: UPLOAD_DOOR_LOCK_DATA_SUCCESS, payload: response.data})
})
.catch(error => {
dispatch({type: UPLOAD_DOOR_LOCK_DATA_FAILED, payload: error.response})
});
};

View File

@@ -7,7 +7,7 @@ import {
const initialState = {
pending: false,
result: {},
error: '',
error: null,
};
export const doorLockData = (state, action) => {
@@ -18,15 +18,18 @@ export const doorLockData = (state, action) => {
case UPLOAD_DOOR_LOCK_DATA_PENDING:
return Object.assign({}, state, {
pending: true,
error: null,
});
case UPLOAD_DOOR_LOCK_DATA_SUCCESS:
return Object.assign({}, state, {
pending: false,
result: action.payload,
error: null,
});
case UPLOAD_DOOR_LOCK_DATA_FAILED:
return Object.assign({}, state, {
pending: false,
result: {},
error: action.payload,
});
default:

View File

@@ -0,0 +1,5 @@
import axios from 'axios';
export default axios.create({
baseURL: '/api/'
});

View File

@@ -1597,6 +1597,13 @@ aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
axios@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
axobject-query@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
@@ -3547,7 +3554,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
dependencies:

24
constants/constants.js Normal file
View File

@@ -0,0 +1,24 @@
const USER_ENTRY_EVENT = 'User Entry';
const ENABLE_PASSAGE_MODE = 'Enable Passage Mode by Group 2';
const DISABLE_PASSAGE_MODE = 'Disable Passage Mode by Group 2';
const USER_LOCKED_DOOR = 'locked';
const USER_UNLOCKED_DOOR = 'unlocked';
const VALID_CSV_HEADERS = ['Date', 'Time', 'User No', 'Name', 'Event'];
const csvParserErrors = {
UNKNOWN_COLUMN: 'Unknown column',
INVALID_ENTRY_EXPECTED_USER: 'Invalid entry type. Expected user entry type',
INVALID_ENTRY_EXPECTED_PASSAGE_MODE: 'Invalid entry type. Expected enable/disable passage mode',
};
module.exports = {
VALID_CSV_HEADERS,
USER_ENTRY_EVENT,
ENABLE_PASSAGE_MODE,
DISABLE_PASSAGE_MODE,
USER_LOCKED_DOOR,
USER_UNLOCKED_DOOR,
csvParserErrors,
};

View File

@@ -1,7 +1,47 @@
'use strict';
const { parseDoorLockDataFile, writeDoorLockEvent } = require("../services/doorLock");
const IncomingForm = require('formidable').IncomingForm;
const uploadDoorLockData = (req, res) => {
res.json({status: 'ok'});
const form = new IncomingForm();
const parsingResults = [];
form.on('file', (field, file) => {
if (file && file.type === 'text/csv') {
parsingResults.push(parseDoorLockDataFile(file));
}
});
form.on('end', () => {
Promise.all(parsingResults)
.then((parserResults) => {
const parsedData = [];
const parserErrors = [];
parserResults.forEach((parserResult) => {
parsedData.push(...parserResult.parsedData);
parserErrors.push(...parserResult.errors);
});
res.json({
parsedData,
parserErrors,
});
parsedData.forEach((entry) => {
writeDoorLockEvent(entry);
});
})
.catch((error) => {
console.log(error);
res.json({result: 'error'});
});
});
form.parse(req);
};
module.exports = {

View File

@@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('doorLockEvents', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
memberName: Sequelize.STRING,
memberNumber: Sequelize.INTEGER,
event: {
type: Sequelize.ENUM,
values: ['locked', 'unlocked']
},
date: Sequelize.DATEONLY,
time: Sequelize.TIME,
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('doorLockEvents');
}
};

20
models/DoorLockEvent.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
const { USER_LOCKED_DOOR, USER_UNLOCKED_DOOR } = require('../constants/constants');
module.exports = (sequelize, DataTypes) => {
const doorLockEvent = sequelize.define('doorLockEvent', {
memberName: DataTypes.STRING,
memberNumber: DataTypes.INTEGER,
event: {
type: DataTypes.ENUM,
values: [USER_LOCKED_DOOR, USER_UNLOCKED_DOOR]
},
date: DataTypes.DATEONLY,
time: DataTypes.TIME,
}, {});
doorLockEvent.associate = function(models) {
// associations can be defined here
};
return doorLockEvent;
};

147
package-lock.json generated
View File

@@ -292,6 +292,30 @@
}
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
@@ -542,8 +566,7 @@
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"create-error-class": {
"version": "3.0.2",
@@ -571,6 +594,56 @@
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
"dev": true
},
"csv-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-2.3.0.tgz",
"integrity": "sha512-yfYRZ9P9LNKuRK0lEFf40Be4HcFxe1XgxWL/QmlkakSE8SHcWbOGFZA/u7YpfGX/hVbRUdbnO5xO8XuYxrcBtA==",
"requires": {
"buffer-alloc": "^1.1.0",
"buffer-from": "^1.0.0",
"execa": "^1.0.0",
"generate-function": "^1.0.1",
"generate-object-property": "^1.0.0",
"minimist": "^1.2.0",
"ndjson": "^1.4.0"
},
"dependencies": {
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"requires": {
"pump": "^3.0.0"
}
}
}
},
"d": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
@@ -1051,6 +1124,11 @@
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
"dev": true
},
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -1633,6 +1711,19 @@
}
}
},
"generate-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-1.1.0.tgz",
"integrity": "sha1-VMIbCAGSsW2Yd3ecW7gWZudyNl8="
},
"generate-object-property": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
"integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
"requires": {
"is-property": "^1.0.0"
}
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
@@ -2006,6 +2097,11 @@
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
},
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
},
"is-redirect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
@@ -2032,8 +2128,7 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isexe": {
"version": "2.0.0",
@@ -2069,6 +2164,11 @@
}
}
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -2264,8 +2364,7 @@
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mixin-deep": {
"version": "1.3.1",
@@ -2347,6 +2446,17 @@
"to-regex": "^3.0.1"
}
},
"ndjson": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz",
"integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=",
"requires": {
"json-stringify-safe": "^5.0.1",
"minimist": "^1.2.0",
"split2": "^2.1.0",
"through2": "^2.0.3"
}
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -2750,8 +2860,7 @@
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"proto-list": {
"version": "1.2.4",
@@ -2824,7 +2933,6 @@
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -3287,6 +3395,14 @@
"extend-shallow": "^3.0.0"
}
},
"split2": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
"integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"requires": {
"through2": "^2.0.2"
}
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -3326,7 +3442,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -3373,6 +3488,15 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"requires": {
"readable-stream": "~2.3.6",
"xtend": "~4.0.1"
}
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
@@ -3641,8 +3765,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",

View File

@@ -21,9 +21,11 @@
"node": "11.12.x"
},
"dependencies": {
"csv-parser": "^2.3.0",
"dotenv": "^8.0.0",
"express": "^4.17.0",
"express-basic-auth": "^1.2.0",
"formidable": "^1.2.1",
"pg": "^7.11.0",
"sequelize": "^5.8.6",
"sequelize-cli": "^5.4.0"

View File

@@ -7,6 +7,6 @@ const express = require('express');
const router = express.Router();
router.get('/', apiStatusCheck);
router.get('/doorLockData', uploadDoorLockData);
router.post('/doorLock/upload', uploadDoorLockData);
module.exports = router;

108
services/doorLock.js Normal file
View File

@@ -0,0 +1,108 @@
'use strict';
const db = require('../models/index');
const fs = require('fs');
const csv = require('csv-parser');
const {
USER_ENTRY_EVENT,
ENABLE_PASSAGE_MODE,
DISABLE_PASSAGE_MODE,
USER_UNLOCKED_DOOR,
USER_LOCKED_DOOR,
VALID_CSV_HEADERS,
csvParserErrors,
} = require('../constants/constants');
const parseDoorLockDataFile = (file) => {
return new Promise ((resolve, reject) => {
const results = [];
const errors = [];
let isValidFile = true;
fs.createReadStream(file.path)
.pipe(csv({
mapHeaders: ({ header, index }) => header.trim().toLowerCase(),
mapValues: ({ header, index, value }) => value.trim()
}))
.on('headers', (headers) => {
headers.forEach((header) => {
if (!VALID_CSV_HEADERS.includes(header.trim())){
isValidFile = false;
console.log('INVALID HEADER');
errors.push({
error: csvParserErrors.UNKNOWN_COLUMN,
details: header,
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 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)){
if (secondEntry && (secondEntry.event === ENABLE_PASSAGE_MODE || secondEntry.event === DISABLE_PASSAGE_MODE)){
const event = (secondEntry.event === ENABLE_PASSAGE_MODE) ? USER_UNLOCKED_DOOR : USER_LOCKED_DOOR;
const entryData = {
memberName: firstEntry.name,
memberNumber: firstEntry['user no'],
date: firstEntry.date,
time: firstEntry.time,
event,
};
parsedData.push(entryData);
i+=2;
} else {
errors.push({
error: csvParserErrors.INVALID_ENTRY_EXPECTED_PASSAGE_MODE,
details: secondEntry || 'Last row in file',
file: file.name,
});
i+=1;
}
} else {
errors.push({
error: csvParserErrors.INVALID_ENTRY_EXPECTED_USER,
details: firstEntry,
file: file.name,
});
i+=1;
}
}
resolve({
parsedData,
errors
});
});
});
};
const writeDoorLockEvent = (entry) => {
db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}})
.then()
.catch();
};
module.exports = {
parseDoorLockDataFile,
writeDoorLockEvent,
};