Initial commit.
This commit is contained in:
commit
0335f5aa93
1168 changed files with 261999 additions and 0 deletions
server
26
server/app.js
Normal file
26
server/app.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('app', function (fs) {
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
app.use(express.bodyParser());
|
||||
|
||||
var clientDir = __dirname + '/../client';
|
||||
|
||||
app.use('/', express.static(clientDir + '/'));
|
||||
app.get('/', function (req, res, next) {
|
||||
fs.readFile(clientDir + '/index.html', 'utf8', function (err, body) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.end(body);
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
});
|
8
server/config.js
Normal file
8
server/config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('config', function () {
|
||||
return {
|
||||
port: 8080,
|
||||
peersPath: '/tmp/peers'
|
||||
};
|
||||
});
|
20
server/libs.js
Normal file
20
server/libs.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
(function () {
|
||||
var module = angular.module('ffffng');
|
||||
|
||||
function lib(name, nodeModule) {
|
||||
if (!nodeModule) {
|
||||
nodeModule = name;
|
||||
}
|
||||
|
||||
module.factory(name, function () {
|
||||
return require(nodeModule);
|
||||
});
|
||||
}
|
||||
|
||||
lib('_', 'underscore');
|
||||
lib('crypto');
|
||||
lib('fs');
|
||||
lib('glob');
|
||||
})();
|
30
server/main.js
Executable file
30
server/main.js
Executable file
|
@ -0,0 +1,30 @@
|
|||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// Dirty hack to allow usage of angular modules.
|
||||
GLOBAL.angular = require('ng-di');
|
||||
|
||||
angular.module('ffffng', []);
|
||||
|
||||
require('./config');
|
||||
|
||||
require('./app');
|
||||
require('./router');
|
||||
require('./libs');
|
||||
|
||||
require('./utils/errorTypes');
|
||||
require('./utils/strings');
|
||||
|
||||
require('./resources/nodeResource');
|
||||
|
||||
require('./services/nodeService');
|
||||
|
||||
require('../shared/validation/constraints');
|
||||
require('../shared/validation/validator');
|
||||
|
||||
angular.injector(['ffffng']).invoke(function (config, app, Router) {
|
||||
Router.init();
|
||||
|
||||
app.listen(config.port, '::');
|
||||
module.exports = app;
|
||||
});
|
97
server/resources/nodeResource.js
Normal file
97
server/resources/nodeResource.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('NodeResource', function (
|
||||
Constraints,
|
||||
Validator,
|
||||
NodeService,
|
||||
_,
|
||||
Strings,
|
||||
ErrorTypes
|
||||
) {
|
||||
function getData(req) {
|
||||
return _.extend({}, req.body, req.params);
|
||||
}
|
||||
|
||||
var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords'];
|
||||
|
||||
function getValidNodeData(reqData) {
|
||||
var node = {};
|
||||
_.each(nodeFields, function (field) {
|
||||
var value = Strings.normalizeString(reqData[field]);
|
||||
if (field === 'mac') {
|
||||
value = Strings.normalizeMac(value);
|
||||
}
|
||||
node[field] = value;
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
function respond(res, httpCode, data) {
|
||||
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function success(res, data) {
|
||||
respond(res, 200, data);
|
||||
}
|
||||
|
||||
function error(res, err) {
|
||||
respond(res, err.type.code, err.data);
|
||||
}
|
||||
|
||||
var isValidNode = Validator.forConstraints(Constraints.node);
|
||||
var isValidToken = Validator.forConstraint(Constraints.token);
|
||||
|
||||
return {
|
||||
create: function (req, res) {
|
||||
var data = getData(req);
|
||||
|
||||
var node = getValidNodeData(data);
|
||||
if (!isValidNode(node)) {
|
||||
return error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
return NodeService.createNode(node, function (err, token, node) {
|
||||
if (err) {
|
||||
return error(res, err);
|
||||
}
|
||||
return success(res, {token: token, node: node});
|
||||
});
|
||||
},
|
||||
|
||||
update: function (req, res) {
|
||||
var data = getData(req);
|
||||
|
||||
var token = Strings.normalizeString(data.token);
|
||||
if (!isValidToken(token)) {
|
||||
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
var node = getValidNodeData(data);
|
||||
if (!isValidNode(node)) {
|
||||
return error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
return NodeService.updateNode(token, node, function (err, token, node) {
|
||||
if (err) {
|
||||
return error(res, err);
|
||||
}
|
||||
return success(res, {token: token, node: node});
|
||||
});
|
||||
},
|
||||
|
||||
get: function (req, res) {
|
||||
var token = Strings.normalizeString(getData(req).token);
|
||||
if (!isValidToken(token)) {
|
||||
return error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
return NodeService.getNodeData(token, function (err, node) {
|
||||
if (err) {
|
||||
return error(res, err);
|
||||
}
|
||||
return success(res, node);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
11
server/router.js
Normal file
11
server/router.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('Router', function (app, NodeResource) {
|
||||
return {
|
||||
init: function () {
|
||||
app.post('/api/node', NodeResource.create);
|
||||
app.put('/api/node/:token', NodeResource.update);
|
||||
app.get('/api/node/:token', NodeResource.get);
|
||||
}
|
||||
};
|
||||
});
|
154
server/services/nodeService.js
Normal file
154
server/services/nodeService.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng')
|
||||
.service('NodeService', function (config, _, crypto, fs, glob, Strings, ErrorTypes) {
|
||||
var linePrefixes = {
|
||||
hostname: '# Knotenname: ',
|
||||
nickname: '# Ansprechpartner: ',
|
||||
email: '# Kontakt: ',
|
||||
coords: '# Koordinaten: ',
|
||||
mac: '# MAC: ',
|
||||
token: '# Token: '
|
||||
};
|
||||
|
||||
function generateToken() {
|
||||
return crypto.randomBytes(8).toString('hex');
|
||||
}
|
||||
|
||||
function findNodeFiles(pattern) {
|
||||
return glob.sync(config.peersPath + '/' + pattern.toLowerCase());
|
||||
}
|
||||
|
||||
function isDuplicate(pattern, token) {
|
||||
var files = findNodeFiles(pattern);
|
||||
if (files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (files.length > 1 || !token) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var file = files[0];
|
||||
return file.substring(file.length - token.length, file.length) !== token;
|
||||
}
|
||||
|
||||
function checkNoDuplicates(token, node) {
|
||||
if (isDuplicate(node.hostname + '@*@*@*', token)) {
|
||||
return {data: {msg: 'Already exists.', field: 'hostname'}, type: ErrorTypes.conflict};
|
||||
}
|
||||
|
||||
if (node.key) {
|
||||
if (isDuplicate('*@*@' + node.key + '@*', token)) {
|
||||
return {data: {msg: 'Already exists.', field: 'key'}, type: ErrorTypes.conflict};
|
||||
}
|
||||
}
|
||||
|
||||
if (isDuplicate('*@' + node.mac + '@*@*', token)) {
|
||||
return {data: {msg: 'Already exists.', field: 'mac'}, type: ErrorTypes.conflict};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeNodeFile(isUpdate, token, node, callback) {
|
||||
var filename =
|
||||
config.peersPath + '/' + (node.hostname + '@' + node.mac + '@' + node.key + '@' + token).toLowerCase();
|
||||
|
||||
var data = '';
|
||||
_.each(linePrefixes, function (prefix, key) {
|
||||
var value = key === 'token' ? token : node[key];
|
||||
if (_.isUndefined(value)) {
|
||||
value = '';
|
||||
}
|
||||
data += prefix + value + '\n';
|
||||
});
|
||||
if (node.key) {
|
||||
data += 'key "' + node.key + '";\n';
|
||||
}
|
||||
|
||||
// since node.js is single threaded we don't need a lock
|
||||
|
||||
var error;
|
||||
|
||||
if (isUpdate) {
|
||||
var files = findNodeFiles('*@*@*@' + token);
|
||||
if (files.length !== 1) {
|
||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||
}
|
||||
|
||||
error = checkNoDuplicates(token, node);
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
var file = files[0];
|
||||
fs.unlinkSync(file);
|
||||
} else {
|
||||
error = checkNoDuplicates(null, node);
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filename, data, 'utf8');
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
return callback({data: 'Could not write node data.', type: ErrorTypes.internalError});
|
||||
}
|
||||
|
||||
return callback(null, token, node);
|
||||
}
|
||||
|
||||
function parseNodeFile(file, callback) {
|
||||
var lines = fs.readFileSync(file).toString();
|
||||
|
||||
var node = {};
|
||||
|
||||
_.each(lines.split('\n'), function (line) {
|
||||
var entries = {};
|
||||
|
||||
for (var key in linePrefixes) {
|
||||
if (linePrefixes.hasOwnProperty(key)) {
|
||||
var prefix = linePrefixes[key];
|
||||
if (line.substring(0, prefix.length) === prefix) {
|
||||
entries[key] = Strings.normalizeString(line.substr(prefix.length));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isEmpty(entries) && line.substring(0, 5) === 'key "') {
|
||||
entries.key = Strings.normalizeString(line.split('"')[1]);
|
||||
}
|
||||
|
||||
_.each(entries, function (value, key) {
|
||||
node[key] = value;
|
||||
});
|
||||
});
|
||||
callback(null, node);
|
||||
}
|
||||
|
||||
return {
|
||||
createNode: function (node, callback) {
|
||||
var token = generateToken();
|
||||
writeNodeFile(false, token, node, callback);
|
||||
},
|
||||
|
||||
updateNode: function (token, node, callback) {
|
||||
writeNodeFile(true, token, node, callback);
|
||||
},
|
||||
|
||||
getNodeData: function (token, callback) {
|
||||
var files = findNodeFiles('*@*@*@' + token);
|
||||
|
||||
if (files.length !== 1) {
|
||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||
}
|
||||
|
||||
var file = files[0];
|
||||
return parseNodeFile(file, callback);
|
||||
}
|
||||
};
|
||||
});
|
10
server/utils/errorTypes.js
Normal file
10
server/utils/errorTypes.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('ErrorTypes', function () {
|
||||
return {
|
||||
badRequest: {code: 400},
|
||||
notFound: {code: 404},
|
||||
conflict: {code: 409},
|
||||
internalError: {code: 500}
|
||||
};
|
||||
});
|
22
server/utils/strings.js
Normal file
22
server/utils/strings.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('ffffng').factory('Strings', function (_) {
|
||||
return {
|
||||
normalizeString: function (str) {
|
||||
return _.isString(str) ? str.trim().replace(/\s+/g, ' ') : str;
|
||||
},
|
||||
|
||||
normalizeMac: function (mac) {
|
||||
// parts only contains values at odd indexes
|
||||
var parts = mac.toUpperCase().replace(/:/g, '').split(/([A-F0-9]{2})/);
|
||||
|
||||
var macParts = [];
|
||||
|
||||
for (var i = 1; i < parts.length; i += 2) {
|
||||
macParts.push(parts[i]);
|
||||
}
|
||||
|
||||
return macParts.join(':');
|
||||
}
|
||||
};
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue