Skip to content

Commit 5da8ed8

Browse files
committed
chore: add all the code :allthethings:
1 parent bbbf130 commit 5da8ed8

14 files changed

+3395
-670
lines changed

.dockerignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.git
2+
.gitignore
3+
npm-debug.log
4+
node_modules
5+
Dockerfile*
6+
docker-compose*
7+
README.md
8+
LICENSE
9+
.vscode
10+
logs
11+
.env

.eslintrc.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module.exports = {
2+
root: true,
3+
extends: ['airbnb-base'],
4+
rules: {
5+
'arrow-parens': 0,
6+
'comma-dangle': [2, 'only-multiline'],
7+
'import/extensions': 0,
8+
'import/no-extraneous-dependencies': 0,
9+
'import/no-unresolved': 0,
10+
'indent': 0,
11+
'indent-legacy': ['error', 'tab', { SwitchCase: 1 }], // we need legacy because eslint-plugin-vue doesn't support the new indent options yet
12+
'max-len': ['error', { code: 180, tabWidth: 4, ignoreUrls: true, ignoreComments: true }],
13+
'no-lonely-if': 0,
14+
'no-mixed-operators': 0,
15+
'no-param-reassign': 0,
16+
'no-plusplus': 0,
17+
'no-tabs': 0,
18+
'no-underscore-dangle': ['error'],
19+
'no-use-before-define': [2, { functions: false, classes: true }],
20+
'object-curly-newline': ['error', {
21+
ObjectExpression: { minProperties: 5, multiline: true, consistent: true },
22+
ObjectPattern: { minProperties: 5, multiline: true, consistent: true }
23+
}],
24+
'prefer-destructuring': ['error', {
25+
VariableDeclarator: {
26+
array: false,
27+
object: true,
28+
},
29+
AssignmentExpression: {
30+
array: false,
31+
object: true,
32+
},
33+
}, {
34+
enforceForRenamedProperties: false,
35+
}],
36+
},
37+
env: {
38+
node: true
39+
},
40+
globals: {
41+
api: true
42+
}
43+
};

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.env
2+
13
# Logs
24
logs
35
*.log

Dockerfile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM node:10.20.1-alpine3.9
2+
EXPOSE 3000
3+
4+
WORKDIR /usr/src/app
5+
6+
COPY package*.json ./
7+
RUN npm install
8+
9+
COPY . /usr/src/app
10+
ENV NODE_ENV production
11+
12+
CMD [ "npm", "start" ]

LICENSE

+17-669
Large diffs are not rendered by default.

Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lint:
2+
npm run lint
3+
4+
ocker:
5+
docker build -t nut-http .

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
# nut2json
1+
# nut-http
22
An API wrapper that outputs the upsc command as a JSON output
3+
4+
TBA

env.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const envalid = require('envalid'); // eslint-disable-line object-curly-newline
2+
3+
/* eslint-disable key-spacing */
4+
5+
module.exports = envalid.cleanEnv(process.env, {
6+
LOG_LEVEL: envalid.str({ choices: ['OFF', 'FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE', 'ALL'], default: 'DEBUG', devDefault: 'DEBUG' }),
7+
8+
SERVER_PORT: envalid.port({ default: 3001, desc: 'The port on which to expose the HTTP server' }),
9+
SERVER_TIMEOUT: envalid.num({ default: 2 * 60 * 1000, desc: 'Global response timeout for incoming HTTP calls' }),
10+
11+
NUT_ADDRESS: envalid.host({ desc: 'The url or ip address of the NUT server' }),
12+
NUT_PORT: envalid.port({ default: 3493, desc: 'The NUT port' }),
13+
}, {
14+
strict: true
15+
});

helpers.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const moment = require('moment');
2+
require('moment-duration-format');
3+
const stereotype = require('stereotype');
4+
5+
module.exports = {
6+
parseList,
7+
parseUpsVars,
8+
getCurrentTimestamp,
9+
getProjectRoot,
10+
time
11+
};
12+
13+
function parseList(list) {
14+
return Object.keys(list).map(val => ({ name: val, description: list[val] }), []);
15+
}
16+
17+
function getCurrentTimestamp() {
18+
return moment().format('YYYY-MM-DD-HH-mm-ss');
19+
}
20+
21+
function getProjectRoot() {
22+
const directory = __dirname.split('/');
23+
24+
return directory.join('/');
25+
}
26+
27+
function time(startDateTime, endDateTime) {
28+
const duration = moment.duration(endDateTime.diff(startDateTime));
29+
if (duration < 1000) {
30+
return duration.format('S [ms]');
31+
}
32+
33+
return duration.format('h [hours] m [minutes] s [seconds]');
34+
}
35+
36+
// http://jsfiddle.net/brandonscript/tfjH3/
37+
function parseUpsVars(obj) {
38+
Object.keys(obj).forEach(key => {
39+
if (key.indexOf('.') !== -1) parseDotNotation(key, obj[key], obj);
40+
});
41+
return obj;
42+
}
43+
44+
function parseDotNotation(str, val, obj) {
45+
const keys = str.split('.');
46+
let currentObj = obj;
47+
let key;
48+
let i = 0;
49+
50+
for (i; i < Math.max(1, keys.length - 1); ++i) {
51+
key = keys[i];
52+
currentObj[key] = currentObj[key] || {};
53+
currentObj = currentObj[key];
54+
}
55+
56+
currentObj[keys[i]] = stereotype(val.trim());
57+
delete obj[str];
58+
}

index.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const restify = require('restify');
2+
const moment = require('moment');
3+
const logger = require('./logger');
4+
const { parseList, time, parseUpsVars } = require('./helpers');
5+
const NUT = require('./node-nut');
6+
const config = require('./env');
7+
8+
logger.info('Environment variables:\n', config);
9+
10+
// nut settings
11+
const nut = new NUT(config.NUT_PORT, config.NUT_ADDRESS);
12+
13+
// api settings
14+
const server = restify.createServer();
15+
server.server.setTimeout(config.SERVER_TIMEOUT);
16+
server.use(restify.plugins.queryParser({ mapParams: false }));
17+
18+
// some handling
19+
nut.on('connect', () => {
20+
logger.info(`nut connection established to ${config.NUT_ADDRESS}:${config.NUT_PORT}`);
21+
});
22+
23+
nut.on('error', err => {
24+
logger.error('nut encountered an error:', err);
25+
});
26+
27+
nut.on('close', () => {
28+
logger.info('nut connection closed');
29+
});
30+
31+
server.on('error', (err) => {
32+
logger.error('server encountered an error', err);
33+
nut.disconnect();
34+
process.exit(1); // if in docker it should be restarted automatically
35+
});
36+
37+
// router
38+
server.get('/devices', (req, res) => {
39+
const startDateTime = moment();
40+
logger.logRequest('nut-http.devices.list');
41+
42+
connectNut();
43+
44+
nut
45+
.getUpsList()
46+
.then((list) => {
47+
if (req.query.parsed && req.query.parsed === 'true') list = parseList(list);
48+
49+
const endDateTime = moment();
50+
logger.debug(`processing took ${time(startDateTime, endDateTime)}`);
51+
52+
res.send(list);
53+
return list;
54+
})
55+
.catch((err) => {
56+
logger.error(err);
57+
res.send(500, { code: 500, message: `an internal error occurred ${err}` });
58+
return err;
59+
});
60+
});
61+
62+
server.get('/devices/:name', (req, res) => {
63+
const startDateTime = moment();
64+
const ups = req.params.name;
65+
logger.logRequest('nut-http.devices.getUps', ups);
66+
67+
connectNut();
68+
69+
nut
70+
.getUpsVars(ups)
71+
.then((vars) => {
72+
if (req.query.parsed && req.query.parsed === 'true') vars = parseUpsVars(vars);
73+
74+
const endDateTime = moment();
75+
logger.debug(`processing took ${time(startDateTime, endDateTime)}`);
76+
77+
res.send(vars);
78+
return vars;
79+
})
80+
.catch((err) => {
81+
if (err === 'UNKNOWN-UPS') {
82+
logger.error('unknown ups requested');
83+
res.send(404, { code: 404, message: `UPS '${ups}' is not unknown at ${config.NUT_ADDRESS}` });
84+
return err;
85+
}
86+
87+
logger.error(err);
88+
res.send(500, { code: 500, message: `an internal error occurred ${err}` });
89+
return err;
90+
});
91+
});
92+
93+
server.listen(config.SERVER_PORT, () => {
94+
logger.info(`nut-http listening on port ${config.SERVER_PORT}`);
95+
});
96+
97+
function connectNut() {
98+
if (!nut.connected) nut.connect();
99+
}

logger.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Get external dependencies
2+
const log4js = require('log4js');
3+
const { getProjectRoot, getCurrentTimestamp } = require('./helpers');
4+
5+
log4js.configure({
6+
appenders: {
7+
console: { type: 'console' },
8+
file: {
9+
type: 'file',
10+
filename: `${getProjectRoot()}/logs/${getCurrentTimestamp()}.log`
11+
}
12+
},
13+
categories: {
14+
default: { appenders: ['console', 'file'], level: 'debug' }
15+
}
16+
});
17+
18+
const logger = log4js.getLogger('nut-http');
19+
20+
// Log4js Log Levels
21+
// OFF
22+
// FATAL
23+
// ERROR
24+
// WARN
25+
// INFO
26+
// DEBUG
27+
// TRACE
28+
// ALL
29+
// The levels are cumulative.
30+
// If you for example set the logging level to WARN all warnings, errors and fatals are logged
31+
32+
module.exports = {
33+
logRequest,
34+
trace,
35+
info,
36+
debug,
37+
warn,
38+
error,
39+
line
40+
};
41+
42+
function logRequest(request, entity) {
43+
request = request.split('.');
44+
45+
// const api = request[0];
46+
const model = request[1];
47+
const method = request[2];
48+
49+
// logger.debug('%s.%s - received %s%s', api, model, method, (entity ? ` for ${entity}` : ''));
50+
logger.debug(`received ${method.toUpperCase()} /${model} ${entity ? `for ${entity}` : ''}`);
51+
}
52+
53+
function trace(...args) {
54+
run(logger.trace, parseArgs(args));
55+
}
56+
57+
function info(...args) {
58+
run(logger.info, parseArgs(args));
59+
}
60+
61+
function debug(...args) {
62+
run(logger.debug, parseArgs(args));
63+
}
64+
65+
function warn(...args) {
66+
run(logger.warn, parseArgs(args));
67+
}
68+
69+
function error(...args) {
70+
run(logger.error, parseArgs(args));
71+
}
72+
73+
function parseArgs(args) {
74+
let sentence = args.shift();
75+
if (typeof sentence === 'object') sentence = JSON.stringify(sentence);
76+
return [sentence].concat(args);
77+
}
78+
79+
function line() {
80+
logger.debug('-------------------------------------------------------------------------------');
81+
}
82+
83+
function run(f, args) {
84+
f.apply(logger, args);
85+
}

0 commit comments

Comments
 (0)